From 8124024cafe78d3e610ef6bf279370010910400a Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Wed, 27 Mar 2024 17:29:19 +0100 Subject: [PATCH] refactor(graph): move UpdatePermission to the permissions service --- .../mocks/drive_item_permissions_provider.go | 59 ++ .../service/v0/api_driveitem_permissions.go | 69 ++ .../v0/api_driveitem_permissions_test.go | 414 ++++++++- services/graph/pkg/service/v0/base.go | 147 ++++ services/graph/pkg/service/v0/driveitems.go | 220 ----- .../graph/pkg/service/v0/driveitems_test.go | 827 +----------------- services/graph/pkg/service/v0/links.go | 6 +- services/graph/pkg/service/v0/service.go | 4 +- 8 files changed, 690 insertions(+), 1056 deletions(-) diff --git a/services/graph/mocks/drive_item_permissions_provider.go b/services/graph/mocks/drive_item_permissions_provider.go index ad2d453b01..0f8bc74a5a 100644 --- a/services/graph/mocks/drive_item_permissions_provider.go +++ b/services/graph/mocks/drive_item_permissions_provider.go @@ -302,6 +302,65 @@ func (_c *DriveItemPermissionsProvider_SpaceRootInvite_Call) RunAndReturn(run fu return _c } +// UpdatePermission provides a mock function with given fields: ctx, itemID, permissionID, newPermission +func (_m *DriveItemPermissionsProvider) UpdatePermission(ctx context.Context, itemID providerv1beta1.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error) { + ret := _m.Called(ctx, itemID, permissionID, newPermission) + + if len(ret) == 0 { + panic("no return value specified for UpdatePermission") + } + + var r0 libregraph.Permission + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string, libregraph.Permission) (libregraph.Permission, error)); ok { + return rf(ctx, itemID, permissionID, newPermission) + } + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string, libregraph.Permission) libregraph.Permission); ok { + r0 = rf(ctx, itemID, permissionID, newPermission) + } else { + r0 = ret.Get(0).(libregraph.Permission) + } + + if rf, ok := ret.Get(1).(func(context.Context, providerv1beta1.ResourceId, string, libregraph.Permission) error); ok { + r1 = rf(ctx, itemID, permissionID, newPermission) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DriveItemPermissionsProvider_UpdatePermission_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdatePermission' +type DriveItemPermissionsProvider_UpdatePermission_Call struct { + *mock.Call +} + +// UpdatePermission is a helper method to define mock.On call +// - ctx context.Context +// - itemID providerv1beta1.ResourceId +// - permissionID string +// - newPermission libregraph.Permission +func (_e *DriveItemPermissionsProvider_Expecter) UpdatePermission(ctx interface{}, itemID interface{}, permissionID interface{}, newPermission interface{}) *DriveItemPermissionsProvider_UpdatePermission_Call { + return &DriveItemPermissionsProvider_UpdatePermission_Call{Call: _e.mock.On("UpdatePermission", ctx, itemID, permissionID, newPermission)} +} + +func (_c *DriveItemPermissionsProvider_UpdatePermission_Call) Run(run func(ctx context.Context, itemID providerv1beta1.ResourceId, permissionID string, newPermission libregraph.Permission)) *DriveItemPermissionsProvider_UpdatePermission_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId), args[2].(string), args[3].(libregraph.Permission)) + }) + return _c +} + +func (_c *DriveItemPermissionsProvider_UpdatePermission_Call) Return(_a0 libregraph.Permission, _a1 error) *DriveItemPermissionsProvider_UpdatePermission_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DriveItemPermissionsProvider_UpdatePermission_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, string, libregraph.Permission) (libregraph.Permission, error)) *DriveItemPermissionsProvider_UpdatePermission_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 { diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index 5d3caac4f4..b3b4244b01 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -36,6 +36,7 @@ type DriveItemPermissionsProvider interface { ListPermissions(ctx context.Context, itemID storageprovider.ResourceId) (libregraph.CollectionOfPermissionsWithAllowedValues, error) ListSpaceRootPermissions(ctx context.Context, driveID storageprovider.ResourceId) (libregraph.CollectionOfPermissionsWithAllowedValues, error) DeletePermission(ctx context.Context, itemID storageprovider.ResourceId, permissionID string) error + UpdatePermission(ctx context.Context, itemID storageprovider.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error) } // DriveItemPermissionsService contains the production business logic for everything that relates to permissions on drive items. @@ -351,6 +352,36 @@ func (s DriveItemPermissionsService) DeletePermission(ctx context.Context, itemI return errorcode.New(errorcode.GeneralException, "failed to delete permission") } +func (s DriveItemPermissionsService) UpdatePermission(ctx context.Context, itemID storageprovider.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error) { + oldPermission, sharedResourceID, err := s.getPermissionByID(ctx, permissionID, &itemID) + if err != nil { + return libregraph.Permission{}, err + } + + // The resourceID of the shared resource need to match the item ID from the Request Path + // otherwise this is an invalid Request. + if !utils.ResourceIDEqual(sharedResourceID, &itemID) { + s.logger.Debug().Msg("resourceID of shared does not match itemID") + return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "permissionID and itemID do not match") + } + + // This is a public link + if _, ok := oldPermission.GetLinkOk(); ok { + updatedPermission, err := s.updatePublicLinkPermission(ctx, permissionID, &itemID, &newPermission) + if err != nil { + return libregraph.Permission{}, err + } + return *updatedPermission, nil + } + + // This is a user share + updatedPermission, err := s.updateUserShare(ctx, permissionID, sharedResourceID, &newPermission) + if err != nil { + return libregraph.Permission{}, err + } + return *updatedPermission, 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 { @@ -498,3 +529,41 @@ func (api DriveItemPermissionsApi) DeletePermission(w http.ResponseWriter, r *ht render.Status(r, http.StatusNoContent) render.NoContent(w, r) } + +func (api DriveItemPermissionsApi) UpdatePermission(w http.ResponseWriter, r *http.Request) { + _, itemID, err := GetDriveAndItemIDParam(r, &api.logger) + if err != nil { + api.logger.Debug().Err(err).Msg(invalidIdMsg) + errorcode.RenderError(w, r, err) + return + } + + permissionID, err := url.PathUnescape(chi.URLParam(r, "permissionID")) + if err != nil { + api.logger.Debug().Err(err).Msg("could not parse permissionID") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid permissionID") + return + } + + permission := libregraph.Permission{} + if err = StrictJSONUnmarshal(r.Body, &permission); 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, permission); 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 + } + + updatedPermission, err := api.driveItemPermissionsService.UpdatePermission(ctx, itemID, permissionID, permission) + if err != nil { + errorcode.RenderError(w, r, err) + return + } + render.Status(r, http.StatusOK) + render.JSON(w, r, &updatedPermission) +} diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go index b6dd76f7a5..755aacb890 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions_test.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_test.go @@ -16,6 +16,7 @@ import ( link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" roleconversions "github.com/cs3org/reva/v2/pkg/conversions" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/status" @@ -31,6 +32,7 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" "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/linktype" 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" @@ -45,12 +47,14 @@ var _ = Describe("DriveItemPermissionsService", func() { gatewaySelector *mocks.Selectable[gateway.GatewayAPIClient] getUserResponse *userpb.GetUserResponse listPublicSharesResponse *link.ListPublicSharesResponse + listSpacesResponse *provider.ListStorageSpacesResponse currentUser = &userpb.User{ Id: &userpb.UserId{ OpaqueId: "user", }, } statResponse *provider.StatResponse + driveItemId provider.ResourceId ctx context.Context ) @@ -82,23 +86,22 @@ var _ = Describe("DriveItemPermissionsService", func() { Status: status.NewOK(ctx), } + driveItemId = provider.ResourceId{ + StorageId: "1", + SpaceId: "2", + OpaqueId: "3", + } + }) Describe("Invite", func() { var ( createShareResponse *collaboration.CreateShareResponse driveItemInvite libregraph.DriveItemInvite - driveItemId provider.ResourceId getGroupResponse *grouppb.GetGroupResponse ) BeforeEach(func() { - driveItemId = provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "3", - } - gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) getGroupResponse = &grouppb.GetGroupResponse{ @@ -208,7 +211,6 @@ var _ = Describe("DriveItemPermissionsService", func() { }) Describe("SpaceRootInvite", func() { var ( - listSpacesResponse *provider.ListStorageSpacesResponse createShareResponse *collaboration.CreateShareResponse driveItemInvite libregraph.DriveItemInvite driveId provider.ResourceId @@ -373,8 +375,7 @@ var _ = Describe("DriveItemPermissionsService", func() { }) Describe("ListSpaceRootPermissions", func() { var ( - listSpacesResponse *provider.ListStorageSpacesResponse - driveId provider.ResourceId + driveId provider.ResourceId ) BeforeEach(func() { @@ -558,6 +559,399 @@ var _ = Describe("DriveItemPermissionsService", func() { }) }) + Describe("UpdatePermission", func() { + var ( + driveItemPermission libregraph.Permission + getShareMockResponse *collaboration.GetShareResponse + getPublicShareMockResponse *link.GetPublicShareResponse + updateShareMockResponse *collaboration.UpdateShareResponse + updatePublicShareMockResponse *link.UpdatePublicShareResponse + ) + const TestLinkName = "Test Link" + BeforeEach(func() { + ctx = revactx.ContextSetUser(context.Background(), currentUser) + driveItemPermission = libregraph.Permission{} + + share := &collaboration.Share{ + Id: &collaboration.ShareId{ + OpaqueId: "permissionid", + }, + ResourceId: &driveItemId, + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: &userpb.UserId{ + OpaqueId: "userid", + }, + }, + }, + Permissions: &collaboration.SharePermissions{ + Permissions: roleconversions.NewViewerRole(true).CS3ResourcePermissions(), + }, + } + getShareMockResponse = &collaboration.GetShareResponse{ + Status: status.NewOK(ctx), + Share: share, + } + + updateShareMockResponse = &collaboration.UpdateShareResponse{ + Status: status.NewOK(ctx), + Share: share, + } + + updatePublicShareMockResponse = &link.UpdatePublicShareResponse{ + Status: status.NewOK(ctx), + Share: &link.PublicShare{DisplayName: TestLinkName}, + } + + getPublicShareMockResponse = &link.GetPublicShareResponse{ + Status: status.NewOK(ctx), + Share: &link.PublicShare{ + Id: &link.PublicShareId{ + OpaqueId: "permissionid", + }, + ResourceId: &driveItemId, + Permissions: &link.PublicSharePermissions{ + Permissions: linktype.NewViewLinkPermissionSet().GetPermissions(), + }, + Token: "token", + }, + } + statResponse = &provider.StatResponse{ + Status: status.NewOK(ctx), + Info: &provider.ResourceInfo{ + Id: &driveItemId, + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + }, + } + + grantMapJSON, _ := json.Marshal( + map[string]*provider.ResourcePermissions{ + "userid": roleconversions.NewSpaceViewerRole().CS3ResourcePermissions(), + }, + ) + spaceOpaque := &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "grants": { + Decoder: "json", + Value: grantMapJSON, + }, + }, + } + listSpacesResponse = &provider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*provider.StorageSpace{ + { + Id: &provider.StorageSpaceId{ + OpaqueId: "2", + }, + Opaque: spaceOpaque, + }, + }, + } + + }) + It("fails when no share is found", func() { + getShareMockResponse.Share = nil + getShareMockResponse.Status = status.NewNotFound(ctx, "not found") + gatewayClient.On("GetShare", mock.Anything, mock.MatchedBy(func(req *collaboration.GetShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getShareMockResponse, nil) + + getPublicShareMockResponse.Share = nil + getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") + gatewayClient.On("GetPublicShare", mock.Anything, mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getPublicShareMockResponse, nil) + + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).To(HaveOccurred()) + Expect(res).To(BeZero()) + }) + It("fails to update permission when the resourceID mismatches with the shared resource's id", func() { + getShareMockResponse.Share = nil + getShareMockResponse.Status = status.NewNotFound(ctx, "not found") + getPublicShareMockResponse.Share.ResourceId = &provider.ResourceId{ + StorageId: "1", + SpaceId: "2", + OpaqueId: "4", + } + gatewayClient.On("GetPublicShare", mock.Anything, mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getPublicShareMockResponse, nil) + + driveItemPermission.SetExpirationDateTime(time.Now().Add(time.Hour)) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "permissionID and itemID do not match"))) + Expect(res).To(BeZero()) + }) + It("succeeds when trying to update a link permission with displayname", func() { + gatewayClient.On("GetPublicShare", mock.Anything, mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getPublicShareMockResponse, nil) + + gatewayClient.On("Stat", mock.Anything, mock.MatchedBy(func(req *provider.StatRequest) bool { + return utils.ResourceIDEqual(req.GetRef().GetResourceId(), &driveItemId) && req.GetRef().GetPath() == "." + })).Return(statResponse, nil) + + gatewayClient.On("UpdatePublicShare", + mock.Anything, + mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { + if req.GetRef().GetId().GetOpaqueId() == "permissionid" { + return req.Update.GetDisplayName() == TestLinkName + } + return false + }), + ).Return(updatePublicShareMockResponse, nil) + + link := libregraph.NewSharingLink() + link.SetLibreGraphDisplayName(TestLinkName) + + driveItemPermission.SetLink(*link) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).ToNot(HaveOccurred()) + Expect(res.Link).ToNot(BeNil()) + Expect(res.Link.GetLibreGraphDisplayName() == TestLinkName) + }) + It("updates the expiration date", func() { + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + getPublicShareMockResponse.Share = nil + getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") + gatewayClient.On("GetPublicShare", mock.Anything, mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getPublicShareMockResponse, nil) + + gatewayClient.On("GetShare", mock.Anything, mock.MatchedBy(func(req *collaboration.GetShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getShareMockResponse, nil) + + expiration := time.Now().Add(time.Hour) + updateShareMockResponse.Share.Expiration = utils.TimeToTS(expiration) + gatewayClient.On("UpdateShare", + mock.Anything, + mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { + if req.GetShare().GetId().GetOpaqueId() == "permissionid" { + return expiration.Equal(utils.TSToTime(req.GetShare().GetExpiration())) + } + return false + }), + ).Return(updateShareMockResponse, nil) + + driveItemPermission.SetExpirationDateTime(expiration) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).ToNot(HaveOccurred()) + Expect(res.GetExpirationDateTime().Equal(expiration)).To(BeTrue()) + }) + It("deletes the expiration date", func() { + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + getPublicShareMockResponse.Share = nil + getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") + gatewayClient.On("GetPublicShare", mock.Anything, mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getPublicShareMockResponse, nil) + + gatewayClient.On("GetShare", mock.Anything, mock.MatchedBy(func(req *collaboration.GetShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getShareMockResponse, nil) + gatewayClient.On("UpdateShare", + mock.Anything, + mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { + if req.GetShare().GetId().GetOpaqueId() == "permissionid" { + return true + } + return false + }), + ).Return(updateShareMockResponse, nil) + + driveItemPermission.SetExpirationDateTimeNil() + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).ToNot(HaveOccurred()) + _, ok := res.GetExpirationDateTimeOk() + Expect(ok).To(BeFalse()) + }) + It("fails to update the share permissions for a file share when setting a space specific role", func() { + getPublicShareMockResponse.Share = nil + getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") + gatewayClient.On("GetPublicShare", mock.Anything, mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getPublicShareMockResponse, nil) + + gatewayClient.On("GetShare", mock.Anything, mock.MatchedBy(func(req *collaboration.GetShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getShareMockResponse, nil) + + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + + driveItemPermission.SetRoles([]string{unifiedrole.NewSpaceViewerUnifiedRole().GetId()}) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource"))) + Expect(res).To(BeZero()) + }) + It("fails to update the space permissions for a space share when setting a file specific role", func() { + getPublicShareMockResponse.Share = nil + getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") + gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareMockResponse, nil) + + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil) + + statResponse.Info.Id = listSpacesResponse.StorageSpaces[0].Root + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + + driveItemPermission.SetRoles([]string{unifiedrole.NewFileEditorUnifiedRole(false).GetId()}) + spaceId := provider.ResourceId{ + StorageId: "1", + SpaceId: "2", + OpaqueId: "2", + } + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), spaceId, "u:userid", driveItemPermission) + Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource"))) + Expect(res).To(BeZero()) + }) + It("updates the share permissions when changing the resource permission actions", func() { + getPublicShareMockResponse.Share = nil + getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") + gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareMockResponse, nil) + + gatewayClient.On("GetShare", mock.Anything, mock.MatchedBy(func(req *collaboration.GetShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + })).Return(getShareMockResponse, nil) + + gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) + + updateShareMockResponse.Share.Permissions = &collaboration.SharePermissions{ + Permissions: &provider.ResourcePermissions{ + GetPath: true, + }, + } + gatewayClient.On("UpdateShare", + mock.Anything, + mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { + return req.GetShare().GetId().GetOpaqueId() == "permissionid" + }), + ).Return(updateShareMockResponse, nil) + + driveItemPermission.SetLibreGraphPermissionsActions([]string{unifiedrole.DriveItemPathRead}) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).ToNot(HaveOccurred()) + _, ok := res.GetRolesOk() + Expect(ok).To(BeFalse()) + _, ok = res.GetLibreGraphPermissionsActionsOk() + Expect(ok).To(BeTrue()) + }) + It("updates the expiration date on a public share", func() { + gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareMockResponse, nil) + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + + expiration := time.Now().UTC().Add(time.Hour) + updatePublicShareMockResponse.Share.Expiration = utils.TimeToTS(expiration) + gatewayClient.On("UpdatePublicShare", + mock.Anything, + mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + }), + ).Return(updatePublicShareMockResponse, nil) + + driveItemPermission.SetExpirationDateTime(expiration) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).ToNot(HaveOccurred()) + Expect(res.GetExpirationDateTime().Equal(expiration)).To(BeTrue()) + }) + It("updates the permissions on a public share", func() { + gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareMockResponse, nil) + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + + newLink := libregraph.NewSharingLink() + newLinkType, err := libregraph.NewSharingLinkTypeFromValue("edit") + Expect(err).ToNot(HaveOccurred()) + newLink.SetType(*newLinkType) + + updatePublicShareMockResponse.Share.Permissions = &link.PublicSharePermissions{ + Permissions: linktype.NewFolderEditLinkPermissionSet().Permissions, + } + gatewayClient.On("UpdatePublicShare", + mock.Anything, + mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + }), + ).Return(updatePublicShareMockResponse, nil) + + driveItemPermission.SetLink(*newLink) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).ToNot(HaveOccurred()) + linkType := res.Link.GetType() + Expect(string(linkType)).To(Equal("edit")) + }) + It("updates the public share to internal link", func() { + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + getPublicShareMockResponse.Share = &link.PublicShare{ + Id: &link.PublicShareId{ + OpaqueId: "permissionid", + }, + ResourceId: &provider.ResourceId{ + StorageId: "1", + SpaceId: "2", + OpaqueId: "3", + }, + PasswordProtected: true, + Permissions: &link.PublicSharePermissions{ + Permissions: linktype.NewFileEditLinkPermissionSet().GetPermissions(), + }, + Token: "token", + } + gatewayClient.On("GetPublicShare", + mock.Anything, + mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + }), + ).Return(getPublicShareMockResponse, nil) + + newLink := libregraph.NewSharingLink() + newLinkType, err := libregraph.NewSharingLinkTypeFromValue("internal") + Expect(err).ToNot(HaveOccurred()) + newLink.SetType(*newLinkType) + + updatePublicShareMockResponse.Share.Permissions = &link.PublicSharePermissions{ + Permissions: linktype.NewInternalLinkPermissionSet().Permissions, + } + gatewayClient.On("UpdatePublicShare", + mock.Anything, + mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + }), + ).Return(updatePublicShareMockResponse, nil) + + driveItemPermission.SetLink(*newLink) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).ToNot(HaveOccurred()) + linkType := res.Link.GetType() + Expect(string(linkType)).To(Equal("internal")) + pp, hasPP := res.GetHasPasswordOk() + Expect(hasPP).To(Equal(true)) + Expect(*pp).To(Equal(false)) + }) + It("fails when updating the expiration date on a public share", func() { + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareMockResponse, nil) + expiration := time.Now().UTC().AddDate(0, 0, -1) + updatePublicShareMock := gatewayClient.On("UpdatePublicShare", + mock.Anything, + mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + }), + ) + + updatePublicShareMockResponse.Share = nil + updatePublicShareMockResponse.Status = status.NewFailedPrecondition(ctx, nil, "expiration date is in the past") + updatePublicShareMock.Return(updatePublicShareMockResponse, nil) + + driveItemPermission.SetExpirationDateTime(expiration) + res, err := driveItemPermissionsService.UpdatePermission(context.Background(), driveItemId, "permissionid", driveItemPermission) + Expect(err).To(MatchError(errorcode.New(errorcode.InvalidRequest, "expiration date is in the past"))) + Expect(res).To(BeZero()) + }) + }) + }) var _ = Describe("DriveItemPermissionsApi", func() { diff --git a/services/graph/pkg/service/v0/base.go b/services/graph/pkg/service/v0/base.go index 3c64d76367..9591913796 100644 --- a/services/graph/pkg/service/v0/base.go +++ b/services/graph/pkg/service/v0/base.go @@ -27,6 +27,7 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/identity" "github.com/owncloud/ocis/v2/services/graph/pkg/linktype" "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" + "google.golang.org/protobuf/types/known/fieldmaskpb" ) // BaseGraphService implements a couple of helper functions that are @@ -532,3 +533,149 @@ func (g BaseGraphService) getCS3UserShareByID(ctx context.Context, permissionID } return getShareResp.GetShare(), nil } + +func (g BaseGraphService) getPermissionByID(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId) (*libregraph.Permission, *storageprovider.ResourceId, error) { + publicShare, err := g.getCS3PublicShareByID(ctx, permissionID) + var errcode errorcode.Error + switch { + case err == nil: + // the id is referencing a public share + permission, err := g.libreGraphPermissionFromCS3PublicShare(publicShare) + if err != nil { + return nil, nil, err + } + return permission, publicShare.GetResourceId(), nil + case IsSpaceRoot(itemID): + // itemID is referencing a spaceroot this is a space permission. Handle + // that next + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") + return nil, nil, err + } + // get space id + resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient) + if err != nil { + return nil, nil, err + } + + perms, err := g.getSpaceRootPermissions(ctx, resourceInfo.GetSpace().GetId()) + if err != nil { + return nil, nil, err + } + for _, p := range perms { + if p.GetId() == permissionID { + return &p, itemID, nil + } + } + case errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound: + // there is no public link with that id, check if this is a user share + share, err := g.getCS3UserShareByID(ctx, permissionID) + if err != nil { + return nil, nil, err + } + permission, err := g.cs3UserShareToPermission(ctx, share, false) + if err != nil { + return nil, nil, err + } + return permission, share.GetResourceId(), nil + } + + return nil, nil, err +} + +func (g BaseGraphService) updateUserShare(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId, newPermission *libregraph.Permission) (*libregraph.Permission, error) { + gatewayClient, err := g.gatewaySelector.Next() + if err != nil { + g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") + return nil, err + } + + var cs3UpdateShareReq collaboration.UpdateShareRequest + // When updating a space root we need to reference the share by resourceId and grantee + if IsSpaceRoot(itemID) { + grantee, err := spacePermissionIdToCS3Grantee(permissionID) + if err != nil { + g.logger.Debug().Err(err).Str("permissionid", permissionID).Msg("failed to parse space permission id") + return nil, err + } + cs3UpdateShareReq.Share = &collaboration.Share{ + ResourceId: itemID, + Grantee: &grantee, + } + cs3UpdateShareReq.Opaque = &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "spacegrant": {}, + }, + } + cs3UpdateShareReq.Opaque = utils.AppendPlainToOpaque(cs3UpdateShareReq.Opaque, "spacetype", _spaceTypeProject) + } else { + cs3UpdateShareReq.Share = &collaboration.Share{ + Id: &collaboration.ShareId{ + OpaqueId: permissionID, + }, + } + } + fieldmask := []string{} + if expiration, ok := newPermission.GetExpirationDateTimeOk(); ok { + fieldmask = append(fieldmask, "expiration") + if expiration != nil { + cs3UpdateShareReq.Share.Expiration = utils.TimeToTS(*expiration) + } + } + var roles, allowedResourceActions []string + var permissionsUpdated, ok bool + if roles, ok = newPermission.GetRolesOk(); ok && len(roles) > 0 { + for _, roleID := range roles { + role, err := unifiedrole.NewUnifiedRoleFromID(roleID, g.config.FilesSharing.EnableResharing) + if err != nil { + g.logger.Debug().Err(err).Interface("role", role).Msg("unable to convert requested role") + return nil, err + } + + condition := unifiedrole.UnifiedRoleConditionGrantee + if IsSpaceRoot(itemID) { + condition = unifiedrole.UnifiedRoleConditionOwner + } + // FIXME: When setting permissions on a space, we need to use UnifiedRoleConditionOwner here + allowedResourceActions = unifiedrole.GetAllowedResourceActions(role, condition) + if len(allowedResourceActions) == 0 { + return nil, errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource") + } + } + permissionsUpdated = true + } else if allowedResourceActions, ok = newPermission.GetLibreGraphPermissionsActionsOk(); ok && len(allowedResourceActions) > 0 { + permissionsUpdated = true + } + + if permissionsUpdated { + cs3ResourcePermissions := unifiedrole.PermissionsToCS3ResourcePermissions( + []*libregraph.UnifiedRolePermission{ + { + + AllowedResourceActions: allowedResourceActions, + }, + }, + ) + cs3UpdateShareReq.Share.Permissions = &collaboration.SharePermissions{ + Permissions: cs3ResourcePermissions, + } + fieldmask = append(fieldmask, "permissions") + } + + cs3UpdateShareReq.UpdateMask = &fieldmaskpb.FieldMask{ + Paths: fieldmask, + } + + updateUserShareResp, err := gatewayClient.UpdateShare(ctx, &cs3UpdateShareReq) + if errCode := errorcode.FromCS3Status(updateUserShareResp.GetStatus(), err); errCode != nil { + return nil, *errCode + } + + permission, err := g.cs3UserShareToPermission(ctx, updateUserShareResp.GetShare(), IsSpaceRoot(itemID)) + if err != nil { + return nil, err + } + + return permission, nil +} diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go index bf91b4ddbc..582d6f666d 100644 --- a/services/graph/pkg/service/v0/driveitems.go +++ b/services/graph/pkg/service/v0/driveitems.go @@ -17,14 +17,11 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/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" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/go-chi/chi/v5" "github.com/go-chi/render" libregraph "github.com/owncloud/libre-graph-api-go" "golang.org/x/crypto/sha3" - "google.golang.org/protobuf/types/known/fieldmaskpb" revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/storagespace" @@ -32,8 +29,6 @@ import ( "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/unifiedrole" - "github.com/owncloud/ocis/v2/services/graph/pkg/validate" ) // CreateUploadSession create an upload session to allow your app to upload files up to the maximum file size. @@ -353,221 +348,6 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, &ListResponse{Value: files}) } -// UpdatePermission updates a Permission of a Drive item -func (g Graph) UpdatePermission(w http.ResponseWriter, r *http.Request) { - _, itemID, err := GetDriveAndItemIDParam(r, g.logger) - if err != nil { - errorcode.RenderError(w, r, err) - return - } - - permissionID, err := url.PathUnescape(chi.URLParam(r, "permissionID")) - - if err != nil { - g.logger.Debug().Err(err).Msg("could not parse permissionID") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid permissionID") - return - } - - permission := &libregraph.Permission{} - if err = StrictJSONUnmarshal(r.Body, permission); 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, permission); 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 - } - - oldPermission, sharedResourceID, err := g.getPermissionByID(ctx, permissionID, &itemID) - if err != nil { - errorcode.RenderError(w, r, err) - return - } - - // The resourceID of the shared resource need to match the item ID from the Request Path - // otherwise this is an invalid Request. - if !utils.ResourceIDEqual(sharedResourceID, &itemID) { - g.logger.Debug().Msg("resourceID of shared does not match itemID") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "permissionID and itemID do not match") - return - } - - // This is a public link - if _, ok := oldPermission.GetLinkOk(); ok { - updatedPermission, err := g.updatePublicLinkPermission(ctx, permissionID, &itemID, permission) - if err != nil { - errorcode.RenderError(w, r, err) - return - } - render.Status(r, http.StatusOK) - render.JSON(w, r, &updatedPermission) - return - } - - // This is a user share - updatedPermission, err := g.updateUserShare(ctx, permissionID, sharedResourceID, permission) - if err != nil { - errorcode.RenderError(w, r, err) - return - } - render.Status(r, http.StatusOK) - render.JSON(w, r, &updatedPermission) -} - -func (g Graph) getPermissionByID(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId) (*libregraph.Permission, *storageprovider.ResourceId, error) { - publicShare, err := g.getCS3PublicShareByID(ctx, permissionID) - if err == nil { - permission, err := g.libreGraphPermissionFromCS3PublicShare(publicShare) - if err != nil { - return nil, nil, err - } - return permission, publicShare.GetResourceId(), nil - } - - // The id is not referencing a public share, if the itemID is referencing - // a spaceroot this is a space permission. Handle that next - if IsSpaceRoot(itemID) { - gatewayClient, err := g.gatewaySelector.Next() - if err != nil { - g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") - return nil, nil, err - } - // get space id - resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient) - if err != nil { - return nil, nil, err - } - - perms, err := g.getSpaceRootPermissions(ctx, resourceInfo.GetSpace().GetId()) - if err != nil { - return nil, nil, err - } - for _, p := range perms { - if p.GetId() == permissionID { - return &p, itemID, nil - } - } - } - - var errcode errorcode.Error - if errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound { - // there is no public link with that id, check if this is a user share - shareById, err := g.getCS3UserShareByID(ctx, permissionID) - if err != nil { - return nil, nil, err - } - permission, err := g.cs3UserShareToPermission(ctx, shareById, false) - if err != nil { - return nil, nil, err - } - return permission, shareById.GetResourceId(), nil - } - - return nil, nil, err - -} - -func (g Graph) updateUserShare(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId, newPermission *libregraph.Permission) (*libregraph.Permission, error) { - gatewayClient, err := g.gatewaySelector.Next() - if err != nil { - g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed") - return nil, err - } - - var cs3UpdateShareReq collaboration.UpdateShareRequest - // When updating a space root we need to reference the share by resourceId and grantee - if IsSpaceRoot(itemID) { - grantee, err := spacePermissionIdToCS3Grantee(permissionID) - if err != nil { - g.logger.Debug().Err(err).Str("permissionid", permissionID).Msg("failed to parse space permission id") - return nil, err - } - cs3UpdateShareReq.Share = &collaboration.Share{ - ResourceId: itemID, - Grantee: &grantee, - } - cs3UpdateShareReq.Opaque = &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "spacegrant": {}, - }, - } - cs3UpdateShareReq.Opaque = utils.AppendPlainToOpaque(cs3UpdateShareReq.Opaque, "spacetype", _spaceTypeProject) - } else { - cs3UpdateShareReq.Share = &collaboration.Share{ - Id: &collaboration.ShareId{ - OpaqueId: permissionID, - }, - } - } - fieldmask := []string{} - if expiration, ok := newPermission.GetExpirationDateTimeOk(); ok { - fieldmask = append(fieldmask, "expiration") - if expiration != nil { - cs3UpdateShareReq.Share.Expiration = utils.TimeToTS(*expiration) - } - } - var roles, allowedResourceActions []string - var permissionsUpdated, ok bool - if roles, ok = newPermission.GetRolesOk(); ok && len(roles) > 0 { - for _, roleID := range roles { - role, err := unifiedrole.NewUnifiedRoleFromID(roleID, g.config.FilesSharing.EnableResharing) - if err != nil { - g.logger.Debug().Err(err).Interface("role", role).Msg("unable to convert requested role") - return nil, err - } - - condition := unifiedrole.UnifiedRoleConditionGrantee - if IsSpaceRoot(itemID) { - condition = unifiedrole.UnifiedRoleConditionOwner - } - // FIXME: When setting permissions on a space, we need to use UnifiedRoleConditionOwner here - allowedResourceActions = unifiedrole.GetAllowedResourceActions(role, condition) - if len(allowedResourceActions) == 0 { - return nil, errorcode.New(errorcode.InvalidRequest, "role not applicable to this resource") - } - } - permissionsUpdated = true - } else if allowedResourceActions, ok = newPermission.GetLibreGraphPermissionsActionsOk(); ok && len(allowedResourceActions) > 0 { - permissionsUpdated = true - } - - if permissionsUpdated { - cs3ResourcePermissions := unifiedrole.PermissionsToCS3ResourcePermissions( - []*libregraph.UnifiedRolePermission{ - { - - AllowedResourceActions: allowedResourceActions, - }, - }, - ) - cs3UpdateShareReq.Share.Permissions = &collaboration.SharePermissions{ - Permissions: cs3ResourcePermissions, - } - fieldmask = append(fieldmask, "permissions") - } - - cs3UpdateShareReq.UpdateMask = &fieldmaskpb.FieldMask{ - Paths: fieldmask, - } - - updateUserShareResp, err := gatewayClient.UpdateShare(ctx, &cs3UpdateShareReq) - if errCode := errorcode.FromCS3Status(updateUserShareResp.GetStatus(), err); errCode != nil { - return nil, *errCode - } - - permission, err := g.cs3UserShareToPermission(ctx, updateUserShareResp.GetShare(), IsSpaceRoot(itemID)) - if err != nil { - return nil, err - } - - return permission, nil -} - func spacePermissionIdToCS3Grantee(permissionID string) (storageprovider.Grantee, error) { // the permission ID for space permission is made of two parts // the grantee type ('u' or user, 'g' for group) and the user or group id diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go index 19c8666da3..44bfef0f37 100644 --- a/services/graph/pkg/service/v0/driveitems_test.go +++ b/services/graph/pkg/service/v0/driveitems_test.go @@ -7,15 +7,11 @@ import ( "io" "net/http" "net/http/httptest" - "strings" "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/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" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/go-chi/chi/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -23,11 +19,6 @@ import ( "github.com/stretchr/testify/mock" "google.golang.org/grpc" - roleconversions "github.com/cs3org/reva/v2/pkg/conversions" - - "github.com/owncloud/ocis/v2/services/graph/pkg/errorcode" - "github.com/owncloud/ocis/v2/services/graph/pkg/linktype" - 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" @@ -40,7 +31,6 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole" ) type itemsList struct { @@ -49,14 +39,13 @@ type itemsList struct { var _ = Describe("Driveitems", func() { var ( - svc service.Service - ctx context.Context - cfg *config.Config - gatewayClient *cs3mocks.GatewayAPIClient - gatewaySelector pool.Selectable[gateway.GatewayAPIClient] - eventsPublisher mocks.Publisher - identityBackend *identitymocks.Backend - listSpacesResponse *provider.ListStorageSpacesResponse + svc service.Service + ctx context.Context + cfg *config.Config + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + eventsPublisher mocks.Publisher + identityBackend *identitymocks.Backend rr *httptest.ResponseRecorder @@ -82,31 +71,6 @@ var _ = Describe("Driveitems", func() { }, ) - grantMapJSON, _ := json.Marshal( - map[string]*provider.ResourcePermissions{ - "userid": roleconversions.NewSpaceViewerRole().CS3ResourcePermissions(), - }, - ) - spaceOpaque := &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "grants": { - Decoder: "json", - Value: grantMapJSON, - }, - }, - } - listSpacesResponse = &provider.ListStorageSpacesResponse{ - Status: status.NewOK(ctx), - StorageSpaces: []*provider.StorageSpace{ - { - Id: &provider.StorageSpaceId{ - OpaqueId: "2", - }, - Opaque: spaceOpaque, - }, - }, - } - identityBackend = &identitymocks.Backend{} newGroup = libregraph.NewGroup() newGroup.SetMembersodataBind([]string{"/users/user1"}) @@ -129,783 +93,6 @@ var _ = Describe("Driveitems", func() { ) }) - Describe("UpdatePermission", func() { - var ( - driveItemPermission *libregraph.Permission - getShareMockResponse *collaboration.GetShareResponse - getPublicShareMockResponse *link.GetPublicShareResponse - getUserMockResponse *userpb.GetUserResponse - updateShareMockResponse *collaboration.UpdateShareResponse - updatePublicShareMockResponse *link.UpdatePublicShareResponse - ) - const TestLinkName = "Test Link" - BeforeEach(func() { - rr = httptest.NewRecorder() - - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "1$2") - rctx.URLParams.Add("itemID", "1$2!3") - rctx.URLParams.Add("permissionID", "permissionid") - - ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) - ctx = revactx.ContextSetUser(ctx, currentUser) - - driveItemPermission = &libregraph.Permission{} - - getUserMock := gatewayClient.On("GetUser", mock.Anything, mock.Anything) - getUserMockResponse = &userpb.GetUserResponse{ - Status: status.NewOK(ctx), - User: &userpb.User{ - Id: &userpb.UserId{OpaqueId: "useri"}, - DisplayName: "Test User", - }, - } - getUserMock.Return(getUserMockResponse, nil) - - getShareMock := gatewayClient.On("GetShare", - mock.Anything, - mock.MatchedBy(func(req *collaboration.GetShareRequest) bool { - return req.GetRef().GetId().GetOpaqueId() == "permissionid" - }), - ) - share := &collaboration.Share{ - Id: &collaboration.ShareId{ - OpaqueId: "permissionid", - }, - ResourceId: &provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "3", - }, - Grantee: &provider.Grantee{ - Type: provider.GranteeType_GRANTEE_TYPE_USER, - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - OpaqueId: "userid", - }, - }, - }, - Permissions: &collaboration.SharePermissions{ - Permissions: roleconversions.NewViewerRole(true).CS3ResourcePermissions(), - }, - } - getShareMockResponse = &collaboration.GetShareResponse{ - Status: status.NewOK(ctx), - Share: share, - } - getShareMock.Return(getShareMockResponse, nil) - - updateShareMockResponse = &collaboration.UpdateShareResponse{ - Status: status.NewOK(ctx), - Share: share, - } - - updatePublicShareMockResponse = &link.UpdatePublicShareResponse{ - Status: status.NewOK(ctx), - Share: &link.PublicShare{DisplayName: TestLinkName}, - } - - getPublicShareMock := gatewayClient.On("GetPublicShare", - mock.Anything, - mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { - return req.GetRef().GetId().GetOpaqueId() == "permissionid" - }), - ) - getPublicShareMockResponse = &link.GetPublicShareResponse{ - Status: status.NewOK(ctx), - Share: &link.PublicShare{ - Id: &link.PublicShareId{ - OpaqueId: "permissionid", - }, - ResourceId: &provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "3", - }, - Permissions: &link.PublicSharePermissions{ - Permissions: linktype.NewViewLinkPermissionSet().GetPermissions(), - }, - Token: "token", - }, - } - getPublicShareMock.Return(getPublicShareMockResponse, nil) - - statMock := gatewayClient.On("Stat", - mock.Anything, - mock.MatchedBy(func(req *provider.StatRequest) bool { - return utils.ResourceIDEqual( - req.GetRef().GetResourceId(), - &provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "3", - }, - ) && req.GetRef().GetPath() == "." - })) - - statResponse := &provider.StatResponse{ - Status: status.NewOK(ctx), - Info: &provider.ResourceInfo{ - Id: &provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "3", - }, - Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, - }, - } - statMock.Return(statResponse, nil) - spaceRootStatMock := gatewayClient.On("Stat", - mock.Anything, - mock.MatchedBy(func(req *provider.StatRequest) bool { - return utils.ResourceIDEqual( - req.GetRef().GetResourceId(), - &provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "2", - }, - ) - })) - - spaceRootStatMock.Return( - &provider.StatResponse{ - Status: status.NewOK(ctx), - Info: &provider.ResourceInfo{ - Id: &provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "2", - }, - Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, - }, - }, nil) - - }) - It("fails when no share is found", func() { - getShareMockResponse.Share = nil - getShareMockResponse.Status = status.NewNotFound(ctx, "not found") - getPublicShareMockResponse.Share = nil - getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") - - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusNotFound)) - }) - It("fails to update permission when no request body is sent", func() { - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", nil). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - It("fails to update password when no request body is sent", func() { - svc.SetLinkPassword( - rr, - httptest.NewRequest(http.MethodPatch, "/", nil). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - It("fails to update password when itemID mismatches with the driveID", func() { - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "1$2") - rctx.URLParams.Add("itemID", "1$4!3") - rctx.URLParams.Add("permissionID", "permissionid") - - ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) - ctx = revactx.ContextSetUser(ctx, currentUser) - svc.SetLinkPassword( - rr, - httptest.NewRequest(http.MethodPatch, "/", nil). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusNotFound)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.OdataError{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - Expect(res.Error.Code).To(Equal(errorcode.ItemNotFound.String())) - Expect(res.Error.Message).To(Equal("driveID and itemID do not match")) - }) - It("fails to update permission when itemID mismatches with the driveID", func() { - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "1$2") - rctx.URLParams.Add("itemID", "1$4!3") - rctx.URLParams.Add("permissionID", "permissionid") - - ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) - ctx = revactx.ContextSetUser(ctx, currentUser) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", nil). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusNotFound)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.OdataError{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - Expect(res.Error.Code).To(Equal(errorcode.ItemNotFound.String())) - Expect(res.Error.Message).To(Equal("driveID and itemID do not match")) - }) - It("fails to update permission when the resourceID mismatches with the shared resource's id", func() { - getShareMockResponse.Share = nil - getShareMockResponse.Status = status.NewNotFound(ctx, "not found") - getPublicShareMockResponse.Share.ResourceId = &provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "4", - } - - driveItemPermission.SetExpirationDateTime(time.Now().Add(time.Hour)) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.OdataError{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - }) - It("fails to update public link password when the permissionID is not parseable", func() { - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "1$2") - rctx.URLParams.Add("itemID", "1$2!3") - rctx.URLParams.Add("permissionID", "permi%ssionid") - - ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) - ctx = revactx.ContextSetUser(ctx, currentUser) - svc.SetLinkPassword( - rr, - httptest.NewRequest(http.MethodPatch, "/", nil). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.OdataError{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - Expect(res.Error.Code).To(Equal(errorcode.InvalidRequest.String())) - Expect(res.Error.Message).To(Equal("invalid permissionID")) - }) - It("fails to update permission when the permissionID is not parseable", func() { - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "1$2") - rctx.URLParams.Add("itemID", "1$2!3") - rctx.URLParams.Add("permissionID", "permi%ssionid") - - ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) - ctx = revactx.ContextSetUser(ctx, currentUser) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", nil). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.OdataError{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - Expect(res.Error.Code).To(Equal(errorcode.InvalidRequest.String())) - Expect(res.Error.Message).To(Equal("invalid permissionID")) - }) - It("succeeds when trying to update a link permission with displayname", func() { - getShareMockResponse.Share = nil - getShareMockResponse.Status = status.NewNotFound(ctx, "not found") - - updatePublicShareMock := gatewayClient.On("UpdatePublicShare", - mock.Anything, - mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { - if req.GetRef().GetId().GetOpaqueId() == "permissionid" { - return req.Update.GetDisplayName() == TestLinkName - } - return false - }), - ) - - updatePublicShareMock.Return(updatePublicShareMockResponse, nil) - - link := libregraph.NewSharingLink() - link.SetLibreGraphDisplayName(TestLinkName) - - driveItemPermission.SetLink(*link) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusOK)) - - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.Permission{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - Expect(res.Link).ToNot(BeNil()) - Expect(res.Link.GetLibreGraphDisplayName() == TestLinkName) - }) - It("fails updating the id", func() { - driveItemPermission.SetId("permissionid") - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - It("fails updating the password flag", func() { - driveItemPermission.SetHasPassword(true) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - It("updates the expiration date", func() { - getPublicShareMockResponse.Share = nil - getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") - expiration := time.Now().Add(time.Hour) - updateShareMock := gatewayClient.On("UpdateShare", - mock.Anything, - mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { - if req.GetShare().GetId().GetOpaqueId() == "permissionid" { - return expiration.Equal(utils.TSToTime(req.GetShare().GetExpiration())) - } - return false - }), - ) - updateShareMockResponse.Share.Expiration = utils.TimeToTS(expiration) - updateShareMock.Return(updateShareMockResponse, nil) - - driveItemPermission.SetExpirationDateTime(expiration) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusOK)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.Permission{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - Expect(res.GetExpirationDateTime().Equal(expiration)).To(BeTrue()) - }) - It("deletes the expiration date", func() { - getPublicShareMockResponse.Share = nil - getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") - updateShareMock := gatewayClient.On("UpdateShare", - mock.Anything, - mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { - if req.GetShare().GetId().GetOpaqueId() == "permissionid" { - return true - } - return false - }), - ) - updateShareMock.Return(updateShareMockResponse, nil) - - driveItemPermission.SetExpirationDateTimeNil() - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusOK)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.Permission{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - _, ok := res.GetExpirationDateTimeOk() - Expect(ok).To(BeFalse()) - }) - // that is resharing test. Please delete after disable resharing feature - - // It("updates the share permissions with changing the role", func() { - // getPublicShareMockResponse.Share = nil - // getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") - // updateShareMock := gatewayClient.On("UpdateShare", - // mock.Anything, - // mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { - // return req.GetShare().GetId().GetOpaqueId() == "permissionid" - // }), - // ) - // updateShareMock.Return(updateShareMockResponse, nil) - // driveItemPermission.SetRoles([]string{unifiedrole.NewViewerUnifiedRole(false).GetId()}) - // body, err := driveItemPermission.MarshalJSON() - // Expect(err).To(BeNil()) - // svc.UpdatePermission( - // rr, - // httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - // WithContext(ctx), - // ) - // Expect(rr.Code).To(Equal(http.StatusOK)) - // data, err := io.ReadAll(rr.Body) - // Expect(err).ToNot(HaveOccurred()) - - // res := libregraph.Permission{} - - // err = json.Unmarshal(data, &res) - // Expect(err).ToNot(HaveOccurred()) - // _, ok := res.GetRolesOk() - // Expect(ok).To(BeTrue()) - // }) - It("fails to update the share permissions for a file share when setting a space specific role", func() { - getPublicShareMockResponse.Share = nil - getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") - updateShareMock := gatewayClient.On("UpdateShare", - mock.Anything, - mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { - return req.GetShare().GetId().GetOpaqueId() == "permissionid" - }), - ) - updateShareMock.Return(updateShareMockResponse, nil) - - driveItemPermission.SetRoles([]string{unifiedrole.NewSpaceViewerUnifiedRole().GetId()}) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - It("fails to update the space permissions for a space share when setting a file specific role", func() { - getPublicShareMockResponse.Share = nil - getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") - gatewayClient.On("GetPublicShare", - mock.Anything, - mock.Anything, - ).Return(getPublicShareMockResponse, nil) - gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listSpacesResponse, nil) - - getPublicShareMockResponse.Share = nil - getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") - updateShareMock := gatewayClient.On("UpdateShare", - mock.Anything, - mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { - return req.GetShare().GetId().GetOpaqueId() == "permissionid" - }), - ) - updateShareMock.Return(updateShareMockResponse, nil) - - driveItemPermission.SetRoles([]string{unifiedrole.NewFileEditorUnifiedRole(false).GetId()}) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", "1$2") - // This is a space root - rctx.URLParams.Add("itemID", "1$2!2") - rctx.URLParams.Add("permissionID", "u:userid") - ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - }) - It("updates the share permissions when changing the resource permission actions", func() { - getPublicShareMockResponse.Share = nil - getPublicShareMockResponse.Status = status.NewNotFound(ctx, "not found") - updateShareMock := gatewayClient.On("UpdateShare", - mock.Anything, - mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool { - return req.GetShare().GetId().GetOpaqueId() == "permissionid" - }), - ) - updateShareMockResponse.Share.Permissions = &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - GetPath: true, - }, - } - - updateShareMock.Return(updateShareMockResponse, nil) - - driveItemPermission.SetLibreGraphPermissionsActions([]string{unifiedrole.DriveItemPathRead}) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusOK)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.Permission{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - _, ok := res.GetRolesOk() - Expect(ok).To(BeFalse()) - _, ok = res.GetLibreGraphPermissionsActionsOk() - Expect(ok).To(BeTrue()) - }) - It("updates the expiration date on a public share", func() { - getShareMockResponse.Share = nil - getShareMockResponse.Status = status.NewNotFound(ctx, "not found") - - expiration := time.Now().UTC().Add(time.Hour) - updatePublicShareMock := gatewayClient.On("UpdatePublicShare", - mock.Anything, - mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { - return req.GetRef().GetId().GetOpaqueId() == "permissionid" - }), - ) - - updatePublicShareMockResponse.Share.Expiration = utils.TimeToTS(expiration) - updatePublicShareMock.Return(updatePublicShareMockResponse, nil) - - driveItemPermission.SetExpirationDateTime(expiration) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusOK)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.Permission{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - Expect(res.GetExpirationDateTime().Equal(expiration)).To(BeTrue()) - }) - It("updates the permissions on a public share", func() { - getShareMockResponse.Share = nil - getShareMockResponse.Status = status.NewNotFound(ctx, "not found") - - newLink := libregraph.NewSharingLink() - newLinkType, err := libregraph.NewSharingLinkTypeFromValue("edit") - Expect(err).ToNot(HaveOccurred()) - newLink.SetType(*newLinkType) - - updatePublicShareMock := gatewayClient.On("UpdatePublicShare", - mock.Anything, - mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { - return req.GetRef().GetId().GetOpaqueId() == "permissionid" - }), - ) - - updatePublicShareMockResponse.Share.Permissions = &link.PublicSharePermissions{ - Permissions: linktype.NewFolderEditLinkPermissionSet().Permissions, - } - updatePublicShareMock.Return(updatePublicShareMockResponse, nil) - - driveItemPermission.SetLink(*newLink) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusOK)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.Permission{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - linkType := res.Link.GetType() - Expect(string(linkType)).To(Equal("edit")) - }) - It("updates the public share to internal link", func() { - getShareMockResponse.Share = nil - getShareMockResponse.Status = status.NewNotFound(ctx, "not found") - - getPublicShareMock := gatewayClient.On("GetPublicShare", - mock.Anything, - mock.MatchedBy(func(req *link.GetPublicShareRequest) bool { - return req.GetRef().GetId().GetOpaqueId() == "permissionid" - }), - ) - getPublicShareMockResponse = &link.GetPublicShareResponse{ - Status: status.NewOK(ctx), - Share: &link.PublicShare{ - Id: &link.PublicShareId{ - OpaqueId: "permissionid", - }, - ResourceId: &provider.ResourceId{ - StorageId: "1", - SpaceId: "2", - OpaqueId: "3", - }, - PasswordProtected: true, - Permissions: &link.PublicSharePermissions{ - Permissions: linktype.NewFileEditLinkPermissionSet().GetPermissions(), - }, - Token: "token", - }, - } - getPublicShareMock.Return(getPublicShareMockResponse, nil) - - newLink := libregraph.NewSharingLink() - newLinkType, err := libregraph.NewSharingLinkTypeFromValue("internal") - Expect(err).ToNot(HaveOccurred()) - newLink.SetType(*newLinkType) - - updatePublicShareMock := gatewayClient.On("UpdatePublicShare", - mock.Anything, - mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { - return req.GetRef().GetId().GetOpaqueId() == "permissionid" - }), - ) - - updatePublicShareMockResponse.Share.Permissions = &link.PublicSharePermissions{ - Permissions: linktype.NewInternalLinkPermissionSet().Permissions, - } - updatePublicShareMock.Return(updatePublicShareMockResponse, nil) - - driveItemPermission.SetLink(*newLink) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusOK)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.Permission{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - linkType := res.Link.GetType() - Expect(string(linkType)).To(Equal("internal")) - pp, hasPP := res.GetHasPasswordOk() - Expect(hasPP).To(Equal(true)) - Expect(*pp).To(Equal(false)) - }) - It("updates the password on a public share", func() { - getShareMockResponse.Share = nil - getShareMockResponse.Status = status.NewNotFound(ctx, "not found") - - newLinkPassword := libregraph.NewSharingLinkPassword() - newLinkPassword.SetPassword("OC123!") - - updatePublicShareMock := gatewayClient.On("UpdatePublicShare", - mock.Anything, - mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { - return req.GetRef().GetId().GetOpaqueId() == "permissionid" - }), - ) - - updatePublicShareMockResponse.Share.Permissions = &link.PublicSharePermissions{ - Permissions: linktype.NewViewLinkPermissionSet().Permissions, - } - updatePublicShareMockResponse.Share.PasswordProtected = true - updatePublicShareMock.Return(updatePublicShareMockResponse, nil) - - body, err := newLinkPassword.MarshalJSON() - Expect(err).To(BeNil()) - svc.SetLinkPassword( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusOK)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.Permission{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - linkType := res.Link.GetType() - Expect(string(linkType)).To(Equal("view")) - Expect(*res.HasPassword).To(BeTrue()) - }) - It("fails when updating the expiration date on a public share", func() { - getShareMockResponse.Share = nil - getShareMockResponse.Status = status.NewNotFound(ctx, "not found") - - expiration := time.Now().UTC().AddDate(0, 0, -1) - updatePublicShareMock := gatewayClient.On("UpdatePublicShare", - mock.Anything, - mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { - return req.GetRef().GetId().GetOpaqueId() == "permissionid" - }), - ) - - updatePublicShareMockResponse.Share = nil - updatePublicShareMockResponse.Status = status.NewFailedPrecondition(ctx, nil, "expiration date is in the past") - updatePublicShareMock.Return(updatePublicShareMockResponse, nil) - - driveItemPermission.SetExpirationDateTime(expiration) - body, err := driveItemPermission.MarshalJSON() - Expect(err).To(BeNil()) - svc.UpdatePermission( - rr, - httptest.NewRequest(http.MethodPatch, "/", strings.NewReader(string(body))). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - data, err := io.ReadAll(rr.Body) - Expect(err).ToNot(HaveOccurred()) - - res := libregraph.OdataError{} - - err = json.Unmarshal(data, &res) - Expect(err).ToNot(HaveOccurred()) - Expect(res.GetError().Code).To(Equal(errorcode.InvalidRequest.String())) - Expect(res.GetError().Message).To(Equal("expiration date is in the past")) - }) - }) - Describe("GetRootDriveChildren", func() { It("handles ListStorageSpaces not found", func() { gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{ diff --git a/services/graph/pkg/service/v0/links.go b/services/graph/pkg/service/v0/links.go index b4e5763cad..0ce402a5fd 100644 --- a/services/graph/pkg/service/v0/links.go +++ b/services/graph/pkg/service/v0/links.go @@ -189,7 +189,7 @@ func parseAndFillUpTime(t *time.Time) *types.Timestamp { } } -func (g Graph) updatePublicLinkPassword(ctx context.Context, permissionID string, password string) (*libregraph.Permission, error) { +func (g BaseGraphService) updatePublicLinkPassword(ctx context.Context, permissionID string, password string) (*libregraph.Permission, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { return nil, err @@ -220,7 +220,7 @@ func (g Graph) updatePublicLinkPassword(ctx context.Context, permissionID string return permission, nil } -func (g Graph) updatePublicLinkPermission(ctx context.Context, permissionID string, itemID *providerv1beta1.ResourceId, newPermission *libregraph.Permission) (perm *libregraph.Permission, err error) { +func (g BaseGraphService) updatePublicLinkPermission(ctx context.Context, permissionID string, itemID *providerv1beta1.ResourceId, newPermission *libregraph.Permission) (perm *libregraph.Permission, err error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { g.logger.Error().Err(err).Msg("could not select next gateway client") @@ -294,7 +294,7 @@ func (g Graph) updatePublicLinkPermission(ctx context.Context, permissionID stri return perm, err } -func (g Graph) updatePublicLink(ctx context.Context, permissionID string, update *link.UpdatePublicShareRequest_Update) (*libregraph.Permission, error) { +func (g BaseGraphService) updatePublicLink(ctx context.Context, permissionID string, update *link.UpdatePublicShareRequest_Update) (*libregraph.Permission, error) { gatewayClient, err := g.gatewaySelector.Next() if err != nil { return nil, err diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index c24dc9703d..a84d928ff9 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -112,8 +112,6 @@ type Service interface { CreateLink(w http.ResponseWriter, r *http.Request) SetLinkPassword(writer http.ResponseWriter, request *http.Request) - UpdatePermission(w http.ResponseWriter, r *http.Request) - CreateUploadSession(w http.ResponseWriter, r *http.Request) GetTags(w http.ResponseWriter, r *http.Request) @@ -251,7 +249,7 @@ func NewService(opts ...Option) (Graph, error) { r.Get("/", driveItemPermissionsApi.ListPermissions) r.Route("/{permissionID}", func(r chi.Router) { r.Delete("/", driveItemPermissionsApi.DeletePermission) - r.Patch("/", svc.UpdatePermission) + r.Patch("/", driveItemPermissionsApi.UpdatePermission) r.Post("/setPassword", svc.SetLinkPassword) }) })