mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-28 23:48:46 -06:00
Merge pull request #7860 from rhafer/graph-update-permissions
graph: update permissions
This commit is contained in:
@@ -27,6 +27,7 @@ import (
|
||||
libregraph "github.com/owncloud/libre-graph-api-go"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/publicshare"
|
||||
"github.com/cs3org/reva/v2/pkg/share"
|
||||
@@ -609,6 +610,65 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, &ListResponse{Value: value})
|
||||
}
|
||||
|
||||
// UpdatePermission updates a Permission of a Drive item
|
||||
func (g Graph) UpdatePermission(w http.ResponseWriter, r *http.Request) {
|
||||
_, itemID, err := g.GetDriveAndItemIDParam(r)
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// We don't implement updating link permissions yet
|
||||
if _, ok := oldPermission.GetLinkOk(); ok {
|
||||
errorcode.NotSupported.Render(w, r, http.StatusNotImplemented, "not implemented")
|
||||
return
|
||||
}
|
||||
// This is a user share
|
||||
updatedPermission, err := g.updateUserShare(ctx, permissionID, oldPermission, permission)
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &updatedPermission)
|
||||
return
|
||||
}
|
||||
|
||||
// DeletePermission removes a Permission from a Drive item
|
||||
func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
|
||||
_, itemID, err := g.GetDriveAndItemIDParam(r)
|
||||
@@ -629,7 +689,7 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Check if the id is refering to a User Share
|
||||
sharedResourceId, err := g.getUserPermissionResourceID(ctx, permissionID)
|
||||
var errcode *errorcode.Error
|
||||
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
|
||||
@@ -666,7 +726,43 @@ func (g Graph) DeletePermission(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
func (g Graph) getPermissionByID(ctx context.Context, permissionID string) (*libregraph.Permission, *storageprovider.ResourceId, error) {
|
||||
share, err := g.getCS3UserShareByID(ctx, permissionID)
|
||||
if err == nil {
|
||||
permission, err := g.cs3UserShareToPermission(ctx, share)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return permission, share.GetResourceId(), nil
|
||||
}
|
||||
|
||||
var errcode errorcode.Error
|
||||
if errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound {
|
||||
// there is no user share with that id, check if this is a public link
|
||||
publicShare, err := g.getCS3PublicShareByID(ctx, permissionID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
permission, err := g.libreGraphPermissionFromCS3PublicShare(publicShare)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return permission, publicShare.GetResourceId(), nil
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
|
||||
}
|
||||
|
||||
func (g Graph) getUserPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) {
|
||||
share, err := g.getCS3UserShareByID(ctx, permissionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return share.GetResourceId(), nil
|
||||
}
|
||||
|
||||
func (g Graph) getCS3UserShareByID(ctx context.Context, permissionID string) (*collaboration.Share, error) {
|
||||
gatewayClient, err := g.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
|
||||
@@ -684,9 +780,86 @@ func (g Graph) getUserPermissionResourceID(ctx context.Context, permissionID str
|
||||
},
|
||||
})
|
||||
if errCode := errorcode.FromCS3Status(getShareResp.GetStatus(), err); errCode != nil {
|
||||
return nil, errCode
|
||||
return nil, *errCode
|
||||
}
|
||||
return getShareResp.Share.GetResourceId(), nil
|
||||
return getShareResp.GetShare(), nil
|
||||
}
|
||||
|
||||
func (g Graph) updateUserShare(ctx context.Context, permissionID string, oldPermission, 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
|
||||
}
|
||||
|
||||
cs3UpdateShareReq := &collaboration.UpdateShareRequest{
|
||||
Ref: &collaboration.ShareReference{
|
||||
Spec: &collaboration.ShareReference_Id{
|
||||
Id: &collaboration.ShareId{
|
||||
OpaqueId: permissionID,
|
||||
},
|
||||
},
|
||||
},
|
||||
Share: &collaboration.Share{},
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// FIXME: When setting permissions on a space, we need to use UnifiedRoleConditionOwner here
|
||||
allowedResourceActions = unifiedrole.GetAllowedResourceActions(role, unifiedrole.UnifiedRoleConditionGrantee)
|
||||
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())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return permission, nil
|
||||
}
|
||||
|
||||
func (g Graph) removeUserShare(ctx context.Context, permissionID string) error {
|
||||
@@ -708,13 +881,21 @@ func (g Graph) removeUserShare(ctx context.Context, permissionID string) error {
|
||||
})
|
||||
|
||||
if errCode := errorcode.FromCS3Status(removeShareResp.GetStatus(), err); errCode != nil {
|
||||
return errCode
|
||||
return *errCode
|
||||
}
|
||||
// We need to return an untyped nil here otherwise the error==nil check won't work
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g Graph) getLinkPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) {
|
||||
share, err := g.getCS3PublicShareByID(ctx, permissionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return share.GetResourceId(), nil
|
||||
}
|
||||
|
||||
func (g Graph) getCS3PublicShareByID(ctx context.Context, permissionID string) (*link.PublicShare, error) {
|
||||
gatewayClient, err := g.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
|
||||
@@ -733,9 +914,9 @@ func (g Graph) getLinkPermissionResourceID(ctx context.Context, permissionID str
|
||||
},
|
||||
)
|
||||
if errCode := errorcode.FromCS3Status(getPublicShareResp.GetStatus(), err); errCode != nil {
|
||||
return nil, errCode
|
||||
return nil, *errCode
|
||||
}
|
||||
return getPublicShareResp.Share.GetResourceId(), nil
|
||||
return getPublicShareResp.GetShare(), nil
|
||||
}
|
||||
|
||||
func (g Graph) removePublicShare(ctx context.Context, permissionID string) error {
|
||||
@@ -756,7 +937,7 @@ func (g Graph) removePublicShare(ctx context.Context, permissionID string) error
|
||||
},
|
||||
})
|
||||
if errcode := errorcode.FromCS3Status(removePublicShareResp.GetStatus(), err); errcode != nil {
|
||||
return errcode
|
||||
return *errcode
|
||||
}
|
||||
// We need to return an untyped nil here otherwise the error==nil check won't work
|
||||
return nil
|
||||
|
||||
@@ -12,10 +12,12 @@ import (
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/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"
|
||||
roleconversions "github.com/cs3org/reva/v2/pkg/conversions"
|
||||
"github.com/go-chi/chi/v5"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -70,8 +72,6 @@ var _ = Describe("Driveitems", func() {
|
||||
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](
|
||||
@@ -248,6 +248,270 @@ var _ = Describe("Driveitems", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("UpdatePermission", func() {
|
||||
var (
|
||||
driveItemPermission *libregraph.Permission
|
||||
getShareMockResponse *collaboration.GetShareResponse
|
||||
getPublicShareMockResponse *link.GetPublicShareResponse
|
||||
getUserMockResponse *user.GetUserResponse
|
||||
updateShareMockResponse *collaboration.UpdateShareResponse
|
||||
)
|
||||
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: &user.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,
|
||||
}
|
||||
|
||||
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: roleconversions.NewViewerRole(true).CS3ResourcePermissions(),
|
||||
},
|
||||
Token: "token",
|
||||
},
|
||||
}
|
||||
getPublicShareMock.Return(getPublicShareMockResponse, 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))
|
||||
})
|
||||
// Updating a public link will be implemented later
|
||||
It("fails when trying to update a link permission", func() {
|
||||
getShareMockResponse.Share = nil
|
||||
getShareMockResponse.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.StatusNotImplemented))
|
||||
})
|
||||
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("updates the expiration date", func() {
|
||||
expiration := time.Now().Add(time.Hour)
|
||||
updateShareMock := gatewayClient.On("UpdateShare",
|
||||
mock.Anything,
|
||||
mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool {
|
||||
if req.GetRef().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() {
|
||||
updateShareMock := gatewayClient.On("UpdateShare",
|
||||
mock.Anything,
|
||||
mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool {
|
||||
if req.GetRef().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())
|
||||
})
|
||||
It("updates the share permissions with changing the role", func() {
|
||||
updateShareMock := gatewayClient.On("UpdateShare",
|
||||
mock.Anything,
|
||||
mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool {
|
||||
return req.GetRef().GetId().GetOpaqueId() == "permissionid"
|
||||
}),
|
||||
)
|
||||
updateShareMock.Return(updateShareMockResponse, nil)
|
||||
|
||||
driveItemPermission.SetRoles([]string{unifiedrole.NewViewerUnifiedRole(true).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("updates the share permissions when changing the resource permission actions", func() {
|
||||
updateShareMock := gatewayClient.On("UpdateShare",
|
||||
mock.Anything,
|
||||
mock.MatchedBy(func(req *collaboration.UpdateShareRequest) bool {
|
||||
return req.GetRef().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())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Invite", func() {
|
||||
var (
|
||||
driveItemInvite *libregraph.DriveItemInvite
|
||||
|
||||
@@ -113,6 +113,7 @@ type Service interface {
|
||||
|
||||
Invite(w http.ResponseWriter, r *http.Request)
|
||||
ListPermissions(w http.ResponseWriter, r *http.Request)
|
||||
UpdatePermission(w http.ResponseWriter, r *http.Request)
|
||||
DeletePermission(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
CreateUploadSession(w http.ResponseWriter, r *http.Request)
|
||||
@@ -210,6 +211,17 @@ func NewService(opts ...Option) (Graph, error) {
|
||||
r.Get("/sharedWithMe", svc.ListSharedWithMe)
|
||||
})
|
||||
})
|
||||
r.Route("/drives/{driveID}/items/{itemID}", func(r chi.Router) {
|
||||
r.Post("/invite", svc.Invite)
|
||||
r.Route("/permissions", func(r chi.Router) {
|
||||
r.Get("/", svc.ListPermissions)
|
||||
r.Route("/{permissionID}", func(r chi.Router) {
|
||||
r.Delete("/", svc.DeletePermission)
|
||||
r.Patch("/", svc.UpdatePermission)
|
||||
})
|
||||
})
|
||||
r.Post("/createLink", svc.CreateLink)
|
||||
})
|
||||
|
||||
r.Route("/drives", func(r chi.Router) {
|
||||
r.Get("/", svc.GetAllDrives(APIVersion_1_Beta_1))
|
||||
|
||||
@@ -124,65 +124,79 @@ func (g Graph) cs3UserSharesToDriveItems(ctx context.Context, shares []*collabor
|
||||
}
|
||||
item = *itemptr
|
||||
}
|
||||
perm := libregraph.Permission{}
|
||||
perm.SetRoles([]string{})
|
||||
perm.SetId(s.Id.OpaqueId)
|
||||
grantedTo := libregraph.SharePointIdentitySet{}
|
||||
var li libregraph.Identity
|
||||
switch s.Grantee.Type {
|
||||
case storageprovider.GranteeType_GRANTEE_TYPE_USER:
|
||||
user, err := g.identityCache.GetUser(ctx, s.Grantee.GetUserId().GetOpaqueId())
|
||||
switch {
|
||||
case errors.Is(err, identity.ErrNotFound):
|
||||
g.logger.Warn().Str("userid", s.Grantee.GetUserId().GetOpaqueId()).Msg("User not found by id")
|
||||
// User does not seem to exist anymore, don't add a permission for this
|
||||
continue
|
||||
case err != nil:
|
||||
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
default:
|
||||
li.SetDisplayName(user.GetDisplayName())
|
||||
li.SetId(user.GetId())
|
||||
grantedTo.SetUser(li)
|
||||
}
|
||||
case storageprovider.GranteeType_GRANTEE_TYPE_GROUP:
|
||||
group, err := g.identityCache.GetGroup(ctx, s.Grantee.GetGroupId().GetOpaqueId())
|
||||
switch {
|
||||
case errors.Is(err, identity.ErrNotFound):
|
||||
g.logger.Warn().Str("groupid", s.Grantee.GetGroupId().GetOpaqueId()).Msg("Group not found by id")
|
||||
// Group not seem to exist anymore, don't add a permission for this
|
||||
continue
|
||||
case err != nil:
|
||||
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
default:
|
||||
li.SetDisplayName(group.GetDisplayName())
|
||||
li.SetId(group.GetId())
|
||||
grantedTo.SetGroup(li)
|
||||
}
|
||||
}
|
||||
perm, err := g.cs3UserShareToPermission(ctx, s)
|
||||
|
||||
// set expiration date
|
||||
if s.GetExpiration() != nil {
|
||||
perm.SetExpirationDateTime(cs3TimestampToTime(s.GetExpiration()))
|
||||
var errcode errorcode.Error
|
||||
switch {
|
||||
case errors.As(err, &errcode) && errcode.GetCode() == errorcode.ItemNotFound:
|
||||
// The Grantee couldn't be found (user/group does not exist anymore)
|
||||
continue
|
||||
case err != nil:
|
||||
return driveItems, err
|
||||
}
|
||||
role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(
|
||||
*s.GetPermissions().GetPermissions(),
|
||||
unifiedrole.UnifiedRoleConditionGrantee,
|
||||
g.config.FilesSharing.EnableResharing,
|
||||
)
|
||||
if role != nil {
|
||||
perm.SetRoles([]string{role.GetId()})
|
||||
} else {
|
||||
actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(*s.GetPermissions().GetPermissions())
|
||||
perm.SetLibreGraphPermissionsActions(actions)
|
||||
perm.SetRoles(nil)
|
||||
}
|
||||
perm.SetGrantedToV2(grantedTo)
|
||||
item.Permissions = append(item.Permissions, perm)
|
||||
item.Permissions = append(item.Permissions, *perm)
|
||||
driveItems[resIDStr] = item
|
||||
}
|
||||
return driveItems, nil
|
||||
}
|
||||
|
||||
func (g Graph) cs3UserShareToPermission(ctx context.Context, share *collaboration.Share) (*libregraph.Permission, error) {
|
||||
perm := libregraph.Permission{}
|
||||
perm.SetRoles([]string{})
|
||||
perm.SetId(share.Id.OpaqueId)
|
||||
grantedTo := libregraph.SharePointIdentitySet{}
|
||||
var li libregraph.Identity
|
||||
switch share.GetGrantee().GetType() {
|
||||
case storageprovider.GranteeType_GRANTEE_TYPE_USER:
|
||||
user, err := g.identityCache.GetUser(ctx, share.Grantee.GetUserId().GetOpaqueId())
|
||||
switch {
|
||||
case errors.Is(err, identity.ErrNotFound):
|
||||
g.logger.Warn().Str("userid", share.Grantee.GetUserId().GetOpaqueId()).Msg("User not found by id")
|
||||
// User does not seem to exist anymore, don't add a permission for this
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, "grantee does not exist")
|
||||
case err != nil:
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
default:
|
||||
li.SetDisplayName(user.GetDisplayName())
|
||||
li.SetId(user.GetId())
|
||||
grantedTo.SetUser(li)
|
||||
}
|
||||
case storageprovider.GranteeType_GRANTEE_TYPE_GROUP:
|
||||
group, err := g.identityCache.GetGroup(ctx, share.Grantee.GetGroupId().GetOpaqueId())
|
||||
switch {
|
||||
case errors.Is(err, identity.ErrNotFound):
|
||||
g.logger.Warn().Str("groupid", share.Grantee.GetGroupId().GetOpaqueId()).Msg("Group not found by id")
|
||||
// Group not seem to exist anymore, don't add a permission for this
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, "grantee does not exist")
|
||||
case err != nil:
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
default:
|
||||
li.SetDisplayName(group.GetDisplayName())
|
||||
li.SetId(group.GetId())
|
||||
grantedTo.SetGroup(li)
|
||||
}
|
||||
}
|
||||
|
||||
// set expiration date
|
||||
if share.GetExpiration() != nil {
|
||||
perm.SetExpirationDateTime(cs3TimestampToTime(share.GetExpiration()))
|
||||
}
|
||||
role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(
|
||||
*share.GetPermissions().GetPermissions(),
|
||||
unifiedrole.UnifiedRoleConditionGrantee,
|
||||
g.config.FilesSharing.EnableResharing,
|
||||
)
|
||||
if role != nil {
|
||||
perm.SetRoles([]string{role.GetId()})
|
||||
} else {
|
||||
actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(*share.GetPermissions().GetPermissions())
|
||||
perm.SetLibreGraphPermissionsActions(actions)
|
||||
perm.SetRoles(nil)
|
||||
}
|
||||
perm.SetGrantedToV2(grantedTo)
|
||||
return &perm, nil
|
||||
}
|
||||
|
||||
func (g Graph) cs3PublicSharesToDriveItems(ctx context.Context, shares []*link.PublicShare, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
||||
for _, s := range shares {
|
||||
g.logger.Debug().Interface("CS3 PublicShare", s).Msg("Got Share")
|
||||
|
||||
@@ -486,3 +486,12 @@ func convert(role *conversions.Role) []string {
|
||||
}
|
||||
return CS3ResourcePermissionsToLibregraphActions(*role.CS3ResourcePermissions())
|
||||
}
|
||||
|
||||
func GetAllowedResourceActions(role *libregraph.UnifiedRoleDefinition, condition string) []string {
|
||||
for _, p := range role.GetRolePermissions() {
|
||||
if p.GetCondition() == condition {
|
||||
return p.GetAllowedResourceActions()
|
||||
}
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
// initLibregraph initializes libregraph validation
|
||||
func initLibregraph(v *validator.Validate) {
|
||||
driveItemInvite(v)
|
||||
permission(v)
|
||||
}
|
||||
|
||||
// driveItemInvite validates libregraph.DriveItemInvite
|
||||
@@ -27,55 +28,79 @@ func driveItemInvite(v *validator.Validate) {
|
||||
v.RegisterStructValidation(func(sl validator.StructLevel) {
|
||||
driveItemInvite := sl.Current().Interface().(libregraph.DriveItemInvite)
|
||||
|
||||
totalRoles := len(driveItemInvite.Roles)
|
||||
totalActions := len(driveItemInvite.LibreGraphPermissionsActions)
|
||||
|
||||
switch {
|
||||
case totalRoles != 0 && totalActions != 0:
|
||||
fallthrough
|
||||
case totalRoles == totalActions:
|
||||
sl.ReportError(driveItemInvite.Roles, "Roles", "Roles", "one_or_another", "")
|
||||
sl.ReportError(driveItemInvite.LibreGraphPermissionsActions, "LibreGraphPermissionsActions", "LibreGraphPermissionsActions", "one_or_another", "")
|
||||
}
|
||||
|
||||
var availableRoles []string
|
||||
var availableActions []string
|
||||
for _, definition := range append(
|
||||
unifiedrole.GetBuiltinRoleDefinitionList(true),
|
||||
unifiedrole.GetBuiltinRoleDefinitionList(false)...,
|
||||
) {
|
||||
if slices.Contains(availableRoles, definition.GetId()) {
|
||||
continue
|
||||
}
|
||||
|
||||
availableRoles = append(availableRoles, definition.GetId())
|
||||
|
||||
for _, permission := range definition.GetRolePermissions() {
|
||||
for _, action := range permission.GetAllowedResourceActions() {
|
||||
if slices.Contains(availableActions, action) {
|
||||
continue
|
||||
}
|
||||
|
||||
availableActions = append(availableActions, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, role := range driveItemInvite.Roles {
|
||||
if slices.Contains(availableRoles, role) {
|
||||
continue
|
||||
}
|
||||
|
||||
sl.ReportError(driveItemInvite.Roles, "Roles", "Roles", "available_role", "")
|
||||
}
|
||||
|
||||
for _, role := range driveItemInvite.LibreGraphPermissionsActions {
|
||||
if slices.Contains(availableActions, role) {
|
||||
continue
|
||||
}
|
||||
|
||||
sl.ReportError(driveItemInvite.LibreGraphPermissionsActions, "LibreGraphPermissionsActions", "LibreGraphPermissionsActions", "available_action", "")
|
||||
}
|
||||
rolesAndActions(sl, driveItemInvite.Roles, driveItemInvite.LibreGraphPermissionsActions, false)
|
||||
|
||||
}, s)
|
||||
}
|
||||
|
||||
// permission validates libregraph.Permission
|
||||
func permission(v *validator.Validate) {
|
||||
s := libregraph.Permission{}
|
||||
|
||||
v.RegisterStructValidationMapRules(map[string]string{
|
||||
"Roles": "max=1",
|
||||
}, s)
|
||||
v.RegisterStructValidation(func(sl validator.StructLevel) {
|
||||
permission := sl.Current().Interface().(libregraph.Permission)
|
||||
|
||||
if _, ok := permission.GetIdOk(); ok {
|
||||
sl.ReportError(permission.Id, "Id", "Id", "readonly", "")
|
||||
}
|
||||
|
||||
rolesAndActions(sl, permission.Roles, permission.LibreGraphPermissionsActions, true)
|
||||
}, s)
|
||||
}
|
||||
|
||||
func rolesAndActions(sl validator.StructLevel, roles, actions []string, allowEmpty bool) {
|
||||
totalRoles := len(roles)
|
||||
totalActions := len(actions)
|
||||
|
||||
switch {
|
||||
case allowEmpty && totalRoles == 0 && totalActions == 0:
|
||||
break
|
||||
case totalRoles != 0 && totalActions != 0:
|
||||
fallthrough
|
||||
case totalRoles == totalActions:
|
||||
sl.ReportError(roles, "Roles", "Roles", "one_or_another", "")
|
||||
sl.ReportError(actions, "LibreGraphPermissionsActions", "LibreGraphPermissionsActions", "one_or_another", "")
|
||||
}
|
||||
|
||||
var availableRoles []string
|
||||
var availableActions []string
|
||||
for _, definition := range append(
|
||||
unifiedrole.GetBuiltinRoleDefinitionList(true),
|
||||
unifiedrole.GetBuiltinRoleDefinitionList(false)...,
|
||||
) {
|
||||
if slices.Contains(availableRoles, definition.GetId()) {
|
||||
continue
|
||||
}
|
||||
|
||||
availableRoles = append(availableRoles, definition.GetId())
|
||||
|
||||
for _, permission := range definition.GetRolePermissions() {
|
||||
for _, action := range permission.GetAllowedResourceActions() {
|
||||
if slices.Contains(availableActions, action) {
|
||||
continue
|
||||
}
|
||||
|
||||
availableActions = append(availableActions, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if slices.Contains(availableRoles, role) {
|
||||
continue
|
||||
}
|
||||
|
||||
sl.ReportError(roles, "Roles", "Roles", "available_role", "")
|
||||
}
|
||||
|
||||
for _, role := range actions {
|
||||
if slices.Contains(availableActions, role) {
|
||||
continue
|
||||
}
|
||||
|
||||
sl.ReportError(actions, "LibreGraphPermissionsActions", "LibreGraphPermissionsActions", "available_action", "")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user