From 2a772480b9ed40ea18a4eff881dc107e882c5ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Tue, 3 Aug 2021 11:02:44 +0200 Subject: [PATCH 01/15] Add a sharesstorageprovider The provider exposes all received shares. It can be mounted to /home/Shares to make a lot of special cases for shares handling in the gateway and storage drivers superfluous. --- .drone.star | 1 + internal/grpc/services/gateway/appprovider.go | 10 - internal/grpc/services/gateway/gateway.go | 43 +- .../services/gateway/publicshareprovider.go | 5 - .../grpc/services/gateway/storageprovider.go | 1178 ++--------------- .../services/gateway/usershareprovider.go | 5 - .../services/gateway/webdavstorageprovider.go | 215 --- internal/grpc/services/loader/loader.go | 1 + .../mocks/GatewayClient.go | 307 +++++ .../sharesstorageprovider.go | 748 +++++++++++ .../sharesstorageprovider_suite_test.go | 31 + .../sharesstorageprovider_test.go | 619 +++++++++ .../usershareprovider/usershareprovider.go | 1 - internal/http/services/owncloud/ocdav/dav.go | 3 + .../http/services/owncloud/ocdav/ocdav.go | 1 + tests/oc-integration-tests/drone/gateway.toml | 2 +- .../drone/storage-shares-ocis.toml | 14 + 17 files changed, 1834 insertions(+), 1350 deletions(-) create mode 100644 internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go create mode 100644 internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go create mode 100644 internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go create mode 100644 internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go create mode 100644 tests/oc-integration-tests/drone/storage-shares-ocis.toml diff --git a/.drone.star b/.drone.star index c11e9fac7e..366dfc0194 100644 --- a/.drone.star +++ b/.drone.star @@ -613,6 +613,7 @@ def ocisIntegrationTests(parallelRuns, skipExceptParts = []): "/drone/src/cmd/revad/revad -c storage-home-ocis.toml &", "/drone/src/cmd/revad/revad -c storage-oc-ocis.toml &", "/drone/src/cmd/revad/revad -c storage-publiclink-ocis.toml &", + "/drone/src/cmd/revad/revad -c storage-shares-ocis.toml &", "/drone/src/cmd/revad/revad -c ldap-users.toml", ], }, diff --git a/internal/grpc/services/gateway/appprovider.go b/internal/grpc/services/gateway/appprovider.go index b834968ced..3b79213120 100644 --- a/internal/grpc/services/gateway/appprovider.go +++ b/internal/grpc/services/gateway/appprovider.go @@ -55,17 +55,7 @@ func (s *svc) OpenInApp(ctx context.Context, req *gateway.OpenInAppRequest) (*pr }, nil } - if s.isSharedFolder(ctx, p) { - return &providerpb.OpenInAppResponse{ - Status: status.NewInvalid(ctx, "gateway: can't open shares folder"), - }, nil - } - resName, resChild := p, "" - if s.isShareChild(ctx, p) { - resName, resChild = s.splitShare(ctx, p) - } - statRes, err := s.stat(ctx, &storageprovider.StatRequest{ Ref: &storageprovider.Reference{Path: resName}, }) diff --git a/internal/grpc/services/gateway/gateway.go b/internal/grpc/services/gateway/gateway.go index 6908174e16..00bc6ca45a 100644 --- a/internal/grpc/services/gateway/gateway.go +++ b/internal/grpc/services/gateway/gateway.go @@ -42,27 +42,28 @@ func init() { } type config struct { - AuthRegistryEndpoint string `mapstructure:"authregistrysvc"` - ApplicationAuthEndpoint string `mapstructure:"applicationauthsvc"` - StorageRegistryEndpoint string `mapstructure:"storageregistrysvc"` - AppRegistryEndpoint string `mapstructure:"appregistrysvc"` - PreferencesEndpoint string `mapstructure:"preferencessvc"` - UserShareProviderEndpoint string `mapstructure:"usershareprovidersvc"` - PublicShareProviderEndpoint string `mapstructure:"publicshareprovidersvc"` - OCMShareProviderEndpoint string `mapstructure:"ocmshareprovidersvc"` - OCMInviteManagerEndpoint string `mapstructure:"ocminvitemanagersvc"` - OCMProviderAuthorizerEndpoint string `mapstructure:"ocmproviderauthorizersvc"` - OCMCoreEndpoint string `mapstructure:"ocmcoresvc"` - UserProviderEndpoint string `mapstructure:"userprovidersvc"` - GroupProviderEndpoint string `mapstructure:"groupprovidersvc"` - DataTxEndpoint string `mapstructure:"datatx"` - DataGatewayEndpoint string `mapstructure:"datagateway"` - CommitShareToStorageGrant bool `mapstructure:"commit_share_to_storage_grant"` - CommitShareToStorageRef bool `mapstructure:"commit_share_to_storage_ref"` - DisableHomeCreationOnLogin bool `mapstructure:"disable_home_creation_on_login"` - TransferSharedSecret string `mapstructure:"transfer_shared_secret"` - TransferExpires int64 `mapstructure:"transfer_expires"` - TokenManager string `mapstructure:"token_manager"` + StorageRules map[string]map[string]interface{} `mapstructure:"storage_rules"` + AuthRegistryEndpoint string `mapstructure:"authregistrysvc"` + ApplicationAuthEndpoint string `mapstructure:"applicationauthsvc"` + StorageRegistryEndpoint string `mapstructure:"storageregistrysvc"` + AppRegistryEndpoint string `mapstructure:"appregistrysvc"` + PreferencesEndpoint string `mapstructure:"preferencessvc"` + UserShareProviderEndpoint string `mapstructure:"usershareprovidersvc"` + PublicShareProviderEndpoint string `mapstructure:"publicshareprovidersvc"` + OCMShareProviderEndpoint string `mapstructure:"ocmshareprovidersvc"` + OCMInviteManagerEndpoint string `mapstructure:"ocminvitemanagersvc"` + OCMProviderAuthorizerEndpoint string `mapstructure:"ocmproviderauthorizersvc"` + OCMCoreEndpoint string `mapstructure:"ocmcoresvc"` + UserProviderEndpoint string `mapstructure:"userprovidersvc"` + GroupProviderEndpoint string `mapstructure:"groupprovidersvc"` + DataTxEndpoint string `mapstructure:"datatx"` + DataGatewayEndpoint string `mapstructure:"datagateway"` + CommitShareToStorageGrant bool `mapstructure:"commit_share_to_storage_grant"` + CommitShareToStorageRef bool `mapstructure:"commit_share_to_storage_ref"` + DisableHomeCreationOnLogin bool `mapstructure:"disable_home_creation_on_login"` + TransferSharedSecret string `mapstructure:"transfer_shared_secret"` + TransferExpires int64 `mapstructure:"transfer_expires"` + TokenManager string `mapstructure:"token_manager"` // ShareFolder is the location where to create shares in the recipient's storage provider. ShareFolder string `mapstructure:"share_folder"` DataTransfersFolder string `mapstructure:"data_transfers_folder"` diff --git a/internal/grpc/services/gateway/publicshareprovider.go b/internal/grpc/services/gateway/publicshareprovider.go index 88bb41b1c9..2d33b5893d 100644 --- a/internal/grpc/services/gateway/publicshareprovider.go +++ b/internal/grpc/services/gateway/publicshareprovider.go @@ -24,16 +24,11 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" "github.com/cs3org/reva/pkg/appctx" - "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/pkg/errors" ) func (s *svc) CreatePublicShare(ctx context.Context, req *link.CreatePublicShareRequest) (*link.CreatePublicShareResponse, error) { - if s.isSharedFolder(ctx, req.ResourceInfo.GetPath()) { - return nil, errtypes.AlreadyExists("gateway: can't create a public share of the share folder itself") - } - log := appctx.GetLogger(ctx) log.Info().Msg("create public share") diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 5dd46af077..b0d556a974 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -20,7 +20,9 @@ package gateway import ( "context" + "crypto/md5" "fmt" + "io" "net/url" "path" "path/filepath" @@ -28,11 +30,6 @@ import ( "sync" "time" - rtrace "github.com/cs3org/reva/pkg/trace" - - collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -41,7 +38,6 @@ import ( "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/storage/utils/etag" "github.com/cs3org/reva/pkg/utils" "github.com/golang-jwt/jwt" "github.com/google/uuid" @@ -321,153 +317,19 @@ func (s *svc) InitiateFileDownload(ctx context.Context, req *provider.InitiateFi }, nil } - if !s.inSharedFolder(ctx, p) { - statReq := &provider.StatRequest{Ref: req.Ref} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - if statRes.Status.Code != rpc.Code_CODE_OK { - return &gateway.InitiateFileDownloadResponse{ - Status: statRes.Status, - }, nil - } - return s.initiateFileDownload(ctx, req) - } - - if s.isSharedFolder(ctx, p) { - log.Debug().Str("path", p).Msg("path points to shared folder") - err := errtypes.PermissionDenied("gateway: cannot download share folder: path=" + p) - log.Err(err).Msg("gateway: error downloading") + statReq := &provider.StatRequest{Ref: req.Ref} + statRes, err := s.stat(ctx, statReq) + if err != nil { return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInvalidArg(ctx, "path points to share folder"), + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), }, nil - } - - if s.isShareName(ctx, p) { - statReq := &provider.StatRequest{Ref: req.Ref} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - if statRes.Status.Code != rpc.Code_CODE_OK { - return &gateway.InitiateFileDownloadResponse{ - Status: statRes.Status, - }, nil - } - - if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - err := errtypes.BadRequest(fmt.Sprintf("gateway: expected reference: got:%+v", statRes.Info)) - log.Err(err).Msg("gateway: error stating share name") - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error initiating download"), - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - // TODO(ishank011): pass this through the datagateway service - // For now, we just expose the file server to the user - ep, opaque, err := s.webdavRefTransferEndpoint(ctx, statRes.Info.Target) - if err != nil { - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error downloading from webdav host: "+p), - }, nil - } - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewOK(ctx), - Protocols: []*gateway.FileDownloadProtocol{ - { - Opaque: opaque, - Protocol: "simple", - DownloadEndpoint: ep, - }, - }, - }, nil - } - - // if it is a file allow download - if ri.Type == provider.ResourceType_RESOURCE_TYPE_FILE { - log.Debug().Str("path", p).Interface("ri", ri).Msg("path points to share name file") - req.Ref.Path = ri.Path - log.Debug().Str("path", ri.Path).Msg("download") - return s.initiateFileDownload(ctx, req) - } - - log.Debug().Str("path", p).Interface("statRes", statRes).Msg("path:%s points to share name") - err = errtypes.PermissionDenied("gateway: cannot download share name: path=" + p) - log.Err(err).Str("path", p).Msg("gateway: error downloading") + if statRes.Status.Code != rpc.Code_CODE_OK { return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInvalidArg(ctx, "path points to share name"), + Status: statRes.Status, }, nil } - - if s.isShareChild(ctx, p) { - log.Debug().Msgf("shared child: %s", p) - shareName, shareChild := s.splitShare(ctx, p) - - statReq := &provider.StatRequest{ - Ref: &provider.Reference{Path: shareName}, - } - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &gateway.InitiateFileDownloadResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - // TODO(ishank011): pass this through the datagateway service - // For now, we just expose the file server to the user - ep, opaque, err := s.webdavRefTransferEndpoint(ctx, statRes.Info.Target, shareChild) - if err != nil { - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error downloading from webdav host: "+p), - }, nil - } - return &gateway.InitiateFileDownloadResponse{ - Status: status.NewOK(ctx), - Protocols: []*gateway.FileDownloadProtocol{ - { - Opaque: opaque, - Protocol: "simple", - DownloadEndpoint: ep, - }, - }, - }, nil - } - - // append child to target - req.Ref.Path = path.Join(ri.Path, shareChild) - log.Debug().Str("path", req.Ref.Path).Msg("download") - return s.initiateFileDownload(ctx, req) - } - - panic("gateway: download: unknown path:" + p) + return s.initiateFileDownload(ctx, req) } func (s *svc) initiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*gateway.InitiateFileDownloadResponse, error) { @@ -534,139 +396,7 @@ func (s *svc) InitiateFileUpload(ctx context.Context, req *provider.InitiateFile }, nil } - if !s.inSharedFolder(ctx, p) { - return s.initiateFileUpload(ctx, req) - } - - if s.isSharedFolder(ctx, p) { - log.Debug().Str("path", p).Msg("path points to shared folder") - err := errtypes.PermissionDenied("gateway: cannot upload to share folder: path=" + p) - log.Err(err).Msg("gateway: error downloading") - return &gateway.InitiateFileUploadResponse{ - Status: status.NewInvalidArg(ctx, "path points to share folder"), - }, nil - - } - - if s.isShareName(ctx, p) { - log.Debug().Str("path", p).Msg("path points to share name") - statReq := &provider.StatRequest{Ref: req.Ref} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &gateway.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - if statRes.Status.Code != rpc.Code_CODE_OK { - return &gateway.InitiateFileUploadResponse{ - Status: statRes.Status, - }, nil - } - - if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - err := errtypes.BadRequest(fmt.Sprintf("gateway: expected reference: got:%+v", statRes.Info)) - log.Err(err).Msg("gateway: error stating share name") - return &gateway.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error initiating upload"), - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &gateway.InitiateFileUploadResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - // TODO(ishank011): pass this through the datagateway service - // For now, we just expose the file server to the user - ep, opaque, err := s.webdavRefTransferEndpoint(ctx, statRes.Info.Target) - if err != nil { - return &gateway.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error downloading from webdav host: "+p), - }, nil - } - return &gateway.InitiateFileUploadResponse{ - Status: status.NewOK(ctx), - Protocols: []*gateway.FileUploadProtocol{ - { - Opaque: opaque, - Protocol: "simple", - UploadEndpoint: ep, - }, - }, - }, nil - } - - // if it is a file allow upload - if ri.Type == provider.ResourceType_RESOURCE_TYPE_FILE { - log.Debug().Str("path", p).Interface("ri", ri).Msg("path points to share name file") - req.Ref.Path = ri.Path - log.Debug().Str("path", ri.Path).Msg("upload") - return s.initiateFileUpload(ctx, req) - } - - err = errtypes.PermissionDenied("gateway: cannot upload to share name: path=" + p) - log.Err(err).Msg("gateway: error uploading") - return &gateway.InitiateFileUploadResponse{ - Status: status.NewInvalidArg(ctx, "path points to share name"), - }, nil - - } - - if s.isShareChild(ctx, p) { - log.Debug().Msgf("shared child: %s", p) - shareName, shareChild := s.splitShare(ctx, p) - - statReq := &provider.StatRequest{Ref: &provider.Reference{Path: shareName}} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &gateway.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &gateway.InitiateFileUploadResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &gateway.InitiateFileUploadResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - // TODO(ishank011): pass this through the datagateway service - // For now, we just expose the file server to the user - ep, opaque, err := s.webdavRefTransferEndpoint(ctx, statRes.Info.Target, shareChild) - if err != nil { - return &gateway.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error uploading to webdav host: "+p), - }, nil - } - return &gateway.InitiateFileUploadResponse{ - Status: status.NewOK(ctx), - Protocols: []*gateway.FileUploadProtocol{ - { - Opaque: opaque, - Protocol: "simple", - UploadEndpoint: ep, - }, - }, - }, nil - } - - // append child to target - req.Ref.Path = path.Join(ri.Path, shareChild) - return s.initiateFileUpload(ctx, req) - } - - panic("gateway: upload: unknown path:" + p) + return s.initiateFileUpload(ctx, req) } func (s *svc) initiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest) (*gateway.InitiateFileUploadResponse, error) { @@ -761,63 +491,7 @@ func (s *svc) CreateContainer(ctx context.Context, req *provider.CreateContainer }, nil } - if !s.inSharedFolder(ctx, p) { - return s.createContainer(ctx, req) - } - - if s.isSharedFolder(ctx, p) || s.isShareName(ctx, p) { - log.Debug().Msgf("path:%s points to shared folder or share name", p) - err := errtypes.PermissionDenied("gateway: cannot create container on share folder or share name: path=" + p) - log.Err(err).Msg("gateway: error creating container") - return &provider.CreateContainerResponse{ - Status: status.NewInvalidArg(ctx, "path points to share folder or share name"), - }, nil - - } - - if s.isShareChild(ctx, p) { - log.Debug().Msgf("shared child: %s", p) - shareName, shareChild := s.splitShare(ctx, p) - - statReq := &provider.StatRequest{Ref: &provider.Reference{Path: shareName}} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &provider.CreateContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.CreateContainerResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &provider.CreateContainerResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - err = s.webdavRefMkdir(ctx, statRes.Info.Target, shareChild) - if err != nil { - return &provider.CreateContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error creating container on webdav host: "+p), - }, nil - } - return &provider.CreateContainerResponse{ - Status: status.NewOK(ctx), - }, nil - } - - // append child to target - req.Ref.Path = path.Join(ri.Path, shareChild) - return s.createContainer(ctx, req) - } - - panic("gateway: create container on unknown path:" + p) + return s.createContainer(ctx, req) } func (s *svc) createContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) { @@ -836,148 +510,15 @@ func (s *svc) createContainer(ctx context.Context, req *provider.CreateContainer return res, nil } -// check if the path contains the prefix of the shared folder -func (s *svc) inSharedFolder(ctx context.Context, p string) bool { - sharedFolder := s.getSharedFolder(ctx) - return strings.HasPrefix(p, sharedFolder) -} - func (s *svc) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { - log := appctx.GetLogger(ctx) - p, st := s.getPath(ctx, req.Ref) + _, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { return &provider.DeleteResponse{ Status: st, }, nil } - ctx, span := rtrace.Provider.Tracer("reva").Start(ctx, "Delete") - defer span.End() - - if !s.inSharedFolder(ctx, p) { - return s.delete(ctx, req) - } - - if s.isSharedFolder(ctx, p) { - // TODO(labkode): deleting share names should be allowed, means unmounting. - err := errtypes.BadRequest("gateway: cannot delete share folder or share name: path=" + p) - span.RecordError(err) - return &provider.DeleteResponse{ - Status: status.NewInvalidArg(ctx, "path points to share folder or share name"), - }, nil - - } - - if s.isShareName(ctx, p) { - log.Debug().Msgf("path:%s points to share name", p) - - sRes, err := s.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil { - return nil, err - } - - statRes, err := s.Stat(ctx, &provider.StatRequest{ - Ref: &provider.Reference{ - Path: p, - }, - }) - if err != nil { - return nil, err - } - - // the following will check that: - // - the resource to delete is a share the current user received - // - signal the storage the delete must not land in the trashbin - // - delete the resource and update the share status to pending - for _, share := range sRes.Shares { - if statRes != nil && (share.Share.ResourceId.OpaqueId == statRes.Info.Id.OpaqueId) && (share.Share.ResourceId.StorageId == statRes.Info.Id.StorageId) { - // this opaque needs explanation. It signals the storage the resource we're about to delete does not - // belong to the current user because it was share to her, thus delete the "node" and don't send it to - // the trash bin, since the share can be mounted as many times as desired. - req.Opaque = &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "deleting_shared_resource": { - Value: []byte("true"), - Decoder: "plain", - }, - }, - } - - // the following block takes care of updating the state of the share to "pending". This will ensure the user - // can "Accept" the share once again. - r := &collaboration.UpdateReceivedShareRequest{ - Ref: &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: share.Share.Id.OpaqueId, - }, - }, - }, - Field: &collaboration.UpdateReceivedShareRequest_UpdateField{ - Field: &collaboration.UpdateReceivedShareRequest_UpdateField_State{ - State: collaboration.ShareState_SHARE_STATE_REJECTED, - }, - }, - } - - _, err := s.UpdateReceivedShare(ctx, r) - if err != nil { - return nil, err - } - } - } - - ref := &provider.Reference{Path: p} - - req.Ref = ref - return s.delete(ctx, req) - } - - if s.isShareChild(ctx, p) { - shareName, shareChild := s.splitShare(ctx, p) - log.Debug().Msgf("path:%s sharename:%s sharechild: %s", p, shareName, shareChild) - - ref := &provider.Reference{Path: shareName} - - statReq := &provider.StatRequest{Ref: ref} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &provider.DeleteResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.DeleteResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &provider.DeleteResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - err = s.webdavRefDelete(ctx, statRes.Info.Target, shareChild) - if err != nil { - return &provider.DeleteResponse{ - Status: status.NewInternal(ctx, err, "gateway: error deleting resource on webdav host: "+p), - }, nil - } - return &provider.DeleteResponse{ - Status: status.NewOK(ctx), - }, nil - } - - // append child to target - req.Ref.Path = path.Join(ri.Path, shareChild) - return s.delete(ctx, req) - } - - panic("gateway: delete called on unknown path:" + p) + return s.delete(ctx, req) } func (s *svc) delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { @@ -998,94 +539,21 @@ func (s *svc) delete(ctx context.Context, req *provider.DeleteRequest) (*provide } func (s *svc) Move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { - log := appctx.GetLogger(ctx) - p, st := s.getPath(ctx, req.Source) + _, st := s.getPath(ctx, req.Source) if st.Code != rpc.Code_CODE_OK { return &provider.MoveResponse{ Status: st, }, nil } - dp, st := s.getPath(ctx, req.Destination) - if st.Code != rpc.Code_CODE_OK && st.Code != rpc.Code_CODE_NOT_FOUND { + _, st2 := s.getPath(ctx, req.Destination) + if st2.Code != rpc.Code_CODE_OK && st2.Code != rpc.Code_CODE_NOT_FOUND { return &provider.MoveResponse{ - Status: st, + Status: st2, }, nil } - if !s.inSharedFolder(ctx, p) && !s.inSharedFolder(ctx, dp) { - return s.move(ctx, req) - } - - // allow renaming the share folder, the mount point, not the target. - if s.isShareName(ctx, p) && s.isShareName(ctx, dp) { - log.Info().Msgf("gateway: move: renaming share mountpoint: from:%s to:%s", p, dp) - return s.move(ctx, req) - } - - // resolve references and check the ref points to the same base path, paranoia check. - if s.isShareChild(ctx, p) && s.isShareChild(ctx, dp) { - shareName, shareChild := s.splitShare(ctx, p) - dshareName, dshareChild := s.splitShare(ctx, dp) - log.Debug().Msgf("srcpath:%s dstpath:%s srcsharename:%s srcsharechild: %s dstsharename:%s dstsharechild:%s ", p, dp, shareName, shareChild, dshareName, dshareChild) - - if shareName != dshareName { - err := errtypes.BadRequest("gateway: move: src and dst points to different targets") - return &provider.MoveResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway: error moving", err), - }, nil - - } - - statReq := &provider.StatRequest{Ref: &provider.Reference{Path: shareName}} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &provider.MoveResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.MoveResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &provider.MoveResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - err = s.webdavRefMove(ctx, statRes.Info.Target, shareChild, dshareChild) - if err != nil { - return &provider.MoveResponse{ - Status: status.NewInternal(ctx, err, "gateway: error moving resource on webdav host: "+p), - }, nil - } - return &provider.MoveResponse{ - Status: status.NewOK(ctx), - }, nil - } - - src := &provider.Reference{ - Path: path.Join(ri.Path, shareChild), - } - dst := &provider.Reference{ - Path: path.Join(ri.Path, dshareChild), - } - - req.Source = src - req.Destination = dst - - return s.move(ctx, req) - } - - return &provider.MoveResponse{ - Status: status.NewStatusFromErrType(ctx, "move", errtypes.BadRequest("gateway: move called on unknown path: "+p)), - }, nil + return s.move(ctx, req) } func (s *svc) move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { @@ -1179,23 +647,6 @@ func (s *svc) statHome(ctx context.Context) (*provider.StatResponse, error) { }, nil } - statSharedFolder, err := s.statSharesFolder(ctx) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), - }, nil - } - if statSharedFolder.Status.Code != rpc.Code_CODE_OK { - // If shares folder is not found, skip updating the etag - if statSharedFolder.Status.Code == rpc.Code_CODE_NOT_FOUND { - return statRes, nil - } - // otherwise return stat of share folder - return &provider.StatResponse{ - Status: statSharedFolder.Status, - }, nil - } - if etagIface, err := s.etagCache.Get(statRes.Info.Owner.OpaqueId + ":" + statRes.Info.Path); err == nil { resMtime := utils.TSToTime(statRes.Info.Mtime) resEtag := etagIface.(etagWithTS) @@ -1203,56 +654,10 @@ func (s *svc) statHome(ctx context.Context) (*provider.StatResponse, error) { if resMtime.Before(resEtag.Timestamp) { statRes.Info.Etag = resEtag.Etag } - } else { - statRes.Info.Etag = etag.GenerateEtagFromResources(statRes.Info, []*provider.ResourceInfo{statSharedFolder.Info}) - if s.c.EtagCacheTTL > 0 { - _ = s.etagCache.Set(statRes.Info.Owner.OpaqueId+":"+statRes.Info.Path, etagWithTS{statRes.Info.Etag, time.Now()}) - } - } - - return statRes, nil -} - -func (s *svc) statSharesFolder(ctx context.Context) (*provider.StatResponse, error) { - statRes, err := s.stat(ctx, &provider.StatRequest{Ref: &provider.Reference{Path: s.getSharedFolder(ctx)}}) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.StatResponse{ - Status: statRes.Status, - }, nil - } - - lsRes, err := s.listSharesFolder(ctx) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing shares folder"), - }, nil - } - if lsRes.Status.Code != rpc.Code_CODE_OK { - return &provider.StatResponse{ - Status: lsRes.Status, - }, nil + } else if s.c.EtagCacheTTL > 0 { + _ = s.etagCache.Set(statRes.Info.Owner.OpaqueId+":"+statRes.Info.Path, etagWithTS{statRes.Info.Etag, time.Now()}) } - if etagIface, err := s.etagCache.Get(statRes.Info.Owner.OpaqueId + ":" + statRes.Info.Path); err == nil { - resMtime := utils.TSToTime(statRes.Info.Mtime) - resEtag := etagIface.(etagWithTS) - // Use the updated etag if the shares folder has been modified, i.e., a new - // reference has been created. - if resMtime.Before(resEtag.Timestamp) { - statRes.Info.Etag = resEtag.Etag - } - } else { - statRes.Info.Etag = etag.GenerateEtagFromResources(statRes.Info, lsRes.Infos) - if s.c.EtagCacheTTL > 0 { - _ = s.etagCache.Set(statRes.Info.Owner.OpaqueId+":"+statRes.Info.Path, etagWithTS{statRes.Info.Etag, time.Now()}) - } - } return statRes, nil } @@ -1272,11 +677,37 @@ func (s *svc) stat(ctx context.Context, req *provider.StatRequest) (*provider.St Status: status.NewInternal(ctx, err, "error connecting to storage provider="+providers[0].Address), }, nil } - rsp, err := c.Stat(ctx, req) - if err != nil || rsp.Status.Code != rpc.Code_CODE_OK { - return rsp, err + + res, err := c.Stat(ctx, req) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "error connecting to storage provider="+providers[0].Address), + }, nil + } + + embeddedMounts := s.findEmbeddedMounts(resPath) + if len(embeddedMounts) > 0 { + etagHash := md5.New() + if res.Info != nil { + _, _ = io.WriteString(etagHash, res.Info.Etag) + } + for _, child := range embeddedMounts { + childStatRes, err := s.stat(ctx, &provider.StatRequest{Ref: &provider.Reference{Path: child}}) + if err != nil { + return &provider.StatResponse{ + Status: status.NewStatusFromErrType(ctx, "stat ref: "+req.Ref.String(), err), + }, nil + } + _, _ = io.WriteString(etagHash, childStatRes.Info.Etag) + } + + if res.Info == nil { + res.Info = &provider.ResourceInfo{} + } + res.Info.Etag = fmt.Sprintf("%x", etagHash.Sum(nil)) } - return rsp, nil + + return res, nil } return s.statAcrossProviders(ctx, req, providers) @@ -1366,190 +797,7 @@ func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.St return s.statHome(ctx) } - if s.isSharedFolder(ctx, p) { - return s.statSharesFolder(ctx) - } - - if !s.inSharedFolder(ctx, p) { - return s.stat(ctx, req) - } - - // we need to provide the info of the target, not the reference. - if s.isShareName(ctx, p) { - statRes, err := s.stat(ctx, req) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+req.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.StatResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &provider.StatResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - ri, err = s.webdavRefStat(ctx, statRes.Info.Target) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error resolving webdav reference: "+p), - }, nil - } - } - - // we need to make sure we don't expose the reference target in the resource - // information. For example, if requests comes to: /home/MyShares/photos and photos - // is reference to /user/peter/Holidays/photos, we need to still return to the user - // /home/MyShares/photos - orgPath := statRes.Info.Path - statRes.Info = ri - statRes.Info.Path = orgPath - return statRes, nil - - } - - if s.isShareChild(ctx, p) { - shareName, shareChild := s.splitShare(ctx, p) - - statReq := &provider.StatRequest{Ref: &provider.Reference{Path: shareName}} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.StatResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &provider.StatResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - ri, err = s.webdavRefStat(ctx, statRes.Info.Target, shareChild) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error resolving webdav reference: "+p), - }, nil - } - ri.Path = p - return &provider.StatResponse{ - Status: status.NewOK(ctx), - Info: ri, - }, nil - } - - // append child to target - req.Ref.Path = path.Join(ri.Path, shareChild) - res, err := s.stat(ctx, req) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+req.Ref.String()), - }, nil - } - if res.Status.Code != rpc.Code_CODE_OK { - return &provider.StatResponse{ - Status: res.Status, - }, nil - } - - // we need to make sure we don't expose the reference target in the resource - // information. - res.Info.Path = p - return res, nil - } - - panic("gateway: stating an unknown path:" + p) -} - -func (s *svc) checkRef(ctx context.Context, ri *provider.ResourceInfo) (*provider.ResourceInfo, string, error) { - if ri.Type != provider.ResourceType_RESOURCE_TYPE_REFERENCE { - panic("gateway: calling checkRef on a non reference type:" + ri.String()) - } - - // reference types MUST have a target resource id. - if ri.Target == "" { - err := errtypes.BadRequest("gateway: ref target is an empty uri") - return nil, "", err - } - - uri, err := url.Parse(ri.Target) - if err != nil { - return nil, "", errors.Wrapf(err, "gateway: error parsing target uri: %s", ri.Target) - } - - switch uri.Scheme { - case "cs3": - ref, err := s.handleCS3Ref(ctx, uri.Opaque) - return ref, "cs3", err - case "webdav": - return nil, "webdav", nil - default: - err := errtypes.BadRequest("gateway: no reference handler for scheme: " + uri.Scheme) - return nil, "", err - } -} - -func (s *svc) handleCS3Ref(ctx context.Context, opaque string) (*provider.ResourceInfo, error) { - // a cs3 ref has the following layout: / - parts := strings.SplitN(opaque, "/", 2) - if len(parts) < 2 { - err := errtypes.BadRequest("gateway: cs3 ref does not follow the layout storageid/opaqueid:" + opaque) - return nil, err - } - - // we could call here the Stat method again, but that is calling for problems in case - // there is a loop of targets pointing to targets, so better avoid it. - - req := &provider.StatRequest{ - Ref: &provider.Reference{ - ResourceId: &provider.ResourceId{ - StorageId: parts[0], - OpaqueId: parts[1], - }, - }, - } - res, err := s.stat(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "gateway: error calling stat") - } - - if res.Status.Code != rpc.Code_CODE_OK { - switch res.Status.Code { - case rpc.Code_CODE_NOT_FOUND: - return nil, errtypes.NotFound(req.Ref.String()) - case rpc.Code_CODE_PERMISSION_DENIED: - return nil, errtypes.PermissionDenied(req.Ref.String()) - case rpc.Code_CODE_INVALID_ARGUMENT, rpc.Code_CODE_FAILED_PRECONDITION, rpc.Code_CODE_OUT_OF_RANGE: - return nil, errtypes.BadRequest(req.Ref.String()) - case rpc.Code_CODE_UNIMPLEMENTED: - return nil, errtypes.NotSupported(req.Ref.String()) - default: - return nil, errtypes.InternalError("gateway: error stating target reference") - } - } - - if res.Info.Type == provider.ResourceType_RESOURCE_TYPE_REFERENCE { - err := errtypes.BadRequest("gateway: error the target of a reference cannot be another reference") - return nil, err - } - - return res.Info, nil + return s.stat(ctx, req) } func (s *svc) ListContainerStream(_ *provider.ListContainerStreamRequest, _ gateway.GatewayAPI_ListContainerStreamServer) error { @@ -1572,64 +820,6 @@ func (s *svc) listHome(ctx context.Context, req *provider.ListContainerRequest) }, nil } - for i := range lcr.Infos { - if s.isSharedFolder(ctx, lcr.Infos[i].GetPath()) { - statSharedFolder, err := s.statSharesFolder(ctx) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), - }, nil - } - if statSharedFolder.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: statSharedFolder.Status, - }, nil - } - lcr.Infos[i] = statSharedFolder.Info - break - } - } - - return lcr, nil -} - -func (s *svc) listSharesFolder(ctx context.Context) (*provider.ListContainerResponse, error) { - lcr, err := s.listContainer(ctx, &provider.ListContainerRequest{Ref: &provider.Reference{Path: s.getSharedFolder(ctx)}}) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing shared folder"), - }, nil - } - if lcr.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: lcr.Status, - }, nil - } - checkedInfos := make([]*provider.ResourceInfo, 0) - for i := range lcr.Infos { - info, protocol, err := s.checkRef(ctx, lcr.Infos[i]) - if err != nil { - // create status to log the proper messages - // this might arise when the shared resource has been moved to the recycle bin - // this might arise when the resource was unshared, but the share reference was not removed - status.NewStatusFromErrType(ctx, "error resolving reference "+lcr.Infos[i].Target, err) - // continue on errors so the user can see a list of the working shares - continue - } - - if protocol == "webdav" { - info, err = s.webdavRefStat(ctx, lcr.Infos[i].Target) - if err != nil { - // Might be the case that the webdav token has expired - continue - } - } - - info.Path = lcr.Infos[i].Path - checkedInfos = append(checkedInfos, info) - } - lcr.Infos = checkedInfos - return lcr, nil } @@ -1690,6 +880,27 @@ func (s *svc) listContainer(ctx context.Context, req *provider.ListContainerRequ infos = append(infos, inf) } + // Inject mountpoints if they do not exist on disk + embeddedMounts := s.findEmbeddedMounts(path.Clean(req.Ref.GetPath())) + for _, mount := range embeddedMounts { + for _, info := range infos { + if info.Path == mount { + continue + } + } + infos = append(infos, + &provider.ResourceInfo{ + Id: &provider.ResourceId{ + StorageId: "/", + OpaqueId: uuid.New().String(), + }, + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + Etag: uuid.New().String(), + Path: mount, + Size: 0, + }) + } + return &provider.ListContainerResponse{ Status: status.NewOK(ctx), Infos: infos, @@ -1758,168 +969,7 @@ func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequ return s.listHome(ctx, req) } - if s.isSharedFolder(ctx, p) { - return s.listSharesFolder(ctx) - } - - if !s.inSharedFolder(ctx, p) { - return s.listContainer(ctx, req) - } - - // we need to provide the info of the target, not the reference. - if s.isShareName(ctx, p) { - statReq := &provider.StatRequest{Ref: &provider.Reference{Path: p}} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating share:"+statReq.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - infos, err := s.webdavRefLs(ctx, statRes.Info.Target) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing webdav reference: "+p), - }, nil - } - - for _, info := range infos { - base := path.Base(info.Path) - info.Path = path.Join(p, base) - } - return &provider.ListContainerResponse{ - Status: status.NewOK(ctx), - Infos: infos, - }, nil - } - - if ri.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { - err := errtypes.NotSupported("gateway: list container: cannot list non-container type:" + ri.Path) - log.Err(err).Msg("gateway: error listing") - return &provider.ListContainerResponse{ - Status: status.NewInvalidArg(ctx, "resource is not a container"), - }, nil - } - - newReq := &provider.ListContainerRequest{ - Ref: &provider.Reference{Path: ri.Path}, - ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, - } - newRes, err := s.listContainer(ctx, newReq) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing "+newReq.Ref.String()), - }, nil - } - - if newRes.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: newRes.Status, - }, nil - } - - // paths needs to be converted - for _, info := range newRes.Infos { - base := path.Base(info.Path) - info.Path = path.Join(p, base) - } - - return newRes, nil - - } - - if s.isShareChild(ctx, p) { - shareName, shareChild := s.splitShare(ctx, p) - - statReq := &provider.StatRequest{Ref: &provider.Reference{Path: shareName}} - statRes, err := s.stat(ctx, statReq) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating share child "+statReq.Ref.String()), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: statRes.Status, - }, nil - } - - ri, protocol, err := s.checkRef(ctx, statRes.Info) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewStatusFromErrType(ctx, "error resolving reference "+statRes.Info.Target, err), - }, nil - } - - if protocol == "webdav" { - infos, err := s.webdavRefLs(ctx, statRes.Info.Target, shareChild) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing webdav reference: "+p), - }, nil - } - - for _, info := range infos { - base := path.Base(info.Path) - info.Path = path.Join(shareName, shareChild, base) - } - return &provider.ListContainerResponse{ - Status: status.NewOK(ctx), - Infos: infos, - }, nil - } - - if ri.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { - err := errtypes.NotSupported("gateway: list container: cannot list non-container type:" + ri.Path) - log.Err(err).Msg("gateway: error listing") - return &provider.ListContainerResponse{ - Status: status.NewInvalidArg(ctx, "resource is not a container"), - }, nil - } - - newReq := &provider.ListContainerRequest{ - Ref: &provider.Reference{Path: path.Join(ri.Path, shareChild)}, - ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, - } - newRes, err := s.listContainer(ctx, newReq) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing "+newReq.Ref.String()), - }, nil - } - - if newRes.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: newRes.Status, - }, nil - } - - // paths needs to be converted - for _, info := range newRes.Infos { - base := path.Base(info.Path) - info.Path = path.Join(shareName, shareChild, base) - } - - return newRes, nil - - } - - panic("gateway: stating an unknown path:" + p) + return s.listContainer(ctx, req) } func (s *svc) getPath(ctx context.Context, ref *provider.Reference, keys ...string) (string, *rpc.Status) { @@ -1941,75 +991,6 @@ func (s *svc) getPath(ctx context.Context, ref *provider.Reference, keys ...stri return "", &rpc.Status{Code: rpc.Code_CODE_INTERNAL} } -// /home/MyShares/ -func (s *svc) isSharedFolder(ctx context.Context, p string) bool { - return s.split(ctx, p, 2) -} - -// /home/MyShares/photos/ -func (s *svc) isShareName(ctx context.Context, p string) bool { - return s.split(ctx, p, 3) -} - -// /home/MyShares/photos/Ibiza/beach.png -func (s *svc) isShareChild(ctx context.Context, p string) bool { - return s.split(ctx, p, 4) -} - -// always validate that the path contains the share folder -// split cannot be called with i<2 -func (s *svc) split(ctx context.Context, p string, i int) bool { - log := appctx.GetLogger(ctx) - if i < 2 { - panic("split called with i < 2") - } - - parts := s.splitPath(ctx, p) - - // validate that we have always at least two elements - if len(parts) < 2 { - return false - } - - // validate the share folder is always the second element, first element is always the hardcoded value of "home" - if parts[1] != s.c.ShareFolder { - log.Debug().Msgf("gateway: split: parts[1]:%+v != shareFolder:%+v", parts[1], s.c.ShareFolder) - return false - } - - log.Debug().Msgf("gateway: split: path:%+v parts:%+v shareFolder:%+v", p, parts, s.c.ShareFolder) - - if len(parts) == i && parts[i-1] != "" { - return true - } - - return false -} - -// path must contain a share path with share children, if not it will panic. -// should be called after checking isShareChild == true -func (s *svc) splitShare(ctx context.Context, p string) (string, string) { - parts := s.splitPath(ctx, p) - if len(parts) != 4 { - panic("gateway: path for splitShare does not contain 4 elements:" + p) - } - - shareName := path.Join("/", parts[0], parts[1], parts[2]) - shareChild := path.Join("/", parts[3]) - return shareName, shareChild -} - -func (s *svc) splitPath(_ context.Context, p string) []string { - p = strings.Trim(p, "/") - return strings.SplitN(p, "/", 4) // ["home", "MyShares", "photos", "Ibiza/beach.png"] -} - -func (s *svc) getSharedFolder(ctx context.Context) string { - home := s.getHome(ctx) - shareFolder := path.Join(home, s.c.ShareFolder) - return shareFolder -} - func (s *svc) CreateSymlink(ctx context.Context, req *provider.CreateSymlinkRequest) (*provider.CreateSymlinkResponse, error) { return &provider.CreateSymlinkResponse{ Status: status.NewUnimplemented(ctx, errtypes.NotSupported("CreateSymlink not implemented"), "CreateSymlink not implemented"), @@ -2149,6 +1130,19 @@ func (s *svc) getStorageProviderClient(_ context.Context, p *registry.ProviderIn return c, nil } +func (s *svc) findEmbeddedMounts(basePath string) []string { + if basePath == "" { + return []string{} + } + mounts := []string{} + for mountPath := range s.c.StorageRules { + if strings.HasPrefix(mountPath, basePath) && mountPath != basePath { + mounts = append(mounts, mountPath) + } + } + return mounts +} + func (s *svc) findProviders(ctx context.Context, ref *provider.Reference) ([]*registry.ProviderInfo, error) { c, err := pool.GetStorageRegistryClient(s.c.StorageRegistryEndpoint) if err != nil { diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index a1bc98ad0e..c057b3b5c9 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -36,11 +36,6 @@ import ( // TODO(labkode): add multi-phase commit logic when commit share or commit ref is enabled. func (s *svc) CreateShare(ctx context.Context, req *collaboration.CreateShareRequest) (*collaboration.CreateShareResponse, error) { - - if s.isSharedFolder(ctx, req.ResourceInfo.GetPath()) { - return nil, errtypes.AlreadyExists("gateway: can't share the share folder itself") - } - c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint) if err != nil { return &collaboration.CreateShareResponse{ diff --git a/internal/grpc/services/gateway/webdavstorageprovider.go b/internal/grpc/services/gateway/webdavstorageprovider.go index 8d98e8688c..10782dba91 100644 --- a/internal/grpc/services/gateway/webdavstorageprovider.go +++ b/internal/grpc/services/gateway/webdavstorageprovider.go @@ -20,18 +20,11 @@ package gateway import ( "context" - "fmt" "net/url" "path" - "strings" - ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/pkg/errors" - "github.com/studio-b12/gowebdav" ) type webdavEndpoint struct { @@ -40,178 +33,6 @@ type webdavEndpoint struct { token string } -func (s *svc) webdavRefStat(ctx context.Context, targetURL string, nameQueries ...string) (*provider.ResourceInfo, error) { - targetURL, err := appendNameQuery(targetURL, nameQueries...) - if err != nil { - return nil, err - } - - ep, err := s.extractEndpointInfo(ctx, targetURL) - if err != nil { - return nil, err - } - webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) - if err != nil { - return nil, err - } - - c := gowebdav.NewClient(webdavEP, "", "") - c.SetHeader(ctxpkg.TokenHeader, ep.token) - - // TODO(ishank011): We need to call PROPFIND ourselves as we need to retrieve - // ownloud-specific fields to get the resource ID and permissions. - info, err := c.Stat(ep.filePath) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("gateway: error statting %s at the webdav endpoint: %s", ep.filePath, webdavEP)) - } - return normalize(info.(*gowebdav.File)), nil -} - -func (s *svc) webdavRefLs(ctx context.Context, targetURL string, nameQueries ...string) ([]*provider.ResourceInfo, error) { - targetURL, err := appendNameQuery(targetURL, nameQueries...) - if err != nil { - return nil, err - } - - ep, err := s.extractEndpointInfo(ctx, targetURL) - if err != nil { - return nil, err - } - webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) - if err != nil { - return nil, err - } - - c := gowebdav.NewClient(webdavEP, "", "") - c.SetHeader(ctxpkg.TokenHeader, ep.token) - - // TODO(ishank011): We need to call PROPFIND ourselves as we need to retrieve - // ownloud-specific fields to get the resource ID and permissions. - infos, err := c.ReadDir(ep.filePath) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("gateway: error listing %s at the webdav endpoint: %s", ep.filePath, webdavEP)) - } - - mds := []*provider.ResourceInfo{} - for _, fi := range infos { - info := fi.(gowebdav.File) - mds = append(mds, normalize(&info)) - } - return mds, nil -} - -func (s *svc) webdavRefMkdir(ctx context.Context, targetURL string, nameQueries ...string) error { - targetURL, err := appendNameQuery(targetURL, nameQueries...) - if err != nil { - return err - } - - ep, err := s.extractEndpointInfo(ctx, targetURL) - if err != nil { - return err - } - webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) - if err != nil { - return err - } - - c := gowebdav.NewClient(webdavEP, "", "") - c.SetHeader(ctxpkg.TokenHeader, ep.token) - - err = c.Mkdir(ep.filePath, 0700) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("gateway: error creating dir %s at the webdav endpoint: %s", ep.filePath, webdavEP)) - } - return nil -} - -func (s *svc) webdavRefMove(ctx context.Context, targetURL, src, destination string) error { - srcURL, err := appendNameQuery(targetURL, src) - if err != nil { - return err - } - srcEP, err := s.extractEndpointInfo(ctx, srcURL) - if err != nil { - return err - } - srcWebdavEP, err := s.getWebdavEndpoint(ctx, srcEP.endpoint) - if err != nil { - return err - } - - destURL, err := appendNameQuery(targetURL, destination) - if err != nil { - return err - } - destEP, err := s.extractEndpointInfo(ctx, destURL) - if err != nil { - return err - } - - c := gowebdav.NewClient(srcWebdavEP, "", "") - c.SetHeader(ctxpkg.TokenHeader, srcEP.token) - - err = c.Rename(srcEP.filePath, destEP.filePath, true) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("gateway: error renaming %s to %s at the webdav endpoint: %s", srcEP.filePath, destEP.filePath, srcWebdavEP)) - } - return nil -} - -func (s *svc) webdavRefDelete(ctx context.Context, targetURL string, nameQueries ...string) error { - targetURL, err := appendNameQuery(targetURL, nameQueries...) - if err != nil { - return err - } - - ep, err := s.extractEndpointInfo(ctx, targetURL) - if err != nil { - return err - } - webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) - if err != nil { - return err - } - - c := gowebdav.NewClient(webdavEP, "", "") - c.SetHeader(ctxpkg.TokenHeader, ep.token) - - err = c.Remove(ep.filePath) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("gateway: error removing %s at the webdav endpoint: %s", ep.filePath, webdavEP)) - } - return nil -} - -func (s *svc) webdavRefTransferEndpoint(ctx context.Context, targetURL string, nameQueries ...string) (string, *types.Opaque, error) { - targetURL, err := appendNameQuery(targetURL, nameQueries...) - if err != nil { - return "", nil, err - } - - ep, err := s.extractEndpointInfo(ctx, targetURL) - if err != nil { - return "", nil, err - } - webdavEP, err := s.getWebdavEndpoint(ctx, ep.endpoint) - if err != nil { - return "", nil, err - } - - return webdavEP, &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "webdav-file-path": { - Decoder: "plain", - Value: []byte(ep.filePath), - }, - "webdav-token": { - Decoder: "plain", - Value: []byte(ep.token), - }, - }, - }, nil -} - func (s *svc) extractEndpointInfo(ctx context.Context, targetURL string) (*webdavEndpoint, error) { if targetURL == "" { return nil, errtypes.BadRequest("gateway: ref target is an empty uri") @@ -237,42 +58,6 @@ func (s *svc) extractEndpointInfo(ctx context.Context, targetURL string) (*webda }, nil } -func (s *svc) getWebdavEndpoint(ctx context.Context, domain string) (string, error) { - meshProvider, err := s.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ - Domain: domain, - }) - if err != nil { - return "", errors.Wrap(err, "gateway: error calling GetInfoByDomain") - } - for _, s := range meshProvider.ProviderInfo.Services { - if strings.ToLower(s.Endpoint.Type.Name) == "webdav" { - return s.Endpoint.Path, nil - } - } - return "", errtypes.NotFound(domain) -} - -func normalize(info *gowebdav.File) *provider.ResourceInfo { - return &provider.ResourceInfo{ - // TODO(ishank011): Add Id, PermissionSet, Owner - Path: info.Path(), - Type: getResourceType(info.IsDir()), - Etag: info.ETag(), - MimeType: info.ContentType(), - Size: uint64(info.Size()), - Mtime: &types.Timestamp{ - Seconds: uint64(info.ModTime().Unix()), - }, - } -} - -func getResourceType(isDir bool) provider.ResourceType { - if isDir { - return provider.ResourceType_RESOURCE_TYPE_CONTAINER - } - return provider.ResourceType_RESOURCE_TYPE_FILE -} - func appendNameQuery(targetURL string, nameQueries ...string) (string, error) { uri, err := url.Parse(targetURL) if err != nil { diff --git a/internal/grpc/services/loader/loader.go b/internal/grpc/services/loader/loader.go index 118eeed39e..4b72259cdf 100644 --- a/internal/grpc/services/loader/loader.go +++ b/internal/grpc/services/loader/loader.go @@ -36,6 +36,7 @@ import ( _ "github.com/cs3org/reva/internal/grpc/services/preferences" _ "github.com/cs3org/reva/internal/grpc/services/publicshareprovider" _ "github.com/cs3org/reva/internal/grpc/services/publicstorageprovider" + _ "github.com/cs3org/reva/internal/grpc/services/sharesstorageprovider" _ "github.com/cs3org/reva/internal/grpc/services/storageprovider" _ "github.com/cs3org/reva/internal/grpc/services/storageregistry" _ "github.com/cs3org/reva/internal/grpc/services/userprovider" diff --git a/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go b/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go new file mode 100644 index 0000000000..bfc3c645e2 --- /dev/null +++ b/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go @@ -0,0 +1,307 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" + + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// GatewayClient is an autogenerated mock type for the GatewayClient type +type GatewayClient struct { + mock.Mock +} + +// CreateContainer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateContainer(ctx context.Context, in *providerv1beta1.CreateContainerRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateContainerResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateContainerResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateContainerRequest, ...grpc.CallOption) *providerv1beta1.CreateContainerResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateContainerResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateContainerRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Delete(ctx context.Context, in *providerv1beta1.DeleteRequest, opts ...grpc.CallOption) (*providerv1beta1.DeleteResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.DeleteResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.DeleteRequest, ...grpc.CallOption) *providerv1beta1.DeleteResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.DeleteResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.DeleteRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InitiateFileDownload provides a mock function with given fields: ctx, req, opts +func (_m *GatewayClient) InitiateFileDownload(ctx context.Context, req *providerv1beta1.InitiateFileDownloadRequest, opts ...grpc.CallOption) (*gatewayv1beta1.InitiateFileDownloadResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.InitiateFileDownloadResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.InitiateFileDownloadRequest, ...grpc.CallOption) *gatewayv1beta1.InitiateFileDownloadResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.InitiateFileDownloadResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.InitiateFileDownloadRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InitiateFileUpload provides a mock function with given fields: ctx, req, opts +func (_m *GatewayClient) InitiateFileUpload(ctx context.Context, req *providerv1beta1.InitiateFileUploadRequest, opts ...grpc.CallOption) (*gatewayv1beta1.InitiateFileUploadResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.InitiateFileUploadResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.InitiateFileUploadRequest, ...grpc.CallOption) *gatewayv1beta1.InitiateFileUploadResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.InitiateFileUploadResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.InitiateFileUploadRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListContainer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListContainer(ctx context.Context, in *providerv1beta1.ListContainerRequest, opts ...grpc.CallOption) (*providerv1beta1.ListContainerResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListContainerResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListContainerRequest, ...grpc.CallOption) *providerv1beta1.ListContainerResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListContainerResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListContainerRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListFileVersions provides a mock function with given fields: ctx, req, opts +func (_m *GatewayClient) ListFileVersions(ctx context.Context, req *providerv1beta1.ListFileVersionsRequest, opts ...grpc.CallOption) (*providerv1beta1.ListFileVersionsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListFileVersionsResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListFileVersionsRequest, ...grpc.CallOption) *providerv1beta1.ListFileVersionsResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListFileVersionsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListFileVersionsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Move provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Move(ctx context.Context, in *providerv1beta1.MoveRequest, opts ...grpc.CallOption) (*providerv1beta1.MoveResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.MoveResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.MoveRequest, ...grpc.CallOption) *providerv1beta1.MoveResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.MoveResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.MoveRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RestoreFileVersion provides a mock function with given fields: ctx, req, opts +func (_m *GatewayClient) RestoreFileVersion(ctx context.Context, req *providerv1beta1.RestoreFileVersionRequest, opts ...grpc.CallOption) (*providerv1beta1.RestoreFileVersionResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.RestoreFileVersionResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.RestoreFileVersionRequest, ...grpc.CallOption) *providerv1beta1.RestoreFileVersionResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.RestoreFileVersionResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.RestoreFileVersionRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Stat provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Stat(ctx context.Context, in *providerv1beta1.StatRequest, opts ...grpc.CallOption) (*providerv1beta1.StatResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.StatResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.StatRequest, ...grpc.CallOption) *providerv1beta1.StatResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.StatResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.StatRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go new file mode 100644 index 0000000000..d116fc694d --- /dev/null +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -0,0 +1,748 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package sharesstorageprovider + +import ( + "context" + "fmt" + "path/filepath" + "strings" + + "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + gstatus "google.golang.org/grpc/status" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rgrpc" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/storage/utils/etag" + ctxuser "github.com/cs3org/reva/pkg/user" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +//go:generate mockery -name GatewayClient + +// GatewayClient describe the interface of a gateway client +type GatewayClient interface { + Stat(ctx context.Context, in *provider.StatRequest, opts ...grpc.CallOption) (*provider.StatResponse, error) + Move(ctx context.Context, in *provider.MoveRequest, opts ...grpc.CallOption) (*provider.MoveResponse, error) + Delete(ctx context.Context, in *provider.DeleteRequest, opts ...grpc.CallOption) (*provider.DeleteResponse, error) + CreateContainer(ctx context.Context, in *provider.CreateContainerRequest, opts ...grpc.CallOption) (*provider.CreateContainerResponse, error) + ListContainer(ctx context.Context, in *provider.ListContainerRequest, opts ...grpc.CallOption) (*provider.ListContainerResponse, error) + ListFileVersions(ctx context.Context, req *provider.ListFileVersionsRequest, opts ...grpc.CallOption) (*provider.ListFileVersionsResponse, error) + RestoreFileVersion(ctx context.Context, req *provider.RestoreFileVersionRequest, opts ...grpc.CallOption) (*provider.RestoreFileVersionResponse, error) + InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest, opts ...grpc.CallOption) (*gateway.InitiateFileDownloadResponse, error) + InitiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest, opts ...grpc.CallOption) (*gateway.InitiateFileUploadResponse, error) +} + +func init() { + rgrpc.Register("sharesstorageprovider", NewDefault) +} + +type config struct { + MountPath string `mapstructure:"mount_path"` + GatewayAddr string `mapstructure:"gateway_addr"` + UserShareProviderEndpoint string `mapstructure:"usershareprovidersvc"` +} + +type service struct { + mountPath string + gateway GatewayClient + UserShareProviderEndpoint string +} + +func (s *service) Close() error { + return nil +} + +func (s *service) UnprotectedEndpoints() []string { + return []string{} +} + +func (s *service) Register(ss *grpc.Server) { + provider.RegisterProviderAPIServer(ss, s) +} + +// NewDefault returns a new instance of the SharesStorageProvider service with default dependencies +func NewDefault(m map[string]interface{}, _ *grpc.Server) (rgrpc.Service, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + err = errors.Wrap(err, "error decoding conf") + return nil, err + } + + gateway, err := pool.GetGatewayServiceClient(c.GatewayAddr) + if err != nil { + return nil, err + } + + return New(c.MountPath, gateway, c.UserShareProviderEndpoint) +} + +// New returns a new instance of the SharesStorageProvider service +func New(mountpath string, gateway GatewayClient, UserShareProviderEndpoint string) (rgrpc.Service, error) { + s := &service{ + mountPath: mountpath, + gateway: gateway, + UserShareProviderEndpoint: UserShareProviderEndpoint, + } + return s, nil +} + +func (s *service) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest) (*provider.SetArbitraryMetadataResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest) (*provider.UnsetArbitraryMetadataResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) { + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got InitiateFileDownload request") + + if reqShare != "" { + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.InitiateFileDownloadResponse{ + Status: statRes.Status, + }, err + } + return &provider.InitiateFileDownloadResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + gwres, err := s.gateway.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + }) + if err != nil { + return &provider.InitiateFileDownloadResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling InitiateFileDownload"), + }, nil + } + + if gwres.Status.Code != rpc.Code_CODE_OK { + return &provider.InitiateFileDownloadResponse{ + Status: gwres.Status, + }, nil + } + + protocols := []*provider.FileDownloadProtocol{} + for p := range gwres.Protocols { + if !strings.HasSuffix(gwres.Protocols[p].DownloadEndpoint, "/") { + gwres.Protocols[p].DownloadEndpoint += "/" + } + gwres.Protocols[p].DownloadEndpoint += gwres.Protocols[p].Token + + protocols = append(protocols, &provider.FileDownloadProtocol{ + Opaque: gwres.Protocols[p].Opaque, + Protocol: gwres.Protocols[p].Protocol, + DownloadEndpoint: gwres.Protocols[p].DownloadEndpoint, + Expose: true, // the gateway already has encoded the upload endpoint + }) + } + + return &provider.InitiateFileDownloadResponse{ + Status: gwres.Status, + Protocols: protocols, + }, nil + } + + return &provider.InitiateFileDownloadResponse{ + Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), + }, nil +} + +func (s *service) InitiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest) (*provider.InitiateFileUploadResponse, error) { + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got InitiateFileUpload request") + + if reqShare != "" { + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.InitiateFileUploadResponse{ + Status: statRes.Status, + }, err + } + return &provider.InitiateFileUploadResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + gwres, err := s.gateway.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + Opaque: req.Opaque, + }) + if err != nil { + return &provider.InitiateFileUploadResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling InitiateFileDownload"), + }, nil + } + + if gwres.Status.Code != rpc.Code_CODE_OK { + return &provider.InitiateFileUploadResponse{ + Status: gwres.Status, + }, nil + } + + protocols := []*provider.FileUploadProtocol{} + for p := range gwres.Protocols { + if !strings.HasSuffix(gwres.Protocols[p].UploadEndpoint, "/") { + gwres.Protocols[p].UploadEndpoint += "/" + } + gwres.Protocols[p].UploadEndpoint += gwres.Protocols[p].Token + + protocols = append(protocols, &provider.FileUploadProtocol{ + Opaque: gwres.Protocols[p].Opaque, + Protocol: gwres.Protocols[p].Protocol, + UploadEndpoint: gwres.Protocols[p].UploadEndpoint, + AvailableChecksums: gwres.Protocols[p].AvailableChecksums, + Expose: true, // the gateway already has encoded the upload endpoint + }) + } + + return &provider.InitiateFileUploadResponse{ + Status: gwres.Status, + Protocols: protocols, + }, nil + } + + return &provider.InitiateFileUploadResponse{ + Status: status.NewInvalidArg(ctx, "sharesstorageprovider: can not upload directly to the shares folder"), + }, nil + +} + +func (s *service) GetPath(ctx context.Context, req *provider.GetPathRequest) (*provider.GetPathResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) GetHome(ctx context.Context, req *provider.GetHomeRequest) (*provider.GetHomeResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) CreateHome(ctx context.Context, req *provider.CreateHomeRequest) (*provider.CreateHomeResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) (*provider.DeleteStorageSpaceResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) CreateContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) { + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got CreateContainer request") + + if reqShare == "" || reqPath == "" { + return &provider.CreateContainerResponse{ + Status: status.NewInvalid(ctx, "sharesstorageprovider: can not create top-level container"), + }, nil + } + + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.CreateContainerResponse{ + Status: statRes.Status, + }, err + } + return &provider.CreateContainerResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + + gwres, err := s.gateway.CreateContainer(ctx, &provider.CreateContainerRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + }) + + if err != nil { + return &provider.CreateContainerResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling InitiateFileDownload"), + }, nil + } + + if gwres.Status.Code != rpc.Code_CODE_OK { + return &provider.CreateContainerResponse{ + Status: gwres.Status, + }, nil + } + + return gwres, nil +} + +func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got Delete request") + + if reqShare == "" || reqPath == "" { + return &provider.DeleteResponse{ + Status: status.NewInvalid(ctx, "sharesstorageprovider: can not delete top-level container"), + }, nil + } + + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.DeleteResponse{ + Status: statRes.Status, + }, err + } + return &provider.DeleteResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + + gwres, err := s.gateway.Delete(ctx, &provider.DeleteRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + }) + + if err != nil { + return &provider.DeleteResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling Delete"), + }, nil + } + + if gwres.Status.Code != rpc.Code_CODE_OK { + return &provider.DeleteResponse{ + Status: gwres.Status, + }, nil + } + + return gwres, nil +} + +func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { + reqShare, reqPath := s.resolvePath(req.Source.GetPath()) + destinationShare, destinationPath := s.resolvePath(req.Destination.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Interface("destinationPath", destinationPath). + Interface("destinationShare", destinationShare). + Msg("sharesstorageprovider: Got Move request") + + if reqShare == "" || reqPath == "" || destinationPath == "" { + return &provider.MoveResponse{ + Status: status.NewInvalid(ctx, "sharesstorageprovider: can not move top-level share"), + }, nil + } + if reqShare != destinationShare { + return &provider.MoveResponse{ + Status: status.NewInvalid(ctx, "sharesstorageprovider: can not move between shares"), + }, nil + } + + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.MoveResponse{ + Status: statRes.Status, + }, err + } + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + + gwres, err := s.gateway.Move(ctx, &provider.MoveRequest{ + Source: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + Destination: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, destinationPath), + }, + }) + + if err != nil { + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling Move"), + }, nil + } + + if gwres.Status.Code != rpc.Code_CODE_OK { + return &provider.MoveResponse{ + Status: gwres.Status, + }, nil + } + + return gwres, nil +} + +func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got Stat request") + + _, ok := ctxuser.ContextGetUser(ctx) + if !ok { + return &provider.StatResponse{ + Status: status.NewNotFound(ctx, "sharesstorageprovider: shares requested for empty user"), + }, nil + } + + shares, err := s.getReceivedShares(ctx) + if err != nil { + return nil, err + } + + res := &provider.StatResponse{ + Info: &provider.ResourceInfo{ + Path: filepath.Join(s.mountPath), + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + }, + } + childInfos := []*provider.ResourceInfo{} + for _, rs := range shares { + if rs.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { + continue + } + + gwres, err := s.gateway.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + ResourceId: rs.Share.ResourceId, + }, + }) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stat from gateway"), + }, nil + } + + if reqShare != "" && filepath.Base(gwres.Info.Path) == reqShare { + if reqPath != "" { + gwres, err = s.gateway.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(gwres.Info.Path, reqPath), + }, + }) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stat from gateway"), + }, nil + } + if gwres.Status.Code != rpc.Code_CODE_OK { + return gwres, nil + } + } + + relPath := strings.SplitAfterN(gwres.Info.Path, reqShare, 2)[1] + gwres.Info.Path = filepath.Join(s.mountPath, reqShare, relPath) + gwres.Info.PermissionSet = rs.Share.Permissions.Permissions + return gwres, nil + } else if reqShare == "" { + childInfos = append(childInfos, gwres.Info) + res.Info.Size += gwres.Info.Size + } + } + + res.Status = status.NewOK(ctx) + res.Info.Etag = etag.GenerateEtagFromResources(res.Info, childInfos) + return res, nil +} +func (s *service) ListContainerStream(req *provider.ListContainerStreamRequest, ss provider.ProviderAPI_ListContainerStreamServer) error { + return gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) ListContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got ListContainer request") + + shares, err := s.getReceivedShares(ctx) + if err != nil { + return nil, err + } + + res := &provider.ListContainerResponse{} + for _, rs := range shares { + if rs.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { + continue + } + + gwres, err := s.gateway.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + ResourceId: rs.Share.ResourceId, + }, + }) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stats from gateway"), + }, nil + } + + if reqShare != "" && filepath.Base(gwres.Info.Path) == reqShare { + gwListRes, err := s.gateway.ListContainer(ctx, &provider.ListContainerRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(filepath.Dir(gwres.Info.Path), reqShare, reqPath), + }, + }) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting listing from gateway"), + }, nil + } + for _, info := range gwListRes.Infos { + relPath := strings.SplitAfterN(info.Path, reqShare, 2)[1] + info.Path = filepath.Join(s.mountPath, reqShare, relPath) + info.PermissionSet = rs.Share.Permissions.Permissions + } + return gwListRes, nil + } else if reqShare == "" { + gwres.Info.Path = filepath.Join(s.mountPath, filepath.Base(gwres.Info.Path)) + res.Infos = append(res.Infos, gwres.Info) + } + } + res.Status = status.NewOK(ctx) + + return res, nil +} +func (s *service) ListFileVersions(ctx context.Context, req *provider.ListFileVersionsRequest) (*provider.ListFileVersionsResponse, error) { + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got ListFileVersions request") + + if reqShare == "" || reqPath == "" { + return &provider.ListFileVersionsResponse{ + Status: status.NewInvalid(ctx, "sharesstorageprovider: can not list versions of a share or share folder"), + }, nil + } + + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.ListFileVersionsResponse{ + Status: statRes.Status, + }, err + } + return &provider.ListFileVersionsResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + + gwres, err := s.gateway.ListFileVersions(ctx, &provider.ListFileVersionsRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + }) + + if err != nil { + return &provider.ListFileVersionsResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling ListFileVersions"), + }, nil + } + + return gwres, nil + +} + +func (s *service) RestoreFileVersion(ctx context.Context, req *provider.RestoreFileVersionRequest) (*provider.RestoreFileVersionResponse, error) { + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got RestoreFileVersion request") + + if reqShare == "" || reqPath == "" { + return &provider.RestoreFileVersionResponse{ + Status: status.NewInvalid(ctx, "sharesstorageprovider: can not restore version of share or shares folder"), + }, nil + } + + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.RestoreFileVersionResponse{ + Status: statRes.Status, + }, err + } + return &provider.RestoreFileVersionResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + + gwres, err := s.gateway.RestoreFileVersion(ctx, &provider.RestoreFileVersionRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + }) + + if err != nil { + return &provider.RestoreFileVersionResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling ListFileVersions"), + }, nil + } + + return gwres, nil +} + +func (s *service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss provider.ProviderAPI_ListRecycleStreamServer) error { + return gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) (*provider.ListRecycleResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecycleItemRequest) (*provider.RestoreRecycleItemResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) ListGrants(ctx context.Context, req *provider.ListGrantsRequest) (*provider.ListGrantsResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) AddGrant(ctx context.Context, req *provider.AddGrantRequest) (*provider.AddGrantResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) CreateReference(ctx context.Context, req *provider.CreateReferenceRequest) (*provider.CreateReferenceResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) CreateSymlink(ctx context.Context, req *provider.CreateSymlinkRequest) (*provider.CreateSymlinkResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) UpdateGrant(ctx context.Context, req *provider.UpdateGrantRequest) (*provider.UpdateGrantResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) RemoveGrant(ctx context.Context, req *provider.RemoveGrantRequest) (*provider.RemoveGrantResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (*provider.GetQuotaResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + +func (s *service) resolvePath(path string) (string, string) { + // //share/path/to/something + parts := strings.SplitN(strings.TrimLeft(strings.TrimPrefix(path, s.mountPath), "/"), "/", 2) + var reqShare, reqPath string + if len(parts) >= 2 { + reqPath = parts[1] + } + if len(parts) >= 1 { + reqShare = parts[0] + } + return reqShare, reqPath +} + +func (s *service) statShare(ctx context.Context, share string) (*provider.StatResponse, error) { + _, ok := ctxuser.ContextGetUser(ctx) + if !ok { + return &provider.StatResponse{ + Status: status.NewNotFound(ctx, "sharesstorageprovider: shares requested for empty user"), + }, nil + } + + shares, err := s.getReceivedShares(ctx) + if err != nil { + return nil, err + } + + for _, rs := range shares { + if rs.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { + continue + } + + statRes, err := s.gateway.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + ResourceId: rs.Share.ResourceId, + }, + }) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), + }, err + } + + if share != "" && filepath.Base(statRes.Info.Path) == share { + return statRes, nil + } + } + + return &provider.StatResponse{ + Status: status.NewNotFound(ctx, "sharesstorageprovider: requested share was not found for user"), + }, nil +} + +func (s *service) getReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { + c, err := pool.GetUserShareProviderClient(s.UserShareProviderEndpoint) + if err != nil { + return nil, errors.Wrap(err, "sharesstorageprovider: error getting UserShareProvider client") + } + lsRes, err := c.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + if err != nil { + return nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest") + } + if lsRes.Status.Code != rpc.Code_CODE_OK { + return nil, fmt.Errorf("sharesstorageprovider: error calling ListReceivedSharesRequest") + } + return lsRes.Shares, nil +} diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go new file mode 100644 index 0000000000..fceaad3ed3 --- /dev/null +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go @@ -0,0 +1,31 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package sharesstorageprovider_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestSharesstorageprovider(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Sharesstorageprovider Suite") +} diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go new file mode 100644 index 0000000000..b40f6ff65e --- /dev/null +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go @@ -0,0 +1,619 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package sharesstorageprovider_test + +import ( + "context" + "io/ioutil" + "os" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + provider "github.com/cs3org/reva/internal/grpc/services/sharesstorageprovider" + mocks "github.com/cs3org/reva/internal/grpc/services/sharesstorageprovider/mocks" + "github.com/cs3org/reva/pkg/rgrpc/status" + _ "github.com/cs3org/reva/pkg/share/manager/loader" + sharemocks "github.com/cs3org/reva/pkg/share/mocks" + "github.com/cs3org/reva/pkg/user" + "google.golang.org/grpc" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" +) + +var _ = Describe("Sharesstorageprovider", func() { + var ( + config = map[string]interface{}{ + "mount_path": "/shares", + "gateway_addr": "127.0.0.1:1234", + "driver": "json", + "drivers": map[string]map[string]interface{}{ + "json": {}, + }, + } + ctx = user.ContextSetUser(context.Background(), &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: "alice", + }, + Username: "alice", + }) + + rootStatReq = &sprovider.StatRequest{ + Ref: &sprovider.Reference{ + Path: "/shares", + }, + } + rootListContainerReq = &sprovider.ListContainerRequest{ + Ref: &sprovider.Reference{ + Path: "/shares", + }, + } + + s sprovider.ProviderAPIServer + sm *sharemocks.Manager + gw *mocks.GatewayClient + ) + + BeforeEach(func() { + sm = &sharemocks.Manager{} + gw = &mocks.GatewayClient{} + + gw.On("ListContainer", mock.Anything, &sprovider.ListContainerRequest{ + Ref: &sprovider.Reference{ + Path: "/share1-shareddir", + }, + }).Return( + &sprovider.ListContainerResponse{ + Status: status.NewOK(context.Background()), + Infos: []*sprovider.ResourceInfo{ + &sprovider.ResourceInfo{ + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: "/share1-shareddir/share1-subdir", + Id: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "subdir", + }, + Size: 1, + }, + }, + }, nil) + + gw.On("ListContainer", mock.Anything, &sprovider.ListContainerRequest{ + Ref: &sprovider.Reference{ + Path: "/share1-shareddir/share1-subdir", + }, + }).Return( + &sprovider.ListContainerResponse{ + Status: status.NewOK(context.Background()), + Infos: []*sprovider.ResourceInfo{ + &sprovider.ResourceInfo{ + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: "/share1-shareddir/share1-subdir/share1-subdir-file", + Id: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "file", + }, + Size: 1, + }, + }, + }, nil) + + gw.On("Stat", mock.Anything, mock.AnythingOfType("*providerv1beta1.StatRequest")).Return( + func(_ context.Context, req *sprovider.StatRequest, _ ...grpc.CallOption) *sprovider.StatResponse { + if req.Ref.GetPath() == "/share1-shareddir/share1-subdir" { + return &sprovider.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &sprovider.ResourceInfo{ + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: "/share1-shareddir/share1-subdir", + Id: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "subdir", + }, + Size: 10, + }, + } + } else if req.Ref.GetPath() == "/share1-shareddir/share1-subdir/share1-subdir-file" { + return &sprovider.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &sprovider.ResourceInfo{ + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: "/share1-shareddir/share1-subdir/share1-subdir-file", + Id: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "file", + }, + Size: 20, + }, + } + } else { + return &sprovider.StatResponse{ + Status: status.NewOK(context.Background()), + Info: &sprovider.ResourceInfo{ + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: "/share1-shareddir", + Id: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "shareddir", + }, + Size: 1234, + }, + } + } + }, + nil) + + }) + + JustBeforeEach(func() { + p, err := provider.New("/shares", gw, sm) + Expect(err).ToNot(HaveOccurred()) + s = p.(sprovider.ProviderAPIServer) + Expect(s).ToNot(BeNil()) + }) + + Describe("NewDefault", func() { + It("returns a new service instance", func() { + tmpfile, err := ioutil.TempFile("", "eos-unit-test-shares-*.json") + Expect(err).ToNot(HaveOccurred()) + defer os.Remove(tmpfile.Name()) + + config["drivers"] = map[string]map[string]interface{}{ + "json": { + "file": tmpfile.Name(), + }, + } + s, err := provider.NewDefault(config, nil) + Expect(err).ToNot(HaveOccurred()) + Expect(s).ToNot(BeNil()) + }) + }) + + Describe("ListContainer", func() { + It("only considers accepted shares", func() { + sm.On("ListReceivedShares", mock.Anything).Return([]*collaboration.ReceivedShare{ + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_INVALID, + }, + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_PENDING, + }, + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_REJECTED, + }, + }, nil) + res, err := s.ListContainer(ctx, rootListContainerReq) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(len(res.Infos)).To(Equal(0)) + }) + }) + + Context("with an accepted share", func() { + BeforeEach(func() { + sm.On("ListReceivedShares", mock.Anything).Return([]*collaboration.ReceivedShare{ + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_ACCEPTED, + Share: &collaboration.Share{ + ResourceId: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "shareddir", + }, + Permissions: &collaboration.SharePermissions{ + Permissions: &sprovider.ResourcePermissions{ + Stat: true, + }, + }, + }, + }, + }, nil) + }) + + Describe("Stat", func() { + It("stats the root shares folder", func() { + res, err := s.Stat(ctx, rootStatReq) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER)) + Expect(res.Info.Path).To(Equal("/shares")) + Expect(res.Info.Size).To(Equal(uint64(1234))) + }) + + It("stats a shares folder", func() { + statReq := &sprovider.StatRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir", + }, + } + res, err := s.Stat(ctx, statReq) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER)) + Expect(res.Info.Path).To(Equal("/shares/share1-shareddir")) + Expect(res.Info.Size).To(Equal(uint64(1234))) + }) + + It("stats a subfolder in a share", func() { + statReq := &sprovider.StatRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-subdir", + }, + } + res, err := s.Stat(ctx, statReq) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER)) + Expect(res.Info.Path).To(Equal("/shares/share1-shareddir/share1-subdir")) + Expect(res.Info.Size).To(Equal(uint64(10))) + }) + + It("stats a shared file", func() { + statReq := &sprovider.StatRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-subdir/share1-subdir-file", + }, + } + res, err := s.Stat(ctx, statReq) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_FILE)) + Expect(res.Info.Path).To(Equal("/shares/share1-shareddir/share1-subdir/share1-subdir-file")) + Expect(res.Info.Size).To(Equal(uint64(20))) + }) + }) + + Describe("ListContainer", func() { + It("lists shares", func() { + res, err := s.ListContainer(ctx, rootListContainerReq) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + Expect(len(res.Infos)).To(Equal(1)) + + entry := res.Infos[0] + Expect(entry.Path).To(Equal("/shares/share1-shareddir")) + }) + + It("traverses into specific shares", func() { + req := &sprovider.ListContainerRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir", + }, + } + res, err := s.ListContainer(ctx, req) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + Expect(len(res.Infos)).To(Equal(1)) + + entry := res.Infos[0] + Expect(entry.Path).To(Equal("/shares/share1-shareddir/share1-subdir")) + }) + + It("traverses into subfolders of specific shares", func() { + req := &sprovider.ListContainerRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-subdir", + }, + } + res, err := s.ListContainer(ctx, req) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + Expect(len(res.Infos)).To(Equal(1)) + + entry := res.Infos[0] + Expect(entry.Path).To(Equal("/shares/share1-shareddir/share1-subdir/share1-subdir-file")) + }) + }) + + Describe("InitiateFileDownload", func() { + It("returns not found when not found", func() { + gw.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gateway.InitiateFileDownloadResponse{ + Status: status.NewNotFound(ctx, "gateway: file not found"), + }, nil) + + req := &sprovider.InitiateFileDownloadRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/does-not-exist", + }, + } + res, err := s.InitiateFileDownload(ctx, req) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_NOT_FOUND)) + }) + + It("initiates the download of an existing file", func() { + gw.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gateway.InitiateFileDownloadResponse{ + Status: status.NewOK(ctx), + Protocols: []*gateway.FileDownloadProtocol{ + &gateway.FileDownloadProtocol{ + Opaque: &types.Opaque{}, + Protocol: "simple", + DownloadEndpoint: "https://localhost:9200/data", + Token: "thetoken", + }, + }, + }, nil) + req := &sprovider.InitiateFileDownloadRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-subdir/share1-subdir-file", + }, + } + res, err := s.InitiateFileDownload(ctx, req) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + Expect(res.Protocols[0].Protocol).To(Equal("simple")) + Expect(res.Protocols[0].DownloadEndpoint).To(Equal("https://localhost:9200/data/thetoken")) + }) + }) + + Describe("CreateContainer", func() { + BeforeEach(func() { + gw.On("CreateContainer", mock.Anything, mock.Anything).Return(&sprovider.CreateContainerResponse{ + Status: status.NewOK(ctx), + }, nil) + }) + + It("refuses to create a top-level container which doesn't belong to a share", func() { + req := &sprovider.CreateContainerRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/invalid-top-level-subdir", + }, + } + res, err := s.CreateContainer(ctx, req) + gw.AssertNotCalled(GinkgoT(), "CreateContainer", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_INVALID_ARGUMENT)) + }) + + It("creates a directory", func() { + req := &sprovider.CreateContainerRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-newsubdir", + }, + } + res, err := s.CreateContainer(ctx, req) + gw.AssertCalled(GinkgoT(), "CreateContainer", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + }) + }) + + Describe("Delete", func() { + BeforeEach(func() { + gw.On("Delete", mock.Anything, mock.Anything).Return(&sprovider.DeleteResponse{ + Status: status.NewOK(ctx), + }, nil) + }) + + It("refuses to delete a share", func() { + req := &sprovider.DeleteRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir", + }, + } + res, err := s.Delete(ctx, req) + gw.AssertNotCalled(GinkgoT(), "Delete", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_INVALID_ARGUMENT)) + }) + + It("deletes a file", func() { + req := &sprovider.DeleteRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-subdir/share1-subdir-file", + }, + } + res, err := s.Delete(ctx, req) + gw.AssertCalled(GinkgoT(), "Delete", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + }) + }) + + Describe("Move", func() { + BeforeEach(func() { + gw.On("Move", mock.Anything, mock.Anything).Return(&sprovider.MoveResponse{ + Status: status.NewOK(ctx), + }, nil) + }) + + It("refuses to move a share", func() { + req := &sprovider.MoveRequest{ + Source: &sprovider.Reference{ + Path: "/shares/share1-shareddir", + }, + Destination: &sprovider.Reference{ + Path: "/shares/newname", + }, + } + res, err := s.Move(ctx, req) + gw.AssertNotCalled(GinkgoT(), "Move", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_INVALID_ARGUMENT)) + }) + + It("refuses to move a file between shares", func() { + req := &sprovider.MoveRequest{ + Source: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-shareddir-file", + }, + Destination: &sprovider.Reference{ + Path: "/shares/share2-shareddir/share2-shareddir-file", + }, + } + res, err := s.Move(ctx, req) + gw.AssertNotCalled(GinkgoT(), "Move", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_INVALID_ARGUMENT)) + }) + + It("moves a file", func() { + req := &sprovider.MoveRequest{ + Source: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-shareddir-file", + }, + Destination: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-shareddir-filenew", + }, + } + res, err := s.Move(ctx, req) + gw.AssertCalled(GinkgoT(), "Move", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + }) + }) + + Describe("ListFileVersions", func() { + BeforeEach(func() { + gw.On("ListFileVersions", mock.Anything, mock.Anything).Return( + &sprovider.ListFileVersionsResponse{ + Status: status.NewOK(ctx), + Versions: []*sprovider.FileVersion{ + { + Size: 10, + Mtime: 1, + Etag: "1", + Key: "1", + }, + { + Size: 20, + Mtime: 2, + Etag: "2", + Key: "2", + }, + }, + }, nil) + }) + + It("does not try to list versions of shares or the top-level dir", func() { + req := &sprovider.ListFileVersionsRequest{ + Ref: &sprovider.Reference{ + Path: "/shares", + }, + } + res, err := s.ListFileVersions(ctx, req) + gw.AssertNotCalled(GinkgoT(), "ListFileVersions", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_INVALID_ARGUMENT)) + + req = &sprovider.ListFileVersionsRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/", + }, + } + res, err = s.ListFileVersions(ctx, req) + gw.AssertNotCalled(GinkgoT(), "ListFileVersions", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_INVALID_ARGUMENT)) + }) + + It("lists versions", func() { + req := &sprovider.ListFileVersionsRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-shareddir-file", + }, + } + res, err := s.ListFileVersions(ctx, req) + gw.AssertCalled(GinkgoT(), "ListFileVersions", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + Expect(len(res.Versions)).To(Equal(2)) + version := res.Versions[0] + Expect(version.Key).To(Equal("1")) + Expect(version.Etag).To(Equal("1")) + Expect(version.Mtime).To(Equal(uint64(1))) + Expect(version.Size).To(Equal(uint64(10))) + }) + }) + + Describe("RestoreFileVersion", func() { + BeforeEach(func() { + gw.On("RestoreFileVersion", mock.Anything, mock.Anything).Return( + &sprovider.RestoreFileVersionResponse{ + Status: status.NewOK(ctx), + }, nil) + }) + + It("restores a file version", func() { + req := &sprovider.RestoreFileVersionRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-shareddir-file", + }, + Key: "1", + } + res, err := s.RestoreFileVersion(ctx, req) + gw.AssertCalled(GinkgoT(), "RestoreFileVersion", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + }) + }) + + Describe("InitiateFileUpload", func() { + BeforeEach(func() { + gw.On("InitiateFileUpload", mock.Anything, mock.Anything).Return( + &gateway.InitiateFileUploadResponse{ + Status: status.NewOK(ctx), + Protocols: []*gateway.FileUploadProtocol{ + { + Opaque: &types.Opaque{}, + Protocol: "simple", + UploadEndpoint: "https://localhost:9200/data", + Token: "thetoken", + }, + }, + }, nil) + }) + + It("initiates a file upload", func() { + req := &sprovider.InitiateFileUploadRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-shareddir-file", + }, + } + res, err := s.InitiateFileUpload(ctx, req) + gw.AssertCalled(GinkgoT(), "InitiateFileUpload", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + Expect(len(res.Protocols)).To(Equal(1)) + Expect(res.Protocols[0].Protocol).To(Equal("simple")) + Expect(res.Protocols[0].UploadEndpoint).To(Equal("https://localhost:9200/data/thetoken")) + }) + }) + }) +}) diff --git a/internal/grpc/services/usershareprovider/usershareprovider.go b/internal/grpc/services/usershareprovider/usershareprovider.go index 4130dacc77..d60f895c73 100644 --- a/internal/grpc/services/usershareprovider/usershareprovider.go +++ b/internal/grpc/services/usershareprovider/usershareprovider.go @@ -87,7 +87,6 @@ func parseConfig(m map[string]interface{}) (*config, error) { // New creates a new user share provider svc func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { - c, err := parseConfig(m) if err != nil { return nil, err diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index 137423013e..d0e4e8d3a1 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -20,6 +20,7 @@ package ocdav import ( "context" + "fmt" "net/http" "path" "strings" @@ -48,6 +49,7 @@ type DavHandler struct { SpacesHandler *SpacesHandler PublicFolderHandler *WebDavHandler PublicFileHandler *PublicFileHandler + SharesHandler *WebDavHandler } func (h *DavHandler) init(c *Config) error { @@ -96,6 +98,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() log := appctx.GetLogger(ctx) + log.Info().Str("request", fmt.Sprintf("%#v", r)).Msg("Got webdav request") // if there is no file in the request url we assume the request url is: "/remote.php/dav/files" // https://github.com/owncloud/core/blob/18475dac812064b21dabcc50f25ef3ffe55691a5/tests/acceptance/features/apiWebdavOperations/propfind.feature diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index 2de1fbbec6..284912c850 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -96,6 +96,7 @@ type Config struct { // and received path is /docs the internal path will be: // /users///docs WebdavNamespace string `mapstructure:"webdav_namespace"` + SharesNamespace string `mapstructure:"shares_namespace"` GatewaySvc string `mapstructure:"gatewaysvc"` Timeout int64 `mapstructure:"timeout"` Insecure bool `mapstructure:"insecure"` diff --git a/tests/oc-integration-tests/drone/gateway.toml b/tests/oc-integration-tests/drone/gateway.toml index 03b2514ba6..06bfe91d8e 100644 --- a/tests/oc-integration-tests/drone/gateway.toml +++ b/tests/oc-integration-tests/drone/gateway.toml @@ -72,7 +72,7 @@ home_provider = "/home" # another mount point might be "/projects/" "/public" = {"address" = "localhost:13000"} - +"/home/Shares" = {"address" = "localhost:14000"} [http] address = "0.0.0.0:19001" diff --git a/tests/oc-integration-tests/drone/storage-shares-ocis.toml b/tests/oc-integration-tests/drone/storage-shares-ocis.toml new file mode 100644 index 0000000000..97403ef3fa --- /dev/null +++ b/tests/oc-integration-tests/drone/storage-shares-ocis.toml @@ -0,0 +1,14 @@ +# This storage.toml config file will start a reva service that: +[shared] +jwt_secret = "Pive-Fumkiu4" +gatewaysvc = "localhost:19000" + +[grpc] +address = "0.0.0.0:14000" + +# This is a storage provider that grants direct access to the wrapped storage +# we have a locally running dataprovider +[grpc.services.sharesstorageprovider] +mount_path = "/home/Shares" +gateway_addr = "0.0.0.0:19000" +usershareprovidersvc = "0.0.0.0:17000" \ No newline at end of file From 1cad996a7299e9e1b9ef5b34443bdcd7d419980a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Tue, 3 Aug 2021 12:47:12 +0200 Subject: [PATCH 02/15] Add missing mock file --- pkg/share/mocks/Manager.go | 211 +++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 pkg/share/mocks/Manager.go diff --git a/pkg/share/mocks/Manager.go b/pkg/share/mocks/Manager.go new file mode 100644 index 0000000000..91b8cf7ae1 --- /dev/null +++ b/pkg/share/mocks/Manager.go @@ -0,0 +1,211 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + + mock "github.com/stretchr/testify/mock" + + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// Manager is an autogenerated mock type for the Manager type +type Manager struct { + mock.Mock +} + +// GetReceivedShare provides a mock function with given fields: ctx, ref +func (_m *Manager) GetReceivedShare(ctx context.Context, ref *collaborationv1beta1.ShareReference) (*collaborationv1beta1.ReceivedShare, error) { + ret := _m.Called(ctx, ref) + + var r0 *collaborationv1beta1.ReceivedShare + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareReference) *collaborationv1beta1.ReceivedShare); ok { + r0 = rf(ctx, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.ReceivedShare) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ShareReference) error); ok { + r1 = rf(ctx, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetShare provides a mock function with given fields: ctx, ref +func (_m *Manager) GetShare(ctx context.Context, ref *collaborationv1beta1.ShareReference) (*collaborationv1beta1.Share, error) { + ret := _m.Called(ctx, ref) + + var r0 *collaborationv1beta1.Share + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareReference) *collaborationv1beta1.Share); ok { + r0 = rf(ctx, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.Share) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ShareReference) error); ok { + r1 = rf(ctx, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListReceivedShares provides a mock function with given fields: ctx +func (_m *Manager) ListReceivedShares(ctx context.Context) ([]*collaborationv1beta1.ReceivedShare, error) { + ret := _m.Called(ctx) + + var r0 []*collaborationv1beta1.ReceivedShare + if rf, ok := ret.Get(0).(func(context.Context) []*collaborationv1beta1.ReceivedShare); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*collaborationv1beta1.ReceivedShare) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListShares provides a mock function with given fields: ctx, filters +func (_m *Manager) ListShares(ctx context.Context, filters []*collaborationv1beta1.ListSharesRequest_Filter) ([]*collaborationv1beta1.Share, error) { + ret := _m.Called(ctx, filters) + + var r0 []*collaborationv1beta1.Share + if rf, ok := ret.Get(0).(func(context.Context, []*collaborationv1beta1.ListSharesRequest_Filter) []*collaborationv1beta1.Share); ok { + r0 = rf(ctx, filters) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*collaborationv1beta1.Share) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, []*collaborationv1beta1.ListSharesRequest_Filter) error); ok { + r1 = rf(ctx, filters) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Share provides a mock function with given fields: ctx, md, g +func (_m *Manager) Share(ctx context.Context, md *providerv1beta1.ResourceInfo, g *collaborationv1beta1.ShareGrant) (*collaborationv1beta1.Share, error) { + ret := _m.Called(ctx, md, g) + + var r0 *collaborationv1beta1.Share + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceInfo, *collaborationv1beta1.ShareGrant) *collaborationv1beta1.Share); ok { + r0 = rf(ctx, md, g) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.Share) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceInfo, *collaborationv1beta1.ShareGrant) error); ok { + r1 = rf(ctx, md, g) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Unshare provides a mock function with given fields: ctx, ref +func (_m *Manager) Unshare(ctx context.Context, ref *collaborationv1beta1.ShareReference) error { + ret := _m.Called(ctx, ref) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareReference) error); ok { + r0 = rf(ctx, ref) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateReceivedShare provides a mock function with given fields: ctx, ref, f +func (_m *Manager) UpdateReceivedShare(ctx context.Context, ref *collaborationv1beta1.ShareReference, f *collaborationv1beta1.UpdateReceivedShareRequest_UpdateField) (*collaborationv1beta1.ReceivedShare, error) { + ret := _m.Called(ctx, ref, f) + + var r0 *collaborationv1beta1.ReceivedShare + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareReference, *collaborationv1beta1.UpdateReceivedShareRequest_UpdateField) *collaborationv1beta1.ReceivedShare); ok { + r0 = rf(ctx, ref, f) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.ReceivedShare) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ShareReference, *collaborationv1beta1.UpdateReceivedShareRequest_UpdateField) error); ok { + r1 = rf(ctx, ref, f) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateShare provides a mock function with given fields: ctx, ref, p +func (_m *Manager) UpdateShare(ctx context.Context, ref *collaborationv1beta1.ShareReference, p *collaborationv1beta1.SharePermissions) (*collaborationv1beta1.Share, error) { + ret := _m.Called(ctx, ref, p) + + var r0 *collaborationv1beta1.Share + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ShareReference, *collaborationv1beta1.SharePermissions) *collaborationv1beta1.Share); ok { + r0 = rf(ctx, ref, p) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.Share) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ShareReference, *collaborationv1beta1.SharePermissions) error); ok { + r1 = rf(ctx, ref, p) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} From 195ee5eff82bf0f14cc54f25357d06c531c3f1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Tue, 3 Aug 2021 15:09:45 +0200 Subject: [PATCH 03/15] Implement methods for setting/unsetting arbitrary metadata --- .../mocks/GatewayClient.go | 60 +++++++++ .../mocks/ReceivedSharesLister.go | 66 ++++++++++ .../sharesstorageprovider.go | 114 +++++++++++++++--- .../sharesstorageprovider_test.go | 84 ++++++++----- 4 files changed, 281 insertions(+), 43 deletions(-) create mode 100644 internal/grpc/services/sharesstorageprovider/mocks/ReceivedSharesLister.go diff --git a/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go b/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go index bfc3c645e2..3ebf3a3c1a 100644 --- a/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go +++ b/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go @@ -276,6 +276,36 @@ func (_m *GatewayClient) RestoreFileVersion(ctx context.Context, req *providerv1 return r0, r1 } +// SetArbitraryMetadata provides a mock function with given fields: ctx, req, opts +func (_m *GatewayClient) SetArbitraryMetadata(ctx context.Context, req *providerv1beta1.SetArbitraryMetadataRequest, opts ...grpc.CallOption) (*providerv1beta1.SetArbitraryMetadataResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.SetArbitraryMetadataResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.SetArbitraryMetadataRequest, ...grpc.CallOption) *providerv1beta1.SetArbitraryMetadataResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.SetArbitraryMetadataResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.SetArbitraryMetadataRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Stat provides a mock function with given fields: ctx, in, opts func (_m *GatewayClient) Stat(ctx context.Context, in *providerv1beta1.StatRequest, opts ...grpc.CallOption) (*providerv1beta1.StatResponse, error) { _va := make([]interface{}, len(opts)) @@ -305,3 +335,33 @@ func (_m *GatewayClient) Stat(ctx context.Context, in *providerv1beta1.StatReque return r0, r1 } + +// UnsetArbitraryMetadata provides a mock function with given fields: ctx, req, opts +func (_m *GatewayClient) UnsetArbitraryMetadata(ctx context.Context, req *providerv1beta1.UnsetArbitraryMetadataRequest, opts ...grpc.CallOption) (*providerv1beta1.UnsetArbitraryMetadataResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.UnsetArbitraryMetadataResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.UnsetArbitraryMetadataRequest, ...grpc.CallOption) *providerv1beta1.UnsetArbitraryMetadataResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.UnsetArbitraryMetadataResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.UnsetArbitraryMetadataRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/internal/grpc/services/sharesstorageprovider/mocks/ReceivedSharesLister.go b/internal/grpc/services/sharesstorageprovider/mocks/ReceivedSharesLister.go new file mode 100644 index 0000000000..c0b8efd61d --- /dev/null +++ b/internal/grpc/services/sharesstorageprovider/mocks/ReceivedSharesLister.go @@ -0,0 +1,66 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" +) + +// ReceivedSharesLister is an autogenerated mock type for the ReceivedSharesLister type +type ReceivedSharesLister struct { + mock.Mock +} + +// ListReceivedShares provides a mock function with given fields: ctx, req, opts +func (_m *ReceivedSharesLister) ListReceivedShares(ctx context.Context, req *collaborationv1beta1.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListReceivedSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.ListReceivedSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ListReceivedSharesRequest, ...grpc.CallOption) *collaborationv1beta1.ListReceivedSharesResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.ListReceivedSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ListReceivedSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index d116fc694d..202ae67f22 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -42,7 +42,7 @@ import ( "github.com/pkg/errors" ) -//go:generate mockery -name GatewayClient +//go:generate mockery -name GatewayClient -name ReceivedSharesLister // GatewayClient describe the interface of a gateway client type GatewayClient interface { @@ -55,6 +55,13 @@ type GatewayClient interface { RestoreFileVersion(ctx context.Context, req *provider.RestoreFileVersionRequest, opts ...grpc.CallOption) (*provider.RestoreFileVersionResponse, error) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest, opts ...grpc.CallOption) (*gateway.InitiateFileDownloadResponse, error) InitiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest, opts ...grpc.CallOption) (*gateway.InitiateFileUploadResponse, error) + SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest, opts ...grpc.CallOption) (*provider.SetArbitraryMetadataResponse, error) + UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest, opts ...grpc.CallOption) (*provider.UnsetArbitraryMetadataResponse, error) +} + +// ReceivedSharesLister lists received shares +type ReceivedSharesLister interface { + ListReceivedShares(ctx context.Context, req *collaboration.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaboration.ListReceivedSharesResponse, error) } func init() { @@ -68,9 +75,9 @@ type config struct { } type service struct { - mountPath string - gateway GatewayClient - UserShareProviderEndpoint string + mountPath string + gateway GatewayClient + receivedSharesLister ReceivedSharesLister } func (s *service) Close() error { @@ -98,25 +105,104 @@ func NewDefault(m map[string]interface{}, _ *grpc.Server) (rgrpc.Service, error) return nil, err } - return New(c.MountPath, gateway, c.UserShareProviderEndpoint) + client, err := pool.GetUserShareProviderClient(c.UserShareProviderEndpoint) + if err != nil { + return nil, errors.Wrap(err, "sharesstorageprovider: error getting UserShareProvider client") + } + + return New(c.MountPath, gateway, client) } // New returns a new instance of the SharesStorageProvider service -func New(mountpath string, gateway GatewayClient, UserShareProviderEndpoint string) (rgrpc.Service, error) { +func New(mountpath string, gateway GatewayClient, l ReceivedSharesLister) (rgrpc.Service, error) { s := &service{ - mountPath: mountpath, - gateway: gateway, - UserShareProviderEndpoint: UserShareProviderEndpoint, + mountPath: mountpath, + gateway: gateway, + receivedSharesLister: l, } return s, nil } func (s *service) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest) (*provider.SetArbitraryMetadataResponse, error) { - return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got SetArbitraryMetadata request") + + if reqShare == "" { + return &provider.SetArbitraryMetadataResponse{ + Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), + }, nil + } + + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.SetArbitraryMetadataResponse{ + Status: statRes.Status, + }, err + } + return &provider.SetArbitraryMetadataResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + + gwres, err := s.gateway.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + ArbitraryMetadata: req.ArbitraryMetadata, + }) + + if err != nil { + return &provider.SetArbitraryMetadataResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling SetArbitraryMetadata"), + }, nil + } + + return gwres, nil } func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest) (*provider.UnsetArbitraryMetadataResponse, error) { - return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") + reqShare, reqPath := s.resolvePath(req.Ref.GetPath()) + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Msg("sharesstorageprovider: Got UnsetArbitraryMetadata request") + + if reqShare == "" { + return &provider.UnsetArbitraryMetadataResponse{ + Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), + }, nil + } + + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + if statRes != nil { + return &provider.UnsetArbitraryMetadataResponse{ + Status: statRes.Status, + }, err + } + return &provider.UnsetArbitraryMetadataResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + + gwres, err := s.gateway.UnsetArbitraryMetadata(ctx, &provider.UnsetArbitraryMetadataRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, + }) + + if err != nil { + return &provider.UnsetArbitraryMetadataResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling UnsetArbitraryMetadata"), + }, nil + } + + return gwres, nil } func (s *service) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) { @@ -733,11 +819,7 @@ func (s *service) statShare(ctx context.Context, share string) (*provider.StatRe } func (s *service) getReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { - c, err := pool.GetUserShareProviderClient(s.UserShareProviderEndpoint) - if err != nil { - return nil, errors.Wrap(err, "sharesstorageprovider: error getting UserShareProvider client") - } - lsRes, err := c.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + lsRes, err := s.receivedSharesLister.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) if err != nil { return nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest") } diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go index b40f6ff65e..531d44e3fd 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go @@ -33,7 +33,6 @@ import ( mocks "github.com/cs3org/reva/internal/grpc/services/sharesstorageprovider/mocks" "github.com/cs3org/reva/pkg/rgrpc/status" _ "github.com/cs3org/reva/pkg/share/manager/loader" - sharemocks "github.com/cs3org/reva/pkg/share/mocks" "github.com/cs3org/reva/pkg/user" "google.golang.org/grpc" @@ -70,15 +69,14 @@ var _ = Describe("Sharesstorageprovider", func() { }, } - s sprovider.ProviderAPIServer - sm *sharemocks.Manager - gw *mocks.GatewayClient + s sprovider.ProviderAPIServer + gw *mocks.GatewayClient + receivedSharesLister *mocks.ReceivedSharesLister ) BeforeEach(func() { - sm = &sharemocks.Manager{} + receivedSharesLister = &mocks.ReceivedSharesLister{} gw = &mocks.GatewayClient{} - gw.On("ListContainer", mock.Anything, &sprovider.ListContainerRequest{ Ref: &sprovider.Reference{ Path: "/share1-shareddir", @@ -167,7 +165,7 @@ var _ = Describe("Sharesstorageprovider", func() { }) JustBeforeEach(func() { - p, err := provider.New("/shares", gw, sm) + p, err := provider.New("/shares", gw, receivedSharesLister) Expect(err).ToNot(HaveOccurred()) s = p.(sprovider.ProviderAPIServer) Expect(s).ToNot(BeNil()) @@ -192,15 +190,18 @@ var _ = Describe("Sharesstorageprovider", func() { Describe("ListContainer", func() { It("only considers accepted shares", func() { - sm.On("ListReceivedShares", mock.Anything).Return([]*collaboration.ReceivedShare{ - &collaboration.ReceivedShare{ - State: collaboration.ShareState_SHARE_STATE_INVALID, - }, - &collaboration.ReceivedShare{ - State: collaboration.ShareState_SHARE_STATE_PENDING, - }, - &collaboration.ReceivedShare{ - State: collaboration.ShareState_SHARE_STATE_REJECTED, + receivedSharesLister.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ + Status: status.NewOK(context.Background()), + Shares: []*collaboration.ReceivedShare{ + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_INVALID, + }, + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_PENDING, + }, + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_REJECTED, + }, }, }, nil) res, err := s.ListContainer(ctx, rootListContainerReq) @@ -212,17 +213,20 @@ var _ = Describe("Sharesstorageprovider", func() { Context("with an accepted share", func() { BeforeEach(func() { - sm.On("ListReceivedShares", mock.Anything).Return([]*collaboration.ReceivedShare{ - &collaboration.ReceivedShare{ - State: collaboration.ShareState_SHARE_STATE_ACCEPTED, - Share: &collaboration.Share{ - ResourceId: &sprovider.ResourceId{ - StorageId: "share1-storageid", - OpaqueId: "shareddir", - }, - Permissions: &collaboration.SharePermissions{ - Permissions: &sprovider.ResourcePermissions{ - Stat: true, + receivedSharesLister.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ + Status: status.NewOK(context.Background()), + Shares: []*collaboration.ReceivedShare{ + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_ACCEPTED, + Share: &collaboration.Share{ + ResourceId: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "shareddir", + }, + Permissions: &collaboration.SharePermissions{ + Permissions: &sprovider.ResourcePermissions{ + Stat: true, + }, }, }, }, @@ -615,5 +619,31 @@ var _ = Describe("Sharesstorageprovider", func() { Expect(res.Protocols[0].UploadEndpoint).To(Equal("https://localhost:9200/data/thetoken")) }) }) + + Describe("SetArbitraryMetadata", func() { + BeforeEach(func() { + gw.On("SetArbitraryMetadata", mock.Anything, mock.Anything).Return(&sprovider.SetArbitraryMetadataResponse{ + Status: status.NewOK(ctx), + }, nil) + }) + + It("sets the metadata", func() { + req := &sprovider.SetArbitraryMetadataRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir/share1-subdir/share1-subdir-file", + }, + ArbitraryMetadata: &sprovider.ArbitraryMetadata{ + Metadata: map[string]string{ + "foo": "bar", + }, + }, + } + res, err := s.SetArbitraryMetadata(ctx, req) + gw.AssertCalled(GinkgoT(), "SetArbitraryMetadata", mock.Anything, mock.Anything) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + }) + }) }) }) From c5b0e78eadc9a7ad9284bb55b5962ee7dd962e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Tue, 3 Aug 2021 15:39:52 +0200 Subject: [PATCH 04/15] Fix tests --- .../services/sharesstorageprovider/sharesstorageprovider.go | 4 ++-- tests/acceptance/expected-failures-on-OCIS-storage.md | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 202ae67f22..0081c82ee6 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -551,7 +551,7 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide }, nil } - if reqShare != "" && filepath.Base(gwres.Info.Path) == reqShare { + if reqShare != "" && gwres.Info != nil && filepath.Base(gwres.Info.Path) == reqShare { if reqPath != "" { gwres, err = s.gateway.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ @@ -808,7 +808,7 @@ func (s *service) statShare(ctx context.Context, share string) (*provider.StatRe }, err } - if share != "" && filepath.Base(statRes.Info.Path) == share { + if share != "" && statRes.Info != nil && filepath.Base(statRes.Info.Path) == share { return statRes, nil } } diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 74da2f08ad..fa8447093b 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1088,12 +1088,6 @@ And other missing implementation of favorites - [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) - [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) - [apiFavorites/favorites.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L218) -- [apiFavorites/favoritesSharingToShares.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L21) -- [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) -- [apiFavorites/favoritesSharingToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L35) -- [apiFavorites/favoritesSharingToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L36) -- [apiFavorites/favoritesSharingToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L48) -- [apiFavorites/favoritesSharingToShares.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L49) - [apiFavorites/favoritesSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L62) - [apiFavorites/favoritesSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L63) From f47cb9dd158c291d4bcb3a9c94c4f5c43b4e4bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Tue, 3 Aug 2021 15:51:05 +0200 Subject: [PATCH 05/15] Adapt to changes from rebase --- .../services/sharesstorageprovider/sharesstorageprovider.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 0081c82ee6..5efe300aa2 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -746,6 +746,10 @@ func (s *service) AddGrant(ctx context.Context, req *provider.AddGrantRequest) ( return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") } +func (s *service) DenyGrant(ctx context.Context, ref *provider.DenyGrantRequest) (*provider.DenyGrantResponse, error) { + return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") +} + func (s *service) CreateReference(ctx context.Context, req *provider.CreateReferenceRequest) (*provider.CreateReferenceResponse, error) { return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") } From 09b11218afc9645b34c38d0933d723539d5ffdbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Wed, 4 Aug 2021 09:36:11 +0200 Subject: [PATCH 06/15] Fix creating references with embedded mounts --- internal/grpc/services/gateway/usershareprovider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index c057b3b5c9..ba37ea2ab1 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -386,7 +386,7 @@ func (s *svc) createReference(ctx context.Context, resourceID *provider.Resource TargetUri: fmt.Sprintf("cs3:%s/%s", resourceID.GetStorageId(), resourceID.GetOpaqueId()), } - c, err = s.findByPath(ctx, refPath) + c, err = s.findByPath(ctx, homeRes.Path) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { return status.NewNotFound(ctx, "storage provider not found") From accd9c393b02c07231e869299f556c68f0d41b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Wed, 4 Aug 2021 14:39:08 +0200 Subject: [PATCH 07/15] Fix corner cases when stating shares --- .../sharesstorageprovider.go | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 5efe300aa2..405fc6c1dc 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -550,6 +550,15 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stat from gateway"), }, nil } + if gwres.Status.Code != rpc.Code_CODE_OK { + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Interface("rs.Share", rs.Share). + Interface("gwres", gwres). + Msg("sharesstorageprovider: Got non-ok Stat response") + continue + } if reqShare != "" && gwres.Info != nil && filepath.Base(gwres.Info.Path) == reqShare { if reqPath != "" { @@ -572,12 +581,18 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide gwres.Info.Path = filepath.Join(s.mountPath, reqShare, relPath) gwres.Info.PermissionSet = rs.Share.Permissions.Permissions return gwres, nil - } else if reqShare == "" { + } else if reqShare == "" && gwres.Info != nil { childInfos = append(childInfos, gwres.Info) res.Info.Size += gwres.Info.Size } } + if reqShare != "" { + return &provider.StatResponse{ + Status: status.NewNotFound(ctx, "sharesstorageprovider: could not find requested share"), + }, nil + } + res.Status = status.NewOK(ctx) res.Info.Etag = etag.GenerateEtagFromResources(res.Info, childInfos) return res, nil @@ -614,6 +629,15 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stats from gateway"), }, nil } + if gwres.Status.Code != rpc.Code_CODE_OK { + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Interface("rs.Share", rs.Share). + Interface("gwres", gwres). + Msg("sharesstorageprovider: Got non-ok ListContainerResponse response") + continue + } if reqShare != "" && filepath.Base(gwres.Info.Path) == reqShare { gwListRes, err := s.gateway.ListContainer(ctx, &provider.ListContainerRequest{ @@ -812,7 +836,16 @@ func (s *service) statShare(ctx context.Context, share string) (*provider.StatRe }, err } - if share != "" && statRes.Info != nil && filepath.Base(statRes.Info.Path) == share { + if statRes.Status.Code != rpc.Code_CODE_OK { + appctx.GetLogger(ctx).Debug(). + Interface("share", share). + Interface("rs.Share", rs.Share). + Interface("statRes", statRes). + Msg("sharesstorageprovider: Got non-ok Stat response") + continue + } + + if filepath.Base(statRes.Info.Path) == share { return statRes, nil } } From b44d1de2de33f731cccdbcc7a530c7b6e1f83d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Thu, 5 Aug 2021 15:41:19 +0200 Subject: [PATCH 08/15] Allow moves between shares on the same storage --- .../sharesstorageprovider.go | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 405fc6c1dc..932e333923 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -467,11 +467,6 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide Status: status.NewInvalid(ctx, "sharesstorageprovider: can not move top-level share"), }, nil } - if reqShare != destinationShare { - return &provider.MoveResponse{ - Status: status.NewInvalid(ctx, "sharesstorageprovider: can not move between shares"), - }, nil - } statRes, err := s.statShare(ctx, reqShare) if err != nil { @@ -485,12 +480,30 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide }, nil } + dstStatRes, err := s.statShare(ctx, destinationShare) + if err != nil { + if dstStatRes != nil { + return &provider.MoveResponse{ + Status: dstStatRes.Status, + }, err + } + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the destination share"), + }, nil + } + + if statRes.Info.Id.StorageId != dstStatRes.Info.Id.StorageId { + return &provider.MoveResponse{ + Status: status.NewInvalid(ctx, "sharesstorageprovider: can not move between shares on different storages"), + }, nil + } + gwres, err := s.gateway.Move(ctx, &provider.MoveRequest{ Source: &provider.Reference{ Path: filepath.Join(statRes.Info.Path, reqPath), }, Destination: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, destinationPath), + Path: filepath.Join(dstStatRes.Info.Path, destinationPath), }, }) From 2f9ded051e6ccf1115a2738c6f5c48f2e386086e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Thu, 5 Aug 2021 15:41:50 +0200 Subject: [PATCH 09/15] Make the storage rules known to the gateway as well --- tests/oc-integration-tests/drone/gateway.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/oc-integration-tests/drone/gateway.toml b/tests/oc-integration-tests/drone/gateway.toml index 06bfe91d8e..5d22be2b0d 100644 --- a/tests/oc-integration-tests/drone/gateway.toml +++ b/tests/oc-integration-tests/drone/gateway.toml @@ -73,6 +73,14 @@ home_provider = "/home" "/public" = {"address" = "localhost:13000"} "/home/Shares" = {"address" = "localhost:14000"} + +[grpc.services.gateway.storage_rules] +"/home" = {"address" = "localhost:12000"} +"/oc" = {"address" = "localhost:11000"} +"123e4567-e89b-12d3-a456-426655440000" = {"address" = "localhost:11000"} +"/public" = {"address" = "localhost:13000"} +"/home/Shares" = {"address" = "localhost:14000"} + [http] address = "0.0.0.0:19001" From 8fb3b39b6703bff6e94f2d23d92e8ef7e07eca89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Fri, 6 Aug 2021 10:18:09 +0200 Subject: [PATCH 10/15] Do not choke on non-existent shares --- .../sharesstorageprovider.go | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 932e333923..e24e6e9d75 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -274,62 +274,62 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate Interface("reqShare", reqShare). Msg("sharesstorageprovider: Got InitiateFileUpload request") - if reqShare != "" { - statRes, err := s.statShare(ctx, reqShare) - if err != nil { - if statRes != nil { - return &provider.InitiateFileUploadResponse{ - Status: statRes.Status, - }, err - } - return &provider.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), - }, nil - } - gwres, err := s.gateway.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), - }, - Opaque: req.Opaque, - }) - if err != nil { - return &provider.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error calling InitiateFileDownload"), - }, nil - } + if reqShare == "" { + return &provider.InitiateFileUploadResponse{ + Status: status.NewInvalidArg(ctx, "sharesstorageprovider: can not upload directly to the shares folder"), + }, nil + } - if gwres.Status.Code != rpc.Code_CODE_OK { - return &provider.InitiateFileUploadResponse{ - Status: gwres.Status, - }, nil - } + statRes, err := s.statShare(ctx, reqShare) + if err != nil { + return &provider.InitiateFileUploadResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } + if statRes.Status.Code != rpc.Code_CODE_OK { + return &provider.InitiateFileUploadResponse{ + Status: statRes.Status, + }, nil + } - protocols := []*provider.FileUploadProtocol{} - for p := range gwres.Protocols { - if !strings.HasSuffix(gwres.Protocols[p].UploadEndpoint, "/") { - gwres.Protocols[p].UploadEndpoint += "/" - } - gwres.Protocols[p].UploadEndpoint += gwres.Protocols[p].Token - - protocols = append(protocols, &provider.FileUploadProtocol{ - Opaque: gwres.Protocols[p].Opaque, - Protocol: gwres.Protocols[p].Protocol, - UploadEndpoint: gwres.Protocols[p].UploadEndpoint, - AvailableChecksums: gwres.Protocols[p].AvailableChecksums, - Expose: true, // the gateway already has encoded the upload endpoint - }) - } + gwres, err := s.gateway.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(statRes.Info.Path, reqPath), + }, + Opaque: req.Opaque, + }) + if err != nil { + return &provider.InitiateFileUploadResponse{ + Status: status.NewInternal(ctx, err, "gateway: error calling InitiateFileDownload"), + }, nil + } + if gwres.Status.Code != rpc.Code_CODE_OK { return &provider.InitiateFileUploadResponse{ - Status: gwres.Status, - Protocols: protocols, + Status: gwres.Status, }, nil } + protocols := []*provider.FileUploadProtocol{} + for p := range gwres.Protocols { + if !strings.HasSuffix(gwres.Protocols[p].UploadEndpoint, "/") { + gwres.Protocols[p].UploadEndpoint += "/" + } + gwres.Protocols[p].UploadEndpoint += gwres.Protocols[p].Token + + protocols = append(protocols, &provider.FileUploadProtocol{ + Opaque: gwres.Protocols[p].Opaque, + Protocol: gwres.Protocols[p].Protocol, + UploadEndpoint: gwres.Protocols[p].UploadEndpoint, + AvailableChecksums: gwres.Protocols[p].AvailableChecksums, + Expose: true, // the gateway already has encoded the upload endpoint + }) + } + return &provider.InitiateFileUploadResponse{ - Status: status.NewInvalidArg(ctx, "sharesstorageprovider: can not upload directly to the shares folder"), + Status: gwres.Status, + Protocols: protocols, }, nil - } func (s *service) GetPath(ctx context.Context, req *provider.GetPathRequest) (*provider.GetPathResponse, error) { From 51a55e84c2f5dbc0629e56dbe7a408ba6410609a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Fri, 6 Aug 2021 10:18:28 +0200 Subject: [PATCH 11/15] Adjust expected failures files --- tests/acceptance/expected-failures-on-OCIS-storage.md | 6 ------ tests/acceptance/expected-failures-on-S3NG-storage.md | 6 ------ 2 files changed, 12 deletions(-) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index fa8447093b..0c49490b90 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -303,12 +303,6 @@ Synchronization features like etag propagation, setting mtime and locking files ### Share File and sync features in a shared scenario -#### [etags don't change for a share receiver](https://github.com/owncloud/product/issues/243) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:244](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L244) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L245) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:314](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L314) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:315](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L315) - #### [Searching sharee with displayname](https://github.com/owncloud/ocis/issues/547) These tests fail because they contain some configuration steps for oc10 specific configuration. - [apiSharees/sharees.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L74) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index b120f7af1d..883224c82b 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -307,12 +307,6 @@ Synchronization features like etag propagation, setting mtime and locking files ### Share File and sync features in a shared scenario -#### [etags don't change for a share receiver](https://github.com/owncloud/product/issues/243) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:244](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L244) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L245) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:314](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L314) -- [apiWebdavEtagPropagation1/moveFileFolder.feature:315](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L315) - #### [Searching sharee with displayname](https://github.com/owncloud/ocis/issues/547) These tests fail because they contain some configuration steps for oc10 specific configuration. - [apiSharees/sharees.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L74) From 862978459bdcf6725e367920811b387d018c9fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Mon, 9 Aug 2021 10:44:08 +0200 Subject: [PATCH 12/15] Adjust expected failures --- tests/acceptance/expected-failures-on-OCIS-storage.md | 2 -- tests/acceptance/expected-failures-on-S3NG-storage.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 0c49490b90..3e9f70b05b 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -364,7 +364,6 @@ These tests fail because we currently can't differentiate between exact matches #### [Response is empty when accepting a share](https://github.com/owncloud/product/issues/207) - [apiShareManagementToShares/acceptShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L82) -- [apiShareManagementToShares/acceptShares.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L207) - [apiShareManagementToShares/acceptShares.feature:261](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L261) #### [sharing with group not available](https://github.com/owncloud/product/issues/293) @@ -719,7 +718,6 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) - [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L66) - [apiShareOperationsToShares1/changingFilesShare.feature:82](https://github.com/owncloud/core/blob/master/test/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L82) - [apiShareOperationsToShares1/changingFilesShare.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L98) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 883224c82b..a27b9446ca 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -368,7 +368,6 @@ These tests fail because we currently can't differentiate between exact matches #### [Response is empty when accepting a share](https://github.com/owncloud/product/issues/207) - [apiShareManagementToShares/acceptShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L82) -- [apiShareManagementToShares/acceptShares.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L207) - [apiShareManagementToShares/acceptShares.feature:261](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L261) #### [file_target in share response](https://github.com/owncloud/product/issues/203) @@ -708,7 +707,6 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) - [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L66) - [apiShareOperationsToShares1/changingFilesShare.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L82) - [apiShareOperationsToShares1/changingFilesShare.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L98) From db97815c0204bd599686121a0004fd653d6ea718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Tue, 10 Aug 2021 09:33:41 +0200 Subject: [PATCH 13/15] Reject a share when it is being deleted --- .../sharesstorageprovider.go | 82 +++++++++++++++++-- .../sharesstorageprovider_test.go | 17 ++-- 2 files changed, 84 insertions(+), 15 deletions(-) diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index e24e6e9d75..432e161203 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -42,7 +42,7 @@ import ( "github.com/pkg/errors" ) -//go:generate mockery -name GatewayClient -name ReceivedSharesLister +//go:generate mockery -name GatewayClient -name SharesProviderClient // GatewayClient describe the interface of a gateway client type GatewayClient interface { @@ -59,9 +59,10 @@ type GatewayClient interface { UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest, opts ...grpc.CallOption) (*provider.UnsetArbitraryMetadataResponse, error) } -// ReceivedSharesLister lists received shares -type ReceivedSharesLister interface { +// SharesProviderClient provides methods for listing and modifying received shares +type SharesProviderClient interface { ListReceivedShares(ctx context.Context, req *collaboration.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaboration.ListReceivedSharesResponse, error) + UpdateReceivedShare(ctx context.Context, req *collaboration.UpdateReceivedShareRequest, opts ...grpc.CallOption) (*collaboration.UpdateReceivedShareResponse, error) } func init() { @@ -77,7 +78,7 @@ type config struct { type service struct { mountPath string gateway GatewayClient - receivedSharesLister ReceivedSharesLister + sharesProviderClient SharesProviderClient } func (s *service) Close() error { @@ -114,11 +115,11 @@ func NewDefault(m map[string]interface{}, _ *grpc.Server) (rgrpc.Service, error) } // New returns a new instance of the SharesStorageProvider service -func New(mountpath string, gateway GatewayClient, l ReceivedSharesLister) (rgrpc.Service, error) { +func New(mountpath string, gateway GatewayClient, c SharesProviderClient) (rgrpc.Service, error) { s := &service{ mountPath: mountpath, gateway: gateway, - receivedSharesLister: l, + sharesProviderClient: c, } return s, nil } @@ -413,12 +414,24 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro Interface("reqShare", reqShare). Msg("sharesstorageprovider: Got Delete request") - if reqShare == "" || reqPath == "" { + if reqShare == "" { return &provider.DeleteResponse{ Status: status.NewInvalid(ctx, "sharesstorageprovider: can not delete top-level container"), }, nil } + if reqPath == "" { + err := s.rejectReceivedShare(ctx, reqShare) + if err != nil { + return &provider.DeleteResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error rejecting share"), + }, nil + } + return &provider.DeleteResponse{ + Status: status.NewOK(ctx), + }, nil + } + statRes, err := s.statShare(ctx, reqShare) if err != nil { if statRes != nil { @@ -869,7 +882,7 @@ func (s *service) statShare(ctx context.Context, share string) (*provider.StatRe } func (s *service) getReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { - lsRes, err := s.receivedSharesLister.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + lsRes, err := s.sharesProviderClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) if err != nil { return nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest") } @@ -878,3 +891,56 @@ func (s *service) getReceivedShares(ctx context.Context) ([]*collaboration.Recei } return lsRes.Shares, nil } + +func (s *service) getReceivedShareByName(ctx context.Context, share string) (*collaboration.ReceivedShare, error) { + shares, err := s.getReceivedShares(ctx) + if err != nil { + return nil, err + } + + for _, rs := range shares { + statRes, err := s.gateway.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + ResourceId: rs.Share.ResourceId, + }, + }) + if err != nil { + return nil, err + } + + if statRes.Status.Code != rpc.Code_CODE_OK { + appctx.GetLogger(ctx).Debug(). + Interface("rs", s). + Interface("statRes", statRes). + Msg("sharesstorageprovider: Got non-ok Stat response") + continue + } + + if filepath.Base(statRes.Info.Path) == share { + return rs, nil + } + } + return nil, fmt.Errorf("sharesstorageprovider: received share '%s' not found", share) +} + +func (s *service) rejectReceivedShare(ctx context.Context, share string) error { + rs, err := s.getReceivedShareByName(ctx, share) + if err != nil { + return err + } + + ref := &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: rs.Share.Id, + }, + } + _, err = s.sharesProviderClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ + Ref: ref, + Field: &collaboration.UpdateReceivedShareRequest_UpdateField{ + Field: &collaboration.UpdateReceivedShareRequest_UpdateField_State{ + State: collaboration.ShareState_SHARE_STATE_REJECTED, + }, + }, + }) + return err +} diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go index 531d44e3fd..616ade38d1 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go @@ -71,11 +71,11 @@ var _ = Describe("Sharesstorageprovider", func() { s sprovider.ProviderAPIServer gw *mocks.GatewayClient - receivedSharesLister *mocks.ReceivedSharesLister + sharesProviderClient *mocks.SharesProviderClient ) BeforeEach(func() { - receivedSharesLister = &mocks.ReceivedSharesLister{} + sharesProviderClient = &mocks.SharesProviderClient{} gw = &mocks.GatewayClient{} gw.On("ListContainer", mock.Anything, &sprovider.ListContainerRequest{ Ref: &sprovider.Reference{ @@ -165,7 +165,7 @@ var _ = Describe("Sharesstorageprovider", func() { }) JustBeforeEach(func() { - p, err := provider.New("/shares", gw, receivedSharesLister) + p, err := provider.New("/shares", gw, sharesProviderClient) Expect(err).ToNot(HaveOccurred()) s = p.(sprovider.ProviderAPIServer) Expect(s).ToNot(BeNil()) @@ -190,7 +190,7 @@ var _ = Describe("Sharesstorageprovider", func() { Describe("ListContainer", func() { It("only considers accepted shares", func() { - receivedSharesLister.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ + sharesProviderClient.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ Status: status.NewOK(context.Background()), Shares: []*collaboration.ReceivedShare{ &collaboration.ReceivedShare{ @@ -213,7 +213,7 @@ var _ = Describe("Sharesstorageprovider", func() { Context("with an accepted share", func() { BeforeEach(func() { - receivedSharesLister.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ + sharesProviderClient.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ Status: status.NewOK(context.Background()), Shares: []*collaboration.ReceivedShare{ &collaboration.ReceivedShare{ @@ -415,7 +415,8 @@ var _ = Describe("Sharesstorageprovider", func() { }, nil) }) - It("refuses to delete a share", func() { + It("rejects the share when deleting a share", func() { + sharesProviderClient.On("UpdateReceivedShare", mock.Anything, mock.Anything).Return(nil, nil) req := &sprovider.DeleteRequest{ Ref: &sprovider.Reference{ Path: "/shares/share1-shareddir", @@ -425,7 +426,9 @@ var _ = Describe("Sharesstorageprovider", func() { gw.AssertNotCalled(GinkgoT(), "Delete", mock.Anything, mock.Anything) Expect(err).ToNot(HaveOccurred()) Expect(res).ToNot(BeNil()) - Expect(res.Status.Code).To(Equal(rpc.Code_CODE_INVALID_ARGUMENT)) + Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) + + sharesProviderClient.AssertCalled(GinkgoT(), "UpdateReceivedShare", mock.Anything, mock.Anything) }) It("deletes a file", func() { From 3894a798b35e6b12542488f5a27ec0c1c1e23e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Tue, 17 Aug 2021 14:14:14 +0200 Subject: [PATCH 14/15] Fix rebase artifacts --- internal/grpc/services/gateway/storageprovider.go | 13 +++---------- .../sharesstorageprovider/sharesstorageprovider.go | 8 ++++---- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index b0d556a974..2e1144767c 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -304,13 +304,11 @@ func (s *svc) getHome(_ context.Context) string { } func (s *svc) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*gateway.InitiateFileDownloadResponse, error) { - log := appctx.GetLogger(ctx) - if utils.IsRelativeReference(req.Ref) { return s.initiateFileDownload(ctx, req) } - p, st := s.getPath(ctx, req.Ref) + _, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { return &gateway.InitiateFileDownloadResponse{ Status: st, @@ -385,11 +383,10 @@ func (s *svc) initiateFileDownload(ctx context.Context, req *provider.InitiateFi } func (s *svc) InitiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest) (*gateway.InitiateFileUploadResponse, error) { - log := appctx.GetLogger(ctx) if utils.IsRelativeReference(req.Ref) { return s.initiateFileUpload(ctx, req) } - p, st := s.getPath(ctx, req.Ref) + _, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { return &gateway.InitiateFileUploadResponse{ Status: st, @@ -478,13 +475,11 @@ func (s *svc) GetPath(ctx context.Context, req *provider.GetPathRequest) (*provi } func (s *svc) CreateContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) { - log := appctx.GetLogger(ctx) - if utils.IsRelativeReference(req.Ref) { return s.createContainer(ctx, req) } - p, st := s.getPath(ctx, req.Ref) + _, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { return &provider.CreateContainerResponse{ Status: st, @@ -952,8 +947,6 @@ func (s *svc) listContainerOnProvider(ctx context.Context, req *provider.ListCon } func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { - log := appctx.GetLogger(ctx) - if utils.IsRelativeReference(req.Ref) { return s.listContainer(ctx, req) } diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 432e161203..c18d46d37a 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -33,11 +33,11 @@ import ( collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" + revactx "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/storage/utils/etag" - ctxuser "github.com/cs3org/reva/pkg/user" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) @@ -542,7 +542,7 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide Interface("reqShare", reqShare). Msg("sharesstorageprovider: Got Stat request") - _, ok := ctxuser.ContextGetUser(ctx) + _, ok := revactx.ContextGetUser(ctx) if !ok { return &provider.StatResponse{ Status: status.NewNotFound(ctx, "sharesstorageprovider: shares requested for empty user"), @@ -833,8 +833,8 @@ func (s *service) resolvePath(path string) (string, string) { return reqShare, reqPath } -func (s *service) statShare(ctx context.Context, share string) (*provider.StatResponse, error) { - _, ok := ctxuser.ContextGetUser(ctx) +func (s *service) statShare(ctx context.Context, share string) (*stattedReceivedShare, error) { + _, ok := revactx.ContextGetUser(ctx) if !ok { return &provider.StatResponse{ Status: status.NewNotFound(ctx, "sharesstorageprovider: shares requested for empty user"), From 29327c20606cdd720580f495ad4abdd1c9246095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Wed, 11 Aug 2021 10:51:16 +0200 Subject: [PATCH 15/15] WIP: Refactor statting shares. Merge shares permissions. --- ...haresLister.go => SharesProviderClient.go} | 36 +- .../sharesstorageprovider.go | 452 ++++++++---------- .../sharesstorageprovider_test.go | 71 +++ 3 files changed, 304 insertions(+), 255 deletions(-) rename internal/grpc/services/sharesstorageprovider/mocks/{ReceivedSharesLister.go => SharesProviderClient.go} (61%) diff --git a/internal/grpc/services/sharesstorageprovider/mocks/ReceivedSharesLister.go b/internal/grpc/services/sharesstorageprovider/mocks/SharesProviderClient.go similarity index 61% rename from internal/grpc/services/sharesstorageprovider/mocks/ReceivedSharesLister.go rename to internal/grpc/services/sharesstorageprovider/mocks/SharesProviderClient.go index c0b8efd61d..747d497de1 100644 --- a/internal/grpc/services/sharesstorageprovider/mocks/ReceivedSharesLister.go +++ b/internal/grpc/services/sharesstorageprovider/mocks/SharesProviderClient.go @@ -30,13 +30,13 @@ import ( mock "github.com/stretchr/testify/mock" ) -// ReceivedSharesLister is an autogenerated mock type for the ReceivedSharesLister type -type ReceivedSharesLister struct { +// SharesProviderClient is an autogenerated mock type for the SharesProviderClient type +type SharesProviderClient struct { mock.Mock } // ListReceivedShares provides a mock function with given fields: ctx, req, opts -func (_m *ReceivedSharesLister) ListReceivedShares(ctx context.Context, req *collaborationv1beta1.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListReceivedSharesResponse, error) { +func (_m *SharesProviderClient) ListReceivedShares(ctx context.Context, req *collaborationv1beta1.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListReceivedSharesResponse, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] @@ -64,3 +64,33 @@ func (_m *ReceivedSharesLister) ListReceivedShares(ctx context.Context, req *col return r0, r1 } + +// UpdateReceivedShare provides a mock function with given fields: ctx, req, opts +func (_m *SharesProviderClient) UpdateReceivedShare(ctx context.Context, req *collaborationv1beta1.UpdateReceivedShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.UpdateReceivedShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.UpdateReceivedShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.UpdateReceivedShareRequest, ...grpc.CallOption) *collaborationv1beta1.UpdateReceivedShareResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.UpdateReceivedShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.UpdateReceivedShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index c18d46d37a..6e3f8a5e11 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -80,6 +80,11 @@ type service struct { gateway GatewayClient sharesProviderClient SharesProviderClient } +type stattedReceivedShare struct { + Stat *provider.ResourceInfo + ReceivedShare *collaboration.ReceivedShare + AllReceivedShares []*collaboration.ReceivedShare +} func (s *service) Close() error { return nil @@ -137,21 +142,15 @@ func (s *service) SetArbitraryMetadata(ctx context.Context, req *provider.SetArb }, nil } - statRes, err := s.statShare(ctx, reqShare) + stattedShare, err := s.statShare(ctx, reqShare) if err != nil { - if statRes != nil { - return &provider.SetArbitraryMetadataResponse{ - Status: statRes.Status, - }, err - } return &provider.SetArbitraryMetadataResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + Status: status.NewInternal(ctx, err, "gateway: error stating share"), }, nil } - gwres, err := s.gateway.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), + Path: filepath.Join(stattedShare.Stat.Path, reqPath), }, ArbitraryMetadata: req.ArbitraryMetadata, }) @@ -178,28 +177,23 @@ func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.Unse }, nil } - statRes, err := s.statShare(ctx, reqShare) + stattedShare, err := s.statShare(ctx, reqShare) if err != nil { - if statRes != nil { - return &provider.UnsetArbitraryMetadataResponse{ - Status: statRes.Status, - }, err - } return &provider.UnsetArbitraryMetadataResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), }, nil } gwres, err := s.gateway.UnsetArbitraryMetadata(ctx, &provider.UnsetArbitraryMetadataRequest{ Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), + Path: filepath.Join(stattedShare.Stat.Path, reqPath), }, ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, }) if err != nil { return &provider.UnsetArbitraryMetadataResponse{ - Status: status.NewInternal(ctx, err, "gateway: error calling UnsetArbitraryMetadata"), + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error calling UnsetArbitraryMetadata"), }, nil } @@ -213,58 +207,54 @@ func (s *service) InitiateFileDownload(ctx context.Context, req *provider.Initia Interface("reqShare", reqShare). Msg("sharesstorageprovider: Got InitiateFileDownload request") - if reqShare != "" { - statRes, err := s.statShare(ctx, reqShare) - if err != nil { - if statRes != nil { - return &provider.InitiateFileDownloadResponse{ - Status: statRes.Status, - }, err - } - return &provider.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), - }, nil - } - gwres, err := s.gateway.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), - }, - }) - if err != nil { - return &provider.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error calling InitiateFileDownload"), - }, nil - } - - if gwres.Status.Code != rpc.Code_CODE_OK { - return &provider.InitiateFileDownloadResponse{ - Status: gwres.Status, - }, nil - } + if reqShare == "" { + return &provider.InitiateFileDownloadResponse{ + Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), + }, nil + } - protocols := []*provider.FileDownloadProtocol{} - for p := range gwres.Protocols { - if !strings.HasSuffix(gwres.Protocols[p].DownloadEndpoint, "/") { - gwres.Protocols[p].DownloadEndpoint += "/" - } - gwres.Protocols[p].DownloadEndpoint += gwres.Protocols[p].Token + stattedShare, err := s.statShare(ctx, reqShare) + if err != nil { + return &provider.InitiateFileDownloadResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + }, nil + } - protocols = append(protocols, &provider.FileDownloadProtocol{ - Opaque: gwres.Protocols[p].Opaque, - Protocol: gwres.Protocols[p].Protocol, - DownloadEndpoint: gwres.Protocols[p].DownloadEndpoint, - Expose: true, // the gateway already has encoded the upload endpoint - }) - } + gwres, err := s.gateway.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(stattedShare.Stat.Path, reqPath), + }, + }) + if err != nil { + return &provider.InitiateFileDownloadResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error calling InitiateFileDownload"), + }, nil + } + if gwres.Status.Code != rpc.Code_CODE_OK { return &provider.InitiateFileDownloadResponse{ - Status: gwres.Status, - Protocols: protocols, + Status: gwres.Status, }, nil } + protocols := []*provider.FileDownloadProtocol{} + for p := range gwres.Protocols { + if !strings.HasSuffix(gwres.Protocols[p].DownloadEndpoint, "/") { + gwres.Protocols[p].DownloadEndpoint += "/" + } + gwres.Protocols[p].DownloadEndpoint += gwres.Protocols[p].Token + + protocols = append(protocols, &provider.FileDownloadProtocol{ + Opaque: gwres.Protocols[p].Opaque, + Protocol: gwres.Protocols[p].Protocol, + DownloadEndpoint: gwres.Protocols[p].DownloadEndpoint, + Expose: true, // the gateway already has encoded the upload endpoint + }) + } + return &provider.InitiateFileDownloadResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), + Status: gwres.Status, + Protocols: protocols, }, nil } @@ -281,21 +271,16 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate }, nil } - statRes, err := s.statShare(ctx, reqShare) + stattedShare, err := s.statShare(ctx, reqShare) if err != nil { return &provider.InitiateFileUploadResponse{ Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), }, nil } - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.InitiateFileUploadResponse{ - Status: statRes.Status, - }, nil - } gwres, err := s.gateway.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), + Path: filepath.Join(stattedShare.Stat.Path, reqPath), }, Opaque: req.Opaque, }) @@ -304,7 +289,6 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate Status: status.NewInternal(ctx, err, "gateway: error calling InitiateFileDownload"), }, nil } - if gwres.Status.Code != rpc.Code_CODE_OK { return &provider.InitiateFileUploadResponse{ Status: gwres.Status, @@ -374,13 +358,8 @@ func (s *service) CreateContainer(ctx context.Context, req *provider.CreateConta }, nil } - statRes, err := s.statShare(ctx, reqShare) + stattedShare, err := s.statShare(ctx, reqShare) if err != nil { - if statRes != nil { - return &provider.CreateContainerResponse{ - Status: statRes.Status, - }, err - } return &provider.CreateContainerResponse{ Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), }, nil @@ -388,7 +367,7 @@ func (s *service) CreateContainer(ctx context.Context, req *provider.CreateConta gwres, err := s.gateway.CreateContainer(ctx, &provider.CreateContainerRequest{ Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), + Path: filepath.Join(stattedShare.Stat.Path, reqPath), }, }) @@ -432,13 +411,8 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro }, nil } - statRes, err := s.statShare(ctx, reqShare) + stattedShare, err := s.statShare(ctx, reqShare) if err != nil { - if statRes != nil { - return &provider.DeleteResponse{ - Status: statRes.Status, - }, err - } return &provider.DeleteResponse{ Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), }, nil @@ -446,7 +420,7 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro gwres, err := s.gateway.Delete(ctx, &provider.DeleteRequest{ Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), + Path: filepath.Join(stattedShare.Stat.Path, reqPath), }, }) @@ -481,31 +455,21 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide }, nil } - statRes, err := s.statShare(ctx, reqShare) + stattedShare, err := s.statShare(ctx, reqShare) if err != nil { - if statRes != nil { - return &provider.MoveResponse{ - Status: statRes.Status, - }, err - } return &provider.MoveResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the source share"), }, nil } - dstStatRes, err := s.statShare(ctx, destinationShare) + dstStattedShare, err := s.statShare(ctx, reqShare) if err != nil { - if dstStatRes != nil { - return &provider.MoveResponse{ - Status: dstStatRes.Status, - }, err - } return &provider.MoveResponse{ Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the destination share"), }, nil } - if statRes.Info.Id.StorageId != dstStatRes.Info.Id.StorageId { + if stattedShare.Stat.Id.StorageId != dstStattedShare.Stat.Id.StorageId { return &provider.MoveResponse{ Status: status.NewInvalid(ctx, "sharesstorageprovider: can not move between shares on different storages"), }, nil @@ -513,10 +477,10 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide gwres, err := s.gateway.Move(ctx, &provider.MoveRequest{ Source: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), + Path: filepath.Join(stattedShare.Stat.Path, reqPath), }, Destination: &provider.Reference{ - Path: filepath.Join(dstStatRes.Info.Path, destinationPath), + Path: filepath.Join(dstStattedShare.Stat.Path, destinationPath), }, }) @@ -549,6 +513,45 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide }, nil } + if reqShare != "" { + stattedShare, err := s.statShare(ctx, reqShare) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the source share"), + }, nil + } + res := &provider.StatResponse{ + Info: stattedShare.Stat, + Status: status.NewOK(ctx), + } + if reqPath != "" { + res, err = s.gateway.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + Path: filepath.Join(stattedShare.Stat.Path, reqPath), + }, + }) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stat from gateway"), + }, nil + } + if res.Status.Code != rpc.Code_CODE_OK { + return res, nil + } + } + + relPath := strings.SplitAfterN(res.Info.Path, reqShare, 2)[1] + res.Info.Path = filepath.Join(s.mountPath, reqShare, relPath) + + appctx.GetLogger(ctx).Debug(). + Interface("reqPath", reqPath). + Interface("reqShare", reqShare). + Interface("res", res). + Msg("sharesstorageprovider: Got Stat request") + + return res, nil + } + shares, err := s.getReceivedShares(ctx) if err != nil { return nil, err @@ -561,68 +564,19 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide }, } childInfos := []*provider.ResourceInfo{} - for _, rs := range shares { - if rs.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { - continue - } - - gwres, err := s.gateway.Stat(ctx, &provider.StatRequest{ - Ref: &provider.Reference{ - ResourceId: rs.Share.ResourceId, - }, - }) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stat from gateway"), - }, nil - } - if gwres.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx).Debug(). - Interface("reqPath", reqPath). - Interface("reqShare", reqShare). - Interface("rs.Share", rs.Share). - Interface("gwres", gwres). - Msg("sharesstorageprovider: Got non-ok Stat response") + for _, shares := range shares { + if shares.ReceivedShare.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { continue } - if reqShare != "" && gwres.Info != nil && filepath.Base(gwres.Info.Path) == reqShare { - if reqPath != "" { - gwres, err = s.gateway.Stat(ctx, &provider.StatRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(gwres.Info.Path, reqPath), - }, - }) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stat from gateway"), - }, nil - } - if gwres.Status.Code != rpc.Code_CODE_OK { - return gwres, nil - } - } - - relPath := strings.SplitAfterN(gwres.Info.Path, reqShare, 2)[1] - gwres.Info.Path = filepath.Join(s.mountPath, reqShare, relPath) - gwres.Info.PermissionSet = rs.Share.Permissions.Permissions - return gwres, nil - } else if reqShare == "" && gwres.Info != nil { - childInfos = append(childInfos, gwres.Info) - res.Info.Size += gwres.Info.Size - } - } - - if reqShare != "" { - return &provider.StatResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: could not find requested share"), - }, nil + childInfos = append(childInfos, shares.Stat) + res.Info.Size += shares.Stat.Size } - res.Status = status.NewOK(ctx) res.Info.Etag = etag.GenerateEtagFromResources(res.Info, childInfos) return res, nil } + func (s *service) ListContainerStream(req *provider.ListContainerStreamRequest, ss provider.ProviderAPI_ListContainerStreamServer) error { return gstatus.Errorf(codes.Unimplemented, "method not implemented") } @@ -634,41 +588,40 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer Interface("reqShare", reqShare). Msg("sharesstorageprovider: Got ListContainer request") - shares, err := s.getReceivedShares(ctx) + stattedShares, err := s.getReceivedShares(ctx) if err != nil { return nil, err } res := &provider.ListContainerResponse{} - for _, rs := range shares { - if rs.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { + for name, stattedShare := range stattedShares { + if stattedShare.ReceivedShare.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { continue } - - gwres, err := s.gateway.Stat(ctx, &provider.StatRequest{ - Ref: &provider.Reference{ - ResourceId: rs.Share.ResourceId, - }, - }) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stats from gateway"), - }, nil - } - if gwres.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx).Debug(). - Interface("reqPath", reqPath). - Interface("reqShare", reqShare). - Interface("rs.Share", rs.Share). - Interface("gwres", gwres). - Msg("sharesstorageprovider: Got non-ok ListContainerResponse response") - continue - } - - if reqShare != "" && filepath.Base(gwres.Info.Path) == reqShare { + // gwres, err := s.gateway.Stat(ctx, &provider.StatRequest{ + // Ref: &provider.Reference{ + // ResourceId: stattedShare.ReceivedShare.Share.ResourceId, + // }, + // }) + // if err != nil { + // return &provider.ListContainerResponse{ + // Status: status.NewInternal(ctx, err, "sharesstorageprovider: error getting stats from gateway"), + // }, nil + // } + // if gwres.Status.Code != rpc.Code_CODE_OK { + // appctx.GetLogger(ctx).Debug(). + // Interface("reqPath", reqPath). + // Interface("reqShare", reqShare). + // Interface("rss.Share", stattedShare.ReceivedShare.Share). + // Interface("gwres", gwres). + // Msg("sharesstorageprovider: Got non-ok ListContainerResponse response") + // continue + // } + + if reqShare != "" && name == reqShare { gwListRes, err := s.gateway.ListContainer(ctx, &provider.ListContainerRequest{ Ref: &provider.Reference{ - Path: filepath.Join(filepath.Dir(gwres.Info.Path), reqShare, reqPath), + Path: filepath.Join(filepath.Dir(stattedShare.Stat.Path), reqShare, reqPath), }, }) if err != nil { @@ -679,12 +632,13 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer for _, info := range gwListRes.Infos { relPath := strings.SplitAfterN(info.Path, reqShare, 2)[1] info.Path = filepath.Join(s.mountPath, reqShare, relPath) - info.PermissionSet = rs.Share.Permissions.Permissions + info.PermissionSet = stattedShare.Stat.PermissionSet } return gwListRes, nil } else if reqShare == "" { - gwres.Info.Path = filepath.Join(s.mountPath, filepath.Base(gwres.Info.Path)) - res.Infos = append(res.Infos, gwres.Info) + stattedShare.Stat.Path = filepath.Join(s.mountPath, filepath.Base(stattedShare.Stat.Path)) + res.Infos = append(res.Infos, stattedShare.Stat) + break } } res.Status = status.NewOK(ctx) @@ -704,21 +658,16 @@ func (s *service) ListFileVersions(ctx context.Context, req *provider.ListFileVe }, nil } - statRes, err := s.statShare(ctx, reqShare) + stattedShare, err := s.statShare(ctx, reqShare) if err != nil { - if statRes != nil { - return &provider.ListFileVersionsResponse{ - Status: statRes.Status, - }, err - } return &provider.ListFileVersionsResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the source share"), }, nil } gwres, err := s.gateway.ListFileVersions(ctx, &provider.ListFileVersionsRequest{ Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), + Path: filepath.Join(stattedShare.Stat.Path, reqPath), }, }) @@ -745,21 +694,15 @@ func (s *service) RestoreFileVersion(ctx context.Context, req *provider.RestoreF }, nil } - statRes, err := s.statShare(ctx, reqShare) + stattedShare, err := s.statShare(ctx, reqShare) if err != nil { - if statRes != nil { - return &provider.RestoreFileVersionResponse{ - Status: statRes.Status, - }, err - } return &provider.RestoreFileVersionResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the requested share"), + Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating the source share"), }, nil } - gwres, err := s.gateway.RestoreFileVersion(ctx, &provider.RestoreFileVersionRequest{ Ref: &provider.Reference{ - Path: filepath.Join(statRes.Info.Path, reqPath), + Path: filepath.Join(stattedShare.Stat.Path, reqPath), }, }) @@ -836,52 +779,22 @@ func (s *service) resolvePath(path string) (string, string) { func (s *service) statShare(ctx context.Context, share string) (*stattedReceivedShare, error) { _, ok := revactx.ContextGetUser(ctx) if !ok { - return &provider.StatResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: shares requested for empty user"), - }, nil + return nil, fmt.Errorf("sharesstorageprovider: shares requested for empty user") } shares, err := s.getReceivedShares(ctx) if err != nil { - return nil, err + return nil, fmt.Errorf("sharesstorageprovider: error getting received shares") } - - for _, rs := range shares { - if rs.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { - continue - } - - statRes, err := s.gateway.Stat(ctx, &provider.StatRequest{ - Ref: &provider.Reference{ - ResourceId: rs.Share.ResourceId, - }, - }) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, err - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx).Debug(). - Interface("share", share). - Interface("rs.Share", rs.Share). - Interface("statRes", statRes). - Msg("sharesstorageprovider: Got non-ok Stat response") - continue - } - - if filepath.Base(statRes.Info.Path) == share { - return statRes, nil - } + stattedShare, ok := shares[share] + if !ok { + return nil, fmt.Errorf("sharesstorageprovider: requested share not found") } - - return &provider.StatResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: requested share was not found for user"), - }, nil + return stattedShare, nil } -func (s *service) getReceivedShares(ctx context.Context) ([]*collaboration.ReceivedShare, error) { +func (s *service) getReceivedShares(ctx context.Context) (map[string]*stattedReceivedShare, error) { + ret := map[string]*stattedReceivedShare{} lsRes, err := s.sharesProviderClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) if err != nil { return nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest") @@ -889,16 +802,16 @@ func (s *service) getReceivedShares(ctx context.Context) ([]*collaboration.Recei if lsRes.Status.Code != rpc.Code_CODE_OK { return nil, fmt.Errorf("sharesstorageprovider: error calling ListReceivedSharesRequest") } - return lsRes.Shares, nil -} + appctx.GetLogger(ctx).Debug(). + Interface("ret", ret). + Interface("lsRes.Shares", lsRes.Shares). + Msg("sharesstorageprovider: Preparing statted share") -func (s *service) getReceivedShareByName(ctx context.Context, share string) (*collaboration.ReceivedShare, error) { - shares, err := s.getReceivedShares(ctx) - if err != nil { - return nil, err - } + for _, rs := range lsRes.Shares { + if rs.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { + continue + } - for _, rs := range shares { statRes, err := s.gateway.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ ResourceId: rs.Share.ResourceId, @@ -910,28 +823,41 @@ func (s *service) getReceivedShareByName(ctx context.Context, share string) (*co if statRes.Status.Code != rpc.Code_CODE_OK { appctx.GetLogger(ctx).Debug(). - Interface("rs", s). + Interface("rs.Share", rs.Share). Interface("statRes", statRes). Msg("sharesstorageprovider: Got non-ok Stat response") continue } - if filepath.Base(statRes.Info.Path) == share { - return rs, nil + name := filepath.Base(statRes.Info.Path) + if _, ok := ret[name]; !ok { + ret[name] = &stattedReceivedShare{ + ReceivedShare: rs, + AllReceivedShares: []*collaboration.ReceivedShare{rs}, + Stat: statRes.Info, + } + ret[name].Stat.PermissionSet = rs.Share.Permissions.Permissions + } else { + ret[name].Stat.PermissionSet = s.mergePermissions(ret[name].Stat.PermissionSet, rs.Share.Permissions.Permissions) + ret[name].AllReceivedShares = append(ret[name].AllReceivedShares, rs) } } - return nil, fmt.Errorf("sharesstorageprovider: received share '%s' not found", share) + + appctx.GetLogger(ctx).Debug(). + Interface("ret", ret). + Msg("sharesstorageprovider: Returning statted share") + return ret, nil } func (s *service) rejectReceivedShare(ctx context.Context, share string) error { - rs, err := s.getReceivedShareByName(ctx, share) + stattedShare, err := s.statShare(ctx, share) if err != nil { return err } ref := &collaboration.ShareReference{ Spec: &collaboration.ShareReference_Id{ - Id: rs.Share.Id, + Id: stattedShare.ReceivedShare.Share.Id, }, } _, err = s.sharesProviderClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ @@ -944,3 +870,25 @@ func (s *service) rejectReceivedShare(ctx context.Context, share string) error { }) return err } + +func (s *service) mergePermissions(a, b *provider.ResourcePermissions) *provider.ResourcePermissions { + a.AddGrant = a.AddGrant || b.AddGrant + a.CreateContainer = a.CreateContainer || b.CreateContainer + a.Delete = a.Delete || b.Delete + a.GetPath = a.GetPath || b.GetPath + a.GetQuota = a.GetQuota || b.GetQuota + a.InitiateFileDownload = a.InitiateFileDownload || b.InitiateFileDownload + a.InitiateFileUpload = a.InitiateFileUpload || b.InitiateFileUpload + a.ListGrants = a.ListGrants || b.ListGrants + a.ListContainer = a.ListContainer || b.ListContainer + a.ListFileVersions = a.ListFileVersions || b.ListFileVersions + a.ListRecycle = a.ListRecycle || b.ListRecycle + a.Move = a.Move || b.Move + a.RemoveGrant = a.RemoveGrant || b.RemoveGrant + a.PurgeRecycle = a.PurgeRecycle || b.PurgeRecycle + a.RestoreFileVersion = a.RestoreFileVersion || b.RestoreFileVersion + a.RestoreRecycleItem = a.RestoreRecycleItem || b.RestoreRecycleItem + a.Stat = a.Stat || b.Stat + a.UpdateGrant = a.UpdateGrant || b.UpdateGrant + return a +} diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go index 616ade38d1..7a392a8118 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go @@ -129,6 +129,9 @@ var _ = Describe("Sharesstorageprovider", func() { StorageId: "share1-storageid", OpaqueId: "subdir", }, + PermissionSet: &sprovider.ResourcePermissions{ + Stat: true, + }, Size: 10, }, } @@ -142,6 +145,9 @@ var _ = Describe("Sharesstorageprovider", func() { StorageId: "share1-storageid", OpaqueId: "file", }, + PermissionSet: &sprovider.ResourcePermissions{ + Stat: true, + }, Size: 20, }, } @@ -155,6 +161,9 @@ var _ = Describe("Sharesstorageprovider", func() { StorageId: "share1-storageid", OpaqueId: "shareddir", }, + PermissionSet: &sprovider.ResourcePermissions{ + Stat: true, + }, Size: 1234, }, } @@ -230,6 +239,20 @@ var _ = Describe("Sharesstorageprovider", func() { }, }, }, + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_ACCEPTED, + Share: &collaboration.Share{ + ResourceId: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "shareddir", + }, + Permissions: &collaboration.SharePermissions{ + Permissions: &sprovider.ResourcePermissions{ + ListContainer: true, + }, + }, + }, + }, }, }, nil) }) @@ -258,6 +281,54 @@ var _ = Describe("Sharesstorageprovider", func() { Expect(res.Info.Size).To(Equal(uint64(1234))) }) + It("merges permissions from multiple shares", func() { + sharesProviderClient.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ + Status: status.NewOK(context.Background()), + Shares: []*collaboration.ReceivedShare{ + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_ACCEPTED, + Share: &collaboration.Share{ + ResourceId: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "shareddir", + }, + Permissions: &collaboration.SharePermissions{ + Permissions: &sprovider.ResourcePermissions{ + Stat: true, + }, + }, + }, + }, + &collaboration.ReceivedShare{ + State: collaboration.ShareState_SHARE_STATE_ACCEPTED, + Share: &collaboration.Share{ + ResourceId: &sprovider.ResourceId{ + StorageId: "share1-storageid", + OpaqueId: "shareddir", + }, + Permissions: &collaboration.SharePermissions{ + Permissions: &sprovider.ResourcePermissions{ + ListContainer: true, + }, + }, + }, + }, + }, + }, nil) + statReq := &sprovider.StatRequest{ + Ref: &sprovider.Reference{ + Path: "/shares/share1-shareddir", + }, + } + res, err := s.Stat(ctx, statReq) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER)) + Expect(res.Info.Path).To(Equal("/shares/share1-shareddir")) + Expect(res.Info.PermissionSet.Stat).To(BeTrue()) + Expect(res.Info.PermissionSet.ListContainer).To(BeTrue()) + }) + It("stats a subfolder in a share", func() { statReq := &sprovider.StatRequest{ Ref: &sprovider.Reference{