From 26400a49b2905d44e050b6d6c2d67f528a986799 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Wed, 27 Mar 2024 20:26:48 +0100 Subject: [PATCH] feat(graph): add more //root/ endpoints This add support for the following graph routes: POST /drives/{driveID}/root/createLink DELETE /drives/{driveID}/root/permissions/{permissionID} PATCH /drives/{driveID}/root/permissions/{permissionID} and POST /drives/{driveID}/root/permissions/{permissionID}/setPassword This should significantly improve handling of permissions on spaces as there is no need to figure to the drive's root itemid anymore. Partial Fix: #8351 --- .../mocks/drive_item_permissions_provider.go | 224 ++++++++++++++++++ .../service/v0/api_driveitem_permissions.go | 108 +++++++++ .../v0/api_driveitem_permissions_links.go | 99 ++++++++ services/graph/pkg/service/v0/service.go | 8 +- 4 files changed, 438 insertions(+), 1 deletion(-) diff --git a/services/graph/mocks/drive_item_permissions_provider.go b/services/graph/mocks/drive_item_permissions_provider.go index 170c86c11bd..59d416f5eda 100644 --- a/services/graph/mocks/drive_item_permissions_provider.go +++ b/services/graph/mocks/drive_item_permissions_provider.go @@ -82,6 +82,64 @@ func (_c *DriveItemPermissionsProvider_CreateLink_Call) RunAndReturn(run func(co return _c } +// CreateSpaceRootLink provides a mock function with given fields: ctx, driveID, createLink +func (_m *DriveItemPermissionsProvider) CreateSpaceRootLink(ctx context.Context, driveID providerv1beta1.ResourceId, createLink libregraph.DriveItemCreateLink) (libregraph.Permission, error) { + ret := _m.Called(ctx, driveID, createLink) + + if len(ret) == 0 { + panic("no return value specified for CreateSpaceRootLink") + } + + 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, driveID, createLink) + } + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemCreateLink) libregraph.Permission); ok { + r0 = rf(ctx, driveID, 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, driveID, createLink) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DriveItemPermissionsProvider_CreateSpaceRootLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateSpaceRootLink' +type DriveItemPermissionsProvider_CreateSpaceRootLink_Call struct { + *mock.Call +} + +// CreateSpaceRootLink is a helper method to define mock.On call +// - ctx context.Context +// - driveID providerv1beta1.ResourceId +// - createLink libregraph.DriveItemCreateLink +func (_e *DriveItemPermissionsProvider_Expecter) CreateSpaceRootLink(ctx interface{}, driveID interface{}, createLink interface{}) *DriveItemPermissionsProvider_CreateSpaceRootLink_Call { + return &DriveItemPermissionsProvider_CreateSpaceRootLink_Call{Call: _e.mock.On("CreateSpaceRootLink", ctx, driveID, createLink)} +} + +func (_c *DriveItemPermissionsProvider_CreateSpaceRootLink_Call) Run(run func(ctx context.Context, driveID providerv1beta1.ResourceId, createLink libregraph.DriveItemCreateLink)) *DriveItemPermissionsProvider_CreateSpaceRootLink_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_CreateSpaceRootLink_Call) Return(_a0 libregraph.Permission, _a1 error) *DriveItemPermissionsProvider_CreateSpaceRootLink_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DriveItemPermissionsProvider_CreateSpaceRootLink_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, libregraph.DriveItemCreateLink) (libregraph.Permission, error)) *DriveItemPermissionsProvider_CreateSpaceRootLink_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) @@ -130,6 +188,54 @@ func (_c *DriveItemPermissionsProvider_DeletePermission_Call) RunAndReturn(run f return _c } +// DeleteSpaceRootPermission provides a mock function with given fields: ctx, driveID, permissionID +func (_m *DriveItemPermissionsProvider) DeleteSpaceRootPermission(ctx context.Context, driveID providerv1beta1.ResourceId, permissionID string) error { + ret := _m.Called(ctx, driveID, permissionID) + + if len(ret) == 0 { + panic("no return value specified for DeleteSpaceRootPermission") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string) error); ok { + r0 = rf(ctx, driveID, permissionID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteSpaceRootPermission' +type DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call struct { + *mock.Call +} + +// DeleteSpaceRootPermission is a helper method to define mock.On call +// - ctx context.Context +// - driveID providerv1beta1.ResourceId +// - permissionID string +func (_e *DriveItemPermissionsProvider_Expecter) DeleteSpaceRootPermission(ctx interface{}, driveID interface{}, permissionID interface{}) *DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call { + return &DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call{Call: _e.mock.On("DeleteSpaceRootPermission", ctx, driveID, permissionID)} +} + +func (_c *DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call) Run(run func(ctx context.Context, driveID providerv1beta1.ResourceId, permissionID string)) *DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId), args[2].(string)) + }) + return _c +} + +func (_c *DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call) Return(_a0 error) *DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, string) error) *DriveItemPermissionsProvider_DeleteSpaceRootPermission_Call { + _c.Call.Return(run) + return _c +} + // Invite provides a mock function with given fields: ctx, resourceId, invite func (_m *DriveItemPermissionsProvider) Invite(ctx context.Context, resourceId providerv1beta1.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) { ret := _m.Called(ctx, resourceId, invite) @@ -361,6 +467,65 @@ func (_c *DriveItemPermissionsProvider_SetPublicLinkPassword_Call) RunAndReturn( return _c } +// SetPublicLinkPasswordOnSpaceRoot provides a mock function with given fields: ctx, driveID, permissionID, password +func (_m *DriveItemPermissionsProvider) SetPublicLinkPasswordOnSpaceRoot(ctx context.Context, driveID providerv1beta1.ResourceId, permissionID string, password string) (libregraph.Permission, error) { + ret := _m.Called(ctx, driveID, permissionID, password) + + if len(ret) == 0 { + panic("no return value specified for SetPublicLinkPasswordOnSpaceRoot") + } + + 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, driveID, permissionID, password) + } + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string, string) libregraph.Permission); ok { + r0 = rf(ctx, driveID, 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, driveID, permissionID, password) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPublicLinkPasswordOnSpaceRoot' +type DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_Call struct { + *mock.Call +} + +// SetPublicLinkPasswordOnSpaceRoot is a helper method to define mock.On call +// - ctx context.Context +// - driveID providerv1beta1.ResourceId +// - permissionID string +// - password string +func (_e *DriveItemPermissionsProvider_Expecter) SetPublicLinkPasswordOnSpaceRoot(ctx interface{}, driveID interface{}, permissionID interface{}, password interface{}) *DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_Call { + return &DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_Call{Call: _e.mock.On("SetPublicLinkPasswordOnSpaceRoot", ctx, driveID, permissionID, password)} +} + +func (_c *DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_Call) Run(run func(ctx context.Context, driveID providerv1beta1.ResourceId, permissionID string, password string)) *DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_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_SetPublicLinkPasswordOnSpaceRoot_Call) Return(_a0 libregraph.Permission, _a1 error) *DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, string, string) (libregraph.Permission, error)) *DriveItemPermissionsProvider_SetPublicLinkPasswordOnSpaceRoot_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) @@ -478,6 +643,65 @@ func (_c *DriveItemPermissionsProvider_UpdatePermission_Call) RunAndReturn(run f return _c } +// UpdateSpaceRootPermission provides a mock function with given fields: ctx, driveID, permissionID, newPermission +func (_m *DriveItemPermissionsProvider) UpdateSpaceRootPermission(ctx context.Context, driveID providerv1beta1.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error) { + ret := _m.Called(ctx, driveID, permissionID, newPermission) + + if len(ret) == 0 { + panic("no return value specified for UpdateSpaceRootPermission") + } + + 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, driveID, permissionID, newPermission) + } + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string, libregraph.Permission) libregraph.Permission); ok { + r0 = rf(ctx, driveID, 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, driveID, permissionID, newPermission) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DriveItemPermissionsProvider_UpdateSpaceRootPermission_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateSpaceRootPermission' +type DriveItemPermissionsProvider_UpdateSpaceRootPermission_Call struct { + *mock.Call +} + +// UpdateSpaceRootPermission is a helper method to define mock.On call +// - ctx context.Context +// - driveID providerv1beta1.ResourceId +// - permissionID string +// - newPermission libregraph.Permission +func (_e *DriveItemPermissionsProvider_Expecter) UpdateSpaceRootPermission(ctx interface{}, driveID interface{}, permissionID interface{}, newPermission interface{}) *DriveItemPermissionsProvider_UpdateSpaceRootPermission_Call { + return &DriveItemPermissionsProvider_UpdateSpaceRootPermission_Call{Call: _e.mock.On("UpdateSpaceRootPermission", ctx, driveID, permissionID, newPermission)} +} + +func (_c *DriveItemPermissionsProvider_UpdateSpaceRootPermission_Call) Run(run func(ctx context.Context, driveID providerv1beta1.ResourceId, permissionID string, newPermission libregraph.Permission)) *DriveItemPermissionsProvider_UpdateSpaceRootPermission_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_UpdateSpaceRootPermission_Call) Return(_a0 libregraph.Permission, _a1 error) *DriveItemPermissionsProvider_UpdateSpaceRootPermission_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DriveItemPermissionsProvider_UpdateSpaceRootPermission_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, string, libregraph.Permission) (libregraph.Permission, error)) *DriveItemPermissionsProvider_UpdateSpaceRootPermission_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 461441d380b..8e6e8968285 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions.go @@ -36,9 +36,13 @@ 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 + DeleteSpaceRootPermission(ctx context.Context, driveID storageprovider.ResourceId, permissionID string) error UpdatePermission(ctx context.Context, itemID storageprovider.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error) + UpdateSpaceRootPermission(ctx context.Context, driveID storageprovider.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error) CreateLink(ctx context.Context, driveItemID storageprovider.ResourceId, createLink libregraph.DriveItemCreateLink) (libregraph.Permission, error) + CreateSpaceRootLink(ctx context.Context, driveID storageprovider.ResourceId, createLink libregraph.DriveItemCreateLink) (libregraph.Permission, error) SetPublicLinkPassword(ctx context.Context, driveItemID storageprovider.ResourceId, permissionID string, password string) (libregraph.Permission, error) + SetPublicLinkPasswordOnSpaceRoot(ctx context.Context, driveID storageprovider.ResourceId, permissionID string, password string) (libregraph.Permission, error) } // DriveItemPermissionsService contains the production business logic for everything that relates to permissions on drive items. @@ -345,6 +349,25 @@ func (s DriveItemPermissionsService) DeletePermission(ctx context.Context, itemI return errorcode.New(errorcode.GeneralException, "failed to delete permission") } +func (s DriveItemPermissionsService) DeleteSpaceRootPermission(ctx context.Context, driveID storageprovider.ResourceId, permissionID string) error { + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return err + } + + space, err := utils.GetSpace(ctx, storagespace.FormatResourceID(driveID), gatewayClient) + if err != nil { + return err + } + + if space.SpaceType != "project" { + return errorcode.New(errorcode.InvalidRequest, "unsupported space type") + } + + rootResourceID := space.GetRoot() + return s.DeletePermission(ctx, *rootResourceID, permissionID) +} + 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 { @@ -375,6 +398,25 @@ func (s DriveItemPermissionsService) UpdatePermission(ctx context.Context, itemI return *updatedPermission, nil } +func (s DriveItemPermissionsService) UpdateSpaceRootPermission(ctx context.Context, driveID storageprovider.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error) { + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return libregraph.Permission{}, err + } + + space, err := utils.GetSpace(ctx, storagespace.FormatResourceID(driveID), gatewayClient) + if err != nil { + return libregraph.Permission{}, err + } + + if space.SpaceType != "project" { + return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "unsupported space type") + } + + rootResourceID := space.GetRoot() + return s.UpdatePermission(ctx, *rootResourceID, permissionID, newPermission) +} + // 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 { @@ -523,6 +565,33 @@ func (api DriveItemPermissionsApi) DeletePermission(w http.ResponseWriter, r *ht render.NoContent(w, r) } +func (api DriveItemPermissionsApi) DeleteSpaceRootPermission(w http.ResponseWriter, r *http.Request) { + driveID, err := parseIDParam(r, "driveID") + if err != nil { + msg := "could not parse driveID" + api.logger.Debug().Err(err).Msg(msg) + errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg) + 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 + } + + ctx := r.Context() + err = api.driveItemPermissionsService.DeleteSpaceRootPermission(ctx, driveID, permissionID) + if err != nil { + errorcode.RenderError(w, r, err) + return + } + + 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 { @@ -560,3 +629,42 @@ func (api DriveItemPermissionsApi) UpdatePermission(w http.ResponseWriter, r *ht render.Status(r, http.StatusOK) render.JSON(w, r, &updatedPermission) } + +func (api DriveItemPermissionsApi) UpdateSpaceRootPermission(w http.ResponseWriter, r *http.Request) { + driveID, err := parseIDParam(r, "driveID") + if err != nil { + msg := "could not parse driveID" + api.logger.Debug().Err(err).Msg(msg) + errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg) + 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.UpdateSpaceRootPermission(ctx, driveID, 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_links.go b/services/graph/pkg/service/v0/api_driveitem_permissions_links.go index d3ce7a8b480..2b2deaf115e 100644 --- a/services/graph/pkg/service/v0/api_driveitem_permissions_links.go +++ b/services/graph/pkg/service/v0/api_driveitem_permissions_links.go @@ -12,6 +12,7 @@ import ( 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/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/chi/v5" "github.com/go-chi/render" @@ -94,6 +95,24 @@ func (s DriveItemPermissionsService) CreateLink(ctx context.Context, driveItemID return *perm, nil } +func (s DriveItemPermissionsService) CreateSpaceRootLink(ctx context.Context, driveID storageprovider.ResourceId, createLink libregraph.DriveItemCreateLink) (libregraph.Permission, error) { + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return libregraph.Permission{}, err + } + space, err := utils.GetSpace(ctx, storagespace.FormatResourceID(driveID), gatewayClient) + if err != nil { + return libregraph.Permission{}, err + } + + if space.SpaceType != "project" { + return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "unsupported space type") + } + + rootResourceID := space.GetRoot() + return s.CreateLink(ctx, *rootResourceID, createLink) +} + 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 { @@ -114,6 +133,23 @@ func (s DriveItemPermissionsService) SetPublicLinkPassword(ctx context.Context, return *permission, nil } +func (s DriveItemPermissionsService) SetPublicLinkPasswordOnSpaceRoot(ctx context.Context, driveID storageprovider.ResourceId, permissionID string, password string) (libregraph.Permission, error) { + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + return libregraph.Permission{}, err + } + space, err := utils.GetSpace(ctx, storagespace.FormatResourceID(driveID), gatewayClient) + if err != nil { + return libregraph.Permission{}, err + } + + if space.SpaceType != "project" { + return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "unsupported space type") + } + rootResourceID := space.GetRoot() + return s.SetPublicLinkPassword(ctx, *rootResourceID, permissionID, password) +} + // 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()) @@ -142,6 +178,35 @@ func (api DriveItemPermissionsApi) CreateLink(w http.ResponseWriter, r *http.Req render.JSON(w, r, perm) } +func (api DriveItemPermissionsApi) CreateSpaceRootLink(w http.ResponseWriter, r *http.Request) { + logger := api.logger.SubloggerWithRequestID(r.Context()) + logger.Info().Msg("calling create link") + + driveID, err := parseIDParam(r, "driveID") + if err != nil { + msg := "could not parse driveID" + api.logger.Debug().Err(err).Msg(msg) + errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg) + return + } + + 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 + } + + perm, err := api.driveItemPermissionsService.CreateSpaceRootLink(r.Context(), driveID, createLink) + if err != nil { + errorcode.RenderError(w, r, err) + return + } + + render.Status(r, http.StatusOK) + render.JSON(w, r, perm) +} + // SetLinkPassword sets public link password on the cs3 api func (api DriveItemPermissionsApi) SetLinkPassword(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -175,6 +240,40 @@ func (api DriveItemPermissionsApi) SetLinkPassword(w http.ResponseWriter, r *htt render.JSON(w, r, newPermission) } +func (api DriveItemPermissionsApi) SetSpaceRootLinkPassword(w http.ResponseWriter, r *http.Request) { + driveID, err := parseIDParam(r, "driveID") + if err != nil { + msg := "could not parse driveID" + api.logger.Debug().Err(err).Msg(msg) + errorcode.InvalidRequest.Render(w, r, http.StatusUnprocessableEntity, msg) + 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 + } + + ctx := r.Context() + newPermission, err := api.driveItemPermissionsService.SetPublicLinkPasswordOnSpaceRoot(ctx, driveID, 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 { diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index f7cda6fda35..df4e99aadf5 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -236,13 +236,20 @@ func NewService(opts ...Option) (Graph, error) { r.Route("/root", func(r chi.Router) { r.Post("/children", drivesDriveItemApi.CreateDriveItem) r.Post("/invite", driveItemPermissionsApi.SpaceRootInvite) + r.Post("/createLink", driveItemPermissionsApi.CreateSpaceRootLink) r.Route("/permissions", func(r chi.Router) { r.Get("/", driveItemPermissionsApi.ListSpaceRootPermissions) + r.Route("/{permissionID}", func(r chi.Router) { + r.Delete("/", driveItemPermissionsApi.DeleteSpaceRootPermission) + r.Patch("/", driveItemPermissionsApi.UpdateSpaceRootPermission) + r.Post("/setPassword", driveItemPermissionsApi.SetLinkPassword) + }) }) }) r.Route("/items/{itemID}", func(r chi.Router) { r.Delete("/", drivesDriveItemApi.DeleteDriveItem) r.Post("/invite", driveItemPermissionsApi.Invite) + r.Post("/createLink", driveItemPermissionsApi.CreateLink) r.Route("/permissions", func(r chi.Router) { r.Get("/", driveItemPermissionsApi.ListPermissions) r.Route("/{permissionID}", func(r chi.Router) { @@ -251,7 +258,6 @@ func NewService(opts ...Option) (Graph, error) { r.Post("/setPassword", driveItemPermissionsApi.SetLinkPassword) }) }) - r.Post("/createLink", driveItemPermissionsApi.CreateLink) }) }) })