mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-31 01:10:20 -06:00
refactor(graph): move "Invite" to new drive item permissions service
This introduces the new DriveItemPermissionsService and DriveItemPermissionsApi to allow for better separation of the business logic and the API handling. As a starting point the Invite method was moved to the new service. More to follow.
This commit is contained in:
committed by
Ralf Haferkamp
parent
531c926853
commit
c6d28caa31
@@ -8,6 +8,7 @@ packages:
|
||||
dir: "mocks"
|
||||
interfaces:
|
||||
DrivesDriveItemProvider:
|
||||
DriveItemPermissionsProvider:
|
||||
HTTPClient:
|
||||
Permissions:
|
||||
Publisher:
|
||||
|
||||
97
services/graph/mocks/drive_item_permissions_provider.go
Normal file
97
services/graph/mocks/drive_item_permissions_provider.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Code generated by mockery v2.40.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
)
|
||||
|
||||
// DriveItemPermissionsProvider is an autogenerated mock type for the DriveItemPermissionsProvider type
|
||||
type DriveItemPermissionsProvider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type DriveItemPermissionsProvider_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *DriveItemPermissionsProvider) EXPECT() *DriveItemPermissionsProvider_Expecter {
|
||||
return &DriveItemPermissionsProvider_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Invite provides a mock function with given fields: ctx, resourceId, invite
|
||||
func (_m *DriveItemPermissionsProvider) Invite(ctx context.Context, resourceId providerv1beta1.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
|
||||
ret := _m.Called(ctx, resourceId, invite)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Invite")
|
||||
}
|
||||
|
||||
var r0 libregraph.Permission
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemInvite) (libregraph.Permission, error)); ok {
|
||||
return rf(ctx, resourceId, invite)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemInvite) libregraph.Permission); ok {
|
||||
r0 = rf(ctx, resourceId, invite)
|
||||
} else {
|
||||
r0 = ret.Get(0).(libregraph.Permission)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemInvite) error); ok {
|
||||
r1 = rf(ctx, resourceId, invite)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DriveItemPermissionsProvider_Invite_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Invite'
|
||||
type DriveItemPermissionsProvider_Invite_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Invite is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - resourceId providerv1beta1.ResourceId
|
||||
// - invite libregraph.DriveItemInvite
|
||||
func (_e *DriveItemPermissionsProvider_Expecter) Invite(ctx interface{}, resourceId interface{}, invite interface{}) *DriveItemPermissionsProvider_Invite_Call {
|
||||
return &DriveItemPermissionsProvider_Invite_Call{Call: _e.mock.On("Invite", ctx, resourceId, invite)}
|
||||
}
|
||||
|
||||
func (_c *DriveItemPermissionsProvider_Invite_Call) Run(run func(ctx context.Context, resourceId providerv1beta1.ResourceId, invite libregraph.DriveItemInvite)) *DriveItemPermissionsProvider_Invite_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId), args[2].(libregraph.DriveItemInvite))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *DriveItemPermissionsProvider_Invite_Call) Return(_a0 libregraph.Permission, _a1 error) *DriveItemPermissionsProvider_Invite_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *DriveItemPermissionsProvider_Invite_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemInvite) (libregraph.Permission, error)) *DriveItemPermissionsProvider_Invite_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewDriveItemPermissionsProvider creates a new instance of DriveItemPermissionsProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewDriveItemPermissionsProvider(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *DriveItemPermissionsProvider {
|
||||
mock := &DriveItemPermissionsProvider{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.40.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.40.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.40.1. DO NOT EDIT.
|
||||
// Code generated by mockery v2.40.2. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
||||
217
services/graph/pkg/service/v0/api_driveitem_permissions.go
Normal file
217
services/graph/pkg/service/v0/api_driveitem_permissions.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/go-chi/render"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/validate"
|
||||
)
|
||||
|
||||
type DriveItemPermissionsProvider interface {
|
||||
Invite(ctx context.Context, resourceId provider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error)
|
||||
}
|
||||
|
||||
// DriveItemPermissionsService contains the production business logic for everything that relates to permissions on drive items.
|
||||
type DriveItemPermissionsService struct {
|
||||
logger log.Logger
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
identityCache identity.IdentityCache
|
||||
resharingEnabled bool
|
||||
}
|
||||
|
||||
// NewDriveItemPermissionsService creates a new DriveItemPermissionsService
|
||||
func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache identity.IdentityCache, resharing bool) (DriveItemPermissionsService, error) {
|
||||
return DriveItemPermissionsService{
|
||||
logger: log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemService").Logger()},
|
||||
gatewaySelector: gatewaySelector,
|
||||
identityCache: identityCache,
|
||||
resharingEnabled: resharing,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Invite invites a user to a drive item.
|
||||
func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId provider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return libregraph.Permission{}, err
|
||||
}
|
||||
|
||||
statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &resourceId}})
|
||||
if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
|
||||
s.logger.Warn().Err(errCode).Interface("stat.res", statResponse).Msg("stat failed")
|
||||
return libregraph.Permission{}, *errCode
|
||||
}
|
||||
|
||||
resourceInfo := statResponse.GetInfo()
|
||||
condition := unifiedrole.UnifiedRoleConditionGrantee
|
||||
if IsSpaceRoot(resourceInfo.GetId()) {
|
||||
condition = unifiedrole.UnifiedRoleConditionOwner
|
||||
}
|
||||
|
||||
unifiedRolePermissions := []*libregraph.UnifiedRolePermission{{AllowedResourceActions: invite.LibreGraphPermissionsActions}}
|
||||
for _, roleID := range invite.GetRoles() {
|
||||
role, err := unifiedrole.NewUnifiedRoleFromID(roleID, s.resharingEnabled)
|
||||
if err != nil {
|
||||
s.logger.Debug().Err(err).Interface("role", invite.GetRoles()[0]).Msg("unable to convert requested role")
|
||||
return libregraph.Permission{}, err
|
||||
}
|
||||
|
||||
allowedResourceActions := unifiedrole.GetAllowedResourceActions(role, condition)
|
||||
if len(allowedResourceActions) == 0 {
|
||||
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource")
|
||||
}
|
||||
|
||||
unifiedRolePermissions = append(unifiedRolePermissions, conversions.ToPointerSlice(role.GetRolePermissions())...)
|
||||
}
|
||||
|
||||
driveRecipient := invite.GetRecipients()[0]
|
||||
|
||||
objectID := driveRecipient.GetObjectId()
|
||||
cs3ResourcePermissions := unifiedrole.PermissionsToCS3ResourcePermissions(unifiedRolePermissions)
|
||||
|
||||
createShareRequest := &collaboration.CreateShareRequest{
|
||||
ResourceInfo: resourceInfo,
|
||||
Grant: &collaboration.ShareGrant{
|
||||
Permissions: &collaboration.SharePermissions{
|
||||
Permissions: cs3ResourcePermissions,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
permission := &libregraph.Permission{}
|
||||
if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, condition, s.resharingEnabled); role != nil {
|
||||
permission.Roles = []string{role.GetId()}
|
||||
}
|
||||
|
||||
if len(permission.GetRoles()) == 0 {
|
||||
permission.LibreGraphPermissionsActions = unifiedrole.CS3ResourcePermissionsToLibregraphActions(*cs3ResourcePermissions)
|
||||
}
|
||||
|
||||
switch driveRecipient.GetLibreGraphRecipientType() {
|
||||
case "group":
|
||||
group, err := s.identityCache.GetGroup(ctx, objectID)
|
||||
if err != nil {
|
||||
s.logger.Debug().Err(err).Interface("groupId", objectID).Msg("failed group lookup")
|
||||
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
createShareRequest.GetGrant().Grantee = &storageprovider.Grantee{
|
||||
Type: storageprovider.GranteeType_GRANTEE_TYPE_GROUP,
|
||||
Id: &storageprovider.Grantee_GroupId{GroupId: &grouppb.GroupId{
|
||||
OpaqueId: group.GetId(),
|
||||
}},
|
||||
}
|
||||
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
|
||||
Group: &libregraph.Identity{
|
||||
DisplayName: group.GetDisplayName(),
|
||||
Id: conversions.ToPointer(group.GetId()),
|
||||
},
|
||||
}
|
||||
default:
|
||||
user, err := s.identityCache.GetUser(ctx, objectID)
|
||||
if err != nil {
|
||||
s.logger.Debug().Err(err).Interface("userId", objectID).Msg("failed user lookup")
|
||||
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
|
||||
createShareRequest.GetGrant().Grantee = &storageprovider.Grantee{
|
||||
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
|
||||
OpaqueId: user.GetId(),
|
||||
}},
|
||||
}
|
||||
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
|
||||
User: &libregraph.Identity{
|
||||
DisplayName: user.GetDisplayName(),
|
||||
Id: conversions.ToPointer(user.GetId()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if invite.ExpirationDateTime != nil {
|
||||
createShareRequest.GetGrant().Expiration = utils.TimeToTS(*invite.ExpirationDateTime)
|
||||
}
|
||||
|
||||
createShareResponse, err := gatewayClient.CreateShare(ctx, createShareRequest)
|
||||
if errCode := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); errCode != nil {
|
||||
s.logger.Debug().Err(err).Msg("share creation failed")
|
||||
return libregraph.Permission{}, *errCode
|
||||
}
|
||||
|
||||
if id := createShareResponse.GetShare().GetId().GetOpaqueId(); id != "" {
|
||||
permission.Id = conversions.ToPointer(id)
|
||||
} else if IsSpaceRoot(resourceInfo.GetId()) {
|
||||
// permissions on a space root are not handled by a share manager so
|
||||
// they don't get a share-id
|
||||
permission.SetId(identitySetToSpacePermissionID(permission.GetGrantedToV2()))
|
||||
}
|
||||
|
||||
if expiration := createShareResponse.GetShare().GetExpiration(); expiration != nil {
|
||||
permission.SetExpirationDateTime(utils.TSToTime(expiration))
|
||||
}
|
||||
|
||||
return *permission, nil
|
||||
}
|
||||
|
||||
// DriveItemPermissionsService is the api that registers the http endpoints which expose needed operation to the graph api.
|
||||
// the business logic is delegated to the permissions service and further down to the cs3 client.
|
||||
type DriveItemPermissionsApi struct {
|
||||
logger log.Logger
|
||||
driveItemPermissionsService DriveItemPermissionsProvider
|
||||
}
|
||||
|
||||
// NewDriveItemPermissionsApi creates a new DriveItemPermissionsApi
|
||||
func NewDriveItemPermissionsApi(driveItemPermissionService DriveItemPermissionsProvider, logger log.Logger) (DriveItemPermissionsApi, error) {
|
||||
return DriveItemPermissionsApi{
|
||||
logger: log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemApi").Logger()},
|
||||
driveItemPermissionsService: driveItemPermissionService,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api DriveItemPermissionsApi) Invite(w http.ResponseWriter, r *http.Request) {
|
||||
_, itemID, err := GetDriveAndItemIDParam(r, &api.logger)
|
||||
if err != nil {
|
||||
msg := "invalid driveID or itemID"
|
||||
api.logger.Debug().Err(err).Msg(msg)
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg)
|
||||
return
|
||||
}
|
||||
|
||||
driveItemInvite := &libregraph.DriveItemInvite{}
|
||||
if err = StrictJSONUnmarshal(r.Body, driveItemInvite); err != nil {
|
||||
api.logger.Debug().Err(err).Interface("Body", r.Body).Msg("failed unmarshalling request body")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
if err = validate.StructCtx(ctx, driveItemInvite); err != nil {
|
||||
api.logger.Debug().Err(err).Interface("Body", r.Body).Msg("invalid request body")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
permission, err := api.driveItemPermissionsService.Invite(ctx, itemID, *driveItemInvite)
|
||||
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &ListResponse{Value: []interface{}{permission}})
|
||||
}
|
||||
289
services/graph/pkg/service/v0/api_driveitem_permissions_test.go
Normal file
289
services/graph/pkg/service/v0/api_driveitem_permissions_test.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package svc_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
|
||||
"github.com/go-chi/chi/v5"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/services/graph/mocks"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
|
||||
svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var _ = Describe("DriveItemPermissionsService", func() {
|
||||
var (
|
||||
driveItemPermissionsService svc.DriveItemPermissionsService
|
||||
gatewayClient *cs3mocks.GatewayAPIClient
|
||||
gatewaySelector *mocks.Selectable[gateway.GatewayAPIClient]
|
||||
currentUser = &userpb.User{
|
||||
Id: &userpb.UserId{
|
||||
OpaqueId: "user",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
logger := log.NewLogger()
|
||||
gatewayClient = cs3mocks.NewGatewayAPIClient(GinkgoT())
|
||||
|
||||
gatewaySelector = mocks.NewSelectable[gateway.GatewayAPIClient](GinkgoT())
|
||||
gatewaySelector.On("Next").Return(gatewayClient, nil)
|
||||
|
||||
cache := identity.NewIdentityCache(identity.IdentityCacheWithGatewaySelector(gatewaySelector))
|
||||
|
||||
service, err := svc.NewDriveItemPermissionsService(logger, gatewaySelector, cache, false)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
driveItemPermissionsService = service
|
||||
})
|
||||
|
||||
Describe("Invite", func() {
|
||||
var (
|
||||
createShareResponse *collaboration.CreateShareResponse
|
||||
driveItemInvite libregraph.DriveItemInvite
|
||||
driveItemId provider.ResourceId
|
||||
statResponse *provider.StatResponse
|
||||
getUserResponse *userpb.GetUserResponse
|
||||
getGroupResponse *grouppb.GetGroupResponse
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
driveItemId = provider.ResourceId{
|
||||
StorageId: "1",
|
||||
SpaceId: "2",
|
||||
OpaqueId: "3",
|
||||
}
|
||||
ctx := revactx.ContextSetUser(context.Background(), currentUser)
|
||||
|
||||
statResponse = &provider.StatResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
|
||||
|
||||
getUserResponse = &userpb.GetUserResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: &userpb.User{
|
||||
Id: &userpb.UserId{OpaqueId: "1"},
|
||||
DisplayName: "Cem Kaner",
|
||||
},
|
||||
}
|
||||
|
||||
getGroupResponse = &grouppb.GetGroupResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Group: &grouppb.Group{
|
||||
Id: &grouppb.GroupId{OpaqueId: "2"},
|
||||
GroupName: "Florida Institute of Technology",
|
||||
DisplayName: "Florida Institute of Technology",
|
||||
},
|
||||
}
|
||||
|
||||
createShareResponse = &collaboration.CreateShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
})
|
||||
|
||||
It("creates user shares as expected (happy path)", func() {
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
|
||||
gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil)
|
||||
driveItemInvite.Recipients = []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
|
||||
}
|
||||
driveItemInvite.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour))
|
||||
createShareResponse.Share = &collaboration.Share{
|
||||
Id: &collaboration.ShareId{OpaqueId: "123"},
|
||||
Expiration: utils.TimeToTS(*driveItemInvite.ExpirationDateTime),
|
||||
}
|
||||
|
||||
permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(permission.GetId()).To(Equal("123"))
|
||||
Expect(permission.GetExpirationDateTime().Equal(*driveItemInvite.ExpirationDateTime)).To(BeTrue())
|
||||
Expect(permission.GrantedToV2.User.GetDisplayName()).To(Equal(getUserResponse.User.DisplayName))
|
||||
Expect(permission.GrantedToV2.User.GetId()).To(Equal("1"))
|
||||
})
|
||||
|
||||
It("creates group shares as expected (happy path)", func() {
|
||||
gatewayClient.On("GetGroup", mock.Anything, mock.Anything).Return(getGroupResponse, nil)
|
||||
gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil)
|
||||
driveItemInvite.Recipients = []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("2"), LibreGraphRecipientType: libregraph.PtrString("group")},
|
||||
}
|
||||
driveItemInvite.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour))
|
||||
createShareResponse.Share = &collaboration.Share{
|
||||
Id: &collaboration.ShareId{OpaqueId: "123"},
|
||||
Expiration: utils.TimeToTS(*driveItemInvite.ExpirationDateTime),
|
||||
}
|
||||
|
||||
permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(permission.GetId()).To(Equal("123"))
|
||||
Expect(permission.GetExpirationDateTime().Equal(*driveItemInvite.ExpirationDateTime)).To(BeTrue())
|
||||
Expect(permission.GrantedToV2.Group.GetDisplayName()).To(Equal(getGroupResponse.Group.DisplayName))
|
||||
Expect(permission.GrantedToV2.Group.GetId()).To(Equal("2"))
|
||||
})
|
||||
|
||||
It("with roles (happy path)", func() {
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
|
||||
gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil)
|
||||
driveItemInvite.Recipients = []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
|
||||
}
|
||||
driveItemInvite.Roles = []string{unifiedrole.NewViewerUnifiedRole(true).GetId()}
|
||||
|
||||
permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(permission.GetRoles()).To(HaveLen(1))
|
||||
Expect(permission.GetRoles()[0]).To(Equal(unifiedrole.NewViewerUnifiedRole(true).GetId()))
|
||||
})
|
||||
|
||||
It("fails with wrong role", func() {
|
||||
driveItemInvite.Recipients = []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
|
||||
}
|
||||
driveItemInvite.Roles = []string{unifiedrole.NewManagerUnifiedRole().GetId()}
|
||||
permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
|
||||
|
||||
Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource")))
|
||||
Expect(permission).To(BeZero())
|
||||
})
|
||||
|
||||
It("with actions (happy path)", func() {
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
|
||||
gatewayClient.On("CreateShare", mock.Anything, mock.Anything).Return(createShareResponse, nil)
|
||||
driveItemInvite.Recipients = []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
|
||||
}
|
||||
driveItemInvite.Roles = nil
|
||||
driveItemInvite.LibreGraphPermissionsActions = []string{unifiedrole.DriveItemContentRead}
|
||||
|
||||
permission, err := driveItemPermissionsService.Invite(context.Background(), driveItemId, driveItemInvite)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(permission).NotTo(BeZero())
|
||||
Expect(permission.GetRoles()).To(HaveLen(0))
|
||||
Expect(permission.GetLibreGraphPermissionsActions()).To(HaveLen(1))
|
||||
Expect(permission.GetLibreGraphPermissionsActions()[0]).To(Equal(unifiedrole.DriveItemContentRead))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("DriveItemPermissionsApiApi", func() {
|
||||
var (
|
||||
mockProvider *mocks.DriveItemPermissionsProvider
|
||||
httpAPI svc.DriveItemPermissionsApi
|
||||
rCTX *chi.Context
|
||||
invite libregraph.DriveItemInvite
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
logger := log.NewLogger()
|
||||
|
||||
mockProvider = mocks.NewDriveItemPermissionsProvider(GinkgoT())
|
||||
api, err := svc.NewDriveItemPermissionsApi(mockProvider, logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
httpAPI = api
|
||||
|
||||
rCTX = chi.NewRouteContext()
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
|
||||
invite = libregraph.DriveItemInvite{
|
||||
Recipients: []libregraph.DriveRecipient{
|
||||
{
|
||||
ObjectId: libregraph.PtrString("1"),
|
||||
LibreGraphRecipientType: libregraph.PtrString("user")},
|
||||
},
|
||||
Roles: []string{unifiedrole.NewViewerUnifiedRole(true).GetId()},
|
||||
}
|
||||
})
|
||||
|
||||
checkDriveIDAndItemIDValidation := func(handler http.HandlerFunc) {
|
||||
rCTX.URLParams.Add("itemID", "3$4!5")
|
||||
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodPost, "/", nil).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
handler(responseRecorder, request)
|
||||
|
||||
Expect(responseRecorder.Code).To(Equal(http.StatusUnprocessableEntity))
|
||||
|
||||
jsonData := gjson.Get(responseRecorder.Body.String(), "error")
|
||||
Expect(jsonData.Get("message").String()).To(Equal("invalid driveID or itemID"))
|
||||
}
|
||||
|
||||
Describe("Invite", func() {
|
||||
It("validates the driveID and itemID url param", func() {
|
||||
checkDriveIDAndItemIDValidation(httpAPI.Invite)
|
||||
})
|
||||
|
||||
It("return an error when the Invite provider errors", func() {
|
||||
rCTX.URLParams.Add("itemID", "1$2!3")
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
inviteJson, err := json.Marshal(invite)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(inviteJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
onInvite := mockProvider.On("Invite", mock.Anything, mock.Anything, mock.Anything)
|
||||
|
||||
onInvite.Return(func(ctx context.Context, resourceID storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
|
||||
return libregraph.Permission{}, errors.New("any")
|
||||
}).Once()
|
||||
|
||||
httpAPI.Invite(responseRecorder, request)
|
||||
|
||||
Expect(responseRecorder.Code).To(Equal(http.StatusInternalServerError))
|
||||
})
|
||||
|
||||
It("call the Invite provider with the correct arguments", func() {
|
||||
rCTX.URLParams.Add("itemID", "1$2!3")
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
inviteJson, err := json.Marshal(invite)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
onInvite := mockProvider.On("Invite", mock.Anything, mock.Anything, mock.Anything)
|
||||
onInvite.Return(func(ctx context.Context, resourceID storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
|
||||
Expect(storagespace.FormatResourceID(resourceID)).To(Equal("1$2!3"))
|
||||
return libregraph.Permission{}, nil
|
||||
}).Once()
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(inviteJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
httpAPI.Invite(responseRecorder, request)
|
||||
|
||||
Expect(responseRecorder.Code).To(Equal(http.StatusOK))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
@@ -452,156 +451,6 @@ func (g Graph) ListPermissions(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, collectionOfPermissions)
|
||||
}
|
||||
|
||||
// Invite invites a user to a storage drive (space).
|
||||
func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
|
||||
gatewayClient, ok := g.GetGatewayClient(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
_, itemID, err := GetDriveAndItemIDParam(r, g.logger)
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
driveItemInvite := &libregraph.DriveItemInvite{}
|
||||
if err := StrictJSONUnmarshal(r.Body, driveItemInvite); err != nil {
|
||||
g.logger.Debug().Err(err).Interface("Body", r.Body).Msg("failed unmarshalling request body")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
if err := validate.StructCtx(ctx, driveItemInvite); err != nil {
|
||||
g.logger.Debug().Err(err).Interface("Body", r.Body).Msg("invalid request body")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
statResponse, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: &itemID}})
|
||||
if errCode := errorcode.FromStat(statResponse, err); errCode != nil {
|
||||
g.logger.Warn().Err(errCode).Interface("stat.res", statResponse).Msg("stat failed")
|
||||
errCode.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
condition := unifiedrole.UnifiedRoleConditionGrantee
|
||||
if IsSpaceRoot(statResponse.GetInfo().GetId()) {
|
||||
condition = unifiedrole.UnifiedRoleConditionOwner
|
||||
}
|
||||
|
||||
unifiedRolePermissions := []*libregraph.UnifiedRolePermission{{AllowedResourceActions: driveItemInvite.LibreGraphPermissionsActions}}
|
||||
for _, roleID := range driveItemInvite.GetRoles() {
|
||||
role, err := unifiedrole.NewUnifiedRoleFromID(roleID, g.config.FilesSharing.EnableResharing)
|
||||
if err != nil {
|
||||
g.logger.Debug().Err(err).Interface("role", driveItemInvite.GetRoles()[0]).Msg("unable to convert requested role")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
allowedResourceActions := unifiedrole.GetAllowedResourceActions(role, condition)
|
||||
if len(allowedResourceActions) == 0 {
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "role not applicable to this resource")
|
||||
return
|
||||
}
|
||||
|
||||
unifiedRolePermissions = append(unifiedRolePermissions, conversions.ToPointerSlice(role.GetRolePermissions())...)
|
||||
}
|
||||
|
||||
driveRecipient := driveItemInvite.GetRecipients()[0]
|
||||
|
||||
objectID := driveRecipient.GetObjectId()
|
||||
cs3ResourcePermissions := unifiedrole.PermissionsToCS3ResourcePermissions(unifiedRolePermissions)
|
||||
|
||||
createShareRequest := &collaboration.CreateShareRequest{
|
||||
ResourceInfo: statResponse.GetInfo(),
|
||||
Grant: &collaboration.ShareGrant{
|
||||
Permissions: &collaboration.SharePermissions{
|
||||
Permissions: cs3ResourcePermissions,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
permission := &libregraph.Permission{}
|
||||
if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, condition, g.config.FilesSharing.EnableResharing); role != nil {
|
||||
permission.Roles = []string{role.GetId()}
|
||||
}
|
||||
|
||||
if len(permission.GetRoles()) == 0 {
|
||||
permission.LibreGraphPermissionsActions = unifiedrole.CS3ResourcePermissionsToLibregraphActions(*cs3ResourcePermissions)
|
||||
}
|
||||
|
||||
switch driveRecipient.GetLibreGraphRecipientType() {
|
||||
case "group":
|
||||
group, err := g.identityCache.GetGroup(ctx, objectID)
|
||||
if err != nil {
|
||||
g.logger.Debug().Err(err).Interface("groupId", objectID).Msg("failed group lookup")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
createShareRequest.GetGrant().Grantee = &storageprovider.Grantee{
|
||||
Type: storageprovider.GranteeType_GRANTEE_TYPE_GROUP,
|
||||
Id: &storageprovider.Grantee_GroupId{GroupId: &grouppb.GroupId{
|
||||
OpaqueId: group.GetId(),
|
||||
}},
|
||||
}
|
||||
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
|
||||
Group: &libregraph.Identity{
|
||||
DisplayName: group.GetDisplayName(),
|
||||
Id: conversions.ToPointer(group.GetId()),
|
||||
},
|
||||
}
|
||||
default:
|
||||
user, err := g.identityCache.GetUser(ctx, objectID)
|
||||
if err != nil {
|
||||
g.logger.Debug().Err(err).Interface("userId", objectID).Msg("failed user lookup")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
createShareRequest.GetGrant().Grantee = &storageprovider.Grantee{
|
||||
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
|
||||
OpaqueId: user.GetId(),
|
||||
}},
|
||||
}
|
||||
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
|
||||
User: &libregraph.Identity{
|
||||
DisplayName: user.GetDisplayName(),
|
||||
Id: conversions.ToPointer(user.GetId()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if driveItemInvite.ExpirationDateTime != nil {
|
||||
createShareRequest.GetGrant().Expiration = utils.TimeToTS(*driveItemInvite.ExpirationDateTime)
|
||||
}
|
||||
|
||||
createShareResponse, err := gatewayClient.CreateShare(ctx, createShareRequest)
|
||||
if errCode := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); errCode != nil {
|
||||
g.logger.Debug().Err(err).Msg("share creation failed")
|
||||
errCode.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if id := createShareResponse.GetShare().GetId().GetOpaqueId(); id != "" {
|
||||
permission.Id = conversions.ToPointer(id)
|
||||
} else if IsSpaceRoot(statResponse.GetInfo().GetId()) {
|
||||
// permissions on a space root are not handled by a share manager, so
|
||||
// they don't get a share-id
|
||||
permission.SetId(identitySetToSpacePermissionID(permission.GetGrantedToV2()))
|
||||
}
|
||||
|
||||
if expiration := createShareResponse.GetShare().GetExpiration(); expiration != nil {
|
||||
permission.SetExpirationDateTime(utils.TSToTime(expiration))
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &ListResponse{Value: []interface{}{permission}})
|
||||
}
|
||||
|
||||
// UpdatePermission updates a Permission of a Drive item
|
||||
func (g Graph) UpdatePermission(w http.ResponseWriter, r *http.Request) {
|
||||
_, itemID, err := GetDriveAndItemIDParam(r, g.logger)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
@@ -22,7 +21,6 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/tidwall/gjson"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
roleconversions "github.com/cs3org/reva/v2/pkg/conversions"
|
||||
@@ -30,15 +28,12 @@ import (
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/linktype"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
"github.com/owncloud/ocis/v2/services/graph/mocks"
|
||||
"github.com/owncloud/ocis/v2/services/graph/pkg/config"
|
||||
@@ -1074,381 +1069,6 @@ var _ = Describe("Driveitems", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Invite", func() {
|
||||
var (
|
||||
driveItemInvite *libregraph.DriveItemInvite
|
||||
statMock *mock.Call
|
||||
statResponse *provider.StatResponse
|
||||
getUserResponse *userpb.GetUserResponse
|
||||
getUserMock *mock.Call
|
||||
getGroupResponse *grouppb.GetGroupResponse
|
||||
getGroupMock *mock.Call
|
||||
createShareResponse *collaboration.CreateShareResponse
|
||||
createShareMock *mock.Call
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("driveID", "1$2")
|
||||
rctx.URLParams.Add("itemID", "1$2!3")
|
||||
|
||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx)
|
||||
ctx = revactx.ContextSetUser(ctx, currentUser)
|
||||
|
||||
driveItemInvite = &libregraph.DriveItemInvite{
|
||||
Recipients: []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
|
||||
},
|
||||
Roles: []string{unifiedrole.NewViewerUnifiedRole(true).GetId()},
|
||||
}
|
||||
|
||||
statMock = gatewayClient.On("Stat", mock.Anything, mock.Anything)
|
||||
statResponse = &provider.StatResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
statMock.Return(statResponse, nil)
|
||||
|
||||
getUserMock = gatewayClient.On("GetUser", mock.Anything, mock.Anything)
|
||||
getUserResponse = &userpb.GetUserResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: &userpb.User{
|
||||
Id: &userpb.UserId{OpaqueId: "1"},
|
||||
DisplayName: "Cem Kaner",
|
||||
},
|
||||
}
|
||||
getUserMock.Return(getUserResponse, nil)
|
||||
|
||||
getGroupMock = gatewayClient.On("GetGroup", mock.Anything, mock.Anything)
|
||||
getGroupResponse = &grouppb.GetGroupResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Group: &grouppb.Group{
|
||||
Id: &grouppb.GroupId{OpaqueId: "2"},
|
||||
GroupName: "Florida Institute of Technology",
|
||||
},
|
||||
}
|
||||
getGroupMock.Return(getGroupResponse, nil)
|
||||
|
||||
createShareMock = gatewayClient.On("CreateShare", mock.Anything, mock.Anything)
|
||||
createShareResponse = &collaboration.CreateShareResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
createShareMock.Return(createShareResponse, nil)
|
||||
})
|
||||
|
||||
toJSONReader := func(v any) *strings.Reader {
|
||||
driveItemInviteBytes, err := json.Marshal(v)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return strings.NewReader(string(driveItemInviteBytes))
|
||||
}
|
||||
|
||||
It("creates user shares as expected (happy path)", func() {
|
||||
driveItemInvite.Recipients = []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("user")},
|
||||
}
|
||||
driveItemInvite.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour))
|
||||
createShareResponse.Share = &collaboration.Share{
|
||||
Id: &collaboration.ShareId{OpaqueId: "123"},
|
||||
Expiration: utils.TimeToTS(*driveItemInvite.ExpirationDateTime),
|
||||
}
|
||||
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
jsonData := gjson.Get(rr.Body.String(), "value")
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
Expect(jsonData.Get("#").Num).To(Equal(float64(1)))
|
||||
|
||||
Expect(jsonData.Get("0.id").Str).To(Equal("123"))
|
||||
Expect(jsonData.Get("0.expirationDateTime").Str).To(Equal(driveItemInvite.ExpirationDateTime.Format(time.RFC3339Nano)))
|
||||
Expect(jsonData.Get("#.grantedToV2.user.displayName").Array()[0].Str).To(Equal(getUserResponse.User.DisplayName))
|
||||
Expect(jsonData.Get("#.grantedToV2.user.id").Array()[0].Str).To(Equal("1"))
|
||||
})
|
||||
|
||||
It("creates group shares as expected (happy path)", func() {
|
||||
driveItemInvite.Recipients = []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("2"), LibreGraphRecipientType: libregraph.PtrString("group")},
|
||||
}
|
||||
driveItemInvite.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour))
|
||||
createShareResponse.Share = &collaboration.Share{
|
||||
Id: &collaboration.ShareId{OpaqueId: "123"},
|
||||
Expiration: utils.TimeToTS(*driveItemInvite.ExpirationDateTime),
|
||||
}
|
||||
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
jsonData := gjson.Get(rr.Body.String(), "value")
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
Expect(jsonData.Get("#").Num).To(Equal(float64(1)))
|
||||
Expect(jsonData.Get("0.id").Str).To(Equal("123"))
|
||||
Expect(jsonData.Get("0.expirationDateTime").Str).To(Equal(driveItemInvite.ExpirationDateTime.Format(time.RFC3339Nano)))
|
||||
Expect(jsonData.Get("#.grantedToV2.group.displayName").Array()[0].Str).To(Equal(getGroupResponse.Group.GroupName))
|
||||
Expect(jsonData.Get("#.grantedToV2.group.id").Array()[0].Str).To(Equal("2"))
|
||||
})
|
||||
|
||||
It("with roles (happy path)", func() {
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
jsonData := gjson.Get(rr.Body.String(), "value")
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
Expect(jsonData.Get(`0.@libre\.graph\.permissions\.actions`).Exists()).To(BeFalse())
|
||||
Expect(jsonData.Get("0.roles.#").Num).To(Equal(float64(1)))
|
||||
Expect(jsonData.Get("0.roles.0").String()).To(Equal(unifiedrole.NewViewerUnifiedRole(true).GetId()))
|
||||
})
|
||||
|
||||
It("fails with wrong role", func() {
|
||||
driveItemInvite.Roles = []string{unifiedrole.NewManagerUnifiedRole().GetId()}
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusBadRequest))
|
||||
})
|
||||
|
||||
It("with actions (happy path)", func() {
|
||||
driveItemInvite.Roles = nil
|
||||
driveItemInvite.LibreGraphPermissionsActions = []string{unifiedrole.DriveItemContentRead}
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
jsonData := gjson.Get(rr.Body.String(), "value")
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
Expect(jsonData.Get("0.roles").Exists()).To(BeFalse())
|
||||
Expect(jsonData.Get(`0.@libre\.graph\.permissions\.actions.#`).Num).To(Equal(float64(1)))
|
||||
Expect(jsonData.Get(`0.@libre\.graph\.permissions\.actions.0`).String()).To(Equal(unifiedrole.DriveItemContentRead))
|
||||
})
|
||||
|
||||
It("fails if the request body is empty", func() {
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", nil).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusBadRequest))
|
||||
})
|
||||
|
||||
DescribeTable("request validations",
|
||||
func(body func() *strings.Reader, code int) {
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", body()).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
Expect(rr.Code).To(Equal(code))
|
||||
},
|
||||
Entry("fails on unknown fields", func() *strings.Reader {
|
||||
return strings.NewReader(`{"unknown":"field"}`)
|
||||
}, http.StatusBadRequest),
|
||||
)
|
||||
|
||||
DescribeTable("GetGroup",
|
||||
func(prep func(), code int) {
|
||||
driveItemInvite.Recipients = []libregraph.DriveRecipient{
|
||||
{ObjectId: libregraph.PtrString("1"), LibreGraphRecipientType: libregraph.PtrString("group")},
|
||||
}
|
||||
|
||||
prep()
|
||||
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
Expect(rr.Code).To(Equal(code))
|
||||
getGroupMock.Parent.AssertNumberOfCalls(GinkgoT(), "GetGroup", 1)
|
||||
},
|
||||
Entry("fails if not ok", func() {
|
||||
getGroupResponse.Status = status.NewNotFound(context.Background(), "")
|
||||
}, http.StatusBadRequest),
|
||||
Entry("fails if errors", func() {
|
||||
getGroupMock.Return(nil, errors.New("error"))
|
||||
}, http.StatusBadRequest),
|
||||
)
|
||||
|
||||
DescribeTable("GetUser",
|
||||
func(prep func(), code int) {
|
||||
prep()
|
||||
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
Expect(rr.Code).To(Equal(code))
|
||||
getUserMock.Parent.AssertNumberOfCalls(GinkgoT(), "GetUser", 1)
|
||||
},
|
||||
Entry("fails if not ok", func() {
|
||||
getUserResponse.Status = status.NewInvalid(context.Background(), "")
|
||||
}, http.StatusBadRequest),
|
||||
Entry("fails if errors", func() {
|
||||
getUserMock.Return(nil, errors.New("error"))
|
||||
}, http.StatusBadRequest),
|
||||
)
|
||||
|
||||
DescribeTable("CreateShare",
|
||||
func(prep func(), code int) {
|
||||
prep()
|
||||
|
||||
svc.Invite(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemInvite)).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
Expect(rr.Code).To(Equal(code))
|
||||
createShareMock.Parent.AssertNumberOfCalls(GinkgoT(), "CreateShare", 1)
|
||||
},
|
||||
Entry("fails if not ok", func() {
|
||||
createShareResponse.Status = status.NewNotFound(context.Background(), "")
|
||||
}, http.StatusNotFound),
|
||||
Entry("fails if errors", func() {
|
||||
createShareMock.Return(nil, errors.New("error"))
|
||||
}, http.StatusInternalServerError),
|
||||
)
|
||||
})
|
||||
|
||||
Describe("ListPermissions", func() {
|
||||
var (
|
||||
statMock *mock.Call
|
||||
statResponse *provider.StatResponse
|
||||
listSharesMock *mock.Call
|
||||
listSharesResponse *collaboration.ListSharesResponse
|
||||
listPublicSharesMock *mock.Call
|
||||
listPublicSharesResponse *link.ListPublicSharesResponse
|
||||
rctx *chi.Context
|
||||
)
|
||||
|
||||
toResourceID := func(in string) *provider.ResourceId {
|
||||
out, err := storagespace.ParseID(in)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
return &out
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
rctx = chi.NewRouteContext()
|
||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx)
|
||||
ctx = revactx.ContextSetUser(ctx, currentUser)
|
||||
|
||||
statMock = gatewayClient.On("Stat", mock.Anything, mock.Anything)
|
||||
statResponse = &provider.StatResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Info: &provider.ResourceInfo{
|
||||
Id: toResourceID("1$2!3"),
|
||||
PermissionSet: unifiedrole.PermissionsToCS3ResourcePermissions(
|
||||
conversions.ToPointerSlice(unifiedrole.NewViewerUnifiedRole(true).GetRolePermissions()),
|
||||
),
|
||||
Owner: &userpb.UserId{},
|
||||
},
|
||||
}
|
||||
statMock.Return(statResponse, nil)
|
||||
|
||||
listSharesMock = gatewayClient.On("ListShares", mock.Anything, mock.Anything)
|
||||
listSharesResponse = &collaboration.ListSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Shares: []*collaboration.Share{{
|
||||
Id: &collaboration.ShareId{OpaqueId: "123"},
|
||||
ResourceId: toResourceID("1$2!3"),
|
||||
Grantee: &provider.Grantee{},
|
||||
Permissions: &collaboration.SharePermissions{
|
||||
Permissions: unifiedrole.PermissionsToCS3ResourcePermissions(
|
||||
conversions.ToPointerSlice(unifiedrole.NewViewerUnifiedRole(true).GetRolePermissions()),
|
||||
),
|
||||
},
|
||||
}},
|
||||
}
|
||||
listSharesMock.Return(listSharesResponse, nil)
|
||||
|
||||
listPublicSharesMock = gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything)
|
||||
listPublicSharesResponse = &link.ListPublicSharesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
}
|
||||
listPublicSharesMock.Return(listPublicSharesResponse, nil)
|
||||
})
|
||||
|
||||
It("lists permissions", func() {
|
||||
rctx.URLParams.Add("driveID", "1$2")
|
||||
rctx.URLParams.Add("itemID", "1$2!3")
|
||||
|
||||
svc.ListPermissions(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodGet, "/", nil).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
actions := gjson.Get(rr.Body.String(), `@libre\.graph\.permissions\.actions\.allowedValues`)
|
||||
Expect(actions.Get("#").Num).To(Equal(float64(7)))
|
||||
|
||||
roles := gjson.Get(rr.Body.String(), `@libre\.graph\.permissions\.roles\.allowedValues`)
|
||||
Expect(roles.Get("#").Num).To(Equal(float64(1)))
|
||||
Expect(roles.Get("0.id").Str).To(Equal("b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5"))
|
||||
Expect(roles.Get("0.rolePermissions").Exists()).To(BeFalse())
|
||||
|
||||
value := gjson.Get(rr.Body.String(), "value")
|
||||
Expect(value.Get("#").Num).To(Equal(float64(1)))
|
||||
Expect(value.Get("0.id").Str).To(Equal("123"))
|
||||
})
|
||||
It("lists permissions on a storage space", func() {
|
||||
rctx.URLParams.Add("driveID", "1$2")
|
||||
rctx.URLParams.Add("itemID", "1$2!2")
|
||||
statResponse.Info.Id.OpaqueId = "2"
|
||||
gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil)
|
||||
|
||||
getUserMock := gatewayClient.On("GetUser", mock.Anything, mock.Anything)
|
||||
getUserMockResponse := &userpb.GetUserResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: &userpb.User{
|
||||
Id: &userpb.UserId{OpaqueId: "userid"},
|
||||
DisplayName: "Test User",
|
||||
},
|
||||
}
|
||||
getUserMock.Return(getUserMockResponse, nil)
|
||||
|
||||
svc.ListPermissions(
|
||||
rr,
|
||||
httptest.NewRequest(http.MethodGet, "/", nil).
|
||||
WithContext(ctx),
|
||||
)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
p := libregraph.NewCollectionOfPermissions()
|
||||
err := json.Unmarshal(rr.Body.Bytes(), p)
|
||||
Expect(err).To(BeNil())
|
||||
permissions := p.GetValue()
|
||||
Expect(len(permissions)).To(Equal(1))
|
||||
Expect(permissions[0].GetId()).ToNot(Equal(""))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("GetRootDriveChildren", func() {
|
||||
It("handles ListStorageSpaces not found", func() {
|
||||
gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{
|
||||
|
||||
@@ -112,7 +112,6 @@ type Service interface {
|
||||
CreateLink(w http.ResponseWriter, r *http.Request)
|
||||
SetLinkPassword(writer http.ResponseWriter, request *http.Request)
|
||||
|
||||
Invite(w http.ResponseWriter, r *http.Request)
|
||||
ListPermissions(w http.ResponseWriter, r *http.Request)
|
||||
UpdatePermission(w http.ResponseWriter, r *http.Request)
|
||||
DeletePermission(w http.ResponseWriter, r *http.Request)
|
||||
@@ -214,6 +213,16 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
return svc, err
|
||||
}
|
||||
|
||||
driveItemPermissionsService, err := NewDriveItemPermissionsService(options.Logger, options.GatewaySelector, identityCache, options.Config.FilesSharing.EnableResharing)
|
||||
if err != nil {
|
||||
return svc, err
|
||||
}
|
||||
|
||||
driveItemPermissionsApi, err := NewDriveItemPermissionsApi(driveItemPermissionsService, options.Logger)
|
||||
if err != nil {
|
||||
return svc, err
|
||||
}
|
||||
|
||||
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
|
||||
r.Use(middleware.StripSlashes)
|
||||
|
||||
@@ -231,7 +240,7 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
r.Post("/root/children", drivesDriveItemApi.CreateDriveItem)
|
||||
r.Route("/items/{itemID}", func(r chi.Router) {
|
||||
r.Delete("/", drivesDriveItemApi.DeleteDriveItem)
|
||||
r.Post("/invite", svc.Invite)
|
||||
r.Post("/invite", driveItemPermissionsApi.Invite)
|
||||
r.Route("/permissions", func(r chi.Router) {
|
||||
r.Get("/", svc.ListPermissions)
|
||||
r.Route("/{permissionID}", func(r chi.Router) {
|
||||
|
||||
@@ -889,7 +889,7 @@ Feature: Send a sharing invitations
|
||||
"code": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"generalException"
|
||||
"invalidRequest"
|
||||
]
|
||||
},
|
||||
"message": {
|
||||
@@ -940,7 +940,7 @@ Feature: Send a sharing invitations
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"pattern": "generalException"
|
||||
"pattern": "invalidRequest"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
@@ -1113,7 +1113,7 @@ Feature: Send a sharing invitations
|
||||
"code": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"generalException"
|
||||
"invalidRequest"
|
||||
]
|
||||
},
|
||||
"message": {
|
||||
@@ -1224,7 +1224,7 @@ Feature: Send a sharing invitations
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"enum": ["generalException"]
|
||||
"enum": ["invalidRequest"]
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
@@ -2375,14 +2375,14 @@ Feature: Send a sharing invitations
|
||||
| sharee | grp1 |
|
||||
| shareType | group |
|
||||
| permissionsAction | <permissions-action> |
|
||||
Then the HTTP status code should be "400"
|
||||
Then the HTTP status code should be "400"
|
||||
When user "Alice" sends the following share invitation using the Graph API:
|
||||
| resource | FolderToShare |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsAction | <permissions-action> |
|
||||
Then the HTTP status code should be "400"
|
||||
Then the HTTP status code should be "400"
|
||||
Examples:
|
||||
| permissions-action |
|
||||
| permissions/create |
|
||||
|
||||
Reference in New Issue
Block a user