diff --git a/.drone.star b/.drone.star index 1861e1dc614..ee9036272ff 100644 --- a/.drone.star +++ b/.drone.star @@ -687,7 +687,6 @@ 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/README.md b/README.md index 69bc152ed5e..58397663478 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,6 @@ This will require some PHP-related tools to run, for instance on Ubuntu you will ../../../cmd/revad/revad -c shares.toml & ../../../cmd/revad/revad -c storage-home.toml & ../../../cmd/revad/revad -c storage-users.toml & - ../../../cmd/revad/revad -c storage-shares.toml & ../../../cmd/revad/revad -c storage-publiclink.toml & ../../../cmd/revad/revad -c ldap-users.toml ``` @@ -145,9 +144,7 @@ This will require some PHP-related tools to run, for instance on Ubuntu you will TEST_WITH_LDAP='true' \ REVA_LDAP_HOSTNAME='localhost' \ TEST_REVA='true' \ - BEHAT_FILTER_TAGS='~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis' \ - EXPECTECTED_FAILURES_FILE='../acceptance/expected-failures-on-OCIS-storage.md' \ - DELETE_USER_DATA_CMD="rm -rf /var/tmp/reva/data/nodes/root/* /var/tmp/reva/data/nodes/*-*-*-* /var/tmp/reva/data/blobs/* /var/tmp/reva/data/spaces/*" \ + BEHAT_FILTER_TAGS='~@skipOnOcis&&~@skipOnOcis-OCIS-Storage&&~@notToImplementOnOCIS' \ make test-acceptance-api ``` diff --git a/changelog/unreleased/sharemanager-api-change.md b/changelog/unreleased/sharemanager-api-change.md new file mode 100644 index 00000000000..8210920deb8 --- /dev/null +++ b/changelog/unreleased/sharemanager-api-change.md @@ -0,0 +1,5 @@ +Change: Sharemanager API change + +This PR updates reva to reflect the share manager CS3 API changes. + +https://github.com/cs3org/reva/pull/2121 \ No newline at end of file diff --git a/changelog/unreleased/tbd.md b/changelog/unreleased/tbd.md deleted file mode 100644 index 73ec3a1ff2c..00000000000 --- a/changelog/unreleased/tbd.md +++ /dev/null @@ -1,6 +0,0 @@ -Enhancement: TBD - -This pr is most likely gonna be split so it's too early to have a changelog. -Can I still have CI pretty please? - -https://github.com/cs3org/reva/pull/2023 \ No newline at end of file diff --git a/internal/grpc/services/gateway/appprovider.go b/internal/grpc/services/gateway/appprovider.go index 091761ab8b5..3a895a98a74 100644 --- a/internal/grpc/services/gateway/appprovider.go +++ b/internal/grpc/services/gateway/appprovider.go @@ -56,7 +56,17 @@ 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 00bc6ca45a9..6908174e166 100644 --- a/internal/grpc/services/gateway/gateway.go +++ b/internal/grpc/services/gateway/gateway.go @@ -42,28 +42,27 @@ func init() { } type config struct { - 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"` + 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/ocmshareprovider.go b/internal/grpc/services/gateway/ocmshareprovider.go index 85ea662aff2..bd7ebb76bb5 100644 --- a/internal/grpc/services/gateway/ocmshareprovider.go +++ b/internal/grpc/services/gateway/ocmshareprovider.go @@ -224,60 +224,64 @@ func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceive return res, nil } - // we don't commit received shares in state invalid - if req.Share.State == ocm.ShareState_SHARE_STATE_INVALID { - log.Error().Msg("the update field is invalid, aborting reference manipulation") - return res, nil - - } - - // TODO(labkode): if update field is displayName we need to do a rename on the storage to align - // share display name and storage filename. - if req.Share.State != ocm.ShareState_SHARE_STATE_INVALID { - if req.Share.State == ocm.ShareState_SHARE_STATE_ACCEPTED { - getShareReq := &ocm.GetReceivedOCMShareRequest{ - Ref: &ocm.ShareReference{ - Spec: &ocm.ShareReference_Id{ - Id: req.Share.Share.Id, + // properties are updated in the order they appear in the field mask + // when an error occurs the request ends and no further fields are updated + for i := range req.UpdateMask.Paths { + switch req.UpdateMask.Paths[i] { + case "state": + switch req.GetShare().GetState() { + case ocm.ShareState_SHARE_STATE_ACCEPTED: + getShareReq := &ocm.GetReceivedOCMShareRequest{ + Ref: &ocm.ShareReference{ + Spec: &ocm.ShareReference_Id{ + Id: req.Share.Share.Id, + }, }, - }, - } - getShareRes, err := s.GetReceivedOCMShare(ctx, getShareReq) - if err != nil { - log.Err(err).Msg("gateway: error calling GetReceivedShare") - return &ocm.UpdateReceivedOCMShareResponse{ - Status: &rpc.Status{ - Code: rpc.Code_CODE_INTERNAL, - }, - }, nil - } - - if getShareRes.Status.Code != rpc.Code_CODE_OK { - log.Error().Msg("gateway: error calling GetReceivedShare") + } + getShareRes, err := s.GetReceivedOCMShare(ctx, getShareReq) + if err != nil { + log.Err(err).Msg("gateway: error calling GetReceivedShare") + return &ocm.UpdateReceivedOCMShareResponse{ + Status: &rpc.Status{ + Code: rpc.Code_CODE_INTERNAL, + }, + }, nil + } + + if getShareRes.Status.Code != rpc.Code_CODE_OK { + log.Error().Msg("gateway: error calling GetReceivedShare") + return &ocm.UpdateReceivedOCMShareResponse{ + Status: &rpc.Status{ + Code: rpc.Code_CODE_INTERNAL, + }, + }, nil + } + + share := getShareRes.Share + if share == nil { + panic("gateway: error updating a received share: the share is nil") + } + + createRefStatus, err := s.createOCMReference(ctx, share.Share) return &ocm.UpdateReceivedOCMShareResponse{ - Status: &rpc.Status{ - Code: rpc.Code_CODE_INTERNAL, - }, - }, nil - } - - share := getShareRes.Share - if share == nil { - panic("gateway: error updating a received share: the share is nil") + Status: createRefStatus, + }, err + case ocm.ShareState_SHARE_STATE_REJECTED: + s.removeReference(ctx, req.GetShare().GetShare().ResourceId) // error is logged inside removeReference + // FIXME we are ignoring an error from removeReference here + return res, nil } - - createRefStatus, err := s.createOCMReference(ctx, share.Share) + case "mount_point": + // TODO(labkode): implementing updating mount point + err = errtypes.NotSupported("gateway: update of mount point is not yet implemented") return &ocm.UpdateReceivedOCMShareResponse{ - Status: createRefStatus, - }, err + Status: status.NewUnimplemented(ctx, err, "error updating received share"), + }, nil + default: + return nil, errtypes.NotSupported("updating " + req.UpdateMask.Paths[i] + " is not supported") } } - - // TODO(labkode): implementing updating display name - err = errtypes.NotSupported("gateway: update of display name is not yet implemented") - return &ocm.UpdateReceivedOCMShareResponse{ - Status: status.NewUnimplemented(ctx, err, "error updating received share"), - }, nil + return res, nil } func (s *svc) GetReceivedOCMShare(ctx context.Context, req *ocm.GetReceivedOCMShareRequest) (*ocm.GetReceivedOCMShareResponse, error) { diff --git a/internal/grpc/services/gateway/publicshareprovider.go b/internal/grpc/services/gateway/publicshareprovider.go index 2d33b5893dd..88bb41b1c95 100644 --- a/internal/grpc/services/gateway/publicshareprovider.go +++ b/internal/grpc/services/gateway/publicshareprovider.go @@ -24,11 +24,16 @@ 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 f152d616369..974349cac9e 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -20,9 +20,7 @@ package gateway import ( "context" - "crypto/md5" "fmt" - "io" "net/url" "path" "path/filepath" @@ -30,6 +28,12 @@ import ( "sync" "time" + rtrace "github.com/cs3org/reva/pkg/trace" + "google.golang.org/protobuf/types/known/fieldmaskpb" + + 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" @@ -38,6 +42,7 @@ 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" @@ -304,30 +309,166 @@ 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) } - _, st := s.getPath(ctx, req.Ref) + p, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { return &gateway.InitiateFileDownloadResponse{ Status: st, }, nil } - statReq := &provider.StatRequest{Ref: req.Ref} - statRes, err := s.stat(ctx, statReq) - if err != 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") return &gateway.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+statReq.Ref.String()), + Status: status.NewInvalidArg(ctx, "path points to share folder"), }, nil + } - if statRes.Status.Code != rpc.Code_CODE_OK { + + 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") return &gateway.InitiateFileDownloadResponse{ - Status: statRes.Status, + Status: status.NewInvalidArg(ctx, "path points to share name"), }, nil } - return s.initiateFileDownload(ctx, req) + + 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) } func (s *svc) initiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*gateway.InitiateFileDownloadResponse, error) { @@ -383,17 +524,150 @@ 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) } - _, st := s.getPath(ctx, req.Ref) + p, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { return &gateway.InitiateFileUploadResponse{ Status: st, }, nil } - return s.initiateFileUpload(ctx, req) + 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) } func (s *svc) initiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest) (*gateway.InitiateFileUploadResponse, error) { @@ -475,18 +749,76 @@ 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) } - _, st := s.getPath(ctx, req.Ref) + p, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { return &provider.CreateContainerResponse{ Status: st, }, nil } - return s.createContainer(ctx, req) + 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) } func (s *svc) createContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) { @@ -505,15 +837,143 @@ 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) { - _, st := s.getPath(ctx, req.Ref) + log := appctx.GetLogger(ctx) + p, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { return &provider.DeleteResponse{ Status: st, }, nil } - return s.delete(ctx, req) + 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 "rejected" + 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 "rejected". This will ensure the user + // can "Accept" the share once again. + // TODO should this be pending? If so, update the two comments above as well. If not, get rid of this comment. + share.State = collaboration.ShareState_SHARE_STATE_REJECTED + r := &collaboration.UpdateReceivedShareRequest{ + Share: share, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}}, + } + + _, err := s.UpdateReceivedShare(ctx, r) + if err != nil { + return nil, err + } + + return &provider.DeleteResponse{ + Status: status.NewOK(ctx), + }, nil + } + } + + return &provider.DeleteResponse{ + Status: status.NewNotFound(ctx, "could not find share"), + }, nil + } + + 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) } func (s *svc) delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { @@ -534,59 +994,156 @@ func (s *svc) delete(ctx context.Context, req *provider.DeleteRequest) (*provide } func (s *svc) Move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { - _, st := s.getPath(ctx, req.Source) + log := appctx.GetLogger(ctx) + p, st := s.getPath(ctx, req.Source) if st.Code != rpc.Code_CODE_OK { return &provider.MoveResponse{ Status: st, }, nil } - _, st2 := s.getPath(ctx, req.Destination) - if st2.Code != rpc.Code_CODE_OK && st2.Code != rpc.Code_CODE_NOT_FOUND { + dp, st := s.getPath(ctx, req.Destination) + if st.Code != rpc.Code_CODE_OK && st.Code != rpc.Code_CODE_NOT_FOUND { return &provider.MoveResponse{ - Status: st2, + Status: st, }, nil } - return s.move(ctx, req) -} - -func (s *svc) move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { - srcProviders, err := s.findProviders(ctx, req.Source) - if err != nil { - return &provider.MoveResponse{ - Status: status.NewStatusFromErrType(ctx, "move src="+req.Source.String(), err), - }, nil + if !s.inSharedFolder(ctx, p) && !s.inSharedFolder(ctx, dp) { + return s.move(ctx, req) } - dstProviders, err := s.findProviders(ctx, req.Destination) - if err != nil { - return &provider.MoveResponse{ - Status: status.NewStatusFromErrType(ctx, "move dst="+req.Destination.String(), err), - }, nil + // 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) } - // if providers are not the same we do not implement cross storage move yet. - if len(srcProviders) != 1 || len(dstProviders) != 1 { - res := &provider.MoveResponse{ - Status: status.NewUnimplemented(ctx, nil, "gateway: cross storage copy not yet implemented"), + // 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) + + srcStatReq := &provider.StatRequest{Ref: &provider.Reference{Path: shareName}} + srcStatRes, err := s.stat(ctx, srcStatReq) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+srcStatReq.Ref.String()), + }, nil } - return res, nil - } - srcProvider, dstProvider := srcProviders[0], dstProviders[0] + if srcStatRes.Status.Code != rpc.Code_CODE_OK { + return &provider.MoveResponse{ + Status: srcStatRes.Status, + }, nil + } - // if providers are not the same we do not implement cross storage copy yet. - if srcProvider.Address != dstProvider.Address { - res := &provider.MoveResponse{ - Status: status.NewUnimplemented(ctx, nil, "gateway: cross storage copy not yet implemented"), + dstStatReq := &provider.StatRequest{Ref: &provider.Reference{Path: dshareName}} + dstStatRes, err := s.stat(ctx, dstStatReq) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating ref:"+srcStatReq.Ref.String()), + }, nil } - return res, nil - } - c, err := s.getStorageProviderClient(ctx, srcProvider) - if err != nil { - return &provider.MoveResponse{ + if dstStatRes.Status.Code != rpc.Code_CODE_OK { + return &provider.MoveResponse{ + Status: srcStatRes.Status, + }, nil + } + + srcRi, srcProtocol, err := s.checkRef(ctx, srcStatRes.Info) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "error resolving reference "+srcStatRes.Info.Target, err), + }, nil + } + + if srcProtocol == "webdav" { + err = s.webdavRefMove(ctx, dstStatRes.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 + } + dstRi, dstProtocol, err := s.checkRef(ctx, dstStatRes.Info) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "error resolving reference "+srcStatRes.Info.Target, err), + }, nil + } + + if dstProtocol == "webdav" { + err = s.webdavRefMove(ctx, dstStatRes.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(srcRi.Path, shareChild), + } + dst := &provider.Reference{ + Path: path.Join(dstRi.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 +} + +func (s *svc) move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { + srcProviders, err := s.findProviders(ctx, req.Source) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "move src="+req.Source.String(), err), + }, nil + } + + dstProviders, err := s.findProviders(ctx, req.Destination) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "move dst="+req.Destination.String(), err), + }, nil + } + + // if providers are not the same we do not implement cross storage move yet. + if len(srcProviders) != 1 || len(dstProviders) != 1 { + res := &provider.MoveResponse{ + Status: status.NewUnimplemented(ctx, nil, "gateway: cross storage copy not yet implemented"), + } + return res, nil + } + + srcProvider, dstProvider := srcProviders[0], dstProviders[0] + + // if providers are not the same we do not implement cross storage copy yet. + if srcProvider.Address != dstProvider.Address { + res := &provider.MoveResponse{ + Status: status.NewUnimplemented(ctx, nil, "gateway: cross storage copy not yet implemented"), + } + return res, nil + } + + c, err := s.getStorageProviderClient(ctx, srcProvider) + if err != nil { + return &provider.MoveResponse{ Status: status.NewInternal(ctx, err, "error connecting to storage provider="+srcProvider.Address), }, nil } @@ -642,6 +1199,23 @@ 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) @@ -649,13 +1223,59 @@ func (s *svc) statHome(ctx context.Context) (*provider.StatResponse, error) { if resMtime.Before(resEtag.Timestamp) { statRes.Info.Etag = resEtag.Etag } - } else if s.c.EtagCacheTTL > 0 { - _ = s.etagCache.Set(statRes.Info.Owner.OpaqueId+":"+statRes.Info.Path, etagWithTS{statRes.Info.Etag, time.Now()}) + } 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 + } + + 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 +} + func (s *svc) stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { providers, err := s.findProviders(ctx, req.Ref) if err != nil { @@ -672,37 +1292,11 @@ 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 } - - 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 + rsp, err := c.Stat(ctx, req) + if err != nil || rsp.Status.Code != rpc.Code_CODE_OK { + return rsp, err } - - 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 res, nil + return rsp, nil } return s.statAcrossProviders(ctx, req, providers) @@ -789,7 +1383,190 @@ func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.St return s.statHome(ctx) } - return s.stat(ctx, req) + 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 } func (s *svc) ListContainerStream(_ *provider.ListContainerStreamRequest, _ gateway.GatewayAPI_ListContainerStreamServer) error { @@ -812,6 +1589,64 @@ 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 } @@ -872,27 +1707,6 @@ 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, @@ -944,6 +1758,8 @@ 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) } @@ -959,7 +1775,168 @@ func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequ return s.listHome(ctx, req) } - return s.listContainer(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) } func (s *svc) getPath(ctx context.Context, ref *provider.Reference, keys ...string) (string, *rpc.Status) { @@ -981,6 +1958,75 @@ 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"), @@ -1120,19 +2166,6 @@ 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 89e8db60717..7243b4bae4b 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -23,8 +23,6 @@ import ( "fmt" "path" - rtrace "github.com/cs3org/reva/pkg/trace" - 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" @@ -39,6 +37,11 @@ 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{ @@ -272,9 +275,27 @@ func (s *svc) GetReceivedShare(ctx context.Context, req *collaboration.GetReceiv // 1) if received share is mounted: we also do a rename in the storage // 2) if received share is not mounted: we only rename in user share provider. func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.UpdateReceivedShareRequest) (*collaboration.UpdateReceivedShareResponse, error) { - t := rtrace.Provider.Tracer("reva") - ctx, span := t.Start(ctx, "Gateway.UpdateReceivedShare") - defer span.End() + log := appctx.GetLogger(ctx) + + // sanity checks + switch { + case req.GetShare() == nil: + return &collaboration.UpdateReceivedShareResponse{ + Status: status.NewInvalidArg(ctx, "updating requires a received share object"), + }, nil + case req.GetShare().GetShare() == nil: + return &collaboration.UpdateReceivedShareResponse{ + Status: status.NewInvalidArg(ctx, "share missing"), + }, nil + case req.GetShare().GetShare().GetId() == nil: + return &collaboration.UpdateReceivedShareResponse{ + Status: status.NewInvalidArg(ctx, "share id missing"), + }, nil + case req.GetShare().GetShare().GetId().GetOpaqueId() == "": + return &collaboration.UpdateReceivedShareResponse{ + Status: status.NewInvalidArg(ctx, "share id empty"), + }, nil + } c, err := pool.GetUserShareProviderClient(s.c.UserShareProviderEndpoint) if err != nil { @@ -284,7 +305,64 @@ func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.Update }, nil } - return c.UpdateReceivedShare(ctx, req) + res, err := c.UpdateReceivedShare(ctx, req) + if err != nil { + log.Err(err).Msg("gateway: error calling UpdateReceivedShare") + return &collaboration.UpdateReceivedShareResponse{ + Status: &rpc.Status{ + Code: rpc.Code_CODE_INTERNAL, + }, + }, nil + } + + // error failing to update share state. + if res.Status.Code != rpc.Code_CODE_OK { + return res, nil + } + + // if we don't need to create/delete references then we return early. + if !s.c.CommitShareToStorageRef { + return res, nil + } + + // check if we have a resource id in the update response that we can use to update references + if res.GetShare().GetShare().GetResourceId() == nil { + log.Err(err).Msg("gateway: UpdateReceivedShare must return a ResourceId") + return &collaboration.UpdateReceivedShareResponse{ + Status: &rpc.Status{ + Code: rpc.Code_CODE_INTERNAL, + }, + }, nil + } + + // properties are updated in the order they appear in the field mask + // when an error occurs the request ends and no further fields are updated + for i := range req.UpdateMask.Paths { + switch req.UpdateMask.Paths[i] { + case "state": + switch req.GetShare().GetState() { + case collaboration.ShareState_SHARE_STATE_ACCEPTED: + rpcStatus := s.createReference(ctx, res.GetShare().GetShare().GetResourceId()) + if rpcStatus.Code != rpc.Code_CODE_OK { + return &collaboration.UpdateReceivedShareResponse{Status: rpcStatus}, nil + } + case collaboration.ShareState_SHARE_STATE_REJECTED: + rpcStatus := s.removeReference(ctx, res.GetShare().GetShare().ResourceId) + if rpcStatus.Code != rpc.Code_CODE_OK && rpcStatus.Code != rpc.Code_CODE_NOT_FOUND { + return &collaboration.UpdateReceivedShareResponse{Status: rpcStatus}, nil + } + } + case "mount_point": + // TODO(labkode): implementing updating mount point + err = errtypes.NotSupported("gateway: update of mount point is not yet implemented") + return &collaboration.UpdateReceivedShareResponse{ + Status: status.NewUnimplemented(ctx, err, "error updating received share"), + }, nil + default: + return nil, errtypes.NotSupported("updating " + req.UpdateMask.Paths[i] + " is not supported") + } + } + return res, nil } func (s *svc) removeReference(ctx context.Context, resourceID *provider.ResourceId) *rpc.Status { @@ -406,7 +484,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, homeRes.Path) + c, err = s.findByPath(ctx, refPath) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { return status.NewNotFound(ctx, "storage provider not found") diff --git a/internal/grpc/services/gateway/webdavstorageprovider.go b/internal/grpc/services/gateway/webdavstorageprovider.go index 10782dba91c..8d98e8688c4 100644 --- a/internal/grpc/services/gateway/webdavstorageprovider.go +++ b/internal/grpc/services/gateway/webdavstorageprovider.go @@ -20,11 +20,18 @@ 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 { @@ -33,6 +40,178 @@ 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") @@ -58,6 +237,42 @@ 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 4b72259cdff..118eeed39ee 100644 --- a/internal/grpc/services/loader/loader.go +++ b/internal/grpc/services/loader/loader.go @@ -36,7 +36,6 @@ 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 deleted file mode 100644 index 3ebf3a3c1a1..00000000000 --- a/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go +++ /dev/null @@ -1,367 +0,0 @@ -// 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 -} - -// 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)) - 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 -} - -// 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/SharesProviderClient.go b/internal/grpc/services/sharesstorageprovider/mocks/SharesProviderClient.go deleted file mode 100644 index 747d497de1d..00000000000 --- a/internal/grpc/services/sharesstorageprovider/mocks/SharesProviderClient.go +++ /dev/null @@ -1,96 +0,0 @@ -// 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" -) - -// 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 *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] - } - 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 -} - -// 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 deleted file mode 100644 index b005579b1c1..00000000000 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ /dev/null @@ -1,980 +0,0 @@ -// 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" - "google.golang.org/protobuf/types/known/fieldmaskpb" - - 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" - 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" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" -) - -//go:generate mockery -name GatewayClient -name SharesProviderClient - -// 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) - 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) -} - -// 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() { - 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 - sharesProviderClient SharesProviderClient -} -type stattedReceivedShare struct { - Stat *provider.ResourceInfo - ReceivedShare *collaboration.ReceivedShare - AllReceivedShares []*collaboration.ReceivedShare -} - -type shareNotFoundError struct { - name string -} - -func (e *shareNotFoundError) Error() string { - return "Unknown share:" + e.name -} - -func isShareNotFoundError(e error) bool { - _, ok := e.(*shareNotFoundError) - return ok -} - -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 - } - - 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, c SharesProviderClient) (rgrpc.Service, error) { - s := &service{ - mountPath: mountpath, - gateway: gateway, - sharesProviderClient: c, - } - return s, nil -} - -func (s *service) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest) (*provider.SetArbitraryMetadataResponse, error) { - 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 - } - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.SetArbitraryMetadataResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.SetArbitraryMetadataResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - gwres, err := s.gateway.SetArbitraryMetadata(ctx, &provider.SetArbitraryMetadataRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(stattedShare.Stat.Path, reqPath), - }, - ArbitraryMetadata: req.ArbitraryMetadata, - }) - - if err != nil { - return &provider.SetArbitraryMetadataResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error calling SetArbitraryMetadata"), - }, nil - } - - return gwres, nil -} - -func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest) (*provider.UnsetArbitraryMetadataResponse, error) { - 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 - } - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.UnsetArbitraryMetadataResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.UnsetArbitraryMetadataResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - - gwres, err := s.gateway.UnsetArbitraryMetadata(ctx, &provider.UnsetArbitraryMetadataRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(stattedShare.Stat.Path, reqPath), - }, - ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, - }) - - if err != nil { - return &provider.UnsetArbitraryMetadataResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error calling UnsetArbitraryMetadata"), - }, nil - } - - return gwres, nil -} - -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 == "" { - return &provider.InitiateFileDownloadResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.InitiateFileDownloadResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.InitiateFileDownloadResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - - 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, - }, 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 -} - -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 == "" { - return &provider.InitiateFileUploadResponse{ - Status: status.NewInvalidArg(ctx, "sharesstorageprovider: can not upload directly to the shares folder"), - }, nil - } - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.InitiateFileUploadResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - - gwres, err := s.gateway.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(stattedShare.Stat.Path, reqPath), - }, - Opaque: req.Opaque, - }) - if err != nil { - return &provider.InitiateFileUploadResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: 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 -} - -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 - } - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.CreateContainerResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.CreateContainerResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - - gwres, err := s.gateway.CreateContainer(ctx, &provider.CreateContainerRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(stattedShare.Stat.Path, reqPath), - }, - }) - - if err != nil { - return &provider.CreateContainerResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: 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 == "" { - 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 - } - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.DeleteResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.DeleteResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - - gwres, err := s.gateway.Delete(ctx, &provider.DeleteRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(stattedShare.Stat.Path, reqPath), - }, - }) - - if err != nil { - return &provider.DeleteResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: 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") - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.MoveResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.MoveResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - - if reqShare != destinationShare && reqPath == "" { - // Change the MountPoint of the share - stattedShare.ReceivedShare.MountPoint = &provider.Reference{Path: destinationShare} - - _, err = s.sharesProviderClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ - Share: stattedShare.ReceivedShare, - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state", "mount_point"}}, - }) - if err != nil { - return &provider.MoveResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: can not change mountpoint of share"), - }, nil - } - return &provider.MoveResponse{ - Status: status.NewOK(ctx), - }, nil - } - - dstStattedShare, err := s.statShare(ctx, destinationShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.MoveResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.MoveResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - - 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 - } - - gwres, err := s.gateway.Move(ctx, &provider.MoveRequest{ - Source: &provider.Reference{ - Path: filepath.Join(stattedShare.Stat.Path, reqPath), - }, - Destination: &provider.Reference{ - Path: filepath.Join(dstStattedShare.Stat.Path, destinationPath), - }, - }) - - if err != nil { - return &provider.MoveResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: 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 := revactx.ContextGetUser(ctx) - if !ok { - return &provider.StatResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: shares requested for empty user"), - }, nil - } - - if reqShare != "" { - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.StatResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating 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 - } - } - - origReqShare := filepath.Base(stattedShare.Stat.Path) - relPath := strings.SplitAfterN(res.Info.Path, origReqShare, 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 - } - - res := &provider.StatResponse{ - Info: &provider.ResourceInfo{ - Path: filepath.Join(s.mountPath), - Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, - }, - } - childInfos := []*provider.ResourceInfo{} - for _, shares := range shares { - if shares.ReceivedShare.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { - continue - } - - 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") -} - -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") - - stattedShares, err := s.getReceivedShares(ctx) - if err != nil { - return nil, err - } - - res := &provider.ListContainerResponse{} - 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{f - // 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 || (stattedShare.ReceivedShare.MountPoint != nil && stattedShare.ReceivedShare.MountPoint.Path == reqShare)) { - origReqShare := filepath.Base(stattedShare.Stat.Path) - gwListRes, err := s.gateway.ListContainer(ctx, &provider.ListContainerRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(filepath.Dir(stattedShare.Stat.Path), origReqShare, 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, origReqShare, 2)[1] - info.Path = filepath.Join(s.mountPath, reqShare, relPath) - info.PermissionSet = stattedShare.Stat.PermissionSet - } - return gwListRes, nil - } else if reqShare == "" { - path := stattedShare.Stat.Path - if stattedShare.ReceivedShare.MountPoint != nil { - path = stattedShare.ReceivedShare.MountPoint.Path - } - stattedShare.Stat.Path = filepath.Join(s.mountPath, filepath.Base(path)) - res.Infos = append(res.Infos, stattedShare.Stat) - } - } - 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 - } - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.ListFileVersionsResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.ListFileVersionsResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - - gwres, err := s.gateway.ListFileVersions(ctx, &provider.ListFileVersionsRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(stattedShare.Stat.Path, reqPath), - }, - }) - - if err != nil { - return &provider.ListFileVersionsResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: 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 - } - - stattedShare, err := s.statShare(ctx, reqShare) - if err != nil { - if isShareNotFoundError(err) { - return &provider.RestoreFileVersionResponse{ - Status: status.NewNotFound(ctx, "sharesstorageprovider: file not found"), - }, nil - } - return &provider.RestoreFileVersionResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: error stating share"), - }, nil - } - gwres, err := s.gateway.RestoreFileVersion(ctx, &provider.RestoreFileVersionRequest{ - Ref: &provider.Reference{ - Path: filepath.Join(stattedShare.Stat.Path, reqPath), - }, - }) - - if err != nil { - return &provider.RestoreFileVersionResponse{ - Status: status.NewInternal(ctx, err, "sharesstorageprovider: 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) 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") -} - -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) (*stattedReceivedShare, error) { - _, ok := revactx.ContextGetUser(ctx) - if !ok { - return nil, fmt.Errorf("sharesstorageprovider: shares requested for empty user") - } - - shares, err := s.getReceivedShares(ctx) - if err != nil { - return nil, fmt.Errorf("sharesstorageprovider: error getting received shares") - } - stattedShare, ok := shares[share] - if !ok { - for _, ss := range shares { - if ss.ReceivedShare.MountPoint != nil && ss.ReceivedShare.MountPoint.Path == share { - stattedShare, ok = ss, true - } - } - } - if !ok { - return nil, &shareNotFoundError{name: share} - } - return stattedShare, nil -} - -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") - } - if lsRes.Status.Code != rpc.Code_CODE_OK { - return nil, fmt.Errorf("sharesstorageprovider: error calling ListReceivedSharesRequest") - } - appctx.GetLogger(ctx).Debug(). - Interface("ret", ret). - Interface("lsRes.Shares", lsRes.Shares). - Msg("sharesstorageprovider: Preparing statted share") - - for _, rs := range lsRes.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 nil, err - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx).Debug(). - Interface("rs.Share", rs.Share). - Interface("statRes", statRes). - Msg("sharesstorageprovider: Got non-ok Stat response") - continue - } - - name := rs.MountPoint.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) - } - } - - 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 { - stattedShare, err := s.statShare(ctx, share) - if err != nil { - return err - } - - stattedShare.ReceivedShare.State = collaboration.ShareState_SHARE_STATE_REJECTED - - _, err = s.sharesProviderClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ - Share: stattedShare.ReceivedShare, - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}}, - }) - 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_suite_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go deleted file mode 100644 index fceaad3ed32..00000000000 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// 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 deleted file mode 100644 index 6d2c77db10b..00000000000 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go +++ /dev/null @@ -1,758 +0,0 @@ -// 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" - ctxpkg "github.com/cs3org/reva/pkg/ctx" - "github.com/cs3org/reva/pkg/rgrpc/status" - _ "github.com/cs3org/reva/pkg/share/manager/loader" - "github.com/cs3org/reva/pkg/utils" - "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 = ctxpkg.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 - gw *mocks.GatewayClient - sharesProviderClient *mocks.SharesProviderClient - ) - - BeforeEach(func() { - sharesProviderClient = &mocks.SharesProviderClient{} - 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{ - { - 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{ - { - 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("ListContainer", mock.Anything, &sprovider.ListContainerRequest{ - Ref: &sprovider.Reference{ - Path: "/share2-shareddir", - }, - }).Return( - &sprovider.ListContainerResponse{ - Status: status.NewOK(context.Background()), - Infos: []*sprovider.ResourceInfo{}, - }, 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", - }, - PermissionSet: &sprovider.ResourcePermissions{ - Stat: true, - }, - 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", - }, - PermissionSet: &sprovider.ResourcePermissions{ - Stat: true, - }, - Size: 20, - }, - } - } else if req.Ref.GetPath() == "/share2-shareddir" || utils.ResourceIDEqual(req.Ref.ResourceId, &sprovider.ResourceId{ - StorageId: "share2-storageid", - OpaqueId: "shareddir", - }) { - return &sprovider.StatResponse{ - Status: status.NewOK(context.Background()), - Info: &sprovider.ResourceInfo{ - Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, - Path: "/share2-shareddir", - Id: &sprovider.ResourceId{ - StorageId: "share2-storageid", - OpaqueId: "shareddir", - }, - PermissionSet: &sprovider.ResourcePermissions{ - Stat: true, - }, - Size: 200, - }, - } - } 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", - }, - PermissionSet: &sprovider.ResourcePermissions{ - Stat: true, - }, - Size: 100, - }, - } - } - }, - nil) - - }) - - JustBeforeEach(func() { - p, err := provider.New("/shares", gw, sharesProviderClient) - 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() { - sharesProviderClient.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ - Status: status.NewOK(context.Background()), - Shares: []*collaboration.ReceivedShare{ - { - State: collaboration.ShareState_SHARE_STATE_INVALID, - }, - { - State: collaboration.ShareState_SHARE_STATE_PENDING, - }, - { - 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 two accepted shares", func() { - BeforeEach(func() { - sharesProviderClient.On("ListReceivedShares", mock.Anything, mock.Anything).Return(&collaboration.ListReceivedSharesResponse{ - Status: status.NewOK(context.Background()), - Shares: []*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, - ListContainer: true, - }, - }, - }, - }, - { - State: collaboration.ShareState_SHARE_STATE_ACCEPTED, - Share: &collaboration.Share{ - ResourceId: &sprovider.ResourceId{ - StorageId: "share2-storageid", - OpaqueId: "shareddir", - }, - Permissions: &collaboration.SharePermissions{ - Permissions: &sprovider.ResourcePermissions{ - Stat: true, - ListContainer: 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(300))) - }) - - 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(100))) - }) - - 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{ - { - 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, - }, - }, - }, - }, - { - 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{ - 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(2)) - - paths := []string{res.Infos[0].Path, res.Infos[1].Path} - Expect(paths).To(ConsistOf("/shares/share1-shareddir", "/shares/share2-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{ - { - 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("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", - }, - } - 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_OK)) - - sharesProviderClient.AssertCalled(GinkgoT(), "UpdateReceivedShare", mock.Anything, mock.Anything) - }) - - 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("renames a share", func() { - sharesProviderClient.On("UpdateReceivedShare", mock.Anything, mock.Anything).Return(nil, nil) - - 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_OK)) - sharesProviderClient.AssertCalled(GinkgoT(), "UpdateReceivedShare", mock.Anything, mock.Anything) - }) - - 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")) - }) - }) - - 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)) - }) - }) - }) -}) diff --git a/internal/grpc/services/usershareprovider/usershareprovider.go b/internal/grpc/services/usershareprovider/usershareprovider.go index 3e5b5c854a5..9f5b0d1fc81 100644 --- a/internal/grpc/services/usershareprovider/usershareprovider.go +++ b/internal/grpc/services/usershareprovider/usershareprovider.go @@ -231,29 +231,7 @@ func (s *service) GetReceivedShare(ctx context.Context, req *collaboration.GetRe } func (s *service) UpdateReceivedShare(ctx context.Context, req *collaboration.UpdateReceivedShareRequest) (*collaboration.UpdateReceivedShareResponse, error) { - - if req.Share == nil { - return &collaboration.UpdateReceivedShareResponse{ - Status: status.NewInvalidArg(ctx, "updating requires a received share object"), - }, nil - } - if req.Share.Share == nil { - return &collaboration.UpdateReceivedShareResponse{ - Status: status.NewInvalidArg(ctx, "share missing"), - }, nil - } - if req.Share.Share.Id == nil { - return &collaboration.UpdateReceivedShareResponse{ - Status: status.NewInvalidArg(ctx, "share id missing"), - }, nil - } - if req.Share.Share.Id.OpaqueId == "" { - return &collaboration.UpdateReceivedShareResponse{ - Status: status.NewInvalidArg(ctx, "share id empty"), - }, nil - } - - share, err := s.sm.UpdateReceivedShare(ctx, req.Share, req.UpdateMask) + share, err := s.sm.UpdateReceivedShare(ctx, req.Share, req.UpdateMask) // TODO(labkode): check what to update if err != nil { return &collaboration.UpdateReceivedShareResponse{ Status: status.NewInternal(ctx, err, "error updating received share"), diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index d0e4e8d3a11..137423013ef 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -20,7 +20,6 @@ package ocdav import ( "context" - "fmt" "net/http" "path" "strings" @@ -49,7 +48,6 @@ type DavHandler struct { SpacesHandler *SpacesHandler PublicFolderHandler *WebDavHandler PublicFileHandler *PublicFileHandler - SharesHandler *WebDavHandler } func (h *DavHandler) init(c *Config) error { @@ -98,7 +96,6 @@ 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 430bcbaf242..812040b6b44 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -98,7 +98,6 @@ 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/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go index 2f9ee42d919..1fe1bf5d399 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go @@ -19,91 +19,33 @@ package shares import ( - "context" - "fmt" "net/http" "path" - "sort" - "strconv" - 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/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/utils" "github.com/go-chi/chi/v5" "github.com/pkg/errors" "google.golang.org/protobuf/types/known/fieldmaskpb" ) -const ( - // shareID is the id of the share to update. It is present in the request URL. - shareID string = "shareid" -) - // AcceptReceivedShare handles Post Requests on /apps/files_sharing/api/v1/shares/{shareid} func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - shareID := chi.URLParam(r, shareID) - - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) - return - } - - share, ocsResponse := getShareFromID(ctx, client, shareID) - if ocsResponse != nil { - response.WriteOCSResponse(w, r, *ocsResponse, nil) - return - } - - sharedResource, ocsResponse := getSharedResource(ctx, client, share) - if ocsResponse != nil { - response.WriteOCSResponse(w, r, *ocsResponse, nil) - return - } - - lrs, ocsResponse := getSharesList(ctx, client) - if ocsResponse != nil { - response.WriteOCSResponse(w, r, *ocsResponse, nil) - return - } - - // we need to sort the received shares by mount point in order to make things easier to evaluate. - var mountPoints []string - for _, s := range lrs.Shares { - if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED && !utils.ResourceIDEqual(s.Share.ResourceId, share.Share.GetResourceId()) { - // only when the share is accepted there is a mount point. - mountPoints = append(mountPoints, s.MountPoint.Path) - } - } - - sort.Strings(mountPoints) - base := path.Base(sharedResource.GetInfo().GetPath()) - mount := base - - // now we have a list of shares, we want to iterate over all of them and check for name collisions - for i, mp := range mountPoints { - if mp == mount { - mount = fmt.Sprintf("%s (%s)", base, strconv.Itoa(i+1)) - } - } - - h.updateReceivedShare(w, r, shareID, false, mount) + shareID := chi.URLParam(r, "shareid") + h.updateReceivedShare(w, r, shareID, false) } // RejectReceivedShare handles DELETE Requests on /apps/files_sharing/api/v1/shares/{shareid} func (h *Handler) RejectReceivedShare(w http.ResponseWriter, r *http.Request) { shareID := chi.URLParam(r, "shareid") - h.updateReceivedShare(w, r, shareID, true, "") + h.updateReceivedShare(w, r, shareID, true) } -func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, shareID string, rejectShare bool, mountPoint string) { +func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, shareID string, rejectShare bool) { ctx := r.Context() logger := appctx.GetLogger(ctx) @@ -113,20 +55,16 @@ func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, sh return } - // we need to add a path to the share shareRequest := &collaboration.UpdateReceivedShareRequest{ Share: &collaboration.ReceivedShare{ Share: &collaboration.Share{Id: &collaboration.ShareId{OpaqueId: shareID}}, - MountPoint: &provider.Reference{ - Path: mountPoint, - }, }, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}}, } if rejectShare { shareRequest.Share.State = collaboration.ShareState_SHARE_STATE_REJECTED } else { - shareRequest.UpdateMask.Paths = append(shareRequest.UpdateMask.Paths, "mount_point") + // TODO find free mount point and pass it on with an updated field mask shareRequest.Share.State = collaboration.ShareState_SHARE_STATE_ACCEPTED } @@ -171,83 +109,3 @@ func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, sh response.WriteOCSSuccess(w, r, []*conversions.ShareData{data}) } - -// getShareFromID uses a client to the gateway to fetch a share based on its ID. -func getShareFromID(ctx context.Context, client gateway.GatewayAPIClient, shareID string) (*collaboration.GetShareResponse, *response.Response) { - s, err := client.GetShare(ctx, &collaboration.GetShareRequest{ - Ref: &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: shareID, - }}, - }, - }) - - if err != nil { - e := errors.Wrap(err, fmt.Sprintf("could not get share with ID: `%s`", shareID)) - return nil, arbitraryOcsResponse(response.MetaServerError.StatusCode, e.Error()) - } - - if s.Status.Code != rpc.Code_CODE_OK { - if s.Status.Code == rpc.Code_CODE_NOT_FOUND { - e := fmt.Errorf("share not found") - return nil, arbitraryOcsResponse(response.MetaNotFound.StatusCode, e.Error()) - } - - e := fmt.Errorf("invalid share: %s", s.GetStatus().GetMessage()) - return nil, arbitraryOcsResponse(response.MetaBadRequest.StatusCode, e.Error()) - } - - return s, nil -} - -// getSharedResource attempts to get a shared resource from the storage from the resource reference. -func getSharedResource(ctx context.Context, client gateway.GatewayAPIClient, share *collaboration.GetShareResponse) (*provider.StatResponse, *response.Response) { - res, err := client.Stat(ctx, &provider.StatRequest{ - Ref: &provider.Reference{ - ResourceId: share.Share.GetResourceId(), - }, - }) - if err != nil { - e := fmt.Errorf("could not get reference") - return nil, arbitraryOcsResponse(response.MetaServerError.StatusCode, e.Error()) - } - - if res.Status.Code != rpc.Code_CODE_OK { - if res.Status.Code == rpc.Code_CODE_NOT_FOUND { - e := fmt.Errorf("not found") - return nil, arbitraryOcsResponse(response.MetaNotFound.StatusCode, e.Error()) - } - e := fmt.Errorf(res.GetStatus().GetMessage()) - return nil, arbitraryOcsResponse(response.MetaServerError.StatusCode, e.Error()) - } - - return res, nil -} - -// getSharedResource gets the list of all shares for the current user. -func getSharesList(ctx context.Context, client gateway.GatewayAPIClient) (*collaboration.ListReceivedSharesResponse, *response.Response) { - shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) - if err != nil { - e := errors.Wrap(err, "error getting shares list") - return nil, arbitraryOcsResponse(response.MetaNotFound.StatusCode, e.Error()) - } - - if shares.Status.Code != rpc.Code_CODE_OK { - if shares.Status.Code == rpc.Code_CODE_NOT_FOUND { - e := fmt.Errorf("not found") - return nil, arbitraryOcsResponse(response.MetaNotFound.StatusCode, e.Error()) - } - e := fmt.Errorf(shares.GetStatus().GetMessage()) - return nil, arbitraryOcsResponse(response.MetaServerError.StatusCode, e.Error()) - } - return shares, nil -} - -// arbitraryOcsResponse abstracts the boilerplate that is creating a response.Response struct. -func arbitraryOcsResponse(statusCode int, message string) *response.Response { - r := response.NewResponse() - r.OCS.Meta.StatusCode = statusCode - r.OCS.Meta.Message = message - return &r -} diff --git a/internal/http/services/owncloud/ocs/response/response.go b/internal/http/services/owncloud/ocs/response/response.go index 85fc4f4cadc..f92af90875f 100644 --- a/internal/http/services/owncloud/ocs/response/response.go +++ b/internal/http/services/owncloud/ocs/response/response.go @@ -45,17 +45,6 @@ type Response struct { OCS *Payload `json:"ocs"` } -// NewResponse returns an empty response -func NewResponse() Response { - return Response{ - OCS: &Payload{ - XMLName: struct{}{}, - Meta: Meta{}, - Data: nil, - }, - } -} - // Payload combines response metadata and data type Payload struct { XMLName struct{} `json:"-" xml:"ocs"` diff --git a/pkg/cbox/share/sql/sql.go b/pkg/cbox/share/sql/sql.go index 918f17f8dc2..2cfe7d57e0c 100644 --- a/pkg/cbox/share/sql/sql.go +++ b/pkg/cbox/share/sql/sql.go @@ -448,8 +448,6 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, share *collaboration.Rece switch fieldMask.Paths[i] { case "state": rs.State = share.State - case "mount_point": - rs.MountPoint = share.MountPoint default: return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") } @@ -457,7 +455,7 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, share *collaboration.Rece var query, queryAccept string params := []interface{}{rs.Share.Id.OpaqueId, conversions.FormatUserID(user.Id)} - switch rs.State { + switch rs.GetState() { case collaboration.ShareState_SHARE_STATE_REJECTED: query = "insert into oc_share_acl(id, rejected_by) values(?, ?)" case collaboration.ShareState_SHARE_STATE_ACCEPTED: diff --git a/pkg/ocm/share/manager/json/json.go b/pkg/ocm/share/manager/json/json.go index 791da87d43c..2d91faa42c6 100644 --- a/pkg/ocm/share/manager/json/json.go +++ b/pkg/ocm/share/manager/json/json.go @@ -644,8 +644,7 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, share *ocm.ReceivedShare, switch fieldMask.Paths[i] { case "state": rs.State = share.State - case "mount_point": - rs.MountPoint = share.MountPoint + // TODO case "mount_point": default: return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") } diff --git a/pkg/share/manager/json/json.go b/pkg/share/manager/json/json.go index 6e9393f38d5..026682ce335 100644 --- a/pkg/share/manager/json/json.go +++ b/pkg/share/manager/json/json.go @@ -96,7 +96,7 @@ func loadOrCreate(file string) (*shareModel, error) { return nil, err } - m := &shareModel{State: j.State, MountPoint: j.MountPoint} + m := &shareModel{State: j.State} for _, s := range j.Shares { var decShare collaboration.Share if err = utils.UnmarshalJSONToProtoV1([]byte(s), &decShare); err != nil { @@ -108,29 +108,24 @@ func loadOrCreate(file string) (*shareModel, error) { if m.State == nil { m.State = map[string]map[string]collaboration.ShareState{} } - if m.MountPoint == nil { - m.MountPoint = map[string]map[string]*provider.Reference{} - } m.file = file return m, nil } type shareModel struct { - file string - State map[string]map[string]collaboration.ShareState `json:"state"` // map[username]map[share_id]ShareState - MountPoint map[string]map[string]*provider.Reference `json:"mount_point"` // map[username]map[share_id]MountPoint - Shares []*collaboration.Share `json:"shares"` + file string + State map[string]map[string]collaboration.ShareState `json:"state"` // map[username]map[share_id]ShareState + Shares []*collaboration.Share `json:"shares"` } type jsonEncoding struct { - State map[string]map[string]collaboration.ShareState `json:"state"` // map[username]map[share_id]ShareState - MountPoint map[string]map[string]*provider.Reference `json:"mount_point"` // map[username]map[share_id]MountPoint - Shares []string `json:"shares"` + State map[string]map[string]collaboration.ShareState `json:"state"` // map[username]map[share_id]ShareState + Shares []string `json:"shares"` } func (m *shareModel) Save() error { - j := &jsonEncoding{State: m.State, MountPoint: m.MountPoint} + j := &jsonEncoding{State: m.State} for _, s := range m.Shares { encShare, err := utils.MarshalProtoV1ToJSON(s) if err != nil { @@ -410,32 +405,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F } } } - - // if there is a mix-up of shares of type group and shares of type user we need to deduplicate them, since it points - // to the same resource. Leave the more explicit and hide the more explicit. In this case we hide the group shares - // and return the user share to the user. - filtered := make([]*collaboration.ReceivedShare, 0) - - for _, s := range rss { - filtered = append(filtered, s) - } - - for i := range rss { - for j := range rss { - if rss[i].Share.ResourceId.GetOpaqueId() == rss[j].Share.ResourceId.GetOpaqueId() { - if rss[i].Share.GetGrantee().GetType() == provider.GranteeType_GRANTEE_TYPE_GROUP && rss[j].Share.GetGrantee().GetType() == provider.GranteeType_GRANTEE_TYPE_USER { - if rss[i].State == rss[j].State { - // remove the group share from the results - filtered[i] = filtered[len(filtered)-1] - filtered[len(filtered)-1] = nil - filtered = filtered[:len(filtered)-1] - } - } - } - } - } - - return filtered, nil + return rss, nil } // convert must be called in a lock-controlled block. @@ -450,11 +420,6 @@ func (m *mgr) convert(ctx context.Context, s *collaboration.Share) *collaboratio rs.State = state } } - if v, ok := m.model.MountPoint[user.Id.String()]; ok { - if mp, ok := v[s.Id.String()]; ok { - rs.MountPoint = mp - } - } return rs } @@ -498,35 +463,22 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, receivedShare *collaborat switch fieldMask.Paths[i] { case "state": rs.State = receivedShare.State - case "mount_point": - rs.MountPoint = receivedShare.MountPoint + // TODO case "mount_point": default: return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") } } - // Persist state if v, ok := m.model.State[user.Id.String()]; ok { - v[rs.Share.Id.String()] = rs.State + v[rs.Share.Id.String()] = rs.GetState() m.model.State[user.Id.String()] = v } else { a := map[string]collaboration.ShareState{ - rs.Share.Id.String(): rs.State, + rs.Share.Id.String(): rs.GetState(), } m.model.State[user.Id.String()] = a } - // Persist mount point - if v, ok := m.model.MountPoint[user.Id.String()]; ok { - v[rs.Share.Id.String()] = rs.MountPoint - m.model.MountPoint[user.Id.String()] = v - } else { - a := map[string]*provider.Reference{ - rs.Share.Id.String(): rs.MountPoint, - } - m.model.MountPoint[user.Id.String()] = a - } - if err := m.model.Save(); err != nil { err = errors.Wrap(err, "error saving model") return nil, err diff --git a/pkg/share/manager/memory/memory.go b/pkg/share/manager/memory/memory.go index 234ad312873..fb365d7aa8b 100644 --- a/pkg/share/manager/memory/memory.go +++ b/pkg/share/manager/memory/memory.go @@ -47,11 +47,9 @@ func init() { // New returns a new manager. func New(c map[string]interface{}) (share.Manager, error) { state := map[string]map[*collaboration.ShareId]collaboration.ShareState{} - mp := map[string]map[*collaboration.ShareId]*provider.Reference{} return &manager{ - shareState: state, - shareMountPoint: mp, - lock: &sync.Mutex{}, + shareState: state, + lock: &sync.Mutex{}, }, nil } @@ -61,9 +59,6 @@ type manager struct { // shareState contains the share state for a user. // map["alice"]["share-id"]state. shareState map[string]map[*collaboration.ShareId]collaboration.ShareState - // shareMountPoint contains the mountpoint of a share for a user. - // map["alice"]["share-id"]reference. - shareMountPoint map[string]map[*collaboration.ShareId]*provider.Reference } func (m *manager) add(ctx context.Context, s *collaboration.Share) { @@ -158,18 +153,6 @@ func (m *manager) get(ctx context.Context, ref *collaboration.ShareReference) (s return s, nil } - // or the grantee - if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(user.Id, s.Grantee.GetUserId()) { - return s, nil - } else if s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - // check if all user groups match this share; TODO(labkode): filter shares created by us. - for _, g := range user.Groups { - if g == s.Grantee.GetGroupId().OpaqueId { - return s, nil - } - } - } - // we return not found to not disclose information return nil, errtypes.NotFound(ref.String()) } @@ -298,11 +281,6 @@ func (m *manager) convert(ctx context.Context, s *collaboration.Share) *collabor rs.State = state } } - if v, ok := m.shareMountPoint[user.Id.String()]; ok { - if mp, ok := v[s.Id]; ok { - rs.MountPoint = mp - } - } return rs } @@ -346,33 +324,21 @@ func (m *manager) UpdateReceivedShare(ctx context.Context, receivedShare *collab switch fieldMask.Paths[i] { case "state": rs.State = receivedShare.State - case "mount_point": - rs.MountPoint = receivedShare.MountPoint + // TODO case "mount_point": default: return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") } } - // Persist state if v, ok := m.shareState[user.Id.String()]; ok { - v[rs.Share.Id] = rs.State + v[rs.Share.Id] = rs.GetState() m.shareState[user.Id.String()] = v } else { a := map[*collaboration.ShareId]collaboration.ShareState{ - rs.Share.Id: rs.State, + rs.Share.Id: rs.GetState(), } m.shareState[user.Id.String()] = a } - // Persist mount point - if v, ok := m.shareMountPoint[user.Id.String()]; ok { - v[rs.Share.Id] = rs.MountPoint - m.shareMountPoint[user.Id.String()] = v - } else { - a := map[*collaboration.ShareId]*provider.Reference{ - rs.Share.Id: rs.MountPoint, - } - m.shareMountPoint[user.Id.String()] = a - } return rs, nil } diff --git a/pkg/share/manager/nextcloud/nextcloud.go b/pkg/share/manager/nextcloud/nextcloud.go index cab6c39d670..44e1985a092 100644 --- a/pkg/share/manager/nextcloud/nextcloud.go +++ b/pkg/share/manager/nextcloud/nextcloud.go @@ -406,7 +406,7 @@ func (sm *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareRef // UpdateReceivedShare updates the received share with share state. func (sm *mgr) UpdateReceivedShare(ctx context.Context, receivedShare *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask) (*collaboration.ReceivedShare, error) { type updateField struct { - State collaboration.ShareState `json:"State"` + State collaboration.ShareState `json:"state"` // TODO: Add support for the new mountpoint field } type paramsObj struct { @@ -419,6 +419,7 @@ func (sm *mgr) UpdateReceivedShare(ctx context.Context, receivedShare *collabora switch fieldMask.Paths[i] { case "state": f.State = receivedShare.State + // TODO case "mount_point": default: return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") } diff --git a/pkg/share/manager/sql/conversions.go b/pkg/share/manager/sql/conversions.go index 28c28f39711..7abf2813d7a 100644 --- a/pkg/share/manager/sql/conversions.go +++ b/pkg/share/manager/sql/conversions.go @@ -20,7 +20,6 @@ package sql import ( "context" - "strings" grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -42,7 +41,7 @@ type DBShare struct { UIDOwner string UIDInitiator string ItemStorage string - FileSource string + ItemSource string ShareWith string Token string Expiration string @@ -53,7 +52,6 @@ type DBShare struct { FileTarget string RejectedBy string State int - Parent int } // UserConverter describes an interface for converting user ids to names and back @@ -141,7 +139,7 @@ func (m *mgr) extractGrantee(ctx context.Context, t int, g string) (*provider.Gr } grantee.Type = provider.GranteeType_GRANTEE_TYPE_USER grantee.Id = &provider.Grantee_UserId{UserId: userid} - case 1, 2: + case 1: grantee.Type = provider.GranteeType_GRANTEE_TYPE_GROUP grantee.Id = &provider.Grantee_GroupId{GroupId: extractGroupID(g)} default: @@ -234,7 +232,7 @@ func (m *mgr) convertToCS3Share(ctx context.Context, s DBShare, storageMountID s }, ResourceId: &provider.ResourceId{ StorageId: storageMountID + "!" + s.ItemStorage, - OpaqueId: s.FileSource, + OpaqueId: s.ItemSource, }, Permissions: &collaboration.SharePermissions{Permissions: permissions}, Grantee: grantee, @@ -257,8 +255,7 @@ func (m *mgr) convertToCS3ReceivedShare(ctx context.Context, s DBShare, storageM state = intToShareState(s.State) } return &collaboration.ReceivedShare{ - Share: share, - State: state, - MountPoint: &provider.Reference{Path: strings.TrimLeft(s.FileTarget, "/")}, + Share: share, + State: state, }, nil } diff --git a/pkg/share/manager/sql/sql.go b/pkg/share/manager/sql/sql.go index 5d63f1d2e2f..516bcbf5266 100644 --- a/pkg/share/manager/sql/sql.go +++ b/pkg/share/manager/sql/sql.go @@ -211,7 +211,7 @@ func (m *mgr) Unshare(ctx context.Context, ref *collaboration.ShareReference) er return err } owner := formatUserID(key.Owner) - query = "DELETE FROM oc_share WHERE uid_owner=? AND file_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" + query = "DELETE FROM oc_share WHERE uid_owner=? AND item_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" params = append(params, owner, key.ResourceId.StorageId, shareType, shareWith, uid, uid) default: return errtypes.NotFound(ref.String()) @@ -253,7 +253,7 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference return nil, err } owner := formatUserID(key.Owner) - query = "update oc_share set permissions=?,stime=? where (uid_owner=? or uid_initiator=?) AND file_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" + query = "update oc_share set permissions=?,stime=? where (uid_owner=? or uid_initiator=?) AND item_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" params = append(params, permissions, time.Now().Unix(), owner, owner, key.ResourceId.StorageId, shareType, shareWith, uid, uid) default: return nil, errtypes.NotFound(ref.String()) @@ -272,12 +272,12 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { uid := ctxpkg.ContextMustGetUser(ctx).Username - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, file_target, id, stime, permissions, share_type FROM oc_share WHERE (uid_owner=? or uid_initiator=?) AND (share_type=? OR share_type=?)" + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE (uid_owner=? or uid_initiator=?) AND (share_type=? OR share_type=?)" var filterQuery string params := []interface{}{uid, uid, 0, 1} for i, f := range filters { if f.Type == collaboration.Filter_TYPE_RESOURCE_ID { - filterQuery += "(file_source=?)" + filterQuery += "(item_source=?)" if i != len(filters)-1 { filterQuery += " AND " } @@ -299,7 +299,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ( var s DBShare shares := []*collaboration.Share{} for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { + if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { continue } share, err := m.convertToCS3Share(ctx, s, m.storageMountID) @@ -326,27 +326,17 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F } homeConcat := "" - if m.driver == "mysql" { // mysql concat - homeConcat = "storages.id = CONCAT('home::', s.uid_owner)" - } else { // sqlite3 concat - homeConcat = "storages.id = 'home::' || s.uid_owner" + if m.driver == "mysql" { // mysql upsert + homeConcat = "storages.id = CONCAT('home::', ts.uid_owner)" + } else { // sqlite3 upsert + homeConcat = "storages.id = 'home::' || ts.uid_owner" } - userSelect := "" + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, ts.id, stime, permissions, share_type, accepted, storages.numeric_id FROM oc_share ts LEFT JOIN oc_storages storages ON " + homeConcat + " WHERE (uid_owner != ? AND uid_initiator != ?) " if len(user.Groups) > 0 { - userSelect = "AND ((share_type != 1 AND share_with=?) OR (share_type = 1 AND share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + ")))" + query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" } else { - userSelect = "AND (share_type != 1 AND share_with=?)" - } - query := ` - WITH results AS - ( - SELECT s.*, storages.numeric_id FROM oc_share s - LEFT JOIN oc_storages storages ON ` + homeConcat + ` - WHERE (uid_owner != ? AND uid_initiator != ?) ` + userSelect + ` - ) - SELECT COALESCE(r.uid_owner, '') AS uid_owner, COALESCE(r.uid_initiator, '') AS uid_initiator, COALESCE(r.share_with, '') - AS share_with, COALESCE(r.file_source, '') AS file_source, COALESCE(r2.file_target, r.file_target), r.id, r.stime, r.permissions, r.share_type, COALESCE(r2.accepted, r.accepted), - r.numeric_id, COALESCE(r.parent, -1) AS parent FROM results r LEFT JOIN results r2 ON r.id = r2.parent WHERE r.parent IS NULL;` + query += "AND (share_with=?)" + } rows, err := m.db.Query(query, params...) if err != nil { @@ -357,7 +347,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F var s DBShare shares := []*collaboration.ReceivedShare{} for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State, &s.ItemStorage, &s.Parent); err != nil { + if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State, &s.ItemStorage); err != nil { continue } share, err := m.convertToCS3ReceivedShare(ctx, s, m.storageMountID) @@ -393,67 +383,39 @@ func (m *mgr) GetReceivedShare(ctx context.Context, ref *collaboration.ShareRefe } -func (m *mgr) UpdateReceivedShare(ctx context.Context, receivedShare *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask) (*collaboration.ReceivedShare, error) { - rs, err := m.GetReceivedShare(ctx, &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: receivedShare.Share.Id}}) +func (m *mgr) UpdateReceivedShare(ctx context.Context, share *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask) (*collaboration.ReceivedShare, error) { + rs, err := m.GetReceivedShare(ctx, &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: share.Share.Id}}) if err != nil { return nil, err } - fields := []string{} - params := []interface{}{} for i := range fieldMask.Paths { switch fieldMask.Paths[i] { case "state": - rs.State = receivedShare.State - fields = append(fields, "accepted=?") - switch rs.State { - case collaboration.ShareState_SHARE_STATE_REJECTED: - params = append(params, 2) - case collaboration.ShareState_SHARE_STATE_ACCEPTED: - params = append(params, 0) - } - case "mount_point": - fields = append(fields, "file_target=?") - rs.MountPoint = receivedShare.MountPoint - params = append(params, rs.MountPoint.Path) + rs.State = share.State + // TODO case "mount_point": default: return nil, errtypes.NotSupported("updating " + fieldMask.Paths[i] + " is not supported") } } - if len(fields) == 0 { - return nil, fmt.Errorf("no valid field provided in the fieldmask") + var queryAccept string + switch rs.GetState() { + case collaboration.ShareState_SHARE_STATE_REJECTED: + queryAccept = "update oc_share set accepted=2 where id=?" + case collaboration.ShareState_SHARE_STATE_ACCEPTED: + queryAccept = "update oc_share set accepted=0 where id=?" } - updateReceivedShare := func(column string) error { - query := "update oc_share set " - query += strings.Join(fields, ",") - query += fmt.Sprintf(" where %s=?", column) - queryParams := append(params, rs.Share.Id.OpaqueId) - - stmt, err := m.db.Prepare(query) - if err != nil { - return err - } - res, err := stmt.Exec(queryParams...) + if queryAccept != "" { + stmt, err := m.db.Prepare(queryAccept) if err != nil { - return err + return nil, err } - affected, err := res.RowsAffected() + _, err = stmt.Exec(rs.Share.Id.OpaqueId) if err != nil { - return err - } - if affected < 1 { - return fmt.Errorf("No rows updated") + return nil, err } - return nil - } - err = updateReceivedShare("parent") // Try to update the child state in case of group shares first - if err != nil { - err = updateReceivedShare("id") - } - if err != nil { - return nil, err } return rs, nil @@ -462,8 +424,8 @@ func (m *mgr) UpdateReceivedShare(ctx context.Context, receivedShare *collaborat func (m *mgr) getByID(ctx context.Context, id *collaboration.ShareId) (*collaboration.Share, error) { uid := ctxpkg.ContextMustGetUser(ctx).Username s := DBShare{ID: id.OpaqueId} - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, file_target, stime, permissions, share_type FROM oc_share WHERE id=? AND (uid_owner=? or uid_initiator=?)" - if err := m.db.QueryRow(query, id.OpaqueId, uid, uid).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.STime, &s.Permissions, &s.ShareType); err != nil { + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, stime, permissions, share_type FROM oc_share WHERE id=? AND (uid_owner=? or uid_initiator=?)" + if err := m.db.QueryRow(query, id.OpaqueId, uid, uid).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.ItemSource, &s.STime, &s.Permissions, &s.ShareType); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(id.OpaqueId) } @@ -484,8 +446,8 @@ func (m *mgr) getByKey(ctx context.Context, key *collaboration.ShareKey) (*colla if err != nil { return nil, err } - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, file_target, id, stime, permissions, share_type FROM oc_share WHERE uid_owner=? AND file_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" - if err = m.db.QueryRow(query, owner, key.ResourceId.StorageId, shareType, shareWith, uid, uid).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, id, stime, permissions, share_type FROM oc_share WHERE uid_owner=? AND item_source=? AND share_type=? AND share_with=? AND (uid_owner=? or uid_initiator=?)" + if err = m.db.QueryRow(query, owner, key.ResourceId.StorageId, shareType, shareWith, uid, uid).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(key.String()) } @@ -498,42 +460,19 @@ func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId) (* user := ctxpkg.ContextMustGetUser(ctx) uid := user.Username - params := []interface{}{id.OpaqueId, id.OpaqueId, uid} + params := []interface{}{id.OpaqueId, uid} for _, v := range user.Groups { params = append(params, v) } - homeConcat := "" - if m.driver == "mysql" { // mysql concat - homeConcat = "storages.id = CONCAT('home::', s.uid_owner)" - } else { // sqlite3 concat - homeConcat = "storages.id = 'home::' || s.uid_owner" - } - userSelect := "" + s := DBShare{ID: id.OpaqueId} + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, stime, permissions, share_type, accepted FROM oc_share ts WHERE ts.id=? " if len(user.Groups) > 0 { - userSelect = "AND ((share_type != 1 AND share_with=?) OR (share_type = 1 AND share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + ")))" + query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" } else { - userSelect = "AND (share_type != 1 AND share_with=?)" - } - - query := ` - WITH results AS - ( - SELECT s.*, storages.numeric_id - FROM oc_share s - LEFT JOIN oc_storages storages ON ` + homeConcat + ` - WHERE s.id=? OR s.parent=?` + userSelect + ` - ) - SELECT COALESCE(r.uid_owner, '') AS uid_owner, COALESCE(r.uid_initiator, '') AS uid_initiator, COALESCE(r.share_with, '') - AS share_with, COALESCE(r.file_source, '') AS file_source, COALESCE(r2.file_target, r.file_target), r.id, r.stime, r.permissions, r.share_type, COALESCE(r2.accepted, r.accepted), - r.numeric_id, COALESCE(r.parent, -1) AS parent - FROM results r - LEFT JOIN results r2 ON r.id = r2.parent - WHERE r.parent IS NULL; - ` - - s := DBShare{} - if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State, &s.ItemStorage, &s.Parent); err != nil { + query += "AND (share_with=?)" + } + if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.ItemSource, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(id.OpaqueId) } @@ -556,14 +495,14 @@ func (m *mgr) getReceivedByKey(ctx context.Context, key *collaboration.ShareKey) } s := DBShare{} - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, file_target, ts.id, stime, permissions, share_type, accepted FROM oc_share ts WHERE uid_owner=? AND file_source=? AND share_type=? AND share_with=? " + query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(item_source, '') as item_source, ts.id, stime, permissions, share_type, accepted FROM oc_share ts WHERE uid_owner=? AND item_source=? AND share_type=? AND share_with=? " if len(user.Groups) > 0 { query += "AND (share_with=? OR share_with in (?" + strings.Repeat(",?", len(user.Groups)-1) + "))" } else { query += "AND (share_with=?)" } - if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { + if err := m.db.QueryRow(query, params...).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.ItemSource, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.State); err != nil { if err == sql.ErrNoRows { return nil, errtypes.NotFound(key.String()) } diff --git a/pkg/share/manager/sql/sql_test.go b/pkg/share/manager/sql/sql_test.go index 5a07082df36..016ced3044e 100644 --- a/pkg/share/manager/sql/sql_test.go +++ b/pkg/share/manager/sql/sql_test.go @@ -23,7 +23,6 @@ import ( "database/sql" "io/ioutil" "os" - "strconv" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -33,10 +32,10 @@ import ( "github.com/cs3org/reva/pkg/share" sqlmanager "github.com/cs3org/reva/pkg/share/manager/sql" mocks "github.com/cs3org/reva/pkg/share/manager/sql/mocks" + "google.golang.org/protobuf/types/known/fieldmaskpb" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/mock" - "google.golang.org/protobuf/types/known/fieldmaskpb" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -47,7 +46,6 @@ var _ = Describe("SQL manager", func() { mgr share.Manager ctx context.Context testDbFile *os.File - sqldb *sql.DB loginAs = func(user *userpb.User) { ctx = ruser.ContextSetUser(context.Background(), user) @@ -67,16 +65,6 @@ var _ = Describe("SQL manager", func() { Type: userpb.UserType_USER_TYPE_PRIMARY, }, Username: "einstein", - Groups: []string{"users"}, - } - yetAnotherUser = &userpb.User{ - Id: &userpb.UserId{ - Idp: "idp", - OpaqueId: "userid2", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Username: "marie", - Groups: []string{"users"}, } shareRef = &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{ @@ -84,26 +72,6 @@ var _ = Describe("SQL manager", func() { OpaqueId: "1", }, }} - - insertShare = func(shareType int, owner string, grantee string, parent int, source int, fileTarget string, permissions int, accepted int) (int, error) { - var parentVal interface{} - if parent >= 0 { - parentVal = parent - } - stmtString := "INSERT INTO oc_share (share_type,uid_owner,uid_initiator,item_type,item_source,file_source,parent,permissions,stime,share_with,file_target,accepted) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)" - stmtValues := []interface{}{shareType, owner, owner, "folder", source, source, parentVal, permissions, 1631779730, grantee, fileTarget, accepted} - - stmt, err := sqldb.Prepare(stmtString) - if err != nil { - return -1, err - } - result, err := stmt.Exec(stmtValues...) - if err != nil { - return -1, err - } - id, err := result.LastInsertId() - return int(id), err - } ) AfterEach(func() { @@ -123,7 +91,7 @@ var _ = Describe("SQL manager", func() { err = testDbFile.Close() Expect(err).ToNot(HaveOccurred()) - sqldb, err = sql.Open("sqlite3", testDbFile.Name()) + sqldb, err := sql.Open("sqlite3", testDbFile.Name()) Expect(err).ToNot(HaveOccurred()) userConverter := &mocks.UserConverter{} @@ -214,123 +182,6 @@ var _ = Describe("SQL manager", func() { }) Describe("ListReceivedShares", func() { - Context("with a pending group share (non-autoaccept) and an accepted child share", func() { - It("only returns one share (of type group share)", func() { - loginAs(otherUser) - parentID, err := insertShare( - 1, // group share - "admin", // owner/initiator - "users", // grantee - -1, // parent - 20, // source - "/groupshared", // file_target - 31, // permissions, - 0, // accepted - ) - Expect(err).ToNot(HaveOccurred()) - _, err = insertShare( - 2, // group child share - "admin", // owner/initiator - "einstein", // grantee - parentID, // parent - 20, // source - "/mygroupshared", // file_target - 31, // permissions, - 0, // accepted - ) - Expect(err).ToNot(HaveOccurred()) - - shares, err := mgr.ListReceivedShares(ctx, []*collaboration.Filter{}) - Expect(err).ToNot(HaveOccurred()) - Expect(len(shares)).To(Equal(2)) - groupShare := shares[1] - Expect(groupShare.MountPoint.Path).To(Equal("mygroupshared")) - Expect(groupShare.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) - Expect(groupShare.Share.Id.OpaqueId).To(Equal(strconv.Itoa(parentID))) - Expect(groupShare.Share.Grantee.Type).To(Equal(provider.GranteeType_GRANTEE_TYPE_GROUP)) - Expect(groupShare.Share.Grantee.GetGroupId().OpaqueId).To(Equal("users")) - }) - }) - - Context("with an accepted group share", func() { - It("lists the group share too", func() { - loginAs(otherUser) - _, err := insertShare( - 1, // group share - "admin", // owner/initiator - "users", // grantee - -1, // parent - 20, // source - "/shared", // file_target - 31, // permissions, - 0, // accepted - ) - Expect(err).ToNot(HaveOccurred()) - - shares, err := mgr.ListReceivedShares(ctx, []*collaboration.Filter{}) - Expect(err).ToNot(HaveOccurred()) - Expect(len(shares)).To(Equal(2)) - groupShare := shares[1] - Expect(groupShare.MountPoint.Path).To(Equal("shared")) - Expect(groupShare.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) - Expect(groupShare.Share.Grantee.Type).To(Equal(provider.GranteeType_GRANTEE_TYPE_GROUP)) - }) - - It("lists the child share information if the user changed the mountpoint", func() { - loginAs(otherUser) - parentID, err := insertShare( - 1, // group share - "admin", // owner/initiator - "users", // grantee - -1, // parent - 20, // source - "/groupshared", // file_target - 31, // permissions, - 1, // accepted - ) - Expect(err).ToNot(HaveOccurred()) - _, err = insertShare( - 2, // group child share - "admin", // owner/initiator - "einstein", // grantee - parentID, // parent - 20, // source - "/mygroupshared", // file_target - 31, // permissions, - 0, // accepted - ) - - shares, err := mgr.ListReceivedShares(ctx, []*collaboration.Filter{}) - Expect(err).ToNot(HaveOccurred()) - Expect(len(shares)).To(Equal(2)) - groupShare := shares[1] - Expect(groupShare.MountPoint.Path).To(Equal("mygroupshared")) - Expect(groupShare.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) - Expect(groupShare.Share.Id.OpaqueId).To(Equal(strconv.Itoa(parentID))) - Expect(groupShare.Share.Grantee.Type).To(Equal(provider.GranteeType_GRANTEE_TYPE_GROUP)) - Expect(groupShare.Share.Grantee.GetGroupId().OpaqueId).To(Equal("users")) - }) - - It("does not lists group shares named like the user", func() { - loginAs(otherUser) - _, err := insertShare( - 1, // group share - "admin", // owner/initiator - "einstein", // grantee - -1, // parent - 20, // source - "/shared", // file_target - 31, // permissions, - 0, // accepted - ) - Expect(err).ToNot(HaveOccurred()) - - shares, err := mgr.ListReceivedShares(ctx, []*collaboration.Filter{}) - Expect(err).ToNot(HaveOccurred()) - Expect(len(shares)).To(Equal(1)) - }) - }) - It("lists received shares", func() { loginAs(otherUser) shares, err := mgr.ListReceivedShares(ctx, []*collaboration.Filter{}) @@ -358,7 +209,6 @@ var _ = Describe("SQL manager", func() { Expect(share.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) share.State = collaboration.ShareState_SHARE_STATE_REJECTED - _, err = mgr.UpdateReceivedShare(ctx, share, &fieldmaskpb.FieldMask{Paths: []string{"foo"}}) Expect(err).To(HaveOccurred()) }) @@ -371,12 +221,6 @@ var _ = Describe("SQL manager", func() { Expect(share).ToNot(BeNil()) Expect(share.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) - share.State = collaboration.ShareState_SHARE_STATE_REJECTED - - share, err = mgr.UpdateReceivedShare(ctx, share, &fieldmaskpb.FieldMask{Paths: []string{"mount_point"}}) - Expect(err).ToNot(HaveOccurred()) - Expect(share.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) - share.State = collaboration.ShareState_SHARE_STATE_REJECTED share, err = mgr.UpdateReceivedShare(ctx, share, &fieldmaskpb.FieldMask{Paths: []string{"state"}}) Expect(err).ToNot(HaveOccurred()) @@ -387,86 +231,6 @@ var _ = Describe("SQL manager", func() { Expect(share).ToNot(BeNil()) Expect(share.State).To(Equal(collaboration.ShareState_SHARE_STATE_REJECTED)) }) - - It("updates the mount_point when the mount_point is set in the mask", func() { - loginAs(otherUser) - - share, err := mgr.GetReceivedShare(ctx, shareRef) - Expect(err).ToNot(HaveOccurred()) - Expect(share).ToNot(BeNil()) - Expect(share.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) - - share.MountPoint = &provider.Reference{Path: "foo"} - - share, err = mgr.UpdateReceivedShare(ctx, share, &fieldmaskpb.FieldMask{Paths: []string{"state"}}) - Expect(err).ToNot(HaveOccurred()) - Expect(share.MountPoint.Path).To(Equal("shared")) - - share.MountPoint = &provider.Reference{Path: "foo"} - share, err = mgr.UpdateReceivedShare(ctx, share, &fieldmaskpb.FieldMask{Paths: []string{"mount_point"}}) - Expect(err).ToNot(HaveOccurred()) - Expect(share.MountPoint.Path).To(Equal("foo")) - - share, err = mgr.GetReceivedShare(ctx, shareRef) - Expect(err).ToNot(HaveOccurred()) - Expect(share).ToNot(BeNil()) - Expect(share.MountPoint.Path).To(Equal("foo")) - }) - - Context("with a group share", func() { - It("updates the child share with the custom information", func() { - loginAs(otherUser) - parentID, err := insertShare( - 1, // group share - "admin", // owner/initiator - "users", // grantee - -1, // parent - 20, // source - "/groupshared", // file_target - 31, // permissions, - 1, // accepted - ) - Expect(err).ToNot(HaveOccurred()) - _, err = insertShare( - 2, // group child share - "admin", // owner/initiator - "einstein", // grantee - parentID, // parent - 20, // source - "/mygroupshared", // file_target - 31, // permissions, - 0, // accepted - ) - parentRef := &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: strconv.Itoa(parentID), - }, - }} - - share, err := mgr.GetReceivedShare(ctx, parentRef) - Expect(err).ToNot(HaveOccurred()) - Expect(share).ToNot(BeNil()) - Expect(share.State).To(Equal(collaboration.ShareState_SHARE_STATE_ACCEPTED)) - - share.MountPoint = &provider.Reference{Path: "foo"} - - By("overriding the child share information for the current user") - share, err = mgr.UpdateReceivedShare(ctx, share, &fieldmaskpb.FieldMask{Paths: []string{"mount_point"}}) - Expect(err).ToNot(HaveOccurred()) - Expect(share.MountPoint.Path).To(Equal("foo")) - - share, err = mgr.GetReceivedShare(ctx, parentRef) - Expect(err).ToNot(HaveOccurred()) - Expect(share).ToNot(BeNil()) - Expect(share.MountPoint.Path).To(Equal("foo")) - - By("not overriding the parent share information") - loginAs(yetAnotherUser) - share, err = mgr.GetReceivedShare(ctx, parentRef) - Expect(err).ToNot(HaveOccurred()) - Expect(share.MountPoint.Path).To(Equal("groupshared")) - }) - }) }) Describe("Unshare", func() { diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 9d61ccd8161..f57731c8470 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -48,7 +48,6 @@ import ( "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" "github.com/pkg/xattr" - "go.opentelemetry.io/otel/codes" ) // PermissionsChecker defines an interface for checking permissions on a Node @@ -304,36 +303,28 @@ func (fs *Decomposedfs) CreateDir(ctx context.Context, ref *provider.Reference) // CreateReference creates a reference as a node folder with the target stored in extended attributes // There is no difference between the /Shares folder and normal nodes because the storage is not supposed to be accessible without the storage provider. // In effect everything is a shadow namespace. -// To mimic the eos and owncloud driver we only allow references as children of the "/Shares" folder +// To mimic the eos end owncloud driver we only allow references as children of the "/Shares" folder +// TODO when home support is enabled should the "/Shares" folder still be listed? func (fs *Decomposedfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) (err error) { - ctx, span := rtrace.Provider.Tracer("reva").Start(ctx, "CreateReference") - defer span.End() p = strings.Trim(p, "/") parts := strings.Split(p, "/") if len(parts) != 2 { - err := errtypes.PermissionDenied("Decomposedfs: references must be a child of the share folder: share_folder=" + fs.o.ShareFolder + " path=" + p) - span.SetStatus(codes.Error, err.Error()) - return err + return errtypes.PermissionDenied("Decomposedfs: references must be a child of the share folder: share_folder=" + fs.o.ShareFolder + " path=" + p) } if parts[0] != strings.Trim(fs.o.ShareFolder, "/") { - err := errtypes.PermissionDenied("Decomposedfs: cannot create references outside the share folder: share_folder=" + fs.o.ShareFolder + " path=" + p) - span.SetStatus(codes.Error, err.Error()) - return err + return errtypes.PermissionDenied("Decomposedfs: cannot create references outside the share folder: share_folder=" + fs.o.ShareFolder + " path=" + p) } // create Shares folder if it does not exist var n *node.Node if n, err = fs.lu.NodeFromPath(ctx, fs.o.ShareFolder, false); err != nil { - err := errtypes.InternalError(err.Error()) - span.SetStatus(codes.Error, err.Error()) - return err + return errtypes.InternalError(err.Error()) } else if !n.Exists { if err = fs.tp.CreateDir(ctx, n); err != nil { - span.SetStatus(codes.Error, err.Error()) - return err + return } } @@ -343,24 +334,16 @@ func (fs *Decomposedfs) CreateReference(ctx context.Context, p string, targetURI if n.Exists { // TODO append increasing number to mountpoint name - err := errtypes.AlreadyExists(p) - span.SetStatus(codes.Error, err.Error()) - return err + return errtypes.AlreadyExists(p) } - if err := fs.tp.CreateDir(ctx, n); err != nil { - span.SetStatus(codes.Error, err.Error()) - return err + if err = fs.tp.CreateDir(ctx, n); err != nil { + return } internal := n.InternalPath() - if err := xattr.Set(internal, xattrs.ReferenceAttr, []byte(targetURI.String())); err != nil { - err := errors.Wrapf(err, "Decomposedfs: error setting the target %s on the reference file %s", - targetURI.String(), - internal, - ) - span.SetStatus(codes.Error, err.Error()) - return err + if err = xattr.Set(internal, xattrs.ReferenceAttr, []byte(targetURI.String())); err != nil { + return errors.Wrapf(err, "Decomposedfs: error setting the target %s on the reference file %s", targetURI.String(), internal) } return nil } diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 6f8d5cbb79d..cabfea6de7e 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1001,6 +1001,12 @@ 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) diff --git a/tests/oc-integration-tests/drone/gateway.toml b/tests/oc-integration-tests/drone/gateway.toml index 5d22be2b0db..03b2514ba6b 100644 --- a/tests/oc-integration-tests/drone/gateway.toml +++ b/tests/oc-integration-tests/drone/gateway.toml @@ -72,14 +72,6 @@ home_provider = "/home" # another mount point might be "/projects/" "/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" diff --git a/tests/oc-integration-tests/drone/storage-shares-ocis.toml b/tests/oc-integration-tests/drone/storage-shares-ocis.toml deleted file mode 100644 index 97403ef3fa5..00000000000 --- a/tests/oc-integration-tests/drone/storage-shares-ocis.toml +++ /dev/null @@ -1,14 +0,0 @@ -# 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 diff --git a/tests/oc-integration-tests/local/gateway.toml b/tests/oc-integration-tests/local/gateway.toml index 5c10072d31b..ca66f9b744f 100644 --- a/tests/oc-integration-tests/local/gateway.toml +++ b/tests/oc-integration-tests/local/gateway.toml @@ -65,14 +65,6 @@ home_provider = "/home" # another mount point might be "/projects/" "/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" diff --git a/tests/oc-integration-tests/local/storage-shares.toml b/tests/oc-integration-tests/local/storage-shares.toml deleted file mode 100644 index 97403ef3fa5..00000000000 --- a/tests/oc-integration-tests/local/storage-shares.toml +++ /dev/null @@ -1,14 +0,0 @@ -# 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 diff --git a/tests/oc-integration-tests/local/storage-users.toml b/tests/oc-integration-tests/local/storage-users.toml index 82784777e80..c2d4d0abf41 100644 --- a/tests/oc-integration-tests/local/storage-users.toml +++ b/tests/oc-integration-tests/local/storage-users.toml @@ -15,7 +15,7 @@ address = "0.0.0.0:11000" # we have a locally running dataprovider [grpc.services.storageprovider] driver = "ocis" -mount_path = "/oc" +mount_path = "/users" mount_id = "123e4567-e89b-12d3-a456-426655440000" expose_data_server = true data_server_url = "http://localhost:11001/data"