rudimentary OCM support in graph

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2024-04-26 17:22:24 +02:00
parent b1f1fafd68
commit ab338884c6
14 changed files with 891 additions and 80 deletions

View File

@@ -0,0 +1,5 @@
Enhancement: Rudimentary OCM support in graph
We now allow creating and accepting OCM shares.
https://github.com/owncloud/ocis/pull/8909

View File

@@ -10,6 +10,8 @@ import (
libregraph "github.com/owncloud/libre-graph-api-go"
mock "github.com/stretchr/testify/mock"
ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
)
// BaseGraphProvider is an autogenerated mock type for the BaseGraphProvider type
@@ -25,6 +27,65 @@ func (_m *BaseGraphProvider) EXPECT() *BaseGraphProvider_Expecter {
return &BaseGraphProvider_Expecter{mock: &_m.Mock}
}
// CS3ReceivedOCMSharesToDriveItems provides a mock function with given fields: ctx, receivedOCMShares
func (_m *BaseGraphProvider) CS3ReceivedOCMSharesToDriveItems(ctx context.Context, receivedOCMShares []*ocmv1beta1.ReceivedShare) ([]libregraph.DriveItem, error) {
ret := _m.Called(ctx, receivedOCMShares)
if len(ret) == 0 {
panic("no return value specified for CS3ReceivedOCMSharesToDriveItems")
}
var r0 []libregraph.DriveItem
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, []*ocmv1beta1.ReceivedShare) ([]libregraph.DriveItem, error)); ok {
return rf(ctx, receivedOCMShares)
}
if rf, ok := ret.Get(0).(func(context.Context, []*ocmv1beta1.ReceivedShare) []libregraph.DriveItem); ok {
r0 = rf(ctx, receivedOCMShares)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]libregraph.DriveItem)
}
}
if rf, ok := ret.Get(1).(func(context.Context, []*ocmv1beta1.ReceivedShare) error); ok {
r1 = rf(ctx, receivedOCMShares)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CS3ReceivedOCMSharesToDriveItems'
type BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call struct {
*mock.Call
}
// CS3ReceivedOCMSharesToDriveItems is a helper method to define mock.On call
// - ctx context.Context
// - receivedOCMShares []*ocmv1beta1.ReceivedShare
func (_e *BaseGraphProvider_Expecter) CS3ReceivedOCMSharesToDriveItems(ctx interface{}, receivedOCMShares interface{}) *BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call {
return &BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call{Call: _e.mock.On("CS3ReceivedOCMSharesToDriveItems", ctx, receivedOCMShares)}
}
func (_c *BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call) Run(run func(ctx context.Context, receivedOCMShares []*ocmv1beta1.ReceivedShare)) *BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].([]*ocmv1beta1.ReceivedShare))
})
return _c
}
func (_c *BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call) Return(_a0 []libregraph.DriveItem, _a1 error) *BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call) RunAndReturn(run func(context.Context, []*ocmv1beta1.ReceivedShare) ([]libregraph.DriveItem, error)) *BaseGraphProvider_CS3ReceivedOCMSharesToDriveItems_Call {
_c.Call.Return(run)
return _c
}
// CS3ReceivedSharesToDriveItems provides a mock function with given fields: ctx, receivedShares
func (_m *BaseGraphProvider) CS3ReceivedSharesToDriveItems(ctx context.Context, receivedShares []*collaborationv1beta1.ReceivedShare) ([]libregraph.DriveItem, error) {
ret := _m.Called(ctx, receivedShares)

View File

@@ -9,6 +9,8 @@ import (
mock "github.com/stretchr/testify/mock"
ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0"
@@ -146,6 +148,65 @@ func (_c *DrivesDriveItemProvider_GetSharesForResource_Call) RunAndReturn(run fu
return _c
}
// MountOCMShare provides a mock function with given fields: ctx, resourceID
func (_m *DrivesDriveItemProvider) MountOCMShare(ctx context.Context, resourceID *providerv1beta1.ResourceId) ([]*ocmv1beta1.ReceivedShare, error) {
ret := _m.Called(ctx, resourceID)
if len(ret) == 0 {
panic("no return value specified for MountOCMShare")
}
var r0 []*ocmv1beta1.ReceivedShare
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId) ([]*ocmv1beta1.ReceivedShare, error)); ok {
return rf(ctx, resourceID)
}
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId) []*ocmv1beta1.ReceivedShare); ok {
r0 = rf(ctx, resourceID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*ocmv1beta1.ReceivedShare)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId) error); ok {
r1 = rf(ctx, resourceID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DrivesDriveItemProvider_MountOCMShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MountOCMShare'
type DrivesDriveItemProvider_MountOCMShare_Call struct {
*mock.Call
}
// MountOCMShare is a helper method to define mock.On call
// - ctx context.Context
// - resourceID *providerv1beta1.ResourceId
func (_e *DrivesDriveItemProvider_Expecter) MountOCMShare(ctx interface{}, resourceID interface{}) *DrivesDriveItemProvider_MountOCMShare_Call {
return &DrivesDriveItemProvider_MountOCMShare_Call{Call: _e.mock.On("MountOCMShare", ctx, resourceID)}
}
func (_c *DrivesDriveItemProvider_MountOCMShare_Call) Run(run func(ctx context.Context, resourceID *providerv1beta1.ResourceId)) *DrivesDriveItemProvider_MountOCMShare_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*providerv1beta1.ResourceId))
})
return _c
}
func (_c *DrivesDriveItemProvider_MountOCMShare_Call) Return(_a0 []*ocmv1beta1.ReceivedShare, _a1 error) *DrivesDriveItemProvider_MountOCMShare_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *DrivesDriveItemProvider_MountOCMShare_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId) ([]*ocmv1beta1.ReceivedShare, error)) *DrivesDriveItemProvider_MountOCMShare_Call {
_c.Call.Return(run)
return _c
}
// MountShare provides a mock function with given fields: ctx, resourceID, name
func (_m *DrivesDriveItemProvider) MountShare(ctx context.Context, resourceID *providerv1beta1.ResourceId, name string) ([]*collaborationv1beta1.ReceivedShare, error) {
ret := _m.Called(ctx, resourceID, name)

View File

@@ -25,10 +25,11 @@ type Config struct {
TokenManager *TokenManager `yaml:"token_manager"`
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
Application Application `yaml:"application"`
Spaces Spaces `yaml:"spaces"`
Identity Identity `yaml:"identity"`
Events Events `yaml:"events"`
Application Application `yaml:"application"`
Spaces Spaces `yaml:"spaces"`
Identity Identity `yaml:"identity"`
IncludeOCMSharees bool `yaml:"include_ocm_sharees" env:"GRAPH_INCLUDE_OCM_SHAREES" desc:"Include OCM sharees when listing users." introductionVersion:"5.0"`
Events Events `yaml:"events"`
Keycloak Keycloak `yaml:"keycloak"`
ServiceAccount ServiceAccount `yaml:"service_account"`

View File

@@ -110,6 +110,12 @@ func CreateUserModelFromCS3(u *cs3user.User) *libregraph.User {
u.Id = &cs3user.UserId{}
}
return &libregraph.User{
Identities: []libregraph.ObjectIdentity{
{
Issuer: &u.GetId().Idp,
IssuerAssignedId: &u.GetId().OpaqueId,
},
},
DisplayName: &u.DisplayName,
Mail: &u.Mail,
OnPremisesSamAccountName: &u.Username,

View File

@@ -110,6 +110,33 @@ func (cache IdentityCache) GetUser(ctx context.Context, userid string) (libregra
return user, nil
}
// GetAcceptedUser looks up a user by id, if the user is not cached, yet it will do a lookup via the CS3 API
func (cache IdentityCache) GetAcceptedUser(ctx context.Context, userid string) (libregraph.User, error) {
var user libregraph.User
if item := cache.users.Get(userid); item == nil {
gatewayClient, err := cache.gatewaySelector.Next()
if err != nil {
return libregraph.User{}, errorcode.New(errorcode.GeneralException, err.Error())
}
cs3UserID := &cs3User.UserId{
OpaqueId: userid,
}
u, err := revautils.GetAcceptedUserWithContext(ctx, cs3UserID, gatewayClient)
if err != nil {
if revautils.IsErrNotFound(err) {
return libregraph.User{}, ErrNotFound
}
return libregraph.User{}, errorcode.New(errorcode.GeneralException, err.Error())
}
user = *CreateUserModelFromCS3(u)
cache.users.Set(userid, user, ttlcache.DefaultTTL)
} else {
user = item.Value()
}
return user, nil
}
// GetGroup looks up a group by id, if the group is not cached, yet it will do a lookup via the CS3 API
func (cache IdentityCache) GetGroup(ctx context.Context, groupID string) (libregraph.Group, error) {
var group libregraph.Group

View File

@@ -2,6 +2,7 @@ package svc
import (
"context"
"errors"
"net/http"
"net/url"
"slices"
@@ -9,18 +10,20 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/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"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/v2/pkg/publicshare"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/share"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
@@ -117,15 +120,6 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId stor
objectID := driveRecipient.GetObjectId()
cs3ResourcePermissions := unifiedrole.PermissionsToCS3ResourcePermissions(unifiedRolePermissions)
createShareRequest := &collaboration.CreateShareRequest{
ResourceInfo: statResponse.GetInfo(),
Grant: &collaboration.ShareGrant{
Permissions: &collaboration.SharePermissions{
Permissions: cs3ResourcePermissions,
},
},
}
permission := &libregraph.Permission{}
if role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*cs3ResourcePermissions, condition); role != nil {
permission.Roles = []string{role.GetId()}
@@ -135,6 +129,8 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId stor
permission.LibreGraphPermissionsActions = unifiedrole.CS3ResourcePermissionsToLibregraphActions(*cs3ResourcePermissions)
}
var shareid string
var expiration *types.Timestamp
switch driveRecipient.GetLibreGraphRecipientType() {
case "group":
group, err := s.identityCache.GetGroup(ctx, objectID)
@@ -142,64 +138,151 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId stor
s.logger.Debug().Err(err).Interface("groupId", objectID).Msg("failed group lookup")
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, err.Error())
}
createShareRequest.GetGrant().Grantee = &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_GROUP,
Id: &storageprovider.Grantee_GroupId{GroupId: &grouppb.GroupId{
OpaqueId: group.GetId(),
}},
}
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
Group: &libregraph.Identity{
DisplayName: group.GetDisplayName(),
Id: conversions.ToPointer(group.GetId()),
},
}
createShareRequest := createShareRequestToGroup(group, statResponse.GetInfo(), cs3ResourcePermissions)
if invite.ExpirationDateTime != nil {
createShareRequest.GetGrant().Expiration = utils.TimeToTS(*invite.ExpirationDateTime)
}
createShareResponse, err := gatewayClient.CreateShare(ctx, createShareRequest)
if err := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
s.logger.Debug().Err(err).Msg("share creation failed")
return libregraph.Permission{}, err
}
shareid = createShareResponse.GetShare().GetId().GetOpaqueId()
expiration = createShareResponse.GetShare().GetExpiration()
default:
federated := false
user, err := s.identityCache.GetUser(ctx, objectID)
if errors.Is(err, identity.ErrNotFound) && s.config.IncludeOCMSharees {
user, err = s.identityCache.GetAcceptedUser(ctx, objectID)
federated = true
}
if err != nil {
s.logger.Debug().Err(err).Interface("userId", objectID).Msg("failed user lookup")
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, err.Error())
}
createShareRequest.GetGrant().Grantee = &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
OpaqueId: user.GetId(),
}},
}
permission.GrantedToV2 = &libregraph.SharePointIdentitySet{
User: &libregraph.Identity{
DisplayName: user.GetDisplayName(),
Id: conversions.ToPointer(user.GetId()),
},
}
if federated {
if len(user.Identities) < 1 {
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "user has no federated identity")
}
providerInfoResp, err := gatewayClient.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{
Domain: *user.Identities[0].Issuer,
})
if err := errorcode.FromCS3Status(providerInfoResp.GetStatus(), err); err != nil {
s.logger.Error().Err(err).Msg("getting provider info failed")
return libregraph.Permission{}, err
}
createShareRequest := createShareRequestToFederatedUser(user, statResponse.GetInfo().GetId(), providerInfoResp.ProviderInfo, cs3ResourcePermissions)
if invite.ExpirationDateTime != nil {
createShareRequest.Expiration = utils.TimeToTS(*invite.ExpirationDateTime)
}
createShareResponse, err := gatewayClient.CreateOCMShare(ctx, createShareRequest)
if err := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
s.logger.Error().Err(err).Msg("share creation failed")
return libregraph.Permission{}, err
}
shareid = createShareResponse.GetShare().GetId().GetOpaqueId()
expiration = createShareResponse.GetShare().GetExpiration()
} else {
createShareRequest := createShareRequestToUser(user, statResponse.GetInfo(), cs3ResourcePermissions)
if invite.ExpirationDateTime != nil {
createShareRequest.GetGrant().Expiration = utils.TimeToTS(*invite.ExpirationDateTime)
}
createShareResponse, err := gatewayClient.CreateShare(ctx, createShareRequest)
if err := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
s.logger.Error().Err(err).Msg("share creation failed")
return libregraph.Permission{}, err
}
shareid = createShareResponse.GetShare().GetId().GetOpaqueId()
expiration = createShareResponse.GetShare().GetExpiration()
}
}
if invite.ExpirationDateTime != nil {
createShareRequest.GetGrant().Expiration = utils.TimeToTS(*invite.ExpirationDateTime)
}
createShareResponse, err := gatewayClient.CreateShare(ctx, createShareRequest)
if err := errorcode.FromCS3Status(createShareResponse.GetStatus(), err); err != nil {
s.logger.Debug().Err(err).Msg("share creation failed")
return libregraph.Permission{}, err
}
if id := createShareResponse.GetShare().GetId().GetOpaqueId(); id != "" {
permission.Id = conversions.ToPointer(id)
if shareid != "" {
permission.Id = conversions.ToPointer(shareid)
} else if IsSpaceRoot(statResponse.GetInfo().GetId()) {
// permissions on a space root are not handled by a share manager so
// they don't get a share-id
permission.SetId(identitySetToSpacePermissionID(permission.GetGrantedToV2()))
}
if expiration := createShareResponse.GetShare().GetExpiration(); expiration != nil {
if expiration != nil {
permission.SetExpirationDateTime(utils.TSToTime(expiration))
}
return *permission, nil
}
func createShareRequestToGroup(group libregraph.Group, info *storageprovider.ResourceInfo, cs3ResourcePermissions *storageprovider.ResourcePermissions) *collaboration.CreateShareRequest {
return &collaboration.CreateShareRequest{
ResourceInfo: info,
Grant: &collaboration.ShareGrant{
Grantee: &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_GROUP,
Id: &storageprovider.Grantee_GroupId{GroupId: &grouppb.GroupId{
OpaqueId: group.GetId(),
}},
},
Permissions: &collaboration.SharePermissions{
Permissions: cs3ResourcePermissions,
},
},
}
}
func createShareRequestToUser(user libregraph.User, info *storageprovider.ResourceInfo, cs3ResourcePermissions *storageprovider.ResourcePermissions) *collaboration.CreateShareRequest {
return &collaboration.CreateShareRequest{
ResourceInfo: info,
Grant: &collaboration.ShareGrant{
Grantee: &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
OpaqueId: user.GetId(),
}},
},
Permissions: &collaboration.SharePermissions{
Permissions: cs3ResourcePermissions,
},
},
}
}
func createShareRequestToFederatedUser(user libregraph.User, resourceId *storageprovider.ResourceId, providerInfo *ocmprovider.ProviderInfo, cs3ResourcePermissions *storageprovider.ResourcePermissions) *ocm.CreateOCMShareRequest {
return &ocm.CreateOCMShareRequest{
ResourceId: resourceId,
Grantee: &storageprovider.Grantee{
Type: storageprovider.GranteeType_GRANTEE_TYPE_USER,
Id: &storageprovider.Grantee_UserId{UserId: &userpb.UserId{
Type: userpb.UserType_USER_TYPE_FEDERATED,
OpaqueId: user.GetId(),
Idp: providerInfo.Domain, // the domain is persisted in the grant as u:{opaqueid}:{domain}
}},
},
RecipientMeshProvider: providerInfo,
AccessMethods: []*ocm.AccessMethod{
{
Term: &ocm.AccessMethod_WebdavOptions{
WebdavOptions: &ocm.WebDAVAccessMethod{
Permissions: cs3ResourcePermissions,
},
},
},
},
}
}
// SpaceRootInvite handles invitation request on project spaces
func (s DriveItemPermissionsService) SpaceRootInvite(ctx context.Context, driveID storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error) {
gatewayClient, err := s.gatewaySelector.Next()

View File

@@ -9,7 +9,6 @@ import (
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/v2/pkg/storagespace"
@@ -17,7 +16,6 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
"github.com/owncloud/ocis/v2/services/graph/pkg/linktype"
)
@@ -66,14 +64,14 @@ func (s DriveItemPermissionsService) CreateLink(ctx context.Context, driveItemID
if isSet {
expireTime := parseAndFillUpTime(expirationDate)
if expireTime == nil {
s.logger.Debug().Interface("createLink", createLink).Msg(err.Error())
s.logger.Debug().Interface("createLink", createLink).Send()
return libregraph.Permission{}, errorcode.New(errorcode.InvalidRequest, "invalid expiration date")
}
req.GetGrant().Expiration = expireTime
}
// set displayname and password protected as arbitrary metadata
req.ResourceInfo.ArbitraryMetadata = &providerv1beta1.ArbitraryMetadata{
req.ResourceInfo.ArbitraryMetadata = &storageprovider.ArbitraryMetadata{
Metadata: map[string]string{
"name": createLink.GetDisplayName(),
"quicklink": strconv.FormatBool(createLink.GetLibreGraphQuickLink()),
@@ -274,7 +272,7 @@ func (api DriveItemPermissionsApi) SetSpaceRootLinkPassword(w http.ResponseWrite
render.JSON(w, r, newPermission)
}
func (s DriveItemPermissionsService) updatePublicLinkPermission(ctx context.Context, permissionID string, itemID *providerv1beta1.ResourceId, newPermission *libregraph.Permission) (perm *libregraph.Permission, err error) {
func (s DriveItemPermissionsService) updatePublicLinkPermission(ctx context.Context, permissionID string, itemID *storageprovider.ResourceId, newPermission *libregraph.Permission) (perm *libregraph.Permission, err error) {
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
s.logger.Error().Err(err).Msg("could not select next gateway client")
@@ -283,8 +281,8 @@ func (s DriveItemPermissionsService) updatePublicLinkPermission(ctx context.Cont
statResp, err := gatewayClient.Stat(
ctx,
&providerv1beta1.StatRequest{
Ref: &providerv1beta1.Reference{
&storageprovider.StatRequest{
Ref: &storageprovider.Reference{
ResourceId: itemID,
Path: ".",
},
@@ -326,6 +324,9 @@ func (s DriveItemPermissionsService) updatePublicLinkPermission(ctx context.Cont
},
statResp.GetInfo().GetType(),
)
if err != nil {
return nil, err
}
update := &link.UpdatePublicShareRequest_Update{
Type: link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS,
Grant: &link.Grant{

View File

@@ -8,6 +8,7 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
@@ -15,6 +16,7 @@ import (
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
@@ -81,6 +83,9 @@ type (
// MountShare mounts a share
MountShare(ctx context.Context, resourceID *storageprovider.ResourceId, name string) ([]*collaboration.ReceivedShare, error)
// MountOCMShare mounts an OCM share
MountOCMShare(ctx context.Context, resourceID *storageprovider.ResourceId /*, name string*/) ([]*ocm.ReceivedShare, error)
// UnmountShare unmounts a share
UnmountShare(ctx context.Context, shareID *collaboration.ShareId) error
@@ -516,21 +521,43 @@ func (api DrivesDriveItemApi) CreateDriveItem(w http.ResponseWriter, r *http.Req
return
}
mountedShares, err := api.drivesDriveItemService.MountShare(ctx, &resourceId, requestDriveItem.GetName())
if err != nil {
api.logger.Debug().Err(err).Msg(ErrMountShare.Error())
var driveItems []libregraph.DriveItem
switch {
case resourceId.GetStorageId() == utils.OCMStorageProviderID:
var mountedOcmShares []*ocm.ReceivedShare
mountedOcmShares, err = api.drivesDriveItemService.MountOCMShare(ctx, &resourceId /*, requestDriveItem.GetName()*/)
if err != nil {
api.logger.Debug().Err(err).Msg(ErrMountShare.Error())
switch e, ok := errorcode.ToError(err); {
case ok && e.GetOrigin() == errorcode.ErrorOriginCS3 && e.GetCode() == errorcode.ItemNotFound:
ErrDriveItemConversion.Render(w, r)
default:
errorcode.RenderError(w, r, err)
switch e, ok := errorcode.ToError(err); {
case ok && e.GetOrigin() == errorcode.ErrorOriginCS3 && e.GetCode() == errorcode.ItemNotFound:
ErrDriveItemConversion.Render(w, r)
default:
errorcode.RenderError(w, r, err)
}
return
}
driveItems, err = api.baseGraphService.CS3ReceivedOCMSharesToDriveItems(ctx, mountedOcmShares)
default:
var mountedShares []*collaboration.ReceivedShare
// Get all shares that the user has received for this resource. There might be multiple
mountedShares, err = api.drivesDriveItemService.MountShare(ctx, &resourceId, requestDriveItem.GetName())
if err != nil {
api.logger.Debug().Err(err).Msg(ErrMountShare.Error())
return
switch e, ok := errorcode.ToError(err); {
case ok && e.GetOrigin() == errorcode.ErrorOriginCS3 && e.GetCode() == errorcode.ItemNotFound:
ErrDriveItemConversion.Render(w, r)
default:
errorcode.RenderError(w, r, err)
}
return
}
driveItems, err = api.baseGraphService.CS3ReceivedSharesToDriveItems(ctx, mountedShares)
}
driveItems, err := api.baseGraphService.CS3ReceivedSharesToDriveItems(ctx, mountedShares)
switch {
case err != nil:
break

View File

@@ -0,0 +1,173 @@
package svc
import (
"context"
"errors"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
)
var (
// ErrUnmountOCMShare is returned when unmounting a share fails
ErrUnmountOCMShare = errorcode.New(errorcode.InvalidRequest, "unmounting ocm share failed")
// ErrMountOCMShare is returned when mounting a share fails
ErrMountOCMShare = errorcode.New(errorcode.InvalidRequest, "mounting ocm share failed")
)
type (
// UpdateOCMShareClosure is a closure that injects required updates into the update request
UpdateOCMShareClosure func(share *ocm.ReceivedShare, request *ocm.UpdateReceivedOCMShareRequest)
)
// GetOCMSharesForResource returns all federated shares for a given resourceID
func (s DrivesDriveItemService) GetOCMSharesForResource(ctx context.Context, resourceID *storageprovider.ResourceId) ([]*ocm.ReceivedShare, error) {
// Find all accepted shares for this resource
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return nil, err
}
receivedOCMSharesResponse, err := gatewayClient.ListReceivedOCMShares(ctx, &ocm.ListReceivedOCMSharesRequest{
/* ocm has no filters, yet
Filters: append([]*collaboration.Filter{
{
Type: collaboration.Filter_TYPE_RESOURCE_ID,
Term: &collaboration.Filter_ResourceId{
ResourceId: resourceID,
},
},
}, filters...),
*/
})
switch {
case err != nil:
return nil, err
case len(receivedOCMSharesResponse.GetShares()) == 0:
return nil, ErrNoShares
default:
return receivedOCMSharesResponse.GetShares(), errorcode.FromCS3Status(receivedOCMSharesResponse.GetStatus(), err)
}
}
// UpdateShares updates multiple shares;
// it could happen that some shares are updated and some are not,
// this will return a list of updated shares and a list of errors;
// there is no guarantee that all updates are successful
func (s DrivesDriveItemService) UpdateOCMShares(ctx context.Context, shares []*ocm.ReceivedShare, updater UpdateOCMShareClosure) ([]*ocm.ReceivedShare, error) {
errs := make([]error, 0, len(shares))
updatedShares := make([]*ocm.ReceivedShare, 0, len(shares))
for _, share := range shares {
err := s.UpdateOCMShare(
ctx,
share,
updater,
)
if err != nil {
errs = append(errs, err)
continue
}
updatedShares = append(updatedShares, share)
}
return updatedShares, errors.Join(errs...)
}
// UpdateOCMShare updates a single share
func (s DrivesDriveItemService) UpdateOCMShare(ctx context.Context, share *ocm.ReceivedShare, updater UpdateOCMShareClosure) error {
gatewayClient, err := s.gatewaySelector.Next()
if err != nil {
return err
}
updateReceivedOCMShareRequest := &ocm.UpdateReceivedOCMShareRequest{
Share: &ocm.ReceivedShare{
Id: share.GetId(),
},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{}},
}
switch updater {
case nil:
return ErrNoUpdater
default:
updater(share, updateReceivedOCMShareRequest)
}
if len(updateReceivedOCMShareRequest.GetUpdateMask().GetPaths()) == 0 {
return ErrNoUpdates
}
updateReceivedOCMShareResponse, err := gatewayClient.UpdateReceivedOCMShare(ctx, updateReceivedOCMShareRequest)
return errorcode.FromCS3Status(updateReceivedOCMShareResponse.GetStatus(), err)
}
func (s DrivesDriveItemService) MountOCMShare(ctx context.Context, resourceID *storageprovider.ResourceId /*, name string*/) ([]*ocm.ReceivedShare, error) {
/*
if filepath.IsAbs(name) {
return nil, ErrAbsoluteNamePath
}
if name != "" {
name = filepath.Clean(name)
}
*/
shares, err := s.GetOCMSharesForResource(ctx, resourceID)
if err != nil {
return nil, err
}
availableShares := make([]*ocm.ReceivedShare, 0, len(shares))
mountedShares := make([]*ocm.ReceivedShare, 0, 1)
for _, v := range shares {
switch v.GetState() {
case ocm.ShareState_SHARE_STATE_ACCEPTED:
mountedShares = append(mountedShares, v)
case ocm.ShareState_SHARE_STATE_PENDING, ocm.ShareState_SHARE_STATE_REJECTED:
availableShares = append(availableShares, v)
}
}
if len(availableShares) == 0 {
if len(mountedShares) > 0 {
return nil, ErrAlreadyMounted
}
return nil, ErrNoShares
}
updatedShares, err := s.UpdateOCMShares(ctx, availableShares, func(share *ocm.ReceivedShare, request *ocm.UpdateReceivedOCMShareRequest) {
request.Share.State = ocm.ShareState_SHARE_STATE_ACCEPTED
request.UpdateMask.Paths = append(request.UpdateMask.Paths, _fieldMaskPathState)
// only update if mountPoint name is not empty and the path has changed
/* ocm shares have no mount point???
if name != "" {
mountPoint := share.GetMountPoint()
if mountPoint == nil {
mountPoint = &storageprovider.Reference{}
}
if filepath.Clean(mountPoint.GetPath()) != name {
mountPoint.Path = name
request.Share.MountPoint = mountPoint
request.UpdateMask.Paths = append(request.UpdateMask.Paths, _fieldMaskPathMountPoint)
}
}
*/
})
errs, ok := err.(interface{ Unwrap() []error })
if ok && len(errs.Unwrap()) == len(availableShares) {
// none of the received ocm shares could be accepted.
// this is an error, return it.
return nil, err
}
return updatedShares, nil
}

View File

@@ -10,10 +10,10 @@ import (
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/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/owncloud/libre-graph-api-go"
@@ -35,6 +35,7 @@ import (
// 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
@@ -71,7 +72,7 @@ func (g BaseGraphService) getDriveItem(ctx context.Context, ref storageprovider.
if err != nil {
return nil, err
}
if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
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())
}
@@ -87,6 +88,15 @@ func (g BaseGraphService) CS3ReceivedSharesToDriveItems(ctx context.Context, rec
return cs3ReceivedSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, receivedShares)
}
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)
}
func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission {
if space.Opaque == nil {
return nil

View File

@@ -5,6 +5,7 @@ import (
"net/http"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
@@ -38,6 +39,25 @@ func (g Graph) listSharedWithMe(ctx context.Context) ([]libregraph.DriveItem, er
g.logger.Error().Err(err).Msg("listing shares failed")
return nil, err
}
driveItems, err := cs3ReceivedSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, listReceivedSharesResponse.GetShares())
if err != nil {
g.logger.Error().Err(err).Msg("could not convert received shares to drive items")
return nil, err
}
return cs3ReceivedSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, listReceivedSharesResponse.GetShares())
if g.config.IncludeOCMSharees {
listReceivedOCMSharesResponse, err := gatewayClient.ListReceivedOCMShares(ctx, &ocm.ListReceivedOCMSharesRequest{})
if err := errorcode.FromCS3Status(listReceivedSharesResponse.GetStatus(), err); err != nil {
g.logger.Error().Err(err).Msg("listing shares failed")
return nil, err
}
ocmDriveItems, err := cs3ReceivedOCMSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, listReceivedOCMSharesResponse.GetShares())
if err != nil {
g.logger.Error().Err(err).Msg("could not convert received shares to drive items")
return nil, err
}
driveItems = append(driveItems, ocmDriveItems...)
}
return driveItems, err
}

View File

@@ -13,28 +13,24 @@ import (
"strconv"
"strings"
"github.com/google/uuid"
"github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults"
"github.com/CiscoM31/godata"
invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/google/uuid"
libregraph "github.com/owncloud/libre-graph-api-go"
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
ocissettingssvc "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0"
"github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults"
)
// GetMe implements the Service interface.
@@ -107,7 +103,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) {
}
func (g Graph) fetchAppRoleAssignments(ctx context.Context, accountuuid string) ([]libregraph.AppRoleAssignment, error) {
lrar, err := g.roleService.ListRoleAssignments(ctx, &settings.ListRoleAssignmentsRequest{
lrar, err := g.roleService.ListRoleAssignments(ctx, &settingssvc.ListRoleAssignmentsRequest{
AccountUuid: accountuuid,
})
if err != nil {
@@ -258,6 +254,34 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
users, err = g.identityBackend.GetUsers(r.Context(), odataReq)
}
if g.config.IncludeOCMSharees {
gwc, err := g.gatewaySelector.Next()
if err != nil {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
term, err := identity.GetSearchValues(odataReq.Query)
if err != nil {
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error())
return
}
remoteUsersRes, err := gwc.FindAcceptedUsers(r.Context(), &invitepb.FindAcceptedUsersRequest{Filter: term})
if err != nil {
// TODO grpc FindAcceptedUsers call failed
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
if remoteUsersRes.Status.Code != cs3rpc.Code_CODE_OK {
// TODO "error searching remote users"
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, remoteUsersRes.Status.Message)
return
}
for _, user := range remoteUsersRes.GetAcceptedUsers() {
users = append(users, identity.CreateUserModelFromCS3(user))
}
}
if err != nil {
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users from backend")
var errcode errorcode.Error
@@ -391,7 +415,7 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) {
if g.roleService != nil && g.config.API.AssignDefaultUserRole {
// All users get the user role by default currently.
// to all new users for now, as create Account request does not have any role field
if _, err = g.roleService.AssignRoleToUser(r.Context(), &settings.AssignRoleToUserRequest{
if _, err = g.roleService.AssignRoleToUser(r.Context(), &settingssvc.AssignRoleToUserRequest{
AccountUuid: *u.Id,
RoleId: ocissettingssvc.BundleUUIDRoleUser,
}); err != nil {
@@ -816,7 +840,7 @@ func (g Graph) patchUser(w http.ResponseWriter, r *http.Request, nameOrID string
}
vID = tvID.String()
}
_, err = g.valueService.SaveValue(r.Context(), &settings.SaveValueRequest{
_, err = g.valueService.SaveValue(r.Context(), &settingssvc.SaveValueRequest{
Value: &settingsmsg.Value{
Id: vID,
BundleId: defaults.BundleUUIDProfile,

View File

@@ -11,18 +11,16 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
cs3User "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"golang.org/x/sync/errgroup"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/graph/pkg/errorcode"
"github.com/owncloud/ocis/v2/services/graph/pkg/identity"
"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
"golang.org/x/sync/errgroup"
)
// StrictJSONUnmarshal is a wrapper around json.Unmarshal that returns an error if the json contains unknown fields.
@@ -472,3 +470,317 @@ func ExtractShareIdFromResourceId(rid storageprovider.ResourceId) *collaboration
OpaqueId: rid.GetOpaqueId(),
}
}
func cs3ReceivedOCMSharesToDriveItems(ctx context.Context,
logger *log.Logger,
gatewayClient gateway.GatewayAPIClient,
identityCache identity.IdentityCache,
receivedShares []*ocm.ReceivedShare) ([]libregraph.DriveItem, error) {
ch := make(chan libregraph.DriveItem)
group := new(errgroup.Group)
// Set max concurrency
group.SetLimit(10)
receivedSharesByResourceID := make(map[string][]*ocm.ReceivedShare, len(receivedShares))
for _, receivedShare := range receivedShares {
rIDStr := receivedShare.GetRemoteShareId()
receivedSharesByResourceID[rIDStr] = append(receivedSharesByResourceID[rIDStr], receivedShare)
}
for _, receivedSharesForResource := range receivedSharesByResourceID {
receivedShares := receivedSharesForResource
group.Go(func() error {
var err error // redeclare
shareStat, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{
Ref: &storageprovider.Reference{
ResourceId: &storageprovider.ResourceId{
// TODO maybe the reference is wrong
StorageId: utils.OCMStorageProviderID,
SpaceId: receivedShares[0].GetId().GetOpaqueId(),
OpaqueId: "", // in OCM resources the opaque id is the base64 encoded path
//OpaqueId: maybe ? receivedShares[0].GetId().GetOpaqueId(),
},
},
})
var errCode errorcode.Error
errors.As(errorcode.FromCS3Status(shareStat.GetStatus(), err), &errCode)
switch {
// skip ItemNotFound shares, they might have been deleted in the meantime or orphans.
case errCode.GetCode() == errorcode.ItemNotFound:
return nil
case err == nil:
break
default:
logger.Error().Err(errCode).Msg("could not stat")
return errCode
}
driveItem, err := fillDriveItemPropertiesFromReceivedOCMShare(ctx, logger, identityCache, receivedShares, shareStat.GetInfo())
if err != nil {
return err
}
if !driveItem.HasUIHidden() {
driveItem.SetUIHidden(false)
}
if !driveItem.HasClientSynchronize() {
driveItem.SetClientSynchronize(false)
if name := shareStat.GetInfo().GetName(); name != "" {
driveItem.SetName(name) // FIXME name is not set???
}
}
remoteItem := driveItem.RemoteItem
{
if id := shareStat.GetInfo().GetId(); id != nil {
remoteItem.SetId(storagespace.FormatResourceID(*id))
}
if name := shareStat.GetInfo().GetName(); name != "" {
remoteItem.SetName(name)
}
if etag := shareStat.GetInfo().GetEtag(); etag != "" {
remoteItem.SetETag(etag)
}
if mTime := shareStat.GetInfo().GetMtime(); mTime != nil {
remoteItem.SetLastModifiedDateTime(cs3TimestampToTime(mTime))
}
if size := shareStat.GetInfo().GetSize(); size != 0 {
remoteItem.SetSize(int64(size))
}
parentReference := libregraph.NewItemReference()
if spaceType := shareStat.GetInfo().GetSpace().GetSpaceType(); spaceType != "" {
parentReference.SetDriveType(spaceType)
}
if root := shareStat.GetInfo().GetSpace().GetRoot(); root != nil {
parentReference.SetDriveId(storagespace.FormatResourceID(*root))
}
if !reflect.ValueOf(*parentReference).IsZero() {
remoteItem.ParentReference = parentReference
}
}
// the parentReference of the outer driveItem should be the drive
// containing the mountpoint i.e. the share jail
driveItem.ParentReference = libregraph.NewItemReference()
driveItem.ParentReference.SetDriveType("virtual")
driveItem.ParentReference.SetDriveId(storagespace.FormatStorageID(utils.ShareStorageProviderID, utils.ShareStorageSpaceID))
driveItem.ParentReference.SetId(storagespace.FormatResourceID(storageprovider.ResourceId{
StorageId: utils.ShareStorageProviderID,
OpaqueId: utils.ShareStorageSpaceID,
SpaceId: utils.ShareStorageSpaceID,
}))
if etag := shareStat.GetInfo().GetEtag(); etag != "" {
driveItem.SetETag(etag)
}
// connect the dots
{
if mTime := shareStat.GetInfo().GetMtime(); mTime != nil {
t := cs3TimestampToTime(mTime)
driveItem.SetLastModifiedDateTime(t)
remoteItem.SetLastModifiedDateTime(t)
}
if size := shareStat.GetInfo().GetSize(); size != 0 {
s := int64(size)
driveItem.SetSize(s)
remoteItem.SetSize(s)
}
if userID := shareStat.GetInfo().GetOwner(); userID != nil && userID.Type != cs3User.UserType_USER_TYPE_SPACE_OWNER {
identity, err := cs3UserIdToIdentity(ctx, identityCache, userID)
if err != nil {
// TODO: define a proper error behavior here. We don't
// want the whole request to fail just because a single
// resource owner couldn't be resolved. But, should we
// really return the affected share in the response?
// For now we just log a warning. The returned
// identitySet will just contain the userid.
logger.Warn().Err(err).Str("userid", userID.String()).Msg("could not get owner of shared resource")
}
remoteItem.SetCreatedBy(libregraph.IdentitySet{User: &identity})
driveItem.SetCreatedBy(libregraph.IdentitySet{User: &identity})
}
switch info := shareStat.GetInfo(); {
case info.GetType() == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER:
folder := libregraph.NewFolder()
remoteItem.Folder = folder
driveItem.Folder = folder
case info.GetType() == storageprovider.ResourceType_RESOURCE_TYPE_FILE:
file := libregraph.NewOpenGraphFile()
if mimeType := info.GetMimeType(); mimeType != "" {
file.MimeType = &mimeType
}
remoteItem.File = file
driveItem.File = file
}
}
ch <- *driveItem
return nil
})
}
var err error
// wait for concurrent requests to finish
go func() {
err = group.Wait()
close(ch)
}()
driveItems := make([]libregraph.DriveItem, 0, len(receivedSharesByResourceID))
for di := range ch {
driveItems = append(driveItems, di)
}
return driveItems, err
}
func fillDriveItemPropertiesFromReceivedOCMShare(ctx context.Context, logger *log.Logger,
identityCache identity.IdentityCache, receivedShares []*ocm.ReceivedShare,
resourceInfo *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
driveItem := libregraph.NewDriveItem()
permissions := make([]libregraph.Permission, 0, len(receivedShares))
var oldestReceivedShare *ocm.ReceivedShare
for _, receivedShare := range receivedShares {
switch {
case oldestReceivedShare == nil:
fallthrough
case utils.TSToTime(receivedShare.GetCtime()).Before(utils.TSToTime(oldestReceivedShare.GetCtime())):
oldestReceivedShare = receivedShare
}
permission, err := cs3ReceivedOCMShareToLibreGraphPermissions(ctx, logger, identityCache, receivedShare, resourceInfo)
if err != nil {
return driveItem, err
}
driveItem.SetName(resourceInfo.GetName())
// If at least one of the shares was accepted, we consider the driveItem's synchronized
// flag enabled.
// Also we use the Mountpoint name of the first accepted mountpoint as the name for
// the driveItem
if receivedShare.GetState() == ocm.ShareState_SHARE_STATE_ACCEPTED {
driveItem.SetClientSynchronize(true)
if name := receivedShare.GetName(); name != "" && driveItem.GetName() == "" {
driveItem.SetName(receivedShare.GetName())
}
}
// if at least one share is marked as hidden, consider the whole driveItem to be hidden
/*
if receivedShare.GetHidden() {
driveItem.SetUIHidden(true)
}
*/
if userID := receivedShare.GetCreator(); userID != nil {
identity, err := cs3UserIdToIdentity(ctx, identityCache, userID)
if err != nil {
logger.Warn().Err(err).Str("userid", userID.String()).Msg("could not get creator of the ocm share")
}
permission.SetInvitation(
libregraph.SharingInvitation{
InvitedBy: &libregraph.IdentitySet{
User: &identity,
},
},
)
}
permissions = append(permissions, *permission)
// To stay compatible with the usershareprovider and the webdav
// service the id of the driveItem is composed of the StorageID and
// SpaceID of the sharestorage appended with the opaque ID of
// the oldest share for the resource:
// '<sharestorageid>$<sharespaceid>!<share-opaque-id>
// Note: This means that the driveitem ID will change when the oldest
// share is removed. It would be good to have are more stable ID here (e.g.
// derived from the shared resource's ID. But as we need to use the same
// ID across all services this means we needed to make similar adjustments
// to the sharejail (usershareprovider, webdav). Which we can't currently do
// as some clients rely on the IDs used there having a special format.
driveItem.SetId(storagespace.FormatResourceID(storageprovider.ResourceId{
StorageId: utils.OCMStorageProviderID,
SpaceId: utils.OCMStorageSpaceID,
OpaqueId: oldestReceivedShare.GetRemoteShareId(),
}))
}
driveItem.RemoteItem = libregraph.NewRemoteItem()
driveItem.RemoteItem.Permissions = permissions
return driveItem, nil
}
func cs3ReceivedOCMShareToLibreGraphPermissions(ctx context.Context, logger *log.Logger,
identityCache identity.IdentityCache, receivedShare *ocm.ReceivedShare,
_ *storageprovider.ResourceInfo) (*libregraph.Permission, error) {
permission := libregraph.NewPermission()
if id := receivedShare.GetId().GetOpaqueId(); id != "" {
permission.SetId(id)
}
if expiration := receivedShare.GetExpiration(); expiration != nil {
permission.SetExpirationDateTime(cs3TimestampToTime(expiration))
}
/*
if permissionSet := receivedShare.GetShare().GetPermissions().GetPermissions(); permissionSet != nil {
condition, err := roleConditionForResourceType(resourceInfo)
if err != nil {
return nil, err
}
role := unifiedrole.CS3ResourcePermissionsToUnifiedRole(*permissionSet, condition)
if role != nil {
permission.SetRoles([]string{role.GetId()})
}
actions := unifiedrole.CS3ResourcePermissionsToLibregraphActions(*permissionSet)
// actions only make sense if no role is set
if role == nil && len(actions) > 0 {
permission.SetLibreGraphPermissionsActions(actions)
}
}
*/
switch grantee := receivedShare.GetGrantee(); {
case grantee.GetType() == storageprovider.GranteeType_GRANTEE_TYPE_USER:
user, err := cs3UserIdToIdentity(ctx, identityCache, grantee.GetUserId())
if err != nil {
logger.Error().Err(err).Msg("could not get user")
return nil, err
}
permission.SetGrantedToV2(libregraph.SharePointIdentitySet{User: &user})
case grantee.GetType() == storageprovider.GranteeType_GRANTEE_TYPE_GROUP:
group, err := groupIdToIdentity(ctx, identityCache, grantee.GetGroupId().GetOpaqueId())
if err != nil {
logger.Error().Err(err).Msg("could not get group")
return nil, err
}
permission.SetGrantedToV2(libregraph.SharePointIdentitySet{Group: &group})
}
return permission, nil
}