From 2d643219e3ea85a9262ee069c35c94ee717bca19 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Wed, 27 Mar 2024 19:06:47 +0100 Subject: [PATCH] refactor(graph): move CreateLink and SetPublicLinkPassword to the permissions service --- .../mocks/drive_item_permissions_provider.go | 117 ++++++ .../service/v0/api_driveitem_permissions.go | 2 + ....go => api_driveitem_permissions_links.go} | 301 +++++++------- .../api_driveitem_permissions_links_test.go | 258 ++++++++++++ services/graph/pkg/service/v0/links_test.go | 381 ------------------ services/graph/pkg/service/v0/service.go | 6 +- 6 files changed, 531 insertions(+), 534 deletions(-) rename services/graph/pkg/service/v0/{links.go => api_driveitem_permissions_links.go} (59%) create mode 100644 services/graph/pkg/service/v0/api_driveitem_permissions_links_test.go delete mode 100644 services/graph/pkg/service/v0/links_test.go diff --git a/services/graph/mocks/drive_item_permissions_provider.go b/services/graph/mocks/drive_item_permissions_provider.go index 0f8bc74a5..170c86c11 100644 --- a/services/graph/mocks/drive_item_permissions_provider.go +++ b/services/graph/mocks/drive_item_permissions_provider.go @@ -24,6 +24,64 @@ func (_m *DriveItemPermissionsProvider) EXPECT() *DriveItemPermissionsProvider_E return &DriveItemPermissionsProvider_Expecter{mock: &_m.Mock} } +// CreateLink provides a mock function with given fields: ctx, driveItemID, createLink +func (_m *DriveItemPermissionsProvider) CreateLink(ctx context.Context, driveItemID providerv1beta1.ResourceId, createLink libregraph.DriveItemCreateLink) (libregraph.Permission, error) { + ret := _m.Called(ctx, driveItemID, createLink) + + if len(ret) == 0 { + panic("no return value specified for CreateLink") + } + + var r0 libregraph.Permission + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemCreateLink) (libregraph.Permission, error)); ok { + return rf(ctx, driveItemID, createLink) + } + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemCreateLink) libregraph.Permission); ok { + r0 = rf(ctx, driveItemID, createLink) + } else { + r0 = ret.Get(0).(libregraph.Permission) + } + + if rf, ok := ret.Get(1).(func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemCreateLink) error); ok { + r1 = rf(ctx, driveItemID, createLink) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DriveItemPermissionsProvider_CreateLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateLink' +type DriveItemPermissionsProvider_CreateLink_Call struct { + *mock.Call +} + +// CreateLink is a helper method to define mock.On call +// - ctx context.Context +// - driveItemID providerv1beta1.ResourceId +// - createLink libregraph.DriveItemCreateLink +func (_e *DriveItemPermissionsProvider_Expecter) CreateLink(ctx interface{}, driveItemID interface{}, createLink interface{}) *DriveItemPermissionsProvider_CreateLink_Call { + return &DriveItemPermissionsProvider_CreateLink_Call{Call: _e.mock.On("CreateLink", ctx, driveItemID, createLink)} +} + +func (_c *DriveItemPermissionsProvider_CreateLink_Call) Run(run func(ctx context.Context, driveItemID providerv1beta1.ResourceId, createLink libregraph.DriveItemCreateLink)) *DriveItemPermissionsProvider_CreateLink_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId), args[2].(libregraph.DriveItemCreateLink)) + }) + return _c +} + +func (_c *DriveItemPermissionsProvider_CreateLink_Call) Return(_a0 libregraph.Permission, _a1 error) *DriveItemPermissionsProvider_CreateLink_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DriveItemPermissionsProvider_CreateLink_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemCreateLink) (libregraph.Permission, error)) *DriveItemPermissionsProvider_CreateLink_Call { + _c.Call.Return(run) + return _c +} + // DeletePermission provides a mock function with given fields: ctx, itemID, permissionID func (_m *DriveItemPermissionsProvider) DeletePermission(ctx context.Context, itemID providerv1beta1.ResourceId, permissionID string) error { ret := _m.Called(ctx, itemID, permissionID) @@ -244,6 +302,65 @@ func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) RunAndRetu return _c } +// SetPublicLinkPassword provides a mock function with given fields: ctx, driveItemID, permissionID, password +func (_m *DriveItemPermissionsProvider) SetPublicLinkPassword(ctx context.Context, driveItemID providerv1beta1.ResourceId, permissionID string, password string) (libregraph.Permission, error) { + ret := _m.Called(ctx, driveItemID, permissionID, password) + + if len(ret) == 0 { + panic("no return value specified for SetPublicLinkPassword") + } + + var r0 libregraph.Permission + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string, string) (libregraph.Permission, error)); ok { + return rf(ctx, driveItemID, permissionID, password) + } + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string, string) libregraph.Permission); ok { + r0 = rf(ctx, driveItemID, permissionID, password) + } else { + r0 = ret.Get(0).(libregraph.Permission) + } + + if rf, ok := ret.Get(1).(func(context.Context, providerv1beta1.ResourceId, string, string) error); ok { + r1 = rf(ctx, driveItemID, permissionID, password) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DriveItemPermissionsProvider_SetPublicLinkPassword_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPublicLinkPassword' +type DriveItemPermissionsProvider_SetPublicLinkPassword_Call struct { + *mock.Call +} + +// SetPublicLinkPassword is a helper method to define mock.On call +// - ctx context.Context +// - driveItemID providerv1beta1.ResourceId +// - permissionID string +// - password string +func (_e *DriveItemPermissionsProvider_Expecter) SetPublicLinkPassword(ctx interface{}, driveItemID interface{}, permissionID interface{}, password interface{}) *DriveItemPermissionsProvider_SetPublicLinkPassword_Call { + return &DriveItemPermissionsProvider_SetPublicLinkPassword_Call{Call: _e.mock.On("SetPublicLinkPassword", ctx, driveItemID, permissionID, password)} +} + +func (_c *DriveItemPermissionsProvider_SetPublicLinkPassword_Call) Run(run func(ctx context.Context, driveItemID providerv1beta1.ResourceId, permissionID string, password string)) *DriveItemPermissionsProvider_SetPublicLinkPassword_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId), args[2].(string), args[3].(string)) + }) + return _c +} + +func (_c *DriveItemPermissionsProvider_SetPublicLinkPassword_Call) Return(_a0 libregraph.Permission, _a1 error) *DriveItemPermissionsProvider_SetPublicLinkPassword_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DriveItemPermissionsProvider_SetPublicLinkPassword_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, string, string) (libregraph.Permission, error)) *DriveItemPermissionsProvider_SetPublicLinkPassword_Call { + _c.Call.Return(run) + return _c +} + // SpaceRootInvite provides a mock function with given fields: ctx, driveID, invite func (_m *DriveItemPermissionsProvider) SpaceRootInvite(ctx context.Context, driveID providerv1beta1.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) { ret := _m.Called(ctx, driveID, invite) diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions.go b/services/graph/pkg/service/v0/api_driveitem_permissions.go index b3b4244b0..24e54316b 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -37,6 +37,8 @@ type DriveItemPermissionsProvider interface { 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) + CreateLink(ctx context.Context, driveItemID storageprovider.ResourceId, createLink libregraph.DriveItemCreateLink) (libregraph.Permission, error) + SetPublicLinkPassword(ctx context.Context, driveItemID storageprovider.ResourceId, permissionID string, password string) (libregraph.Permission, error) } // DriveItemPermissionsService contains the production business logic for everything that relates to permissions on drive items. diff --git a/services/graph/pkg/service/v0/links.go b/services/graph/pkg/service/v0/api_driveitem_permissions_links.go similarity index 59% rename from services/graph/pkg/service/v0/links.go rename to services/graph/pkg/service/v0/api_driveitem_permissions_links.go index 0ce402a5f..d3ce7a8b4 100644 --- a/services/graph/pkg/service/v0/links.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_links.go @@ -2,8 +2,6 @@ package svc import ( "context" - "encoding/json" - "io" "net/http" "net/url" "strconv" @@ -12,6 +10,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" providerv1beta1 "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" "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/chi/v5" @@ -22,118 +21,36 @@ import ( "github.com/owncloud/ocis/v2/services/graph/pkg/linktype" ) -// CreateLink creates a public link on the cs3 api -func (g Graph) CreateLink(w http.ResponseWriter, r *http.Request) { - logger := g.logger.SubloggerWithRequestID(r.Context()) - logger.Info().Msg("calling create link") - - _, driveItemID, err := GetDriveAndItemIDParam(r, g.logger) +func (s DriveItemPermissionsService) CreateLink(ctx context.Context, driveItemID storageprovider.ResourceId, createLink libregraph.DriveItemCreateLink) (libregraph.Permission, error) { + gatewayClient, err := s.gatewaySelector.Next() if err != nil { - errorcode.RenderError(w, r, err) - return - } - - var createLink libregraph.DriveItemCreateLink - body, err := io.ReadAll(r.Body) - if err := json.Unmarshal(body, &createLink); err != nil { - logger.Error().Err(err).Interface("body", r.Body).Msg("could not create link: invalid body schema definition") - errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid body schema definition") - return - } - - createdLink, err := g.createLink(r.Context(), &driveItemID, createLink) - if err != nil { - errorcode.RenderError(w, r, err) - return - } - - perm, err := g.libreGraphPermissionFromCS3PublicShare(createdLink) - if err != nil { - errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) - return - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, *perm) -} - -// SetLinkPassword sets public link password on the cs3 api -func (g Graph) SetLinkPassword(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - _, 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 - } - - password := &libregraph.SharingLinkPassword{} - if err := StrictJSONUnmarshal(r.Body, password); 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 - } - - publicShare, err := g.getCS3PublicShareByID(ctx, permissionID) - 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(publicShare.GetResourceId(), &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 - } - - newPermission, err := g.updatePublicLinkPassword(ctx, permissionID, password.GetPassword()) - if err != nil { - errorcode.RenderError(w, r, err) - return - } - - render.Status(r, http.StatusOK) - render.JSON(w, r, *newPermission) -} - -func (g Graph) createLink(ctx context.Context, driveItemID *providerv1beta1.ResourceId, createLink libregraph.DriveItemCreateLink) (*link.PublicShare, error) { - gatewayClient, err := g.gatewaySelector.Next() - if err != nil { - g.logger.Error().Err(err).Msg("could not select next gateway client") - return nil, errorcode.New(errorcode.GeneralException, err.Error()) + s.logger.Error().Err(err).Msg("could not select next gateway client") + return libregraph.Permission{}, errorcode.New(errorcode.GeneralException, err.Error()) } statResp, err := gatewayClient.Stat( ctx, - &providerv1beta1.StatRequest{ - Ref: &providerv1beta1.Reference{ - ResourceId: driveItemID, + &storageprovider.StatRequest{ + Ref: &storageprovider.Reference{ + ResourceId: &driveItemID, Path: ".", }, }) if err != nil { - g.logger.Error().Err(err).Msg("transport error, could not stat resource") - return nil, errorcode.New(errorcode.GeneralException, err.Error()) + s.logger.Error().Err(err).Msg("transport error, could not stat resource") + return libregraph.Permission{}, errorcode.New(errorcode.GeneralException, err.Error()) } if code := statResp.GetStatus().GetCode(); code != rpc.Code_CODE_OK { - g.logger.Debug().Interface("itemID", driveItemID).Msg(statResp.GetStatus().GetMessage()) - return nil, errorcode.New(cs3StatusToErrCode(code), statResp.GetStatus().GetMessage()) + s.logger.Debug().Interface("itemID", driveItemID).Msg(statResp.GetStatus().GetMessage()) + return libregraph.Permission{}, errorcode.New(cs3StatusToErrCode(code), statResp.GetStatus().GetMessage()) } permissions, err := linktype.CS3ResourcePermissionsFromSharingLink(createLink, statResp.GetInfo().GetType()) if err != nil { - g.logger.Debug().Interface("createLink", createLink).Msg(err.Error()) - return nil, errorcode.New(errorcode.InvalidRequest, "invalid link type") + s.logger.Debug().Interface("createLink", createLink).Msg(err.Error()) + return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "invalid link type") } if createLink.GetType() == libregraph.INTERNAL && len(createLink.GetPassword()) > 0 { - return nil, errorcode.New(errorcode.InvalidRequest, "password is redundant for the internal link") + return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "password is redundant for the internal link") } req := link.CreatePublicShareRequest{ ResourceInfo: statResp.GetInfo(), @@ -148,8 +65,8 @@ func (g Graph) createLink(ctx context.Context, driveItemID *providerv1beta1.Reso if isSet { expireTime := parseAndFillUpTime(expirationDate) if expireTime == nil { - g.logger.Debug().Interface("createLink", createLink).Msg(err.Error()) - return nil, errorcode.New(errorcode.InvalidRequest, "invalid expiration date") + s.logger.Debug().Interface("createLink", createLink).Msg(err.Error()) + return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "invalid expiration date") } req.GetGrant().Expiration = expireTime } @@ -163,67 +80,105 @@ func (g Graph) createLink(ctx context.Context, driveItemID *providerv1beta1.Reso } createResp, err := gatewayClient.CreatePublicShare(ctx, &req) if err != nil { - g.logger.Error().Err(err).Msg("transport error, could not create link") - return nil, errorcode.New(errorcode.GeneralException, err.Error()) + s.logger.Error().Err(err).Msg("transport error, could not create link") + return libregraph.Permission{}, errorcode.New(errorcode.GeneralException, err.Error()) } if statusCode := createResp.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK { - return nil, errorcode.New(cs3StatusToErrCode(statusCode), createResp.Status.Message) + return libregraph.Permission{}, errorcode.New(cs3StatusToErrCode(statusCode), createResp.Status.Message) } - return createResp.GetShare(), nil + link := createResp.GetShare() + perm, err := s.libreGraphPermissionFromCS3PublicShare(link) + if err != nil { + return libregraph.Permission{}, errorcode.New(errorcode.GeneralException, err.Error()) + } + return *perm, nil } -func parseAndFillUpTime(t *time.Time) *types.Timestamp { - if t == nil || t.IsZero() { - return nil +func (s DriveItemPermissionsService) SetPublicLinkPassword(ctx context.Context, driveItemId storageprovider.ResourceId, permissionID string, password string) (libregraph.Permission, error) { + publicShare, err := s.getCS3PublicShareByID(ctx, permissionID) + if err != nil { + return libregraph.Permission{}, err } - // the link needs to be valid for the whole day - tLink := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) - tLink = tLink.Add(23*time.Hour + 59*time.Minute + 59*time.Second) - - final := tLink.UnixNano() - - return &types.Timestamp{ - Seconds: uint64(final / 1000000000), - Nanos: uint32(final % 1000000000), + // 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(publicShare.GetResourceId(), &driveItemId) { + s.logger.Debug().Msg("resourceID of shared does not match itemID") + return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "permissionID and itemID do not match") } + + permission, err := s.updatePublicLinkPassword(ctx, permissionID, password) + if err != nil { + return libregraph.Permission{}, err + } + return *permission, nil } -func (g BaseGraphService) updatePublicLinkPassword(ctx context.Context, permissionID string, password string) (*libregraph.Permission, error) { - gatewayClient, err := g.gatewaySelector.Next() +// CreateLink creates a public link on the cs3 api +func (api DriveItemPermissionsApi) CreateLink(w http.ResponseWriter, r *http.Request) { + logger := api.logger.SubloggerWithRequestID(r.Context()) + logger.Info().Msg("calling create link") + + _, driveItemID, err := GetDriveAndItemIDParam(r, &logger) if err != nil { - return nil, err + errorcode.RenderError(w, r, err) + return } - changeLinkRes, err := gatewayClient.UpdatePublicShare(ctx, &link.UpdatePublicShareRequest{ - Update: &link.UpdatePublicShareRequest_Update{ - Type: link.UpdatePublicShareRequest_Update_TYPE_PASSWORD, - Grant: &link.Grant{ - Password: password, - }, - }, - Ref: &link.PublicShareReference{ - Spec: &link.PublicShareReference_Id{ - Id: &link.PublicShareId{ - OpaqueId: permissionID, - }, - }, - }, - }) - if errCode := errorcode.FromCS3Status(changeLinkRes.GetStatus(), err); errCode != nil { - return nil, *errCode + var createLink libregraph.DriveItemCreateLink + if err = StrictJSONUnmarshal(r.Body, &createLink); err != nil { + logger.Error().Err(err).Interface("body", r.Body).Msg("could not create link: invalid body schema definition") + errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid body schema definition") + return } - permission, err := g.libreGraphPermissionFromCS3PublicShare(changeLinkRes.GetShare()) + + perm, err := api.driveItemPermissionsService.CreateLink(r.Context(), driveItemID, createLink) if err != nil { - return nil, err + errorcode.RenderError(w, r, err) + return } - return permission, nil + + render.Status(r, http.StatusOK) + render.JSON(w, r, perm) } -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() +// SetLinkPassword sets public link password on the cs3 api +func (api DriveItemPermissionsApi) SetLinkPassword(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + _, itemID, err := GetDriveAndItemIDParam(r, &api.logger) if err != nil { - g.logger.Error().Err(err).Msg("could not select next gateway client") + 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 + } + + password := &libregraph.SharingLinkPassword{} + if err = StrictJSONUnmarshal(r.Body, password); 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 + } + + newPermission, err := api.driveItemPermissionsService.SetPublicLinkPassword(ctx, itemID, permissionID, password.GetPassword()) + if err != nil { + errorcode.RenderError(w, r, err) + return + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, newPermission) +} + +func (s DriveItemPermissionsService) updatePublicLinkPermission(ctx context.Context, permissionID string, itemID *providerv1beta1.ResourceId, newPermission *libregraph.Permission) (perm *libregraph.Permission, err error) { + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + s.logger.Error().Err(err).Msg("could not select next gateway client") return nil, errorcode.New(errorcode.GeneralException, err.Error()) } @@ -246,7 +201,7 @@ func (g BaseGraphService) updatePublicLinkPermission(ctx context.Context, permis Type: link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION, Grant: &link.Grant{Expiration: parseAndFillUpTime(&expirationDate)}, } - perm, err = g.updatePublicLink(ctx, permissionID, update) + perm, err = s.updatePublicLink(ctx, permissionID, update) if err != nil { return nil, err } @@ -258,7 +213,7 @@ func (g BaseGraphService) updatePublicLinkPermission(ctx context.Context, permis Type: link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME, DisplayName: changedLink.GetLibreGraphDisplayName(), } - perm, err = g.updatePublicLink(ctx, permissionID, update) + perm, err = s.updatePublicLink(ctx, permissionID, update) if err != nil { return nil, err } @@ -278,13 +233,13 @@ func (g BaseGraphService) updatePublicLinkPermission(ctx context.Context, permis Permissions: &link.PublicSharePermissions{Permissions: permissions}, }, } - perm, err = g.updatePublicLink(ctx, permissionID, update) + perm, err = s.updatePublicLink(ctx, permissionID, update) if err != nil { return nil, err } // reset the password for the internal link if changedLink == libregraph.INTERNAL { - perm, err = g.updatePublicLinkPassword(ctx, permissionID, "") + perm, err = s.updatePublicLinkPassword(ctx, permissionID, "") if err != nil { return nil, err } @@ -294,8 +249,39 @@ func (g BaseGraphService) updatePublicLinkPermission(ctx context.Context, permis return perm, err } -func (g BaseGraphService) updatePublicLink(ctx context.Context, permissionID string, update *link.UpdatePublicShareRequest_Update) (*libregraph.Permission, error) { - gatewayClient, err := g.gatewaySelector.Next() +func (s DriveItemPermissionsService) updatePublicLinkPassword(ctx context.Context, permissionID string, password string) (*libregraph.Permission, error) { + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return nil, err + } + + changeLinkRes, err := gatewayClient.UpdatePublicShare(ctx, &link.UpdatePublicShareRequest{ + Update: &link.UpdatePublicShareRequest_Update{ + Type: link.UpdatePublicShareRequest_Update_TYPE_PASSWORD, + Grant: &link.Grant{ + Password: password, + }, + }, + Ref: &link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: &link.PublicShareId{ + OpaqueId: permissionID, + }, + }, + }, + }) + if errCode := errorcode.FromCS3Status(changeLinkRes.GetStatus(), err); errCode != nil { + return nil, *errCode + } + permission, err := s.libreGraphPermissionFromCS3PublicShare(changeLinkRes.GetShare()) + if err != nil { + return nil, err + } + return permission, nil +} + +func (s DriveItemPermissionsService) updatePublicLink(ctx context.Context, permissionID string, update *link.UpdatePublicShareRequest_Update) (*libregraph.Permission, error) { + gatewayClient, err := s.gatewaySelector.Next() if err != nil { return nil, err } @@ -315,9 +301,26 @@ func (g BaseGraphService) updatePublicLink(ctx context.Context, permissionID str return nil, *errCode } - permission, err := g.libreGraphPermissionFromCS3PublicShare(changeLinkRes.GetShare()) + permission, err := s.libreGraphPermissionFromCS3PublicShare(changeLinkRes.GetShare()) if err != nil { return nil, err } return permission, nil } + +func parseAndFillUpTime(t *time.Time) *types.Timestamp { + if t == nil || t.IsZero() { + return nil + } + + // the link needs to be valid for the whole day + tLink := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + tLink = tLink.Add(23*time.Hour + 59*time.Minute + 59*time.Second) + + final := tLink.UnixNano() + + return &types.Timestamp{ + Seconds: uint64(final / 1000000000), + Nanos: uint32(final % 1000000000), + } +} diff --git a/services/graph/pkg/service/v0/api_driveitem_permissions_links_test.go b/services/graph/pkg/service/v0/api_driveitem_permissions_links_test.go new file mode 100644 index 000000000..df913a782 --- /dev/null +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_links_test.go @@ -0,0 +1,258 @@ +package svc_test + +import ( + "context" + "errors" + "time" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "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/utils" + cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" + . "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/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" + service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" + "github.com/stretchr/testify/mock" +) + +var _ = Describe("createLinkTests", func() { + var ( + svc service.DriveItemPermissionsService + driveItemId provider.ResourceId + ctx context.Context + gatewayClient *cs3mocks.GatewayAPIClient + gatewaySelector *mocks.Selectable[gateway.GatewayAPIClient] + currentUser = &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: "user", + }, + } + ) + const ( + ViewerLinkString = "Viewer Link" + ) + + BeforeEach(func() { + var err error + 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)) + + cfg := defaults.FullDefaultConfig() + svc, err = service.NewDriveItemPermissionsService(logger, gatewaySelector, cache, cfg) + Expect(err).ToNot(HaveOccurred()) + driveItemId = provider.ResourceId{ + StorageId: "1", + SpaceId: "2", + OpaqueId: "3", + } + ctx = revactx.ContextSetUser(context.Background(), currentUser) + }) + + Describe("CreateLink", func() { + var ( + driveItemCreateLink libregraph.DriveItemCreateLink + statResponse *provider.StatResponse + createLinkResponse *link.CreatePublicShareResponse + ) + + BeforeEach(func() { + driveItemCreateLink = libregraph.DriveItemCreateLink{ + Type: nil, + ExpirationDateTime: nil, + Password: nil, + DisplayName: nil, + LibreGraphQuickLink: nil, + } + + statResponse = &provider.StatResponse{ + Status: status.NewOK(ctx), + Info: &provider.ResourceInfo{Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER}, + } + + createLinkResponse = &link.CreatePublicShareResponse{ + Status: status.NewOK(ctx), + } + + linkType, err := libregraph.NewSharingLinkTypeFromValue("view") + Expect(err).ToNot(HaveOccurred()) + driveItemCreateLink.Type = linkType + driveItemCreateLink.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour)) + permissions, err := linktype.CS3ResourcePermissionsFromSharingLink(driveItemCreateLink, provider.ResourceType_RESOURCE_TYPE_CONTAINER) + Expect(err).ToNot(HaveOccurred()) + createLinkResponse.Share = &link.PublicShare{ + Id: &link.PublicShareId{OpaqueId: "123"}, + Expiration: utils.TimeToTS(*driveItemCreateLink.ExpirationDateTime), + PasswordProtected: false, + DisplayName: ViewerLinkString, + Token: "SomeGOODCoffee", + Permissions: &link.PublicSharePermissions{Permissions: permissions}, + } + + }) + + // Public Shares / "links" in graph terms + It("creates a public link as expected (happy path)", func() { + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + gatewayClient.On("CreatePublicShare", mock.Anything, mock.Anything).Return(createLinkResponse, nil) + perm, err := svc.CreateLink(context.Background(), driveItemId, driveItemCreateLink) + Expect(err).ToNot(HaveOccurred()) + Expect(perm.GetId()).To(Equal("123")) + Expect(perm.GetExpirationDateTime().Unix()).To(Equal(driveItemCreateLink.ExpirationDateTime.Unix())) + Expect(perm.GetHasPassword()).To(Equal(false)) + Expect(perm.GetLink().LibreGraphDisplayName).To(Equal(libregraph.PtrString(ViewerLinkString))) + link := perm.GetLink() + respLinkType := link.GetType() + expected, err := libregraph.NewSharingLinkTypeFromValue("view") + Expect(err).ToNot(HaveOccurred()) + Expect(&respLinkType).To(Equal(expected)) + }) + + It("handles a failing CreateLink", func() { + statResponse.Info = &provider.ResourceInfo{Type: provider.ResourceType_RESOURCE_TYPE_FILE} + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + gatewayClient.On("CreatePublicShare", mock.Anything, mock.Anything).Return(createLinkResponse, nil) + + linkType, err := libregraph.NewSharingLinkTypeFromValue("edit") + Expect(err).ToNot(HaveOccurred()) + driveItemCreateLink.Type = linkType + permissions, err := linktype.CS3ResourcePermissionsFromSharingLink(driveItemCreateLink, provider.ResourceType_RESOURCE_TYPE_CONTAINER) + Expect(err).ToNot(HaveOccurred()) + createLinkResponse.Status = status.NewInternal(ctx, "transport error") + createLinkResponse.Share = &link.PublicShare{ + Id: &link.PublicShareId{OpaqueId: "123"}, + Permissions: &link.PublicSharePermissions{Permissions: permissions}, + } + + perm, err := svc.CreateLink(context.Background(), driveItemId, driveItemCreateLink) + Expect(err).To(MatchError(errorcode.New(errorcode.GeneralException, "transport error"))) + Expect(perm).To(BeZero()) + }) + + It("fails when the stat returns access denied", func() { + err := errors.New("no permission to stat the file") + statResponse.Status = status.NewPermissionDenied(ctx, err, err.Error()) + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + perm, err := svc.CreateLink(context.Background(), driveItemId, driveItemCreateLink) + Expect(err).To(MatchError(errorcode.New(errorcode.AccessDenied, "no permission to stat the file"))) + Expect(perm).To(BeZero()) + }) + + It("fails when the stat returns resource is locked", func() { + err := errors.New("the resource is locked") + statResponse.Status = status.NewLocked(ctx, err.Error()) + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + perm, err := svc.CreateLink(context.Background(), driveItemId, driveItemCreateLink) + Expect(err).To(MatchError(errorcode.New(errorcode.ItemIsLocked, "the resource is locked"))) + Expect(perm).To(BeZero()) + }) + + It("succeeds when the link type mapping is not successful", func() { + // we need to send a valid link type + linkType, err := libregraph.NewSharingLinkTypeFromValue("edit") + Expect(err).ToNot(HaveOccurred()) + driveItemCreateLink.Type = linkType + permissions := &provider.ResourcePermissions{ + CreateContainer: true, + InitiateFileUpload: true, + Move: true, + } + // return different permissions which do not match a link type + createLinkResponse.Share = &link.PublicShare{ + Id: &link.PublicShareId{OpaqueId: "123"}, + Expiration: utils.TimeToTS(*driveItemCreateLink.ExpirationDateTime), + PasswordProtected: false, + DisplayName: ViewerLinkString, + Token: "SomeGOODCoffee", + Permissions: &link.PublicSharePermissions{Permissions: permissions}, + } + gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil) + gatewayClient.On("CreatePublicShare", mock.Anything, mock.Anything).Return(createLinkResponse, nil) + + perm, err := svc.CreateLink(context.Background(), driveItemId, driveItemCreateLink) + Expect(err).ToNot(HaveOccurred()) + + Expect(perm.GetId()).To(Equal("123")) + Expect(perm.GetExpirationDateTime().Unix()).To(Equal(driveItemCreateLink.ExpirationDateTime.Unix())) + Expect(perm.GetHasPassword()).To(Equal(false)) + Expect(perm.GetLink().LibreGraphDisplayName).To(Equal(libregraph.PtrString(ViewerLinkString))) + respLink := perm.GetLink() + // some conversion gymnastics + respLinkType := respLink.GetType() + Expect(err).ToNot(HaveOccurred()) + mockLink := libregraph.SharingLink{} + lt, _ := linktype.SharingLinkTypeFromCS3Permissions(&link.PublicSharePermissions{Permissions: permissions}) + mockLink.Type = lt + expectedType := mockLink.GetType() + Expect(&respLinkType).To(Equal(&expectedType)) + libreGraphActions := perm.LibreGraphPermissionsActions + Expect(libreGraphActions[0]).To(Equal("libre.graph/driveItem/children/create")) + Expect(libreGraphActions[1]).To(Equal("libre.graph/driveItem/upload/create")) + Expect(libreGraphActions[2]).To(Equal("libre.graph/driveItem/path/update")) + }) + }) + Describe("SetLinPassword", func() { + var ( + updatePublicShareMockResponse link.UpdatePublicShareResponse + getPublicShareResponse link.GetPublicShareResponse + ) + + const TestLinkName = "Test Link" + + BeforeEach(func() { + updatePublicShareMockResponse = link.UpdatePublicShareResponse{ + Status: status.NewOK(ctx), + Share: &link.PublicShare{DisplayName: TestLinkName}, + } + getPublicShareResponse = 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", + }, + } + }) + + It("updates the password on a public share", func() { + gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(&getPublicShareResponse, nil) + + updatePublicShareMockResponse.Share.Permissions = &link.PublicSharePermissions{ + Permissions: linktype.NewViewLinkPermissionSet().Permissions, + } + updatePublicShareMockResponse.Share.PasswordProtected = true + gatewayClient.On("UpdatePublicShare", + mock.Anything, + mock.MatchedBy(func(req *link.UpdatePublicShareRequest) bool { + return req.GetRef().GetId().GetOpaqueId() == "permissionid" + }), + ).Return(&updatePublicShareMockResponse, nil) + + perm, err := svc.SetPublicLinkPassword(context.Background(), driveItemId, "permissionid", "OC123!") + Expect(err).ToNot(HaveOccurred()) + linkType := perm.Link.GetType() + Expect(string(linkType)).To(Equal("view")) + Expect(perm.GetHasPassword()).To(BeTrue()) + }) + }) +}) diff --git a/services/graph/pkg/service/v0/links_test.go b/services/graph/pkg/service/v0/links_test.go deleted file mode 100644 index 4fd05fa73..000000000 --- a/services/graph/pkg/service/v0/links_test.go +++ /dev/null @@ -1,381 +0,0 @@ -package svc_test - -import ( - "context" - "encoding/json" - "errors" - "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" - link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" - provider "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/rgrpc/todo/pool" - "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/shared" - "github.com/owncloud/ocis/v2/services/graph/mocks" - "github.com/owncloud/ocis/v2/services/graph/pkg/config" - "github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults" - identitymocks "github.com/owncloud/ocis/v2/services/graph/pkg/identity/mocks" - "github.com/owncloud/ocis/v2/services/graph/pkg/linktype" - service "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" - "github.com/stretchr/testify/mock" - "google.golang.org/grpc" -) - -var _ = Describe("createLinkTests", 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 - currentUser = &userpb.User{ - Id: &userpb.UserId{ - OpaqueId: "user", - }, - } - - rr *httptest.ResponseRecorder - ) - const ( - ViewerLinkString = "Viewer Link" - ItemID = "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!1177add3-b4eb-434e-a2e8-1859b31b17bf" - DriveId = "f0042750-23c5-441c-9f2c-ff7c53e5bd2a$cd621428-dfbe-44c1-9393-65bf0dd440a6!cd621428-dfbe-44c1-9393-65bf0dd440a6" - ) - - BeforeEach(func() { - eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - rr = httptest.NewRecorder() - - pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") - gatewayClient = &cs3mocks.GatewayAPIClient{} - gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient]( - "GatewaySelector", - "com.owncloud.api.gateway", - func(cc *grpc.ClientConn) gateway.GatewayAPIClient { - return gatewayClient - }, - ) - - identityBackend = &identitymocks.Backend{} - - rr = httptest.NewRecorder() - ctx = context.Background() - - cfg = defaults.FullDefaultConfig() - cfg.Identity.LDAP.CACert = "" // skip the startup checks, we don't use LDAP at all in this tests - cfg.TokenManager.JWTSecret = "loremipsum" - cfg.Commons = &shared.Commons{} - cfg.GRPCClientTLS = &shared.GRPCClientTLS{} - cfg.FilesSharing.EnableResharing = true - - svc, _ = service.NewService( - service.Config(cfg), - service.WithGatewaySelector(gatewaySelector), - service.EventsPublisher(&eventsPublisher), - service.WithIdentityBackend(identityBackend), - ) - }) - - Describe("CreateLink", func() { - var ( - itemID string - driveItemCreateLink *libregraph.DriveItemCreateLink - statMock *mock.Call - statResponse *provider.StatResponse - createLinkResponse *link.CreatePublicShareResponse - createLinkMock *mock.Call - ) - - BeforeEach(func() { - itemID = ItemID - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", DriveId) - rctx.URLParams.Add("itemID", itemID) - - ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) - ctx = revactx.ContextSetUser(ctx, currentUser) - - driveItemCreateLink = &libregraph.DriveItemCreateLink{ - Type: nil, - ExpirationDateTime: nil, - Password: nil, - DisplayName: nil, - LibreGraphQuickLink: nil, - } - - statMock = gatewayClient.On("Stat", mock.Anything, mock.Anything) - statResponse = &provider.StatResponse{ - Status: status.NewOK(ctx), - } - statMock.Return(statResponse, nil) - - createLinkMock = gatewayClient.On("CreatePublicShare", mock.Anything, mock.Anything) - createLinkResponse = &link.CreatePublicShareResponse{ - Status: status.NewOK(ctx), - } - createLinkMock.Return(createLinkResponse, nil) - - linkType, err := libregraph.NewSharingLinkTypeFromValue("view") - Expect(err).ToNot(HaveOccurred()) - driveItemCreateLink.Type = linkType - driveItemCreateLink.ExpirationDateTime = libregraph.PtrTime(time.Now().Add(time.Hour)) - permissions, err := linktype.CS3ResourcePermissionsFromSharingLink(*driveItemCreateLink, provider.ResourceType_RESOURCE_TYPE_CONTAINER) - Expect(err).ToNot(HaveOccurred()) - createLinkResponse.Share = &link.PublicShare{ - Id: &link.PublicShareId{OpaqueId: "123"}, - Expiration: utils.TimeToTS(*driveItemCreateLink.ExpirationDateTime), - PasswordProtected: false, - DisplayName: ViewerLinkString, - Token: "SomeGOODCoffee", - Permissions: &link.PublicSharePermissions{Permissions: permissions}, - } - - statResponse.Info = &provider.ResourceInfo{Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER} - }) - - toJSONReader := func(v any) *strings.Reader { - driveItemInviteBytes, err := json.Marshal(v) - Expect(err).ToNot(HaveOccurred()) - - return strings.NewReader(string(driveItemInviteBytes)) - } - - // Public Shares / "links" in graph terms - It("creates a public link as expected (happy path)", func() { - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(http.StatusOK)) - - var createLinkResponseBody *libregraph.Permission - err := json.Unmarshal(rr.Body.Bytes(), &createLinkResponseBody) - Expect(err).ToNot(HaveOccurred()) - Expect(createLinkResponseBody.GetId()).To(Equal("123")) - Expect(createLinkResponseBody.GetExpirationDateTime().Unix()).To(Equal(driveItemCreateLink.ExpirationDateTime.Unix())) - Expect(createLinkResponseBody.GetHasPassword()).To(Equal(false)) - Expect(createLinkResponseBody.GetLink().LibreGraphDisplayName).To(Equal(libregraph.PtrString(ViewerLinkString))) - link := createLinkResponseBody.GetLink() - respLinkType := link.GetType() - expected, err := libregraph.NewSharingLinkTypeFromValue("view") - Expect(err).ToNot(HaveOccurred()) - Expect(&respLinkType).To(Equal(expected)) - }) - - It("handles a failing CreateLink", func() { - linkType, err := libregraph.NewSharingLinkTypeFromValue("edit") - Expect(err).ToNot(HaveOccurred()) - driveItemCreateLink.Type = linkType - permissions, err := linktype.CS3ResourcePermissionsFromSharingLink(*driveItemCreateLink, provider.ResourceType_RESOURCE_TYPE_CONTAINER) - Expect(err).ToNot(HaveOccurred()) - createLinkResponse.Status = status.NewInternal(ctx, "transport error") - createLinkResponse.Share = &link.PublicShare{ - Id: &link.PublicShareId{OpaqueId: "123"}, - Permissions: &link.PublicSharePermissions{Permissions: permissions}, - } - - statResponse.Info = &provider.ResourceInfo{Type: provider.ResourceType_RESOURCE_TYPE_FILE} - - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusInternalServerError)) - var odataError libregraph.OdataError - err = json.Unmarshal(rr.Body.Bytes(), &odataError) - Expect(err).ToNot(HaveOccurred()) - getError := odataError.GetError() - Expect(getError.GetCode()).To(Equal("generalException")) - Expect(getError.GetMessage()).To(Equal("transport error")) - }) - - It("fails due to an invalid spaceID", func() { - driveItemCreateLink = libregraph.NewDriveItemCreateLink() - - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/graph/v1beta1/drives/space-id/items/item-id/createShare", nil), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - var odataError libregraph.OdataError - err := json.Unmarshal(rr.Body.Bytes(), &odataError) - Expect(err).ToNot(HaveOccurred()) - getError := odataError.GetError() - Expect(getError.GetCode()).To(Equal("invalidRequest")) - Expect(getError.GetMessage()).To(Equal("invalid driveID")) - }) - - It("fails due to an empty itemID", func() { - driveItemCreateLink = libregraph.NewDriveItemCreateLink() - - itemID = "" - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", DriveId) - rctx.URLParams.Add("itemID", itemID) - - ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) - ctx = revactx.ContextSetUser(ctx, currentUser) - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/", nil). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - var odataError libregraph.OdataError - err := json.Unmarshal(rr.Body.Bytes(), &odataError) - Expect(err).ToNot(HaveOccurred()) - getError := odataError.GetError() - Expect(getError.GetCode()).To(Equal("invalidRequest")) - Expect(getError.GetMessage()).To(Equal("invalid itemID")) - }) - - It("fails due to an itemID on a different storage", func() { - driveItemCreateLink = libregraph.NewDriveItemCreateLink() - - // use wrong storageID within itemID - itemID = "f0042750-23c5-441c-9f2c-ff7c53e5bd2b$cd621428-dfbe-44c1-9393-65bf0dd440a6!1177add3-b4eb-434e-a2e8-1859b31b17bf" - rctx := chi.NewRouteContext() - rctx.URLParams.Add("driveID", DriveId) - rctx.URLParams.Add("itemID", itemID) - - ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) - ctx = revactx.ContextSetUser(ctx, currentUser) - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/", nil). - WithContext(ctx), - ) - Expect(rr.Code).To(Equal(http.StatusNotFound)) - var odataError libregraph.OdataError - err := json.Unmarshal(rr.Body.Bytes(), &odataError) - Expect(err).ToNot(HaveOccurred()) - getError := odataError.GetError() - Expect(getError.GetCode()).To(Equal("itemNotFound")) - Expect(getError.GetMessage()).To(Equal("driveID and itemID do not match")) - }) - - // Public Shares / "links" in graph terms - It("fails when creating a public link with an empty request body", func() { - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/", nil). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(http.StatusBadRequest)) - - var odataError libregraph.OdataError - err := json.Unmarshal(rr.Body.Bytes(), &odataError) - Expect(err).ToNot(HaveOccurred()) - getError := odataError.GetError() - Expect(getError.GetCode()).To(Equal("invalidRequest")) - Expect(getError.GetMessage()).To(Equal("invalid body schema definition")) - - }) - - It("fails when the stat returns access denied", func() { - err := errors.New("no permission to stat the file") - statResponse.Status = status.NewPermissionDenied(ctx, err, err.Error()) - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(http.StatusForbidden)) - - var odataError libregraph.OdataError - err = json.Unmarshal(rr.Body.Bytes(), &odataError) - Expect(err).ToNot(HaveOccurred()) - getError := odataError.GetError() - Expect(getError.GetCode()).To(Equal("accessDenied")) - Expect(getError.GetMessage()).To(Equal("no permission to stat the file")) - }) - - It("fails when the stat returns resource is locked", func() { - err := errors.New("the resource is locked") - statResponse.Status = status.NewLocked(ctx, err.Error()) - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(http.StatusLocked)) - - var odataError libregraph.OdataError - err = json.Unmarshal(rr.Body.Bytes(), &odataError) - Expect(err).ToNot(HaveOccurred()) - getError := odataError.GetError() - Expect(getError.GetCode()).To(Equal("itemIsLocked")) - Expect(getError.GetMessage()).To(Equal("the resource is locked")) - }) - - It("succeeds when the link type mapping is not successful", func() { - // we need to send a valid link type - linkType, err := libregraph.NewSharingLinkTypeFromValue("edit") - Expect(err).ToNot(HaveOccurred()) - driveItemCreateLink.Type = linkType - permissions := &provider.ResourcePermissions{ - CreateContainer: true, - InitiateFileUpload: true, - Move: true, - } - // return different permissions which do not match a link type - createLinkResponse.Share = &link.PublicShare{ - Id: &link.PublicShareId{OpaqueId: "123"}, - Expiration: utils.TimeToTS(*driveItemCreateLink.ExpirationDateTime), - PasswordProtected: false, - DisplayName: ViewerLinkString, - Token: "SomeGOODCoffee", - Permissions: &link.PublicSharePermissions{Permissions: permissions}, - } - svc.CreateLink( - rr, - httptest.NewRequest(http.MethodPost, "/", toJSONReader(driveItemCreateLink)). - WithContext(ctx), - ) - - Expect(rr.Code).To(Equal(http.StatusOK)) - - var createLinkResponseBody *libregraph.Permission - err = json.Unmarshal(rr.Body.Bytes(), &createLinkResponseBody) - Expect(err).ToNot(HaveOccurred()) - Expect(createLinkResponseBody.GetId()).To(Equal("123")) - Expect(createLinkResponseBody.GetExpirationDateTime().Unix()).To(Equal(driveItemCreateLink.ExpirationDateTime.Unix())) - Expect(createLinkResponseBody.GetHasPassword()).To(Equal(false)) - Expect(createLinkResponseBody.GetLink().LibreGraphDisplayName).To(Equal(libregraph.PtrString(ViewerLinkString))) - respLink := createLinkResponseBody.GetLink() - // some conversion gymnastics - respLinkType := respLink.GetType() - Expect(err).ToNot(HaveOccurred()) - mockLink := libregraph.SharingLink{} - lt, _ := linktype.SharingLinkTypeFromCS3Permissions(&link.PublicSharePermissions{Permissions: permissions}) - mockLink.Type = lt - expectedType := mockLink.GetType() - Expect(&respLinkType).To(Equal(&expectedType)) - libreGraphActions := createLinkResponseBody.LibreGraphPermissionsActions - Expect(libreGraphActions[0]).To(Equal("libre.graph/driveItem/children/create")) - Expect(libreGraphActions[1]).To(Equal("libre.graph/driveItem/upload/create")) - Expect(libreGraphActions[2]).To(Equal("libre.graph/driveItem/path/update")) - }) - }) -}) diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index a84d928ff..f7cda6fda 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -109,8 +109,6 @@ type Service interface { GetRootDriveChildren(w http.ResponseWriter, r *http.Request) GetDriveItem(w http.ResponseWriter, r *http.Request) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) - CreateLink(w http.ResponseWriter, r *http.Request) - SetLinkPassword(writer http.ResponseWriter, request *http.Request) CreateUploadSession(w http.ResponseWriter, r *http.Request) @@ -250,10 +248,10 @@ func NewService(opts ...Option) (Graph, error) { r.Route("/{permissionID}", func(r chi.Router) { r.Delete("/", driveItemPermissionsApi.DeletePermission) r.Patch("/", driveItemPermissionsApi.UpdatePermission) - r.Post("/setPassword", svc.SetLinkPassword) + r.Post("/setPassword", driveItemPermissionsApi.SetLinkPassword) }) }) - r.Post("/createLink", svc.CreateLink) + r.Post("/createLink", driveItemPermissionsApi.CreateLink) }) }) })