mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-13 22:29:01 -04:00
Introducing gowrap as a build-time tool to generate interface delegate structs from templates: * added as a 'make go-generate' target in services/graph, * added as a build-time dependency in .bingo/ Introduce an LDAP client abstraction interface to be able to wrap the go-ldap client API with metrics transparently (and possibly hooks and such in the future), in order to use delegation patterns to measure the time LDAP (client) operations take to finish, as well as to track their results (success, failure, not-found). Has two implementations that are generated using gowrap: * a go-ldap adapter implementation that directly delegates to a go-ldap connection * a time measuring and metrics collecting implementation that delegates to another LdapClient The metrics collecting one is disabled by default, can be enabled with GRAPH_LDAP_METRICS_DISABLE=false It collects durations of outbound LDAP client operations into a histogram, as well as the number of concurrent outbound LDAP operations in a gauge (via an atomic int and a gauge function, as that performs best). Add an HTTP middleware that measures how long Graph HTTP API requests take, storing taken time into a histogram along with labels for * method, * path pattern (from the chi routes), * Graph API version prefix, * Graph API resource name, * and the resulting status code. It also tracks the number of concurrent inbound Graph API HTTP requests using a gauge (also using an atomic int and a gauge function). Disabled by default, can be enabled with GRAPH_HTTP_METRICS_DISABLE=false Add Backend and EducationBackend delegate implementations that measure execution time on the level of the higher API call operations there (CreateUser, DeleteUser, ..., CreateSchool, ...), generated using gowrap. Disabled by default, can be enabled with GRAPH_IDENTITY_BACKEND_METRICS_DISABLE=false Also added a small k6 script to produce some read-only load on the Graph API, for a casual test of the metrics, as well as k6 in mise.toml. Make an internal changes to how singular LDAP entry searches work in the LDAP identity backends: * check whether searches for a singular entry returns more than one result, in which case a new error TooManyResults is returned, instead of leaving that undetected, blindly taking the first result, and potentially risking data inconsistencies Improve the loggers in identity backends by adding attributes for their request targets (Reva gateway address or LDAP URI, respectively). Also add a "backend" attribute for all Graph API logs (set to "ldap" or "cs3"), to help debug potential issues, and remove them from all the logger debug calls at the beginning of each LDAP-related function as those should really be part of the logger and set beforehand. The LDAP identity backend logger also has two new attributes to help debugging with logs: * write (bool): whether write operations are enabled * refint (bool): whether refint is enabled or not Also adds a dedicated counter metric for user password change operations. Minor campfire improvements: * add a constructor func for the CS3 backend * add a constructor func for the LDAP backend * in the LDAP identity backend, in searchLDAPEntryByFilter (used by all search/get public functions), errors that occur when performing LDAP SEARCH operations were blindly mapped to a ItemNotFound error, instead of being analyzed as it could be caused by a technical error * in the requireadmin middleware, add debug logging to explain why a request is denied * when an LDAP password change fails because the user entry was not found in LDAP, we now have a log message that tracks that
1262 lines
40 KiB
Go
1262 lines
40 KiB
Go
package svc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"path"
|
|
"time"
|
|
|
|
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
|
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
|
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
|
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
|
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
|
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
|
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
|
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
|
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
|
"golang.org/x/sync/errgroup"
|
|
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
|
|
|
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
|
"github.com/opencloud-eu/reva/v2/pkg/share"
|
|
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
|
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
|
|
|
"github.com/opencloud-eu/opencloud/pkg/log"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/identity"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/identity/cache"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/linktype"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/unifiedrole"
|
|
)
|
|
|
|
// BaseGraphProvider is the interface that wraps shared methods between the different graph providers
|
|
type BaseGraphProvider interface {
|
|
CS3ReceivedSharesToDriveItems(ctx context.Context, receivedShares []*collaboration.ReceivedShare) ([]libregraph.DriveItem, error)
|
|
CS3ReceivedOCMSharesToDriveItems(ctx context.Context, receivedOCMShares []*ocm.ReceivedShare) ([]libregraph.DriveItem, error)
|
|
}
|
|
|
|
// BaseGraphService implements a couple of helper functions that are
|
|
// shared between the different graph services
|
|
type BaseGraphService struct {
|
|
logger *log.Logger
|
|
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
|
identityCache cache.IdentityCache
|
|
config *config.Config
|
|
availableRoles []*libregraph.UnifiedRoleDefinition
|
|
publicBaseURL *url.URL
|
|
}
|
|
|
|
func (g BaseGraphService) getDriveItem(ctx context.Context, ref *storageprovider.Reference) (*libregraph.DriveItem, error) {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: ref})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
|
refStr, _ := storagespace.FormatReference(ref)
|
|
return nil, fmt.Errorf("could not stat %s: %s", refStr, res.GetStatus().GetMessage())
|
|
}
|
|
return cs3ResourceToDriveItem(g.logger, g.publicBaseURL, res.GetInfo())
|
|
}
|
|
|
|
func (g BaseGraphService) CS3ReceivedSharesToDriveItems(ctx context.Context, receivedShares []*collaboration.ReceivedShare) ([]libregraph.DriveItem, error) {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cs3ReceivedSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, receivedShares, g.availableRoles)
|
|
}
|
|
|
|
func (g BaseGraphService) CS3ReceivedOCMSharesToDriveItems(ctx context.Context, receivedShares []*ocm.ReceivedShare) ([]libregraph.DriveItem, error) {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cs3ReceivedOCMSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, receivedShares, g.availableRoles)
|
|
}
|
|
|
|
func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, countOnly bool, apiVersion APIVersion) ([]libregraph.Permission, int) {
|
|
if space.Opaque == nil {
|
|
return nil, 0
|
|
}
|
|
logger := g.logger.SubloggerWithRequestID(ctx)
|
|
|
|
var permissionsMap map[string]*storageprovider.ResourcePermissions
|
|
opaqueGrants, ok := space.Opaque.Map["grants"]
|
|
if ok {
|
|
err := json.Unmarshal(opaqueGrants.Value, &permissionsMap)
|
|
if err != nil {
|
|
logger.Debug().
|
|
Err(err).
|
|
Interface("space", space.Root).
|
|
Bytes("grants", opaqueGrants.Value).
|
|
Msg("unable to parse space: failed to read spaces grants")
|
|
}
|
|
}
|
|
if len(permissionsMap) == 0 {
|
|
return nil, 0
|
|
}
|
|
|
|
if countOnly {
|
|
// If we only need the count, we can return early
|
|
return nil, len(permissionsMap)
|
|
}
|
|
|
|
var permissionsExpirations map[string]*types.Timestamp
|
|
opaqueGrantsExpirations, ok := space.Opaque.Map["grants_expirations"]
|
|
if ok {
|
|
err := json.Unmarshal(opaqueGrantsExpirations.Value, &permissionsExpirations)
|
|
if err != nil {
|
|
logger.Debug().
|
|
Err(err).
|
|
Interface("space", space.Root).
|
|
Bytes("grants_expirations", opaqueGrantsExpirations.Value).
|
|
Msg("unable to parse space: failed to read spaces grants expirations")
|
|
}
|
|
}
|
|
|
|
var groupsMap map[string]struct{}
|
|
opaqueGroups, ok := space.Opaque.Map["groups"]
|
|
if ok {
|
|
err := json.Unmarshal(opaqueGroups.Value, &groupsMap)
|
|
if err != nil {
|
|
logger.Debug().
|
|
Err(err).
|
|
Interface("space", space.Root).
|
|
Bytes("groups", opaqueGroups.Value).
|
|
Msg("unable to parse space: failed to read spaces groups")
|
|
}
|
|
}
|
|
|
|
permissions := make([]libregraph.Permission, 0, len(permissionsMap))
|
|
for id, perm := range permissionsMap {
|
|
// This temporary variable is necessary since we need to pass a pointer to the
|
|
// libregraph.Identity and if we pass the pointer from the loop every identity
|
|
// will have the same id.
|
|
tmp := id
|
|
isGroup := false
|
|
var cs3Identity libregraph.Identity
|
|
var err error
|
|
var p libregraph.Permission
|
|
if _, ok := groupsMap[id]; ok {
|
|
cs3Identity, err = groupIdToIdentity(ctx, g.identityCache, tmp)
|
|
if err != nil {
|
|
g.logger.Warn().Str("groupid", tmp).Msg("Group not found by id")
|
|
}
|
|
isGroup = true
|
|
} else {
|
|
tenantId := revactx.ContextMustGetUser(ctx).GetId().GetTenantId()
|
|
cs3Identity, err = userIdToIdentity(ctx, g.identityCache, tenantId, tmp)
|
|
if err != nil {
|
|
g.logger.Warn().Str("userid", tmp).Msg("User not found by id")
|
|
}
|
|
}
|
|
switch apiVersion {
|
|
case APIVersion_1:
|
|
var identitySet libregraph.IdentitySet
|
|
if isGroup {
|
|
identitySet.SetGroup(cs3Identity)
|
|
} else {
|
|
identitySet.SetUser(cs3Identity)
|
|
}
|
|
p.SetGrantedToV2(libregraph.SharePointIdentitySet{User: identitySet.User, Group: identitySet.Group})
|
|
// FIXME: needs to be removed
|
|
p.SetGrantedToIdentities([]libregraph.IdentitySet{identitySet})
|
|
case APIVersion_1_Beta_1:
|
|
var identitySet libregraph.SharePointIdentitySet
|
|
if isGroup {
|
|
identitySet.SetGroup(cs3Identity)
|
|
} else {
|
|
identitySet.SetUser(cs3Identity)
|
|
}
|
|
p.SetId(identitySetToSpacePermissionID(identitySet))
|
|
p.SetGrantedToV2(identitySet)
|
|
}
|
|
|
|
if exp := permissionsExpirations[id]; exp != nil {
|
|
p.SetExpirationDateTime(time.Unix(int64(exp.GetSeconds()), int64(exp.GetNanos())))
|
|
}
|
|
|
|
if role := unifiedrole.CS3ResourcePermissionsToRole(
|
|
g.availableRoles,
|
|
perm,
|
|
unifiedrole.UnifiedRoleConditionDrive,
|
|
false,
|
|
); role != nil {
|
|
switch apiVersion {
|
|
case APIVersion_1:
|
|
if r := unifiedrole.GetLegacyRoleName(*role); r != "" {
|
|
p.SetRoles([]string{r})
|
|
}
|
|
case APIVersion_1_Beta_1:
|
|
p.SetRoles([]string{role.GetId()})
|
|
}
|
|
}
|
|
|
|
// if there is no role, we need to set the actions as a fallback
|
|
// this could happen if a role is disabled or unknown
|
|
if !p.HasRoles() {
|
|
p.SetLibreGraphPermissionsActions(unifiedrole.CS3ResourcePermissionsToLibregraphActions(perm))
|
|
}
|
|
|
|
permissions = append(permissions, p)
|
|
}
|
|
return permissions, len(permissions)
|
|
}
|
|
|
|
func (g BaseGraphService) libreGraphPermissionFromCS3PublicShare(createdLink *link.PublicShare) (*libregraph.Permission, error) {
|
|
webURL, err := url.Parse(g.config.Spaces.WebDavBase)
|
|
if err != nil {
|
|
g.logger.Error().
|
|
Err(err).
|
|
Str("url", g.config.Spaces.WebDavBase).
|
|
Msg("failed to parse webURL base url")
|
|
return nil, err
|
|
}
|
|
lt, actions := linktype.SharingLinkTypeFromCS3Permissions(createdLink.GetPermissions())
|
|
perm := libregraph.NewPermission()
|
|
perm.Id = libregraph.PtrString(createdLink.GetId().GetOpaqueId())
|
|
perm.Link = &libregraph.SharingLink{
|
|
Type: lt,
|
|
PreventsDownload: libregraph.PtrBool(false),
|
|
LibreGraphDisplayName: libregraph.PtrString(createdLink.GetDisplayName()),
|
|
LibreGraphQuickLink: libregraph.PtrBool(createdLink.GetQuicklink()),
|
|
}
|
|
perm.LibreGraphPermissionsActions = actions
|
|
webURL.Path = path.Join(webURL.Path, "s", createdLink.GetToken())
|
|
perm.Link.SetWebUrl(webURL.String())
|
|
|
|
// set expiration date
|
|
if createdLink.GetExpiration() != nil {
|
|
perm.SetExpirationDateTime(cs3TimestampToTime(createdLink.GetExpiration()).UTC())
|
|
}
|
|
|
|
// set cTime
|
|
if createdLink.GetCtime() != nil {
|
|
perm.SetCreatedDateTime(cs3TimestampToTime(createdLink.GetCtime()).UTC())
|
|
}
|
|
|
|
perm.SetHasPassword(createdLink.GetPasswordProtected())
|
|
|
|
return perm, nil
|
|
}
|
|
|
|
func (g BaseGraphService) listUserShares(ctx context.Context, filters []*collaboration.Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
|
return g.listSharesWithSpaceRootFilter(ctx, false, filters, driveItems)
|
|
}
|
|
|
|
// listSpaceRootUserShares lists user/group shares whose resource is a space root (i.e. space
|
|
// memberships). It uses SpaceRootFilter(true) so only space-root shares are returned, mirroring
|
|
// how listUserShares works for regular file/folder shares.
|
|
func (g BaseGraphService) listSpaceRootUserShares(ctx context.Context, filters []*collaboration.Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
|
return g.listSharesWithSpaceRootFilter(ctx, true, filters, driveItems)
|
|
}
|
|
|
|
func (g BaseGraphService) listSharesWithSpaceRootFilter(ctx context.Context, spaceRoot bool, filters []*collaboration.Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
g.logger.Error().Err(err).Msg("could not select next gateway client")
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
|
|
concreteFilters := []*collaboration.Filter{
|
|
share.UserGranteeFilter(),
|
|
share.GroupGranteeFilter(),
|
|
share.SpaceRootFilter(spaceRoot),
|
|
}
|
|
concreteFilters = append(concreteFilters, filters...)
|
|
|
|
lsUserSharesRequest := collaboration.ListSharesRequest{
|
|
Filters: concreteFilters,
|
|
}
|
|
|
|
lsUserSharesResponse, err := gatewayClient.ListShares(ctx, &lsUserSharesRequest)
|
|
if err != nil {
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
if statusCode := lsUserSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK {
|
|
return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsUserSharesResponse.Status.Message)
|
|
}
|
|
driveItems, err = g.cs3UserSharesToDriveItems(ctx, lsUserSharesResponse.Shares, driveItems)
|
|
if err != nil {
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
return driveItems, nil
|
|
}
|
|
|
|
func (g BaseGraphService) listOCMShares(ctx context.Context, filters []*ocm.ListOCMSharesRequest_Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
g.logger.Error().Err(err).Msg("could not select next gateway client")
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
|
|
concreteFilters := []*ocm.ListOCMSharesRequest_Filter{}
|
|
concreteFilters = append(concreteFilters, filters...)
|
|
|
|
lsOCMSharesRequest := ocm.ListOCMSharesRequest{
|
|
Filters: concreteFilters,
|
|
}
|
|
|
|
lsOCMSharesResponse, err := gatewayClient.ListOCMShares(ctx, &lsOCMSharesRequest)
|
|
if err != nil {
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
if statusCode := lsOCMSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK {
|
|
return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsOCMSharesResponse.Status.Message)
|
|
}
|
|
driveItems, err = g.cs3OCMSharesToDriveItems(ctx, lsOCMSharesResponse.Shares, driveItems)
|
|
if err != nil {
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
return driveItems, nil
|
|
}
|
|
|
|
func (g BaseGraphService) listPublicShares(ctx context.Context, filters []*link.ListPublicSharesRequest_Filter, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
|
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
g.logger.Error().Err(err).Msg("could not select next gateway client")
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
|
|
var concreteFilters []*link.ListPublicSharesRequest_Filter
|
|
concreteFilters = append(concreteFilters, filters...)
|
|
|
|
req := link.ListPublicSharesRequest{
|
|
Filters: concreteFilters,
|
|
}
|
|
|
|
lsPublicSharesResponse, err := gatewayClient.ListPublicShares(ctx, &req)
|
|
if err != nil {
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
if statusCode := lsPublicSharesResponse.GetStatus().GetCode(); statusCode != rpc.Code_CODE_OK {
|
|
return driveItems, errorcode.New(cs3StatusToErrCode(statusCode), lsPublicSharesResponse.Status.Message)
|
|
}
|
|
driveItems, err = g.cs3PublicSharesToDriveItems(ctx, lsPublicSharesResponse.Share, driveItems)
|
|
if err != nil {
|
|
return driveItems, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
return driveItems, nil
|
|
|
|
}
|
|
|
|
func (g BaseGraphService) cs3UserSharesToDriveItems(ctx context.Context, shares []*collaboration.Share, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
|
errg, ctx := errgroup.WithContext(ctx)
|
|
|
|
// group shares by resource id
|
|
sharesByResource := make(map[string][]*collaboration.Share)
|
|
for _, share := range shares {
|
|
sharesByResource[share.GetResourceId().String()] = append(sharesByResource[share.GetResourceId().String()], share)
|
|
}
|
|
|
|
type resourceShares struct {
|
|
ResourceID *storageprovider.ResourceId
|
|
Shares []*collaboration.Share
|
|
}
|
|
|
|
work := make(chan resourceShares, len(shares))
|
|
results := make(chan *libregraph.DriveItem, len(shares))
|
|
|
|
// Distribute work
|
|
errg.Go(func() error {
|
|
defer close(work)
|
|
|
|
for _, shares := range sharesByResource {
|
|
select {
|
|
case work <- resourceShares{ResourceID: shares[0].GetResourceId(), Shares: shares}:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Spawn workers that'll concurrently work the queue
|
|
numWorkers := g.config.MaxConcurrency
|
|
if len(sharesByResource) < numWorkers {
|
|
numWorkers = len(sharesByResource)
|
|
}
|
|
for i := 0; i < numWorkers; i++ {
|
|
errg.Go(func() error {
|
|
for sharesByResource := range work {
|
|
resIDStr := storagespace.FormatResourceID(sharesByResource.ResourceID)
|
|
// check if we already have the drive item in the map
|
|
item, ok := driveItems[resIDStr]
|
|
if !ok {
|
|
itemptr, err := g.getDriveItem(ctx, &storageprovider.Reference{ResourceId: sharesByResource.ResourceID})
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Str("storage", sharesByResource.ResourceID.StorageId).Str("space", sharesByResource.ResourceID.SpaceId).Str("node", sharesByResource.ResourceID.OpaqueId).Msg("could not stat resource, skipping")
|
|
continue
|
|
}
|
|
item = *itemptr
|
|
}
|
|
|
|
var condition string
|
|
switch {
|
|
case item.Root != nil:
|
|
condition = unifiedrole.UnifiedRoleConditionDrive
|
|
case item.Folder != nil:
|
|
condition = unifiedrole.UnifiedRoleConditionFolder
|
|
case item.File != nil:
|
|
condition = unifiedrole.UnifiedRoleConditionFile
|
|
}
|
|
for _, share := range sharesByResource.Shares {
|
|
perm, err := g.cs3UserShareToPermission(ctx, share, condition)
|
|
if err != nil {
|
|
if errorcode.IsErrorCode(err, errorcode.ItemNotFound) {
|
|
// The Grantee couldn't be found (user/group does not exist anymore)
|
|
continue
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
item.Permissions = append(item.Permissions, *perm)
|
|
}
|
|
if len(item.Permissions) == 0 {
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case results <- &item:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
// Wait for things to settle down, then close results chan
|
|
go func() {
|
|
_ = errg.Wait() // error is checked later
|
|
close(results)
|
|
}()
|
|
|
|
for item := range results {
|
|
driveItems[item.GetId()] = *item
|
|
}
|
|
|
|
if err := errg.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return driveItems, nil
|
|
}
|
|
|
|
func (g BaseGraphService) cs3OCMSharesToDriveItems(ctx context.Context, shares []*ocm.Share, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
|
errg, ctx := errgroup.WithContext(ctx)
|
|
|
|
// group shares by resource id
|
|
sharesByResource := make(map[string][]*ocm.Share)
|
|
for _, share := range shares {
|
|
sharesByResource[share.GetResourceId().String()] = append(sharesByResource[share.GetResourceId().String()], share)
|
|
}
|
|
|
|
type resourceShares struct {
|
|
ResourceID *storageprovider.ResourceId
|
|
Shares []*ocm.Share
|
|
}
|
|
|
|
work := make(chan resourceShares, len(shares))
|
|
results := make(chan *libregraph.DriveItem, len(shares))
|
|
|
|
// Distribute work
|
|
errg.Go(func() error {
|
|
defer close(work)
|
|
|
|
for _, shares := range sharesByResource {
|
|
select {
|
|
case work <- resourceShares{ResourceID: shares[0].GetResourceId(), Shares: shares}:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Spawn workers that'll concurrently work the queue
|
|
numWorkers := g.config.MaxConcurrency
|
|
if len(sharesByResource) < numWorkers {
|
|
numWorkers = len(sharesByResource)
|
|
}
|
|
for i := 0; i < numWorkers; i++ {
|
|
errg.Go(func() error {
|
|
for sharesByResource := range work {
|
|
resIDStr := storagespace.FormatResourceID(sharesByResource.ResourceID)
|
|
item, ok := driveItems[resIDStr]
|
|
if !ok {
|
|
itemptr, err := g.getDriveItem(ctx, &storageprovider.Reference{ResourceId: sharesByResource.ResourceID})
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Interface("Share", sharesByResource.ResourceID).Msg("could not stat ocm share, skipping")
|
|
continue
|
|
}
|
|
item = *itemptr
|
|
}
|
|
|
|
var condition string
|
|
switch {
|
|
case item.Folder != nil:
|
|
condition = unifiedrole.UnifiedRoleConditionFolderFederatedUser
|
|
case item.File != nil:
|
|
condition = unifiedrole.UnifiedRoleConditionFileFederatedUser
|
|
}
|
|
for _, share := range sharesByResource.Shares {
|
|
perm, err := g.cs3OCMShareToPermission(ctx, share, condition)
|
|
|
|
if err != nil {
|
|
if errorcode.IsErrorCode(err, errorcode.ItemNotFound) {
|
|
// The Grantee couldn't be found (user/group does not exist anymore)
|
|
continue
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
item.Permissions = append(item.Permissions, *perm)
|
|
}
|
|
if len(item.Permissions) == 0 {
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case results <- &item:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
// Wait for things to settle down, then close results chan
|
|
go func() {
|
|
_ = errg.Wait() // error is checked later
|
|
close(results)
|
|
}()
|
|
|
|
for item := range results {
|
|
driveItems[item.GetId()] = *item
|
|
}
|
|
|
|
if err := errg.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return driveItems, nil
|
|
}
|
|
|
|
func (g BaseGraphService) cs3UserShareToPermission(ctx context.Context, share *collaboration.Share, roleCondition string) (*libregraph.Permission, error) {
|
|
perm := libregraph.Permission{}
|
|
perm.SetRoles([]string{})
|
|
perm.SetId(share.GetId().GetOpaqueId())
|
|
grantedTo := libregraph.SharePointIdentitySet{}
|
|
switch share.GetGrantee().GetType() {
|
|
case storageprovider.GranteeType_GRANTEE_TYPE_USER:
|
|
user, err := cs3UserIdToIdentity(ctx, g.identityCache, share.Grantee.GetUserId())
|
|
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:
|
|
grantedTo.SetUser(user)
|
|
}
|
|
case storageprovider.GranteeType_GRANTEE_TYPE_GROUP:
|
|
group, err := groupIdToIdentity(ctx, g.identityCache, 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:
|
|
grantedTo.SetGroup(group)
|
|
}
|
|
}
|
|
|
|
// set expiration date
|
|
if share.GetExpiration() != nil {
|
|
perm.SetExpirationDateTime(cs3TimestampToTime(share.GetExpiration()))
|
|
}
|
|
// set cTime
|
|
if share.GetCtime() != nil {
|
|
perm.SetCreatedDateTime(cs3TimestampToTime(share.GetCtime()))
|
|
}
|
|
role := unifiedrole.CS3ResourcePermissionsToRole(
|
|
g.availableRoles,
|
|
share.GetPermissions().GetPermissions(),
|
|
roleCondition,
|
|
false,
|
|
)
|
|
if role != nil {
|
|
perm.SetRoles([]string{role.GetId()})
|
|
} else {
|
|
actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(share.GetPermissions().GetPermissions())
|
|
// neither a role nor actions are set, we need to return "none" as a hint in the actions
|
|
if len(actions) == 0 {
|
|
actions = []string{"none"}
|
|
}
|
|
perm.SetLibreGraphPermissionsActions(actions)
|
|
perm.SetRoles(nil)
|
|
}
|
|
perm.SetGrantedToV2(grantedTo)
|
|
if share.GetCreator() != nil {
|
|
cs3Identity, err := cs3UserIdToIdentity(ctx, g.identityCache, share.GetCreator())
|
|
if err != nil {
|
|
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
perm.SetInvitation(
|
|
libregraph.SharingInvitation{
|
|
InvitedBy: &libregraph.IdentitySet{
|
|
User: &cs3Identity,
|
|
},
|
|
},
|
|
)
|
|
}
|
|
return &perm, nil
|
|
}
|
|
func (g BaseGraphService) cs3OCMShareToPermission(ctx context.Context, share *ocm.Share, roleCondition string) (*libregraph.Permission, error) {
|
|
perm := libregraph.Permission{}
|
|
perm.SetRoles([]string{})
|
|
if roleCondition != unifiedrole.UnifiedRoleConditionDrive {
|
|
perm.SetId(share.GetId().GetOpaqueId())
|
|
}
|
|
grantedTo := libregraph.SharePointIdentitySet{}
|
|
// hm or use share.GetShareType() to determine the type of share???
|
|
switch share.GetGrantee().GetType() {
|
|
case storageprovider.GranteeType_GRANTEE_TYPE_USER:
|
|
user, err := cs3UserIdToIdentity(ctx, g.identityCache, share.Grantee.GetUserId())
|
|
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:
|
|
grantedTo.SetUser(user)
|
|
if roleCondition == unifiedrole.UnifiedRoleConditionDrive {
|
|
perm.SetId("u:" + user.GetId())
|
|
}
|
|
}
|
|
case storageprovider.GranteeType_GRANTEE_TYPE_GROUP:
|
|
group, err := groupIdToIdentity(ctx, g.identityCache, 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:
|
|
grantedTo.SetGroup(group)
|
|
if roleCondition == unifiedrole.UnifiedRoleConditionDrive {
|
|
perm.SetId("g:" + group.GetId())
|
|
}
|
|
}
|
|
}
|
|
|
|
// set expiration date
|
|
if share.GetExpiration() != nil {
|
|
perm.SetExpirationDateTime(cs3TimestampToTime(share.GetExpiration()))
|
|
}
|
|
// set cTime
|
|
if share.GetCtime() != nil {
|
|
perm.SetCreatedDateTime(cs3TimestampToTime(share.GetCtime()))
|
|
}
|
|
var permissions *storageprovider.ResourcePermissions
|
|
for _, role := range share.GetAccessMethods() {
|
|
if role.GetWebdavOptions().GetPermissions() != nil {
|
|
permissions = role.GetWebdavOptions().GetPermissions()
|
|
}
|
|
}
|
|
|
|
role := unifiedrole.CS3ResourcePermissionsToRole(
|
|
g.availableRoles,
|
|
permissions,
|
|
roleCondition,
|
|
true,
|
|
)
|
|
if role != nil {
|
|
perm.SetRoles([]string{role.GetId()})
|
|
} else {
|
|
actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(permissions)
|
|
perm.SetLibreGraphPermissionsActions(actions)
|
|
perm.SetRoles(nil)
|
|
}
|
|
perm.SetGrantedToV2(grantedTo)
|
|
if share.GetCreator() != nil {
|
|
cs3Identity, err := cs3UserIdToIdentity(ctx, g.identityCache, share.GetCreator())
|
|
if err != nil {
|
|
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
|
}
|
|
perm.SetInvitation(
|
|
libregraph.SharingInvitation{
|
|
InvitedBy: &libregraph.IdentitySet{
|
|
User: &cs3Identity,
|
|
},
|
|
},
|
|
)
|
|
}
|
|
return &perm, nil
|
|
}
|
|
|
|
func (g BaseGraphService) cs3PublicSharesToDriveItems(ctx context.Context, shares []*link.PublicShare, driveItems driveItemsByResourceID) (driveItemsByResourceID, error) {
|
|
errg, ctx := errgroup.WithContext(ctx)
|
|
|
|
// group shares by resource id
|
|
sharesByResource := make(map[string][]*link.PublicShare)
|
|
for _, share := range shares {
|
|
sharesByResource[share.GetResourceId().String()] = append(sharesByResource[share.GetResourceId().String()], share)
|
|
}
|
|
|
|
type resourceShares struct {
|
|
ResourceID *storageprovider.ResourceId
|
|
Shares []*link.PublicShare
|
|
}
|
|
|
|
work := make(chan resourceShares, len(shares))
|
|
results := make(chan *libregraph.DriveItem, len(shares))
|
|
|
|
// Distribute work
|
|
errg.Go(func() error {
|
|
defer close(work)
|
|
|
|
for _, shares := range sharesByResource {
|
|
select {
|
|
case work <- resourceShares{ResourceID: shares[0].GetResourceId(), Shares: shares}:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Spawn workers that'll concurrently work the queue
|
|
numWorkers := g.config.MaxConcurrency
|
|
if len(sharesByResource) < numWorkers {
|
|
numWorkers = len(sharesByResource)
|
|
}
|
|
for i := 0; i < numWorkers; i++ {
|
|
errg.Go(func() error {
|
|
for sharesByResource := range work {
|
|
resIDStr := storagespace.FormatResourceID(sharesByResource.ResourceID)
|
|
item, ok := driveItems[resIDStr]
|
|
if !ok {
|
|
itemptr, err := g.getDriveItem(ctx, &storageprovider.Reference{ResourceId: sharesByResource.ResourceID})
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Interface("Share", sharesByResource.ResourceID).Msg("could not stat share, skipping")
|
|
continue
|
|
}
|
|
item = *itemptr
|
|
}
|
|
for _, share := range sharesByResource.Shares {
|
|
|
|
perm, err := g.libreGraphPermissionFromCS3PublicShare(share)
|
|
if err != nil {
|
|
g.logger.Error().Err(err).Interface("Link", sharesByResource.ResourceID).Msg("could not convert link to libregraph")
|
|
return err
|
|
}
|
|
|
|
item.Permissions = append(item.Permissions, *perm)
|
|
}
|
|
if len(item.Permissions) == 0 {
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case results <- &item:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
// Wait for things to settle down, then close results chan
|
|
go func() {
|
|
_ = errg.Wait() // error is checked later
|
|
close(results)
|
|
}()
|
|
|
|
for item := range results {
|
|
driveItems[item.GetId()] = *item
|
|
}
|
|
|
|
if err := errg.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return driveItems, nil
|
|
}
|
|
|
|
func (g BaseGraphService) getLinkPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) {
|
|
cs3Share, err := g.getCS3PublicShareByID(ctx, permissionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cs3Share.GetResourceId(), nil
|
|
}
|
|
|
|
func (g BaseGraphService) 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")
|
|
return nil, err
|
|
}
|
|
|
|
getPublicShareResp, err := gatewayClient.GetPublicShare(ctx,
|
|
&link.GetPublicShareRequest{
|
|
Ref: &link.PublicShareReference{
|
|
Spec: &link.PublicShareReference_Id{
|
|
Id: &link.PublicShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
if err := errorcode.FromCS3Status(getPublicShareResp.GetStatus(), err); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return getPublicShareResp.GetShare(), nil
|
|
}
|
|
|
|
func (g BaseGraphService) removeOCMPermission(ctx context.Context, permissionID string) error {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
|
|
return err
|
|
}
|
|
|
|
removePublicShareResp, err := gatewayClient.RemoveOCMShare(ctx,
|
|
&ocm.RemoveOCMShareRequest{
|
|
Ref: &ocm.ShareReference{
|
|
Spec: &ocm.ShareReference_Id{
|
|
Id: &ocm.ShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
if err := errorcode.FromCS3Status(removePublicShareResp.GetStatus(), err); err != nil {
|
|
return err
|
|
}
|
|
|
|
// We need to return an untyped nil here otherwise the error==nil check won't work
|
|
return nil
|
|
}
|
|
|
|
func (g BaseGraphService) removePublicShare(ctx context.Context, permissionID string) error {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
|
|
return err
|
|
}
|
|
|
|
removePublicShareResp, err := gatewayClient.RemovePublicShare(ctx,
|
|
&link.RemovePublicShareRequest{
|
|
Ref: &link.PublicShareReference{
|
|
Spec: &link.PublicShareReference_Id{
|
|
Id: &link.PublicShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
if err := errorcode.FromCS3Status(removePublicShareResp.GetStatus(), err); err != nil {
|
|
return err
|
|
}
|
|
|
|
// We need to return an untyped nil here otherwise the error==nil check won't work
|
|
return nil
|
|
}
|
|
|
|
func (g BaseGraphService) removeUserShare(ctx context.Context, permissionID string) error {
|
|
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_Id{
|
|
Id: &collaboration.ShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
if err := errorcode.FromCS3Status(removeShareResp.GetStatus(), err); err != nil {
|
|
return err
|
|
}
|
|
|
|
// We need to return an untyped nil here otherwise the error==nil check won't work
|
|
return nil
|
|
}
|
|
|
|
func (g BaseGraphService) getOCMPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) {
|
|
cs3Share, err := g.getCS3OCMShareByID(ctx, permissionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cs3Share.GetResourceId(), nil
|
|
}
|
|
|
|
func (g BaseGraphService) getCS3OCMShareByID(ctx context.Context, permissionID string) (*ocm.Share, error) {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
|
|
return nil, err
|
|
}
|
|
|
|
getShareResp, err := gatewayClient.GetOCMShare(ctx,
|
|
&ocm.GetOCMShareRequest{
|
|
Ref: &ocm.ShareReference{
|
|
Spec: &ocm.ShareReference_Id{
|
|
Id: &ocm.ShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
if err := errorcode.FromCS3Status(getShareResp.GetStatus(), err); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return getShareResp.GetShare(), nil
|
|
}
|
|
|
|
func (g BaseGraphService) getUserPermissionResourceID(ctx context.Context, permissionID string) (*storageprovider.ResourceId, error) {
|
|
cs3Share, err := g.getCS3UserShareByID(ctx, permissionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cs3Share.GetResourceId(), nil
|
|
}
|
|
|
|
func (g BaseGraphService) 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")
|
|
return nil, err
|
|
}
|
|
|
|
getShareResp, err := gatewayClient.GetShare(ctx,
|
|
&collaboration.GetShareRequest{
|
|
Ref: &collaboration.ShareReference{
|
|
Spec: &collaboration.ShareReference_Id{
|
|
Id: &collaboration.ShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
if err := errorcode.FromCS3Status(getShareResp.GetStatus(), err); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return getShareResp.GetShare(), nil
|
|
}
|
|
|
|
func (g BaseGraphService) getOCMPermissionByID(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId) (*libregraph.Permission, *storageprovider.ResourceId, error) {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Msg("selecting gatewaySelevtor failed")
|
|
return nil, nil, err
|
|
}
|
|
|
|
ocmShare, err := g.getCS3OCMShareByID(ctx, permissionID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
condition, err := roleConditionForResourceType(resourceInfo)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
permission, err := g.cs3OCMShareToPermission(ctx, ocmShare, condition)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return permission, ocmShare.GetResourceId(), nil
|
|
}
|
|
|
|
func (g BaseGraphService) getPermissionByID(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId) (*libregraph.Permission, *storageprovider.ResourceId, error) {
|
|
gatewayClient, err := g.gatewaySelector.Next()
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
|
|
return nil, nil, err
|
|
}
|
|
publicShare, err := g.getCS3PublicShareByID(ctx, permissionID)
|
|
switch {
|
|
case err == nil:
|
|
// the id is referencing a public share
|
|
permission, err := g.libreGraphPermissionFromCS3PublicShare(publicShare)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return permission, publicShare.GetResourceId(), nil
|
|
case IsSpaceRoot(itemID):
|
|
// itemID is referencing a space root — use the share manager to look up the permission
|
|
driveItems := make(driveItemsByResourceID)
|
|
driveItems, err = g.listSpaceRootUserShares(ctx, []*collaboration.Filter{
|
|
share.ResourceIDFilter(itemID),
|
|
}, driveItems)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
for _, item := range driveItems {
|
|
for i := range item.Permissions {
|
|
if item.Permissions[i].GetId() == permissionID {
|
|
return &item.Permissions[i], itemID, nil
|
|
}
|
|
}
|
|
}
|
|
case errorcode.IsErrorCode(err, errorcode.ItemNotFound):
|
|
// there is no public link with that id, check if this is a user share
|
|
cs3Share, err := g.getCS3UserShareByID(ctx, permissionID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
condition, err := roleConditionForResourceType(resourceInfo)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
permission, err := g.cs3UserShareToPermission(ctx, cs3Share, condition)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return permission, cs3Share.GetResourceId(), nil
|
|
}
|
|
|
|
return nil, nil, err
|
|
}
|
|
|
|
func (g BaseGraphService) updateOCMPermission(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId, 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
|
|
}
|
|
|
|
resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
condition, err := federatedRoleConditionForResourceType(resourceInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var cs3UpdateOCMShareReq ocm.UpdateOCMShareRequest
|
|
cs3UpdateOCMShareReq.Ref = &ocm.ShareReference{
|
|
Spec: &ocm.ShareReference_Id{
|
|
Id: &ocm.ShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
},
|
|
}
|
|
|
|
if expiration, ok := newPermission.GetExpirationDateTimeOk(); ok {
|
|
cs3UpdateOCMShareReq.Field = append(
|
|
cs3UpdateOCMShareReq.Field,
|
|
&ocm.UpdateOCMShareRequest_UpdateField{
|
|
Field: &ocm.UpdateOCMShareRequest_UpdateField_Expiration{
|
|
Expiration: utils.TimeToTS(*expiration),
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
var allowedResourceActions []string
|
|
var permissionsUpdated bool
|
|
if roles, ok := newPermission.GetRolesOk(); ok {
|
|
if len(roles) > 0 {
|
|
for _, roleID := range roles {
|
|
role, err := unifiedrole.GetRole(unifiedrole.RoleFilterIDs(roleID))
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Interface("role", role).Msg("unable to convert requested role")
|
|
return nil, err
|
|
}
|
|
|
|
allowedResourceActions = unifiedrole.GetAllowedResourceActions(role, condition)
|
|
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 {
|
|
cs3UpdateOCMShareReq.Field = append(cs3UpdateOCMShareReq.Field, &ocm.UpdateOCMShareRequest_UpdateField{
|
|
Field: &ocm.UpdateOCMShareRequest_UpdateField_AccessMethods{
|
|
AccessMethods: &ocm.AccessMethod{
|
|
Term: &ocm.AccessMethod_WebdavOptions{
|
|
WebdavOptions: &ocm.WebDAVAccessMethod{
|
|
Permissions: unifiedrole.PermissionsToCS3ResourcePermissions(
|
|
[]*libregraph.UnifiedRolePermission{
|
|
{
|
|
AllowedResourceActions: allowedResourceActions,
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
updateOCMShareResp, err := gatewayClient.UpdateOCMShare(ctx, &cs3UpdateOCMShareReq)
|
|
if err := errorcode.FromCS3Status(updateOCMShareResp.GetStatus(), err); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ocmShareResp, err := gatewayClient.GetOCMShare(ctx, &ocm.GetOCMShareRequest{
|
|
Ref: &ocm.ShareReference{
|
|
Spec: &ocm.ShareReference_Id{
|
|
Id: &ocm.ShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err := errorcode.FromCS3Status(ocmShareResp.GetStatus(), err); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
permission, err := g.cs3OCMShareToPermission(ctx, ocmShareResp.GetShare(), condition)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return permission, nil
|
|
}
|
|
|
|
func (g BaseGraphService) updateUserShare(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId, 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
|
|
}
|
|
|
|
resourceInfo, err := utils.GetResourceByID(ctx, itemID, gatewayClient)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
condition, err := roleConditionForResourceType(resourceInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var cs3UpdateShareReq collaboration.UpdateShareRequest
|
|
cs3UpdateShareReq.Share = &collaboration.Share{
|
|
Id: &collaboration.ShareId{
|
|
OpaqueId: permissionID,
|
|
},
|
|
}
|
|
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.GetRole(unifiedrole.RoleFilterIDs(roleID))
|
|
if err != nil {
|
|
g.logger.Debug().Err(err).Interface("role", role).Msg("unable to convert requested role")
|
|
return nil, err
|
|
}
|
|
|
|
allowedResourceActions = unifiedrole.GetAllowedResourceActions(role, condition)
|
|
if len(allowedResourceActions) == 0 && role.GetId() != unifiedrole.UnifiedRoleDeniedID {
|
|
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 err := errorcode.FromCS3Status(updateUserShareResp.GetStatus(), err); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
permission, err := g.cs3UserShareToPermission(ctx, updateUserShareResp.GetShare(), condition)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return permission, nil
|
|
}
|