feat(sharing-ng): Add support for deleting space permissions

This commit is contained in:
Ralf Haferkamp
2024-03-25 12:30:33 +01:00
committed by Ralf Haferkamp
parent a5287af3bd
commit dd2d6f4f85
3 changed files with 175 additions and 85 deletions
+104 -33
View File
@@ -42,6 +42,15 @@ import (
"github.com/owncloud/ocis/v2/services/graph/pkg/validate"
)
type PermissionType int
const (
Unknown PermissionType = iota
Public
User
Space
)
// CreateUploadSession create an upload session to allow your app to upload files up to the maximum file size.
// An upload session allows your app to upload ranges of the file in sequential API requests, which allows the
// transfer to be resumed if a connection is dropped while the upload is in progress.
@@ -409,31 +418,11 @@ func (g Graph) ListPermissions(w http.ResponseWriter, r *http.Request) {
driveItems := make(driveItemsByResourceID)
if IsSpaceRoot(statResponse.GetInfo().GetId()) {
// this is a space root, get permissions via storage space API
filters := []*storageprovider.ListStorageSpacesRequest_Filter{
listStorageSpacesIDFilter(statResponse.GetInfo().GetSpace().GetId().GetOpaqueId()),
}
res, err := g.ListStorageSpacesWithFilters(ctx, filters, true)
switch {
case err != nil:
g.logger.Error().Err(err).Msg("could not get drive: transport error")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
case res.Status.Code != cs3rpc.Code_CODE_OK:
if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
// the client is doing a lookup for a specific space, therefore we need to return
// not found to the caller
g.logger.Debug().Msg("could not get drive: not found")
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
return
}
g.logger.Debug().
Str("grpcmessage", res.GetStatus().GetMessage()).
Msg("could not get drive: grpc error")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
permissions, err := g.getSpaceRootPermissions(ctx, statResponse.GetInfo().GetSpace().GetId())
if err != nil {
errorcode.RenderError(w, r, err)
return
}
permissions := g.cs3PermissionsToLibreGraph(ctx, res.GetStorageSpaces()[0], APIVersion_1_Beta_1)
collectionOfPermissions.Value = permissions
} else {
// "normal" driveItem, populate user permissions via share providers
@@ -695,15 +684,26 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
}
ctx := r.Context()
isUserPermission := true
var permissionType PermissionType
// Check if the id is referring to a User Share
sharedResourceID, err := g.getUserPermissionResourceID(ctx, permissionID)
var errcode errorcode.Error
if err != nil && errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound {
// there is no user share with that ID, so lets check if it is referring to a public link
isUserPermission = false
sharedResourceID, err = g.getLinkPermissionResourceID(ctx, permissionID)
sharedResourceID, err := g.getLinkPermissionResourceID(ctx, permissionID)
switch {
// Check if the ID is referring to a public share
case err == nil:
permissionType = Public
// If the item id is referring to a space root an this is not a public share
// we have to deal with space permissions
case IsSpaceRoot(&itemID):
permissionType = Space
sharedResourceID = &itemID
err = nil
// If this is neither a public share nor a space permission, check if this is a
// user share
default:
sharedResourceID, err = g.getUserPermissionResourceID(ctx, permissionID)
if err == nil {
permissionType = User
}
}
if err != nil {
@@ -719,10 +719,13 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
return
}
if isUserPermission {
switch permissionType {
case User:
err = g.removeUserShare(ctx, permissionID)
} else {
case Public:
err = g.removePublicShare(ctx, permissionID)
case Space:
err = g.removeSpacePermission(ctx, permissionID, sharedResourceID)
}
if err != nil {
@@ -763,6 +766,20 @@ func (g Graph) getPermissionByID(ctx context.Context, permissionID string) (*lib
}
func (g Graph) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId) ([]libregraph.Permission, error) {
gatewayClient, err := g.gatewaySelector.Next()
if err != nil {
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
return nil, err
}
space, err := utils.GetSpace(ctx, spaceID.GetOpaqueId(), gatewayClient)
if err != nil {
return nil, err
}
return g.cs3SpacePermissionsToLibreGraph(ctx, space, APIVersion_1_Beta_1), nil
}
func (g Graph) getUserPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) {
share, err := g.getCS3UserShareByID(ctx, permissionID)
if err != nil {
@@ -893,6 +910,60 @@ func (g Graph) removeUserShare(ctx context.Context, permissionID string) error {
return nil
}
func (g Graph) removeSpacePermission(ctx context.Context, permissionID string, resourceId *storageprovider.ResourceId) error {
grantee, err := spacePermissionIdToCS3Grantee(permissionID)
if err != nil {
return err
}
gatewayClient, err := g.gatewaySelector.Next()
if err != nil {
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
return err
}
removeShareResp, err := gatewayClient.RemoveShare(ctx, &collaboration.RemoveShareRequest{
Ref: &collaboration.ShareReference{
Spec: &collaboration.ShareReference_Key{
Key: &collaboration.ShareKey{
ResourceId: resourceId,
Grantee: &grantee,
},
},
},
})
if errCode := errorcode.FromCS3Status(removeShareResp.GetStatus(), err); errCode != nil {
return *errCode
}
// We need to return an untyped nil here otherwise the error==nil check won't work
return 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
var grantee storageprovider.Grantee
parts := strings.SplitN(permissionID, ":", 2)
if len(parts) != 2 {
return grantee, errorcode.New(errorcode.InvalidRequest, "invalid space permission id")
}
switch parts[0] {
case "u":
grantee.Type = storageprovider.GranteeType_GRANTEE_TYPE_USER
case "g":
grantee.Type = storageprovider.GranteeType_GRANTEE_TYPE_GROUP
default:
return grantee, errorcode.New(errorcode.InvalidRequest, "invalid space permission id")
}
grantee.Id = &storageprovider.Grantee_UserId{
UserId: &userpb.UserId{
OpaqueId: parts[1],
},
}
return grantee, nil
}
func (g Graph) getLinkPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) {
share, err := g.getCS3PublicShareByID(ctx, permissionID)
if err != nil {
@@ -55,13 +55,15 @@ 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
svc service.Service
ctx context.Context
cfg *config.Config
gatewayClient *cs3mocks.GatewayAPIClient
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
eventsPublisher mocks.Publisher
identityBackend *identitymocks.Backend
getPublicShareResponse *link.GetPublicShareResponse
getShareResponse *collaboration.GetShareResponse
rr *httptest.ResponseRecorder
@@ -86,6 +88,12 @@ var _ = Describe("Driveitems", func() {
return gatewayClient
},
)
getPublicShareResponse = &link.GetPublicShareResponse{
Status: status.NewNotFound(ctx, "not found"),
}
getShareResponse = &collaboration.GetShareResponse{
Status: status.NewNotFound(ctx, "not found"),
}
identityBackend = &identitymocks.Backend{}
newGroup = libregraph.NewGroup()
@@ -111,26 +119,25 @@ var _ = Describe("Driveitems", func() {
Describe("DeletePermission", func() {
It("deletes a user permission as expected", func() {
getShareMock := gatewayClient.On("GetShare",
gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareResponse, nil)
getShareResponse.Status = status.NewOK(ctx)
getShareResponse.Share = &collaboration.Share{
Id: &collaboration.ShareId{
OpaqueId: "permissionid",
},
ResourceId: &provider.ResourceId{
StorageId: "1",
SpaceId: "2",
OpaqueId: "3",
},
}
gatewayClient.On("GetShare",
mock.Anything,
mock.MatchedBy(func(req *collaboration.GetShareRequest) bool {
return req.GetRef().GetId().GetOpaqueId() == "permissionid"
}),
)
getShareMockResponse := &collaboration.GetShareResponse{
Status: status.NewOK(ctx),
Share: &collaboration.Share{
Id: &collaboration.ShareId{
OpaqueId: "permissionid",
},
ResourceId: &provider.ResourceId{
StorageId: "1",
SpaceId: "2",
OpaqueId: "3",
},
},
}
getShareMock.Return(getShareMockResponse, nil)
).Return(getShareResponse, nil)
rmShareMock := gatewayClient.On("RemoveShare",
mock.Anything,
@@ -158,17 +165,6 @@ var _ = Describe("Driveitems", func() {
Expect(rr.Code).To(Equal(http.StatusNoContent))
})
It("deletes a link permission as expected", func() {
getShareMock := gatewayClient.On("GetShare",
mock.Anything,
mock.MatchedBy(func(req *collaboration.GetShareRequest) bool {
return req.GetRef().GetId().GetOpaqueId() == "linkpermissionid"
}),
)
getShareMockResponse := &collaboration.GetShareResponse{
Status: status.NewNotFound(ctx, "not found"),
}
getShareMock.Return(getShareMockResponse, nil)
getPublicShareMock := gatewayClient.On("GetPublicShare",
mock.Anything,
mock.MatchedBy(func(req *link.GetPublicShareRequest) bool {
@@ -214,28 +210,51 @@ var _ = Describe("Driveitems", func() {
Expect(rr.Code).To(Equal(http.StatusNoContent))
})
It("deletes a space permission as expected", func() {
gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareResponse, nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "1$2")
rctx.URLParams.Add("itemID", "1$2!2")
rctx.URLParams.Add("permissionID", "u:userid")
gatewayClient.On("RemoveShare",
mock.Anything,
mock.Anything,
).Return(func(ctx context.Context, in *collaboration.RemoveShareRequest, opts ...grpc.CallOption) (*collaboration.RemoveShareResponse, error) {
Expect(in.Ref.GetKey()).ToNot(BeNil())
Expect(in.Ref.GetKey().GetGrantee().GetUserId().GetOpaqueId()).To(Equal("userid"))
return &collaboration.RemoveShareResponse{Status: status.NewOK(ctx)}, nil
})
ctx = context.WithValue(context.Background(), chi.RouteCtxKey, rctx)
svc.DeletePermission(
rr,
httptest.NewRequest(http.MethodPost, "/", nil).WithContext(ctx),
)
Expect(rr.Code).To(Equal(http.StatusNoContent))
})
It("fails to delete permission when the item id does not match the shared resource's id", func() {
getShareMock := gatewayClient.On("GetShare",
gatewayClient.On("GetPublicShare", mock.Anything, mock.Anything).Return(getPublicShareResponse, nil)
getShareResponse.Status = status.NewOK(ctx)
getShareResponse.Share = &collaboration.Share{
Id: &collaboration.ShareId{
OpaqueId: "permissionid",
},
ResourceId: &provider.ResourceId{
StorageId: "3",
SpaceId: "4",
OpaqueId: "5",
},
}
gatewayClient.On("GetShare",
mock.Anything,
mock.MatchedBy(func(req *collaboration.GetShareRequest) bool {
return req.GetRef().GetId().GetOpaqueId() == "permissionid"
}),
)
getShareMockResponse := &collaboration.GetShareResponse{
Status: status.NewOK(ctx),
Share: &collaboration.Share{
Id: &collaboration.ShareId{
OpaqueId: "permissionid",
},
ResourceId: &provider.ResourceId{
StorageId: "3",
SpaceId: "4",
OpaqueId: "5",
},
},
}
getShareMock.Return(getShareMockResponse, nil)
).Return(getShareResponse, nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "1$2")
+2 -2
View File
@@ -734,7 +734,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
}
spaceID := storagespace.FormatResourceID(spaceRid)
permissions := g.cs3PermissionsToLibreGraph(ctx, space, apiVersion)
permissions := g.cs3SpacePermissionsToLibreGraph(ctx, space, apiVersion)
drive := &libregraph.Drive{
Id: libregraph.PtrString(spaceID),
@@ -830,7 +830,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
return drive, nil
}
func (g Graph) cs3PermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission {
func (g Graph) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission {
if space.Opaque == nil {
return nil
}