Merge pull request #7860 from rhafer/graph-update-permissions

graph: update permissions
This commit is contained in:
Michael Barz
2023-12-08 12:12:27 +01:00
committed by GitHub
6 changed files with 615 additions and 110 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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")

View File

@@ -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{}
}

View File

@@ -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", "")
}
}