mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-05 11:51:16 -06:00
rudimentary OCM support in graph
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
5
changelog/unreleased/initial-ocm-support-for-graph.md
Normal file
5
changelog/unreleased/initial-ocm-support-for-graph.md
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
|
||||
173
services/graph/pkg/service/v0/api_drives_drive_item_ocm.go
Normal file
173
services/graph/pkg/service/v0/api_drives_drive_item_ocm.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user