From 649b6a74bcaa149386ab3c01f503ded55c1f8c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 4 Jan 2022 21:04:58 +0100 Subject: [PATCH] Ocdav spaces aware (#2396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * allow sending path filter on ListStorageSpaces call Signed-off-by: Jörn Friedrich Dreyer * make sharesstorageprovider filter by nodeid Signed-off-by: Jörn Friedrich Dreyer * ocdax + spaces: introduce and refactor lookUpStorageSpacesForPath Signed-off-by: Jörn Friedrich Dreyer * make propfind spaces aware, still flaky, infinite depth commented for now Signed-off-by: Jörn Friedrich Dreyer * add changelog Signed-off-by: Jörn Friedrich Dreyer * split lookup into two dedicated functions Signed-off-by: Jörn Friedrich Dreyer * make delete spaces aware Signed-off-by: Jörn Friedrich Dreyer * use explicit grant and mountpoint types Signed-off-by: Jörn Friedrich Dreyer * make ref debuggable Signed-off-by: Jörn Friedrich Dreyer * make dav get spaces aware * fix requested path check * refactor spaces handler to use mountpoint Signed-off-by: Jörn Friedrich Dreyer * make head spaces aware * make mkcol spaces aware * make move spaces aware * make put spaces aware * make proppatch spaces aware * fix move: use dedicated intermediate resource Signed-off-by: Jörn Friedrich Dreyer * make copy spaces aware * fix propfind for subfolders and subspaces * add FIXMEs when using absolute references without an ID Signed-off-by: Jörn Friedrich Dreyer * update config Signed-off-by: Jörn Friedrich Dreyer * fix propfind for subfolders * change copy reference to spaces reference * remove node via os instead tree when create fails Signed-off-by: jkoberg * fix unit tests Signed-off-by: jkoberg * fix linting Signed-off-by: jkoberg * fix integration tests Signed-off-by: jkoberg * pass opaque through gateway * add option to return to do exact path matching Signed-off-by: jkoberg * refine path matching logic * send resourceid when stating parent Signed-off-by: jkoberg * actually use space and shareid in sharesstorageprovider Signed-off-by: jkoberg * add deepest mountpath in unique case Signed-off-by: jkoberg * Add resourceid when stating in put handler Signed-off-by: jkoberg * son't overwrite path on copy Signed-off-by: jkoberg * implement depth=infinity logic Signed-off-by: jkoberg * use relative path when stating parent * refine IsSpaceRoot logic Signed-off-by: jkoberg * linting Signed-off-by: jkoberg * do not add child info when depth is 0 Signed-off-by: jkoberg * remove rename logic from gateway Signed-off-by: jkoberg * remove path changing logic Signed-off-by: jkoberg * fix some flakyness Signed-off-by: jkoberg * don't send path when statting from sharesstorageprovider Signed-off-by: jkoberg * refine logic when to send path on stat Signed-off-by: jkoberg * fix panic in unit tests Signed-off-by: jkoberg * add resourceid when purging recycle Signed-off-by: jkoberg * add resourceid for other recycle endpoints too Signed-off-by: jkoberg * lame way of fixing listrecycle Signed-off-by: jkoberg * fix duplicates in propfind response Signed-off-by: Jörn Friedrich Dreyer * fix PROPFIND for subfolders Signed-off-by: Jörn Friedrich Dreyer * fix shared file aggregetion Signed-off-by: Jörn Friedrich Dreyer * fix litmus test setup * continue on ListContainer errors Signed-off-by: Jörn Friedrich Dreyer * fix lint Signed-off-by: Jörn Friedrich Dreyer * fix depth infinity Signed-off-by: Jörn Friedrich Dreyer * use GetPath properly Signed-off-by: Jörn Friedrich Dreyer * prevent panic when owner is nil Signed-off-by: Jörn Friedrich Dreyer * work on public link access Signed-off-by: Jörn Friedrich Dreyer * fix webdav path copy * fix public link listing Signed-off-by: Jörn Friedrich Dreyer * fix public link children Signed-off-by: Jörn Friedrich Dreyer * small public link size fix Signed-off-by: Jörn Friedrich Dreyer * fix webdav spaces mkcol * fix webdav spaces delete * work on depth infinity Signed-off-by: Jörn Friedrich Dreyer * add userid to unittest mock Signed-off-by: jkoberg * work on etag propagation Signed-off-by: Jörn Friedrich Dreyer * fix mostRecentChild logic Signed-off-by: Jörn Friedrich Dreyer * add missing license header Signed-off-by: Jörn Friedrich Dreyer * fix webdav spaces put and get * fix spaces webdav endpoints I removed the namespace from the spaces handlers again because the namespace depends on the respective space. * drop unnecessary GetPath call in public file PROPFIND Signed-off-by: Jörn Friedrich Dreyer * fix webdav spaces propfind href * fix on public file propfind/get/head Signed-off-by: Jörn Friedrich Dreyer * fix makeRelativeReference Signed-off-by: Jörn Friedrich Dreyer * fix non existing rootinfo Signed-off-by: Jörn Friedrich Dreyer * fix makeRelativeReference for spaces webdav * use relative path when copying Signed-off-by: Jörn Friedrich Dreyer * use source when listing while copying Signed-off-by: jkoberg Co-authored-by: David Christofas Co-authored-by: jkoberg --- .drone.star | 9 + changelog/unreleased/ocdav-spaces-aware.md | 5 + .../grpc/services/gateway/storageprovider.go | 247 +++++------- .../publicstorageprovider.go | 72 +++- .../sharesstorageprovider.go | 256 +++++++++--- .../sharesstorageprovider_test.go | 52 ++- internal/http/services/owncloud/ocdav/copy.go | 86 ++-- internal/http/services/owncloud/ocdav/dav.go | 9 +- .../http/services/owncloud/ocdav/delete.go | 17 +- internal/http/services/owncloud/ocdav/get.go | 16 +- internal/http/services/owncloud/ocdav/head.go | 15 +- .../http/services/owncloud/ocdav/mkcol.go | 20 +- internal/http/services/owncloud/ocdav/move.go | 61 +-- .../http/services/owncloud/ocdav/propfind.go | 377 +++++++++++------- .../http/services/owncloud/ocdav/proppatch.go | 17 +- .../services/owncloud/ocdav/publicfile.go | 71 +--- internal/http/services/owncloud/ocdav/put.go | 22 +- .../http/services/owncloud/ocdav/spaces.go | 112 +++++- .../http/services/owncloud/ocdav/trashbin.go | 61 ++- internal/http/services/owncloud/ocdav/tus.go | 7 +- .../sharing/shares/mocks/GatewayClient.go | 32 +- .../handlers/apps/sharing/shares/pending.go | 24 +- .../handlers/apps/sharing/shares/shares.go | 133 +++--- .../apps/sharing/shares/shares_test.go | 7 +- .../ocs/handlers/apps/sharing/shares/user.go | 6 - .../ocs/handlers/cloud/users/users.go | 5 +- pkg/auth/scope/publicshare.go | 10 +- pkg/auth/scope/resourceinfo.go | 23 ++ pkg/storage/registry/spaces/spaces.go | 152 ++++--- pkg/storage/utils/decomposedfs/node/node.go | 14 +- .../utils/decomposedfs/node/permissions.go | 3 +- pkg/storage/utils/decomposedfs/spaces.go | 30 +- pkg/storage/utils/decomposedfs/tree/tree.go | 17 +- .../grpc/gateway_storageprovider_test.go | 6 +- tests/oc-integration-tests/drone/gateway.toml | 4 +- tests/oc-integration-tests/local/gateway.toml | 4 +- 36 files changed, 1242 insertions(+), 760 deletions(-) create mode 100644 changelog/unreleased/ocdav-spaces-aware.md diff --git a/.drone.star b/.drone.star index a4f49c4411..98da4ac1b6 100644 --- a/.drone.star +++ b/.drone.star @@ -513,6 +513,9 @@ def litmusOcisOldWebdav(): "/drone/src/cmd/revad/revad -c frontend.toml &", "/drone/src/cmd/revad/revad -c gateway.toml &", "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", + "/drone/src/cmd/revad/revad -c storage-shares.toml &", + "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", + "/drone/src/cmd/revad/revad -c shares.toml &", "/drone/src/cmd/revad/revad -c users.toml", ], }, @@ -565,6 +568,9 @@ def litmusOcisNewWebdav(): "/drone/src/cmd/revad/revad -c frontend.toml &", "/drone/src/cmd/revad/revad -c gateway.toml &", "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", + "/drone/src/cmd/revad/revad -c storage-shares.toml &", + "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", + "/drone/src/cmd/revad/revad -c shares.toml &", "/drone/src/cmd/revad/revad -c users.toml", ] }, @@ -618,6 +624,9 @@ def litmusOcisSpacesDav(): "/drone/src/cmd/revad/revad -c frontend.toml &", "/drone/src/cmd/revad/revad -c gateway.toml &", "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", + "/drone/src/cmd/revad/revad -c storage-shares.toml &", + "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", + "/drone/src/cmd/revad/revad -c shares.toml &", "/drone/src/cmd/revad/revad -c users.toml", ] }, diff --git a/changelog/unreleased/ocdav-spaces-aware.md b/changelog/unreleased/ocdav-spaces-aware.md new file mode 100644 index 0000000000..efb57eea10 --- /dev/null +++ b/changelog/unreleased/ocdav-spaces-aware.md @@ -0,0 +1,5 @@ +Change: the ocdav handler is now spaces aware + +It will use LookupStorageSpaces and make only relative requests to the gateway. Temp comment + +https://github.com/cs3org/reva/pull/2396 diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 252f9fdae9..3550ab153a 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -27,7 +27,6 @@ import ( "path" "path/filepath" "strings" - "sync" "time" "github.com/BurntSushi/toml" @@ -211,7 +210,6 @@ func (s *svc) CreateStorageSpace(ctx context.Context, req *provider.CreateStorag } func (s *svc) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) { - log := appctx.GetLogger(ctx) // TODO update CS3 api to forward the filters to the registry so it can filter the number of providers the gateway needs to query filters := map[string]string{} @@ -241,9 +239,8 @@ func (s *svc) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSp return nil, errors.Wrap(err, "gateway: error getting storage registry client") } - listReq := ®istry.ListStorageProvidersRequest{} + listReq := ®istry.ListStorageProvidersRequest{Opaque: req.Opaque} if len(filters) > 0 { - listReq.Opaque = &typesv1beta1.Opaque{} sdk.EncodeOpaqueMap(listReq.Opaque, filters) } res, err := c.ListStorageProviders(ctx, listReq) @@ -258,44 +255,32 @@ func (s *svc) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSp }, nil } - // TODO the providers now have an opaque "spaces_paths" property - providerInfos := res.Providers - - spacesFromProviders := make([][]*provider.StorageSpace, len(providerInfos)) - errors := make([]error, len(providerInfos)) - - var wg sync.WaitGroup - for i, p := range providerInfos { - // we need to ask the provider for the space details - wg.Add(1) - go s.listStorageSpacesOnProvider(ctx, req, &spacesFromProviders[i], p, &errors[i], &wg) - } - wg.Wait() - - uniqueSpaces := map[string]*provider.StorageSpace{} - for i := range providerInfos { - if errors[i] != nil { - if len(providerInfos) > 1 { - log.Debug().Err(errors[i]).Msg("skipping provider") + spaces := []*provider.StorageSpace{} + for _, providerInfo := range res.Providers { + spacePaths := decodeSpacePaths(providerInfo) + for spaceID, spacePath := range spacePaths { + storageid, opaqueid, err := utils.SplitStorageSpaceID(spaceID) + if err != nil { + // TODO: log? error? continue } - return &provider.ListStorageSpacesResponse{ - Status: status.NewStatusFromErrType(ctx, "error listing space", errors[i]), - }, nil - } - for j := range spacesFromProviders[i] { - uniqueSpaces[spacesFromProviders[i][j].Id.OpaqueId] = spacesFromProviders[i][j] + spaces = append(spaces, &provider.StorageSpace{ + Id: &provider.StorageSpaceId{OpaqueId: spaceID}, + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte(spacePath), + }, + }, + }, + Root: &provider.ResourceId{ + StorageId: storageid, + OpaqueId: opaqueid, + }, + }) } } - spaces := make([]*provider.StorageSpace, 0, len(uniqueSpaces)) - for spaceID := range uniqueSpaces { - spaces = append(spaces, uniqueSpaces[spaceID]) - } - if len(spaces) == 0 { - return &provider.ListStorageSpacesResponse{ - Status: status.NewNotFound(ctx, "space not found"), - }, nil - } return &provider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), @@ -303,23 +288,6 @@ func (s *svc) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSp }, nil } -func (s *svc) listStorageSpacesOnProvider(ctx context.Context, req *provider.ListStorageSpacesRequest, res *[]*provider.StorageSpace, p *registry.ProviderInfo, e *error, wg *sync.WaitGroup) { - defer wg.Done() - c, err := s.getStorageProviderClient(ctx, p) - if err != nil { - *e = errors.Wrap(err, "error connecting to storage provider="+p.Address) - return - } - - r, err := c.ListStorageSpaces(ctx, req) - if err != nil { - *e = errors.Wrap(err, "gateway: error calling ListStorageSpaces") - return - } - - *res = r.StorageSpaces -} - func (s *svc) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { log := appctx.GetLogger(ctx) // TODO: needs to be fixed @@ -552,22 +520,34 @@ func (s *svc) InitiateFileUpload(ctx context.Context, req *provider.InitiateFile } func (s *svc) GetPath(ctx context.Context, req *provider.GetPathRequest) (*provider.GetPathResponse, error) { - statReq := &provider.StatRequest{Ref: &provider.Reference{ResourceId: req.ResourceId}} - statRes, err := s.Stat(ctx, statReq) + c, p, err := s.find(ctx, &provider.Reference{ResourceId: req.ResourceId}) + if err != nil { + return &provider.GetPathResponse{ + Status: status.NewStatusFromErrType(ctx, "getpath ref="+req.String(), err), + }, nil + } + + mountPath := "" + for _, spacePath := range decodeSpacePaths(p) { + mountPath = spacePath + break // TODO can there be more than one space for a path? + } + + res, err := c.GetPath(ctx, req) if err != nil { - err = errors.Wrap(err, "gateway: error stating ref:"+statReq.Ref.String()) + err = errors.Wrap(err, "gateway: error getting path:"+req.String()) return nil, err } - if statRes.Status.Code != rpc.Code_CODE_OK { + if res.Status.Code != rpc.Code_CODE_OK { return &provider.GetPathResponse{ - Status: statRes.Status, + Status: res.Status, }, nil } return &provider.GetPathResponse{ - Status: statRes.Status, - Path: statRes.GetInfo().GetPath(), + Status: res.Status, + Path: filepath.Join(mountPath, res.GetPath()), }, nil } @@ -639,10 +619,6 @@ func (s *svc) Move(ctx context.Context, req *provider.MoveRequest) (*provider.Mo var sourceProviderInfo, destinationProviderInfo *registry.ProviderInfo var err error - rename := utils.IsAbsolutePathReference(req.Source) && - utils.IsAbsolutePathReference(req.Destination) && - filepath.Dir(req.Source.Path) == filepath.Dir(req.Destination.Path) - c, sourceProviderInfo, req.Source, err = s.findAndUnwrap(ctx, req.Source) if err != nil { return &provider.MoveResponse{ @@ -652,7 +628,7 @@ func (s *svc) Move(ctx context.Context, req *provider.MoveRequest) (*provider.Mo // do we try to rename the root of a mountpoint? // TODO how do we determine if the destination resides on the same storage space? - if rename && req.Source.Path == "." { + if req.Source.Path == "." { req.Destination.ResourceId = req.Source.ResourceId req.Destination.Path = utils.MakeRelativePath(filepath.Base(req.Destination.Path)) } else { @@ -737,7 +713,7 @@ func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.St requestPath := req.Ref.Path // find the providers - providerInfos, err := s.findProviders(ctx, req.Ref) + providerInfos, err := s.findSpaces(ctx, req.Ref) if err != nil { // we have no provider -> not found return &provider.StatResponse{ @@ -879,7 +855,7 @@ func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequ requestPath := req.Ref.Path // find the providers - providerInfos, err := s.findProviders(ctx, req.Ref) + providerInfos, err := s.findSpaces(ctx, req.Ref) if err != nil { // we have no provider -> not found return &provider.ListContainerResponse{ @@ -1102,8 +1078,7 @@ func (s *svc) ListRecycleStream(_ *provider.ListRecycleStreamRequest, _ gateway. // TODO use the ListRecycleRequest.Ref to only list the trash of a specific storage func (s *svc) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) (*provider.ListRecycleResponse, error) { - requestPath := req.Ref.Path - providerInfos, err := s.findProviders(ctx, req.Ref) + providerInfos, err := s.findSpaces(ctx, req.Ref) if err != nil { return &provider.ListRecycleResponse{ Status: status.NewStatusFromErrType(ctx, "ListRecycle ref="+req.Ref.String(), err), @@ -1148,28 +1123,26 @@ func (s *svc) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) // we can ignore spaces below the mount point // -> only match exact references - if requestPath == mountPath { - - res, err := c.ListRecycle(ctx, &provider.ListRecycleRequest{ - Opaque: req.Opaque, - FromTs: req.FromTs, - ToTs: req.ToTs, - Ref: providerRef, - Key: req.Key, - }) - if err != nil { - return nil, errors.Wrap(err, "gateway: error calling ListRecycle") - } - if utils.IsAbsoluteReference(req.Ref) { - for j := range res.RecycleItems { - // wrap(res.RecycleItems[j].Ref, p) only handles ResourceInfo - res.RecycleItems[j].Ref.Path = path.Join(mountPath, res.RecycleItems[j].Ref.Path) - } - } + res, err := c.ListRecycle(ctx, &provider.ListRecycleRequest{ + Opaque: req.Opaque, + FromTs: req.FromTs, + ToTs: req.ToTs, + Ref: providerRef, + Key: req.Key, + }) + if err != nil { + return nil, errors.Wrap(err, "gateway: error calling ListRecycle") + } - return res, nil + if utils.IsAbsoluteReference(req.Ref) { + for j := range res.RecycleItems { + // wrap(res.RecycleItems[j].Ref, p) only handles ResourceInfo + res.RecycleItems[j].Ref.Path = path.Join(mountPath, res.RecycleItems[j].Ref.Path) + } } + + return res, nil } } @@ -1181,7 +1154,7 @@ func (s *svc) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) func (s *svc) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecycleItemRequest) (*provider.RestoreRecycleItemResponse, error) { // requestPath := req.Ref.Path - providerInfos, err := s.findProviders(ctx, req.Ref) + providerInfos, err := s.findSpaces(ctx, req.Ref) if err != nil { return &provider.RestoreRecycleItemResponse{ Status: status.NewStatusFromErrType(ctx, "RestoreRecycleItem source ref="+req.Ref.String(), err), @@ -1224,7 +1197,7 @@ func (s *svc) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecyc } // find destination - dstProviderInfos, err := s.findProviders(ctx, req.RestoreRef) + dstProviderInfos, err := s.findSpaces(ctx, req.RestoreRef) if err != nil { return &provider.RestoreRecycleItemResponse{ Status: status.NewStatusFromErrType(ctx, "RestoreRecycleItem source ref="+req.Ref.String(), err), @@ -1257,34 +1230,6 @@ func (s *svc) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecyc dstProvider = providerInfos[i] break } - /* - if utils.IsAbsolutePathReference(req.RestoreRef) { - // find deepest mount - // if iteration path is longer than current path && iteration path is shorter or exact dst path - if dstProvider == nil || ((len(dstProviderInfos[i].ProviderPath) > len(dstProvider.ProviderPath)) && (len(dstProviderInfos[i].ProviderPath) <= len(req.RestoreRef.Path))) { - dstProvider = dstProviderInfos[i] - r, mountPath, root - if dstRef, err = unwrap(req.RestoreRef, dstProvider.ProviderPath); err != nil { - return nil, err - } - dstRef.Path = utils.MakeRelativePath(dstRef.Path) - parts := strings.SplitN(dstProvider.ProviderId, "!", 2) - if len(parts) != 2 { - return nil, errtypes.BadRequest("gateway: invalid provider id, expected ! format, got " + dstProviderInfos[i].ProviderId) - } - dstRef.ResourceId = &provider.ResourceId{StorageId: parts[0], OpaqueId: parts[1]} - } - } else { - // TODO implement other cases - return &provider.RestoreRecycleItemResponse{ - Status: &rpc.Status{ - Code: rpc.Code_CODE_UNIMPLEMENTED, - Message: "RestoreRecycleItem not yet implementad ref=" + req.RestoreRef.String(), - }, - }, nil - - } - */ } if dstProvider == nil || dstRef == nil { @@ -1372,7 +1317,7 @@ func (s *svc) findByPath(ctx context.Context, path string) (provider.ProviderAPI // - contains the provider path, which is the mount point of the provider // - may contain a list of storage spaces with their id and space path func (s *svc) find(ctx context.Context, ref *provider.Reference) (provider.ProviderAPIClient, *registry.ProviderInfo, error) { - p, err := s.findProviders(ctx, ref) + p, err := s.findSpaces(ctx, ref) if err != nil { return nil, nil, err } @@ -1431,23 +1376,27 @@ func (s *svc) getStorageRegistryClient(_ context.Context, address string) (regis return s.cache.StorageRegistryClient(c), nil } -/* -func userKey(ctx context.Context) string { - u := ctxpkg.ContextMustGetUser(ctx) - sb := strings.Builder{} - if u.Id != nil { - sb.WriteString(u.Id.OpaqueId) - sb.WriteString("@") - sb.WriteString(u.Id.Idp) - } else { - // fall back to username - sb.WriteString(u.Username) - } - return sb.String() -} -*/ +// func (s *svc) findMountPoint(ctx context.Context, id *provider.ResourceId) ([]*registry.ProviderInfo, error) { +// // TODO can we use a provider cache for mount points? +// if id == nil { +// return nil, errtypes.BadRequest("invalid reference, at least path or id must be set") +// } + +// filters := map[string]string{ +// "type": "mountpoint", +// "storage_id": id.StorageId, +// "opaque_id": id.OpaqueId, +// } + +// listReq := ®istry.ListStorageProvidersRequest{ +// Opaque: &typesv1beta1.Opaque{}, +// } +// sdk.EncodeOpaqueMap(listReq.Opaque, filters) -func (s *svc) findProviders(ctx context.Context, ref *provider.Reference) ([]*registry.ProviderInfo, error) { +// return s.findProvider(ctx, listReq) +// } + +func (s *svc) findSpaces(ctx context.Context, ref *provider.Reference) ([]*registry.ProviderInfo, error) { switch { case ref == nil: return nil, errtypes.BadRequest("missing reference") @@ -1459,12 +1408,6 @@ func (s *svc) findProviders(ctx context.Context, ref *provider.Reference) ([]*re return nil, errtypes.BadRequest("invalid reference, at least path or id must be set") } - // lookup - c, err := s.getStorageRegistryClient(ctx, s.c.StorageRegistryEndpoint) - if err != nil { - return nil, errors.Wrap(err, "gateway: error getting storage registry client") - } - filters := map[string]string{ "path": ref.Path, } @@ -1477,6 +1420,16 @@ func (s *svc) findProviders(ctx context.Context, ref *provider.Reference) ([]*re Opaque: &typesv1beta1.Opaque{}, } sdk.EncodeOpaqueMap(listReq.Opaque, filters) + + return s.findProvider(ctx, listReq) +} + +func (s *svc) findProvider(ctx context.Context, listReq *registry.ListStorageProvidersRequest) ([]*registry.ProviderInfo, error) { + // lookup + c, err := pool.GetStorageRegistryClient(s.c.StorageRegistryEndpoint) + if err != nil { + return nil, errors.Wrap(err, "gateway: error getting storage registry client") + } res, err := c.ListStorageProviders(ctx, listReq) if err != nil { return nil, errors.Wrap(err, "gateway: error calling ListStorageProviders") @@ -1486,13 +1439,13 @@ func (s *svc) findProviders(ctx context.Context, ref *provider.Reference) ([]*re switch res.Status.Code { case rpc.Code_CODE_NOT_FOUND: // TODO use tombstone cache item? - return nil, errtypes.NotFound("gateway: storage provider not found for reference:" + ref.String()) + return nil, errtypes.NotFound("gateway: storage provider not found for reference:" + listReq.String()) case rpc.Code_CODE_PERMISSION_DENIED: - return nil, errtypes.PermissionDenied("gateway: " + res.Status.Message + " for " + ref.String() + " with code " + res.Status.Code.String()) + return nil, errtypes.PermissionDenied("gateway: " + res.Status.Message + " for " + listReq.String() + " with code " + res.Status.Code.String()) case rpc.Code_CODE_INVALID_ARGUMENT, rpc.Code_CODE_FAILED_PRECONDITION, rpc.Code_CODE_OUT_OF_RANGE: - return nil, errtypes.BadRequest("gateway: " + res.Status.Message + " for " + ref.String() + " with code " + res.Status.Code.String()) + return nil, errtypes.BadRequest("gateway: " + res.Status.Message + " for " + listReq.String() + " with code " + res.Status.Code.String()) case rpc.Code_CODE_UNIMPLEMENTED: - return nil, errtypes.NotSupported("gateway: " + res.Status.Message + " for " + ref.String() + " with code " + res.Status.Code.String()) + return nil, errtypes.NotSupported("gateway: " + res.Status.Message + " for " + listReq.String() + " with code " + res.Status.Code.String()) default: return nil, status.NewErrorFromCode(res.Status.Code, "gateway") } diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index 5d959c5c09..67d9625618 100644 --- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -143,7 +143,7 @@ func (s *service) InitiateFileDownload(ctx context.Context, req *provider.Initia func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider.Reference) (*provider.Reference, string, *link.PublicShare, *rpc.Status, error) { log := appctx.GetLogger(ctx) - tkn, relativePath, err := s.unwrap(ctx, ref) + tkn, opaqueid, relativePath, err := s.unwrap(ctx, ref) if err != nil { return nil, "", nil, nil, err } @@ -169,9 +169,16 @@ func (s *service) translatePublicRefToCS3Ref(ctx context.Context, ref *provider. // path = utils.MakeRelativePath(relativePath) } + if opaqueid == "" { + opaqueid = shareInfo.Id.OpaqueId + } + cs3Ref := &provider.Reference{ - ResourceId: shareInfo.Id, - Path: path, + ResourceId: &provider.ResourceId{ + StorageId: shareInfo.Id.StorageId, + OpaqueId: opaqueid, + }, + Path: path, } log.Debug(). @@ -326,7 +333,12 @@ func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStora for _, f := range req.Filters { switch f.Type { case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: - if f.GetSpaceType() != SpaceTypePublic { + switch f.GetSpaceType() { + case SpaceTypePublic: + continue + case "mountpoint", "+mountpoint": + continue + default: return &provider.ListStorageSpacesResponse{ Status: &rpc.Status{Code: rpc.Code_CODE_OK}, }, nil @@ -537,7 +549,7 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide Value: attribute.StringValue(req.Ref.String()), }) - tkn, relativePath, err := s.unwrap(ctx, req.Ref) + tkn, opaqueid, relativePath, err := s.unwrap(ctx, req.Ref) if err != nil { return nil, err } @@ -565,9 +577,16 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide return res, nil } + if opaqueid == "" { + opaqueid = share.ResourceId.OpaqueId + } + ref := &provider.Reference{ - ResourceId: share.ResourceId, - Path: utils.MakeRelativePath(relativePath), + ResourceId: &provider.ResourceId{ + StorageId: share.ResourceId.StorageId, + OpaqueId: opaqueid, + }, + Path: utils.MakeRelativePath(relativePath), } statResponse, err := s.gateway.Stat(ctx, &provider.StatRequest{Ref: ref}) @@ -628,7 +647,7 @@ func (s *service) ListContainerStream(req *provider.ListContainerStreamRequest, } func (s *service) ListContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { - tkn, relativePath, err := s.unwrap(ctx, req.Ref) + tkn, opaqueid, relativePath, err := s.unwrap(ctx, req.Ref) if err != nil { return nil, err } @@ -648,11 +667,18 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer }, nil } + if opaqueid == "" { + opaqueid = shareInfo.Id.OpaqueId + } + listContainerR, err := s.gateway.ListContainer( ctx, &provider.ListContainerRequest{ Ref: &provider.Reference{ - ResourceId: shareInfo.Id, + ResourceId: &provider.ResourceId{ + StorageId: shareInfo.Id.StorageId, + OpaqueId: opaqueid, + }, // prefix relative path with './' to make it a CS3 relative path Path: utils.MakeRelativePath(relativePath), }, @@ -696,34 +722,38 @@ func filterPermissions(l *provider.ResourcePermissions, r *provider.ResourcePerm l.UpdateGrant = l.UpdateGrant && r.UpdateGrant } -func (s *service) unwrap(ctx context.Context, ref *provider.Reference) (token string, relativePath string, err error) { +func (s *service) unwrap(ctx context.Context, ref *provider.Reference) (token, opaqueid, relativePath string, err error) { isValidReference := func(r *provider.Reference) bool { return r != nil && r.ResourceId != nil && r.ResourceId.StorageId != "" && r.ResourceId.OpaqueId != "" } switch { case !isValidReference(ref): - return "", "", errtypes.BadRequest("resourceid required, got " + ref.String()) - case ref.Path == "": - // id based stat - parts := strings.SplitN(ref.ResourceId.OpaqueId, "/", 2) - if len(parts) < 2 { - return "", "", errtypes.BadRequest("OpaqueId needs to have form {token}/{shared node id}: got " + ref.String()) - } - token = parts[0] - relativePath = "" - default: + return "", "", "", errtypes.BadRequest("resourceid required, got " + ref.String()) + case utils.ResourceIDEqual(ref.ResourceId, &provider.ResourceId{ + StorageId: utils.PublicStorageProviderID, + OpaqueId: utils.PublicStorageProviderID, + }): // path has the form "./{token}/relative/path/" parts := strings.SplitN(ref.Path, "/", 3) if len(parts) < 2 { // FIXME ... we should expose every public link as a storage space // but do we need to list them then? - return "", "", errtypes.BadRequest("need at least token in ref: got " + ref.String()) + return "", "", "", errtypes.BadRequest("need at least token in ref: got " + ref.String()) } token = parts[1] if len(parts) > 2 { relativePath = parts[2] } + default: + // id based stat + parts := strings.SplitN(ref.ResourceId.OpaqueId, "/", 2) + if len(parts) < 2 { + return "", "", "", errtypes.BadRequest("OpaqueId needs to have form {token}/{shared node id}: got " + ref.String()) + } + token = parts[0] + opaqueid = parts[1] + relativePath = ref.Path } return diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 00d420a628..a82156732a 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -34,6 +34,7 @@ import ( 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" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc" @@ -303,17 +304,28 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt // should be found. func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) { - + spaceTypes := []string{} + res := &provider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + } + var fetchShares bool + appendTypes := []string{} + var spaceID *provider.ResourceId for _, f := range req.Filters { switch f.Type { case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: - if f.GetSpaceType() != "share" { - return &provider.ListStorageSpacesResponse{ - Status: &rpc.Status{Code: rpc.Code_CODE_OK}, - }, nil + spaceType := f.GetSpaceType() + // do we need to fetch the shares? + if spaceType == "mountpoint" || spaceType == "grant" { + spaceTypes = append(spaceTypes, spaceType) + fetchShares = true + } + if spaceType == "+mountpoint" || spaceType == "+grant" { + appendTypes = append(appendTypes, strings.TrimPrefix(spaceType, "+")) + fetchShares = true } case provider.ListStorageSpacesRequest_Filter_TYPE_ID: - spaceid, _, err := utils.SplitStorageSpaceID(f.GetId().OpaqueId) + spaceid, shareid, err := utils.SplitStorageSpaceID(f.GetId().OpaqueId) if err != nil { continue } @@ -323,60 +335,117 @@ func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStora Status: &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}, }, nil } + + spaceID = &provider.ResourceId{StorageId: spaceid, OpaqueId: shareid} } } - 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") + if len(spaceTypes) == 0 { + spaceTypes = []string{"virtual", "mountpoint"} + fetchShares = true } - res := &provider.ListStorageSpacesResponse{} - for i := range lsRes.Shares { + spaceTypes = append(spaceTypes, appendTypes...) - if lsRes.Shares[i].MountPoint == nil { - // the gateway needs a name to use as the path segment in the dir listing - continue + var receivedShares []*collaboration.ReceivedShare + if fetchShares { + lsRes, err := s.sharesProviderClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ + // FIXME filter by received shares for resource id - listing all shares is tooo expensive! + }) + if err != nil { + return nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest") } - space := &provider.StorageSpace{ - Id: &provider.StorageSpaceId{ - // Do we need a unique spaceid for every share? - // we are going to use the opaque id of the resource as the spaceid - OpaqueId: "a0ca6a90-a365-4782-871e-d44447bbc668!" + lsRes.Shares[i].Share.ResourceId.OpaqueId, - }, - SpaceType: "share", - Owner: &userv1beta1.User{Id: lsRes.Shares[i].Share.Owner}, - // return the actual resource id - //Root: lsRes.Shares[i].Share.ResourceId, - Root: &provider.ResourceId{ - StorageId: utils.ShareStorageProviderID, - OpaqueId: lsRes.Shares[i].Share.ResourceId.OpaqueId, - }, - // TODO in the future the spaces registry will handle the alias for share spaces. - // for now use the name - Name: lsRes.Shares[i].MountPoint.Path, + if lsRes.Status.Code != rpc.Code_CODE_OK { + return nil, fmt.Errorf("sharesstorageprovider: error calling ListReceivedSharesRequest") } - - // TODO the gateway needs to stat if it needs the mtime - /* - info, st, err := s.statResource(ctx, lsRes.Shares[i].Share.ResourceId, "") - if err != nil { - return nil, err + receivedShares = lsRes.Shares + } + for i := range spaceTypes { + switch spaceTypes[i] { + case "virtual": + virtualRootID := &provider.ResourceId{ + StorageId: utils.ShareStorageProviderID, + OpaqueId: utils.ShareStorageProviderID, } - if st.Code != rpc.Code_CODE_OK { - continue + if spaceID == nil || utils.ResourceIDEqual(virtualRootID, spaceID) { + + space := &provider.StorageSpace{ + Id: &provider.StorageSpaceId{ + OpaqueId: virtualRootID.StorageId + "!" + virtualRootID.OpaqueId, + }, + SpaceType: "virtual", + //Owner: &userv1beta1.User{Id: receivedShare.Share.Owner}, // FIXME actually, the mount point belongs to the recipient + // the sharesstorageprovider keeps track of mount points + Root: virtualRootID, + Name: "Shares Jail", + } + res.StorageSpaces = append(res.StorageSpaces, space) } - space.Mtime = info.Mtime - */ - - // what if we don't have a name? - res.StorageSpaces = append(res.StorageSpaces, space) + case "grant": + for _, receivedShare := range receivedShares { + root := receivedShare.Share.ResourceId + // do we filter by id? + if spaceID != nil && !utils.ResourceIDEqual(spaceID, root) { + // none of our business + continue + } + // we know a grant for this resource + space := &provider.StorageSpace{ + Id: &provider.StorageSpaceId{ + OpaqueId: root.StorageId + "!" + root.OpaqueId, + }, + SpaceType: "grant", + Owner: &userv1beta1.User{Id: receivedShare.Share.Owner}, + // the sharesstorageprovider keeps track of mount points + Root: root, + } + + res.StorageSpaces = append(res.StorageSpaces, space) + } + case "mountpoint": + for _, receivedShare := range receivedShares { + if receivedShare.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { + continue + } + root := &provider.ResourceId{ + StorageId: utils.ShareStorageProviderID, + OpaqueId: receivedShare.Share.Id.OpaqueId, + //OpaqueId: utils.ShareStorageProviderID, + } + // do we filter by id + if spaceID != nil { + switch { + case utils.ResourceIDEqual(spaceID, root): + // we have a virtual node + case utils.ResourceIDEqual(spaceID, receivedShare.Share.ResourceId): + // we have a mount point + root = receivedShare.Share.ResourceId + default: + // none of our business + continue + } + } + space := &provider.StorageSpace{ + Id: &provider.StorageSpaceId{ + OpaqueId: root.StorageId + "!" + root.OpaqueId, + }, + SpaceType: "mountpoint", + Owner: &userv1beta1.User{Id: receivedShare.Share.Owner}, // FIXME actually, the mount point belongs to the recipient + // the sharesstorageprovider keeps track of mount points + Root: root, + } + + // TODO in the future the spaces registry will handle the alias for share spaces. + // for now use the name from the share to override the name determined by stat + if receivedShare.MountPoint != nil { + space.Name = receivedShare.MountPoint.Path + } + + // what if we don't have a name? + res.StorageSpaces = append(res.StorageSpaces, space) + } + } } - res.Status = status.NewOK(ctx) - return res, nil } @@ -480,7 +549,10 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide len(strings.SplitN(req.Destination.Path, "/", 3)) == 2 { // Change the MountPoint of the share, it has no relative prefix - srcReceivedShare.MountPoint = &provider.Reference{Path: filepath.Base(req.Destination.Path)} + srcReceivedShare.MountPoint = &provider.Reference{ + // FIXME actually it does have a resource id: the one of the sharesstorageprovider + Path: filepath.Base(req.Destination.Path), + } _, err = s.sharesProviderClient.UpdateReceivedShare(ctx, &collaboration.UpdateReceivedShareRequest{ Share: srcReceivedShare, @@ -524,6 +596,34 @@ func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provide } func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { + if isVirtualRoot(req.Ref.ResourceId) && (req.Ref.Path == "" || req.Ref.Path == ".") { + // The root is empty, it is filled by mountpoints + return &provider.StatResponse{ + Status: status.NewOK(ctx), + Info: &provider.ResourceInfo{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "root": { + Decoder: "plain", + Value: []byte(utils.ShareStorageProviderID), + }, + }, + }, + Id: &provider.ResourceId{ + StorageId: utils.ShareStorageProviderID, + OpaqueId: utils.ShareStorageProviderID, + }, + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + Mtime: &typesv1beta1.Timestamp{}, + Path: "/", + MimeType: "httpd/unix-directory", + Size: 0, + PermissionSet: &provider.ResourcePermissions{ + // TODO + }, + }, + }, nil + } receivedShare, rpcStatus, err := s.resolveReference(ctx, req.Ref) appctx.GetLogger(ctx).Debug(). Interface("ref", req.Ref). @@ -538,12 +638,24 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide Status: rpcStatus, }, nil } + if receivedShare.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { + return &provider.StatResponse{ + Status: &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}, + // not mounted yet + }, nil + } + + path := req.Ref.Path + if receivedShare.MountPoint.Path == strings.TrimPrefix(req.Ref.Path, "./") { + path = "." + } + // TODO return reference? return s.gateway.Stat(ctx, &provider.StatRequest{ Opaque: req.Opaque, Ref: &provider.Reference{ ResourceId: receivedShare.Share.ResourceId, - Path: req.Ref.Path, + Path: path, }, ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, }) @@ -553,7 +665,20 @@ func (s *service) ListContainerStream(req *provider.ListContainerStreamRequest, return gstatus.Errorf(codes.Unimplemented, "method not implemented") } +func isVirtualRoot(id *provider.ResourceId) bool { + return utils.ResourceIDEqual(id, &provider.ResourceId{ + StorageId: utils.ShareStorageProviderID, + OpaqueId: utils.ShareStorageProviderID, + }) +} func (s *service) ListContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { + if isVirtualRoot(req.Ref.ResourceId) { + // The root is empty, it is filled by mountpoints + return &provider.ListContainerResponse{ + Status: status.NewOK(ctx), + Infos: []*provider.ResourceInfo{}, + }, nil + } receivedShare, rpcStatus, err := s.resolveReference(ctx, req.Ref) appctx.GetLogger(ctx).Debug(). Interface("ref", req.Ref). @@ -688,18 +813,37 @@ func (s *service) resolveReference(ctx context.Context, ref *provider.Reference) ref.Path = "." } if utils.IsRelativeReference(ref) { + if ref.ResourceId.StorageId != utils.ShareStorageProviderID { + return nil, status.NewNotFound(ctx, "sharesstorageprovider: not found "+ref.String()), nil + } // look up share for this resourceid - lsRes, err := s.sharesProviderClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{}) + lsRes, err := s.sharesProviderClient.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{ + // FIXME filter by received shares for resource id - listing all shares is tooo expensive! + }) if err != nil { return nil, nil, errors.Wrap(err, "sharesstorageprovider: error calling ListReceivedSharesRequest") } if lsRes.Status.Code != rpc.Code_CODE_OK { return nil, nil, fmt.Errorf("sharesstorageprovider: error calling ListReceivedSharesRequest") } - for _, rs := range lsRes.Shares { - // match the opaque id - if rs.Share.ResourceId.OpaqueId == ref.ResourceId.OpaqueId { - return rs, nil, nil + for _, receivedShare := range lsRes.Shares { + if receivedShare.State != collaboration.ShareState_SHARE_STATE_ACCEPTED { + continue + } + root := &provider.ResourceId{ + StorageId: utils.ShareStorageProviderID, + OpaqueId: receivedShare.Share.Id.OpaqueId, + } + + switch { + case utils.ResourceIDEqual(ref.ResourceId, root): + // we have a virtual node + return receivedShare, nil, nil + case utils.ResourceIDEqual(ref.ResourceId, receivedShare.Share.ResourceId): + // we have a mount point + return receivedShare, nil, nil + default: + continue } } return nil, status.NewNotFound(ctx, "sharesstorageprovider: not found "+ref.String()), nil diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go index ef4aa3e6af..cbd1628ca8 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go @@ -46,6 +46,9 @@ var ( BaseShare = &collaboration.ReceivedShare{ State: collaboration.ShareState_SHARE_STATE_ACCEPTED, Share: &collaboration.Share{ + Id: &collaboration.ShareId{ + OpaqueId: "shareid", + }, ResourceId: &sprovider.ResourceId{ StorageId: utils.ShareStorageProviderID, OpaqueId: "shareddir", @@ -57,11 +60,17 @@ var ( }, }, }, + MountPoint: &sprovider.Reference{ + Path: "", + }, } BaseShareTwo = &collaboration.ReceivedShare{ State: collaboration.ShareState_SHARE_STATE_ACCEPTED, Share: &collaboration.Share{ + Id: &collaboration.ShareId{ + OpaqueId: "shareidtwo", + }, ResourceId: &sprovider.ResourceId{ StorageId: utils.ShareStorageProviderID, OpaqueId: "shareddir", @@ -73,12 +82,15 @@ var ( }, }, }, + MountPoint: &sprovider.Reference{ + Path: "", + }, } BaseStatRequest = &sprovider.StatRequest{ Ref: &sprovider.Reference{ ResourceId: &sprovider.ResourceId{ - StorageId: "share1-storageid", + StorageId: utils.ShareStorageProviderID, OpaqueId: "shareddir", }, Path: ".", @@ -88,7 +100,7 @@ var ( BaseListContainerRequest = &sprovider.ListContainerRequest{ Ref: &sprovider.Reference{ ResourceId: &sprovider.ResourceId{ - StorageId: "share1-storageid", + StorageId: utils.ShareStorageProviderID, OpaqueId: "shareddir", }, Path: ".", @@ -126,12 +138,12 @@ var _ = Describe("Sharesstorageprovider", func() { gw.On("Stat", mock.Anything, mock.AnythingOfType("*providerv1beta1.StatRequest")).Return( func(_ context.Context, req *sprovider.StatRequest, _ ...grpc.CallOption) *sprovider.StatResponse { switch req.Ref.GetPath() { - case "./share1-shareddir/share1-subdir": + case "./share1-subdir": return &sprovider.StatResponse{ Status: status.NewOK(context.Background()), Info: &sprovider.ResourceInfo{ Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, - Path: "/share1-shareddir/share1-subdir", + Path: "share1-subdir", Id: &sprovider.ResourceId{ StorageId: "share1-storageid", OpaqueId: "subdir", @@ -142,12 +154,12 @@ var _ = Describe("Sharesstorageprovider", func() { Size: 10, }, } - case "./share1-shareddir/share1-subdir/share1-subdir-file": + case "./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", + Path: "share1-subdir-file", Id: &sprovider.ResourceId{ StorageId: "share1-storageid", OpaqueId: "file", @@ -158,12 +170,12 @@ var _ = Describe("Sharesstorageprovider", func() { Size: 20, }, } - default: + case ".": return &sprovider.StatResponse{ Status: status.NewOK(context.Background()), Info: &sprovider.ResourceInfo{ Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, - Path: "/share1-shareddir", + Path: "share1-shareddir", Id: &sprovider.ResourceId{ StorageId: "share1-storageid", OpaqueId: "shareddir", @@ -174,6 +186,8 @@ var _ = Describe("Sharesstorageprovider", func() { Size: 100, }, } + default: + return nil } }, nil) @@ -191,7 +205,7 @@ var _ = Describe("Sharesstorageprovider", func() { case ".": resp.Infos = append(resp.Infos, &sprovider.ResourceInfo{ Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, - Path: "/share1-shareddir/share1-subdir", + Path: "share1-shareddir/share1-subdir", Id: &sprovider.ResourceId{ StorageId: "share1-storageid", OpaqueId: "subdir", @@ -201,7 +215,7 @@ var _ = Describe("Sharesstorageprovider", func() { case "./share1-subdir": resp.Infos = append(resp.Infos, &sprovider.ResourceInfo{ Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, - Path: "/share1-shareddir/share1-subdir/share1-subdir-file", + Path: "share1-shareddir/share1-subdir/share1-subdir-file", Id: &sprovider.ResourceId{ StorageId: "share1-storageid", OpaqueId: "file", @@ -292,7 +306,7 @@ var _ = Describe("Sharesstorageprovider", func() { Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) Expect(res.Info).ToNot(BeNil()) Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER)) - Expect(res.Info.Path).To(Equal("/share1-shareddir")) + Expect(res.Info.Path).To(Equal("share1-shareddir")) // Expect(res.Info.Size).To(Equal(uint64(300))) TODO: Why 300? Expect(res.Info.Size).To(Equal(uint64(100))) }) @@ -306,7 +320,7 @@ var _ = Describe("Sharesstorageprovider", func() { Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) Expect(res.Info).ToNot(BeNil()) Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER)) - Expect(res.Info.Path).To(Equal("/share1-shareddir")) + Expect(res.Info.Path).To(Equal("share1-shareddir")) Expect(res.Info.Size).To(Equal(uint64(100))) }) @@ -354,34 +368,34 @@ var _ = Describe("Sharesstorageprovider", func() { Expect(res.Info).ToNot(BeNil()) Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER)) - Expect(res.Info.Path).To(Equal("/share1-shareddir")) + Expect(res.Info.Path).To(Equal("share1-shareddir")) Expect(res.Info.PermissionSet.Stat).To(BeTrue()) // Expect(res.Info.PermissionSet.ListContainer).To(BeTrue()) // TODO reenable }) It("stats a subfolder in a share", func() { statReq := BaseStatRequest - statReq.Ref.Path = "./share1-shareddir/share1-subdir" + statReq.Ref.Path = "./share1-subdir" res, err := s.Stat(ctx, statReq) Expect(err).ToNot(HaveOccurred()) Expect(res).ToNot(BeNil()) Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) Expect(res.Info).ToNot(BeNil()) Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_CONTAINER)) - Expect(res.Info.Path).To(Equal("/share1-shareddir/share1-subdir")) + Expect(res.Info.Path).To(Equal("share1-subdir")) Expect(res.Info.Size).To(Equal(uint64(10))) }) It("stats a shared file", func() { statReq := BaseStatRequest - statReq.Ref.Path = "./share1-shareddir/share1-subdir/share1-subdir-file" + statReq.Ref.Path = "./share1-subdir/share1-subdir-file" res, err := s.Stat(ctx, statReq) Expect(err).ToNot(HaveOccurred()) Expect(res).ToNot(BeNil()) Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK)) Expect(res.Info).ToNot(BeNil()) Expect(res.Info.Type).To(Equal(sprovider.ResourceType_RESOURCE_TYPE_FILE)) - Expect(res.Info.Path).To(Equal("/share1-shareddir/share1-subdir/share1-subdir-file")) + Expect(res.Info.Path).To(Equal("share1-subdir-file")) Expect(res.Info.Size).To(Equal(uint64(20))) }) }) @@ -396,7 +410,7 @@ var _ = Describe("Sharesstorageprovider", func() { Expect(len(res.Infos)).To(Equal(1)) entry := res.Infos[0] - Expect(entry.Path).To(Equal("/share1-shareddir/share1-subdir")) + Expect(entry.Path).To(Equal("share1-shareddir/share1-subdir")) }) It("traverses into subfolders of specific shares", func() { @@ -409,7 +423,7 @@ var _ = Describe("Sharesstorageprovider", func() { Expect(len(res.Infos)).To(Equal(1)) entry := res.Infos[0] - Expect(entry.Path).To(Equal("/share1-shareddir/share1-subdir/share1-subdir-file")) + Expect(entry.Path).To(Equal("share1-shareddir/share1-subdir/share1-subdir-file")) }) }) diff --git a/internal/http/services/owncloud/ocdav/copy.go b/internal/http/services/owncloud/ocdav/copy.go index 54e2159550..350fb50a2c 100644 --- a/internal/http/services/owncloud/ocdav/copy.go +++ b/internal/http/services/owncloud/ocdav/copy.go @@ -23,6 +23,7 @@ import ( "fmt" "net/http" "path" + "path/filepath" "strconv" "strings" @@ -40,14 +41,13 @@ import ( ) type copy struct { + source *provider.Reference sourceInfo *provider.ResourceInfo destination *provider.Reference depth string successCode int } -type intermediateDirRefFunc func() (*provider.Reference, *rpc.Status, error) - func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) { ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), "copy") defer span.End() @@ -68,18 +68,28 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger() - srcRef := &provider.Reference{Path: src} - - // check dst exists - dstRef := &provider.Reference{Path: dst} - - intermediateDirRefFunc := func() (*provider.Reference, *rpc.Status, error) { - intermediateDir := path.Dir(dst) - ref := &provider.Reference{Path: intermediateDir} - return ref, &rpc.Status{Code: rpc.Code_CODE_OK}, nil + srcSpace, status, err := s.lookUpStorageSpaceForPath(ctx, src) + if err != nil { + sublog.Error().Err(err).Str("path", src).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return + } + dstSpace, status, err := s.lookUpStorageSpaceForPath(ctx, dst) + if err != nil { + sublog.Error().Err(err).Str("path", dst).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return } - cp := s.prepareCopy(ctx, w, r, srcRef, dstRef, intermediateDirRefFunc, &sublog) + cp := s.prepareCopy(ctx, w, r, makeRelativeReference(srcSpace, src, false), makeRelativeReference(dstSpace, dst, false), &sublog) if cp == nil { return } @@ -132,7 +142,7 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie // descend for children listReq := &provider.ListContainerRequest{ - Ref: &provider.Reference{Path: cp.sourceInfo.Path}, + Ref: cp.source, } res, err := client.ListContainer(ctx, listReq) if err != nil { @@ -144,8 +154,16 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie } for i := range res.Infos { - childDst := &provider.Reference{Path: path.Join(cp.destination.Path, path.Base(res.Infos[i].Path))} - err := s.executePathCopy(ctx, client, w, r, ©{sourceInfo: res.Infos[i], destination: childDst, depth: cp.depth, successCode: cp.successCode}) + child := filepath.Base(res.Infos[i].Path) + src := &provider.Reference{ + ResourceId: cp.source.ResourceId, + Path: utils.MakeRelativePath(filepath.Join(cp.source.Path, child)), + } + childDst := &provider.Reference{ + ResourceId: cp.destination.ResourceId, + Path: utils.MakeRelativePath(filepath.Join(cp.destination.Path, child)), + } + err := s.executePathCopy(ctx, client, w, r, ©{source: src, sourceInfo: res.Infos[i], destination: childDst, depth: cp.depth, successCode: cp.successCode}) if err != nil { return err } @@ -157,7 +175,7 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie // 1. get download url dReq := &provider.InitiateFileDownloadRequest{ - Ref: &provider.Reference{Path: cp.sourceInfo.Path}, + Ref: cp.source, } dRes, err := client.InitiateFileDownload(ctx, dReq) @@ -269,7 +287,7 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Str("destination", dst).Logger() // retrieve a specific storage space - srcRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + srcRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -284,7 +302,7 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s dstSpaceID, dstRelPath := router.ShiftPath(dst) // retrieve a specific storage space - dstRef, status, err := s.lookUpStorageSpaceReference(ctx, dstSpaceID, dstRelPath) + dstRef, status, err := s.lookUpStorageSpaceReference(ctx, dstSpaceID, dstRelPath, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -296,12 +314,7 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s return } - intermediateDirRefFunc := func() (*provider.Reference, *rpc.Status, error) { - intermediateDir := path.Dir(dstRelPath) - return s.lookUpStorageSpaceReference(ctx, dstSpaceID, intermediateDir) - } - - cp := s.prepareCopy(ctx, w, r, srcRef, dstRef, intermediateDirRefFunc, &sublog) + cp := s.prepareCopy(ctx, w, r, srcRef, dstRef, &sublog) if cp == nil { return } @@ -474,7 +487,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie return nil } -func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, srcRef, dstRef *provider.Reference, intermediateDirRef intermediateDirRefFunc, log *zerolog.Logger) *copy { +func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, srcRef, dstRef *provider.Reference, log *zerolog.Logger) *copy { overwrite, err := extractOverwrite(w, r) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -570,20 +583,13 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re HandleErrorStatus(log, w, delRes.Status) return nil } - } else { + } else if p := path.Dir(dstRef.Path); p != "" { // check if an intermediate path / the parent exists - intermediateRef, status, err := intermediateDirRef() - if err != nil { - log.Error().Err(err).Msg("error sending a grpc request") - w.WriteHeader(http.StatusInternalServerError) - return nil - } - - if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(log, w, status) - return nil + pRef := &provider.Reference{ + ResourceId: dstRef.ResourceId, + Path: utils.MakeRelativePath(p), } - intStatReq := &provider.StatRequest{Ref: intermediateRef} + intStatReq := &provider.StatRequest{Ref: pRef} intStatRes, err := client.Stat(ctx, intStatReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") @@ -593,17 +599,17 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re if intStatRes.Status.Code != rpc.Code_CODE_OK { if intStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { // 409 if intermediate dir is missing, see https://tools.ietf.org/html/rfc4918#section-9.8.5 - log.Debug().Interface("parent", intermediateRef).Interface("status", intStatRes.Status).Msg("conflict") + log.Debug().Interface("parent", pRef).Interface("status", intStatRes.Status).Msg("conflict") w.WriteHeader(http.StatusConflict) } else { - HandleErrorStatus(log, w, srcStatRes.Status) + HandleErrorStatus(log, w, intStatRes.Status) } return nil } // TODO what if intermediate is a file? } - return ©{sourceInfo: srcStatRes.Info, depth: depth, successCode: successCode, destination: dstRef} + return ©{source: srcRef, sourceInfo: srcStatRes.Info, depth: depth, successCode: successCode, destination: dstRef} } func extractOverwrite(w http.ResponseWriter, r *http.Request) (string, error) { diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index 65af86f3a4..a830c399b8 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -33,6 +33,7 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp/router" + "github.com/cs3org/reva/pkg/utils" "google.golang.org/grpc/metadata" ) @@ -279,7 +280,13 @@ func (h *DavHandler) Handler(s *svc) http.Handler { } func getTokenStatInfo(ctx context.Context, client gatewayv1beta1.GatewayAPIClient, token string) (*provider.StatResponse, error) { - return client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{Path: path.Join("/public", token)}}) + return client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ + ResourceId: &provider.ResourceId{ + StorageId: utils.PublicStorageProviderID, + OpaqueId: utils.PublicStorageProviderID, + }, + Path: utils.MakeRelativePath(token), + }}) } func handleBasicAuth(ctx context.Context, c gatewayv1beta1.GatewayAPIClient, token, pw string) (*gatewayv1beta1.AuthenticateResponse, error) { diff --git a/internal/http/services/owncloud/ocdav/delete.go b/internal/http/services/owncloud/ocdav/delete.go index ed8f365539..7e2d954f82 100644 --- a/internal/http/services/owncloud/ocdav/delete.go +++ b/internal/http/services/owncloud/ocdav/delete.go @@ -35,8 +35,18 @@ func (s *svc) handlePathDelete(w http.ResponseWriter, r *http.Request, ns string fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(r.Context()).With().Str("path", fn).Logger() - ref := &provider.Reference{Path: fn} - s.handleDelete(r.Context(), w, r, ref, sublog) + space, status, err := s.lookUpStorageSpaceForPath(r.Context(), fn) + if err != nil { + sublog.Error().Err(err).Msg("error sending a grpc request") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return + } + + s.handleDelete(r.Context(), w, r, makeRelativeReference(space, fn, false), sublog) } func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -100,8 +110,9 @@ func (s *svc) handleSpacesDelete(w http.ResponseWriter, r *http.Request, spaceID defer span.End() sublog := appctx.GetLogger(ctx).With().Logger() + // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/get.go b/internal/http/services/owncloud/ocdav/get.go index 55358955a5..eea0d19673 100644 --- a/internal/http/services/owncloud/ocdav/get.go +++ b/internal/http/services/owncloud/ocdav/get.go @@ -48,8 +48,18 @@ func (s *svc) handlePathGet(w http.ResponseWriter, r *http.Request, ns string) { sublog := appctx.GetLogger(ctx).With().Str("path", fn).Str("svc", "ocdav").Str("handler", "get").Logger() - ref := &provider.Reference{Path: fn} - s.handleGet(ctx, w, r, ref, "spaces", sublog) + space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + if err != nil { + sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return + } + + s.handleGet(ctx, w, r, makeRelativeReference(space, fn, false), "spaces", sublog) } func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, dlProtocol string, log zerolog.Logger) { @@ -166,7 +176,7 @@ func (s *svc) handleSpacesGet(w http.ResponseWriter, r *http.Request, spaceID st sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "get").Logger() // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/head.go b/internal/http/services/owncloud/ocdav/head.go index 7acdca6a8e..3b112c8a37 100644 --- a/internal/http/services/owncloud/ocdav/head.go +++ b/internal/http/services/owncloud/ocdav/head.go @@ -45,9 +45,18 @@ func (s *svc) handlePathHead(w http.ResponseWriter, r *http.Request, ns string) fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() + space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + if err != nil { + sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return + } - ref := &provider.Reference{Path: fn} - s.handleHead(ctx, w, r, ref, sublog) + s.handleHead(ctx, w, r, makeRelativeReference(space, fn, false), sublog) } func (s *svc) handleHead(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -95,7 +104,7 @@ func (s *svc) handleSpacesHead(w http.ResponseWriter, r *http.Request, spaceID s sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/mkcol.go b/internal/http/services/owncloud/ocdav/mkcol.go index 17cd6543fa..1df6812276 100644 --- a/internal/http/services/owncloud/ocdav/mkcol.go +++ b/internal/http/services/owncloud/ocdav/mkcol.go @@ -44,10 +44,20 @@ func (s *svc) handlePathMkcol(w http.ResponseWriter, r *http.Request, ns string) } sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - parentRef := &provider.Reference{Path: path.Dir(fn)} - childRef := &provider.Reference{Path: fn} + parentPath := path.Dir(fn) - s.handleMkcol(ctx, w, r, parentRef, childRef, sublog) + space, status, err := s.lookUpStorageSpaceForPath(ctx, parentPath) + if err != nil { + sublog.Error().Err(err).Str("path", parentPath).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return + } + + s.handleMkcol(ctx, w, r, makeRelativeReference(space, parentPath, false), makeRelativeReference(space, fn, false), sublog) } func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID string) { @@ -56,7 +66,7 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "mkcol").Logger() - parentRef, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, path.Dir(r.URL.Path)) + parentRef, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, path.Dir(r.URL.Path), true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -68,7 +78,7 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID return } - childRef, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + childRef, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/move.go b/internal/http/services/owncloud/ocdav/move.go index 53d3852f8c..cf7f382531 100644 --- a/internal/http/services/owncloud/ocdav/move.go +++ b/internal/http/services/owncloud/ocdav/move.go @@ -30,6 +30,7 @@ import ( "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp/router" rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils" "github.com/cs3org/reva/pkg/utils/resourceid" "github.com/rs/zerolog" ) @@ -55,15 +56,34 @@ func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string) dstPath = path.Join(ns, dstPath) sublog := appctx.GetLogger(ctx).With().Str("src", srcPath).Str("dst", dstPath).Logger() - src := &provider.Reference{Path: srcPath} - dst := &provider.Reference{Path: dstPath} - intermediateDirRefFunc := func() (*provider.Reference, *rpc.Status, error) { - intermediateDir := path.Dir(dstPath) - ref := &provider.Reference{Path: intermediateDir} - return ref, &rpc.Status{Code: rpc.Code_CODE_OK}, nil + srcSpace, status, err := s.lookUpStorageSpaceForPath(ctx, srcPath) + if err != nil { + sublog.Error().Err(err).Str("path", srcPath).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return } - s.handleMove(ctx, w, r, src, dst, intermediateDirRefFunc, sublog) + dstSpace, status, err := s.lookUpStorageSpaceForPath(ctx, dstPath) + if err != nil { + sublog.Error().Err(err).Str("path", srcPath).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return + } + + // FIXME I suck + if dstSpace.Root.OpaqueId == utils.ShareStorageProviderID { + dstSpace.Root = srcSpace.Root + } + + s.handleMove(ctx, w, r, makeRelativeReference(srcSpace, srcPath, false), makeRelativeReference(dstSpace, dstPath, false), sublog) } func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceID string) { @@ -78,7 +98,7 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI sublog := appctx.GetLogger(ctx).With().Str("spaceid", srcSpaceID).Str("path", r.URL.Path).Logger() // retrieve a specific storage space - srcRef, status, err := s.lookUpStorageSpaceReference(ctx, srcSpaceID, r.URL.Path) + srcRef, status, err := s.lookUpStorageSpaceReference(ctx, srcSpaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -93,7 +113,7 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI dstSpaceID, dstRelPath := router.ShiftPath(dst) // retrieve a specific storage space - dstRef, status, err := s.lookUpStorageSpaceReference(ctx, dstSpaceID, dstRelPath) + dstRef, status, err := s.lookUpStorageSpaceReference(ctx, dstSpaceID, dstRelPath, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -105,14 +125,10 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI return } - intermediateDirRefFunc := func() (*provider.Reference, *rpc.Status, error) { - intermediateDir := path.Dir(dstRelPath) - return s.lookUpStorageSpaceReference(ctx, dstSpaceID, intermediateDir) - } - s.handleMove(ctx, w, r, srcRef, dstRef, intermediateDirRefFunc, sublog) + s.handleMove(ctx, w, r, srcRef, dstRef, sublog) } -func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Request, src, dst *provider.Reference, intermediateDirRef intermediateDirRefFunc, log zerolog.Logger) { +func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Request, src, dst *provider.Reference, log zerolog.Logger) { overwrite := r.Header.Get(HeaderOverwrite) log.Debug().Str("overwrite", overwrite).Msg("move") @@ -193,17 +209,10 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req } } else { // check if an intermediate path / the parent exists - dst, status, err := intermediateDirRef() - if err != nil { - log.Error().Err(err).Msg("error sending a grpc request") - w.WriteHeader(http.StatusInternalServerError) - return - } else if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, status) - return - } - - intStatReq := &provider.StatRequest{Ref: dst} + intStatReq := &provider.StatRequest{Ref: &provider.Reference{ + ResourceId: dst.ResourceId, + Path: utils.MakeRelativePath(path.Dir(dst.Path)), + }} intStatRes, err := client.Stat(ctx, intStatReq) if err != nil { log.Error().Err(err).Msg("error sending grpc stat request") diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index a9adf2848d..0e0fe0ea40 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -33,6 +33,7 @@ import ( "strings" "time" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" @@ -42,6 +43,7 @@ import ( "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/publicshare" + "github.com/cs3org/reva/pkg/rhttp/router" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/cs3org/reva/pkg/utils" "github.com/cs3org/reva/pkg/utils/resourceid" @@ -72,7 +74,7 @@ func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns stri span.SetAttributes(attribute.String("component", "ocdav")) - fn := path.Join(ns, r.URL.Path) + fn := path.Join(ns, r.URL.Path) // TODO do we still need to jail if we query the registry about the spaces? sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() @@ -83,14 +85,25 @@ func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns stri return } - ref := &provider.Reference{Path: fn} + // retrieve a specific storage space + spaces, rpcStatus, err := s.lookUpStorageSpacesForPathWithChildren(ctx, fn) + if err != nil { + sublog.Error().Err(err).Msg("error sending a grpc request") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if rpcStatus.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, rpcStatus) + return + } - parentInfo, resourceInfos, ok := s.getResourceInfos(ctx, w, r, pf, ref, false, sublog) + resourceInfos, sendTusHeaders, ok := s.getResourceInfos(ctx, w, r, pf, spaces, fn, false, sublog) if !ok { // getResourceInfos handles responses in case of an error so we can just return here. return } - s.propfindResponse(ctx, w, r, ns, pf, parentInfo, resourceInfos, sublog) + s.propfindResponse(ctx, w, r, ns, pf, sendTusHeaders, resourceInfos, sublog) } func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, spaceID string) { @@ -107,7 +120,7 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space } // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + space, rpcStatus, err := s.lookUpStorageSpaceByID(ctx, spaceID) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -119,29 +132,22 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space return } - parentInfo, resourceInfos, ok := s.getResourceInfos(ctx, w, r, pf, ref, true, sublog) + resourceInfos, sendTusHeaders, ok := s.getResourceInfos(ctx, w, r, pf, []*provider.StorageSpace{space}, r.URL.Path, true, sublog) if !ok { // getResourceInfos handles responses in case of an error so we can just return here. return } - // parentInfo Path is the name but we need / - if r.URL.Path != "" { - parentInfo.Path = r.URL.Path - } else { - parentInfo.Path = "/" - } - // prefix space id to paths for i := range resourceInfos { resourceInfos[i].Path = path.Join("/", spaceID, resourceInfos[i].Path) } - s.propfindResponse(ctx, w, r, "", pf, parentInfo, resourceInfos, sublog) + s.propfindResponse(ctx, w, r, "", pf, sendTusHeaders, resourceInfos, sublog) } -func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, parentInfo *provider.ResourceInfo, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { +func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, sendTusHeaders bool, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { ctx, span := rtrace.Provider.Tracer("ocdav").Start(ctx, "propfind_response") defer span.End() @@ -178,33 +184,40 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht w.Header().Set(HeaderDav, "1, 3, extended-mkcol") w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") - var disableTus bool - // let clients know this collection supports tus.io POST requests to start uploads - if parentInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - if parentInfo.Opaque != nil { - _, disableTus = parentInfo.Opaque.Map["disable_tus"] - } - if !disableTus { - w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderTusVersion, HeaderTusExtension}, ", ")) - w.Header().Set(HeaderTusResumable, "1.0.0") - w.Header().Set(HeaderTusVersion, "1.0.0") - w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") - } + if sendTusHeaders { + w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderTusVersion, HeaderTusExtension}, ", ")) + w.Header().Set(HeaderTusResumable, "1.0.0") + w.Header().Set(HeaderTusVersion, "1.0.0") + w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") } + w.WriteHeader(http.StatusMultiStatus) if _, err := w.Write([]byte(propRes)); err != nil { log.Err(err).Msg("error writing response") } } -func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *http.Request, pf propfindXML, ref *provider.Reference, spacesPropfind bool, log zerolog.Logger) (*provider.ResourceInfo, []*provider.ResourceInfo, bool) { +// TODO this is just a stat -> rename +func (s *svc) statSpace(ctx context.Context, client gateway.GatewayAPIClient, space *provider.StorageSpace, ref *provider.Reference, metadataKeys []string) (*provider.ResourceInfo, *rpc.Status, error) { + req := &provider.StatRequest{ + Ref: ref, + ArbitraryMetadataKeys: metadataKeys, + } + res, err := client.Stat(ctx, req) + if err != nil || res == nil || res.Status == nil || res.Status.Code != rpc.Code_CODE_OK { + return nil, res.Status, err + } + return res.Info, res.Status, nil +} + +func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *http.Request, pf propfindXML, spaces []*provider.StorageSpace, requestPath string, spacesPropfind bool, log zerolog.Logger) ([]*provider.ResourceInfo, bool, bool) { depth := r.Header.Get(HeaderDepth) if depth == "" { depth = "1" } // see https://tools.ietf.org/html/rfc4918#section-9.1 if depth != "0" && depth != "1" && depth != "infinity" { - log.Debug().Str("depth", depth).Msgf("invalid Depth header value") + log.Debug().Str("depth", depth).Msg("invalid Depth header value") w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Invalid Depth header value: %v", depth) b, err := Marshal(exception{ @@ -212,14 +225,14 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht message: m, }) HandleWebdavError(&log, w, b, err) - return nil, nil, false + return nil, false, false } client, err := s.getClient() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) - return nil, nil, false + return nil, false, false } var metadataKeys []string @@ -238,141 +251,220 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht } } } - req := &provider.StatRequest{ - Ref: ref, - ArbitraryMetadataKeys: metadataKeys, - } - res, err := client.Stat(ctx, req) - if err != nil { - log.Error().Err(err).Interface("req", req).Msg("error sending a grpc stat request") - w.WriteHeader(http.StatusInternalServerError) - return nil, nil, false - } else if res.Status.Code != rpc.Code_CODE_OK { - if res.Status.Code == rpc.Code_CODE_NOT_FOUND { - w.WriteHeader(http.StatusNotFound) - m := fmt.Sprintf("Resource %v not found", ref.Path) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&log, w, b, err) - return nil, nil, false + + // we need to stat all spaces to aggregate the root etag, mtime and size + // TODO cache per space (hah, no longer per user + per space!) + var rootInfo *provider.ResourceInfo + var mostRecentChildInfo *provider.ResourceInfo + var aggregatedChildSize uint64 + spaceInfos := make([]*provider.ResourceInfo, 0, len(spaces)) + for _, space := range spaces { + if space.Opaque == nil || space.Opaque.Map == nil || space.Opaque.Map["path"] == nil || space.Opaque.Map["path"].Decoder != "plain" { + continue // not mounted + } + spacePath := string(space.Opaque.Map["path"].Value) + // TODO separate stats to the path or to the children, after statting all children update the mtime/etag + // TODO get mtime, and size from space as well, so we no longer have to stat here? + spaceRef := makeRelativeReference(space, requestPath, spacesPropfind) + info, status, err := s.statSpace(ctx, client, space, spaceRef, metadataKeys) + if err != nil || status.Code != rpc.Code_CODE_OK { + continue } - HandleErrorStatus(&log, w, res.Status) - return nil, nil, false - } - if spacesPropfind { - res.Info.Path = ref.Path - } + // adjust path + if !spacesPropfind { + info.Path = filepath.Join(spacePath, spaceRef.Path) + } else { + info.Path = spaceRef.Path + } - parentInfo := res.Info - resourceInfos := []*provider.ResourceInfo{parentInfo} + spaceInfos = append(spaceInfos, info) - switch { - case !spacesPropfind && parentInfo.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER: - // The propfind is requested for a file that exists - // In this case, we can stat the parent directory and return both - parentPath := path.Dir(parentInfo.Path) - resourceInfos = append(resourceInfos, parentInfo) - parentRes, err := client.Stat(ctx, &provider.StatRequest{ - Ref: &provider.Reference{Path: parentPath}, - ArbitraryMetadataKeys: metadataKeys, - }) - if err != nil { - log.Error().Err(err).Interface("req", req).Msg("error sending a grpc stat request") - w.WriteHeader(http.StatusInternalServerError) - return nil, nil, false - } else if parentRes.Status.Code != rpc.Code_CODE_OK { - if parentRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - w.WriteHeader(http.StatusNotFound) - m := fmt.Sprintf("Resource %v not found", parentPath) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&log, w, b, err) - return nil, nil, false + if rootInfo == nil && requestPath == info.Path || spacesPropfind && requestPath == path.Join("/", info.Path) { + rootInfo = info + } + + // Check if the space is a child of the requested path + if requestPath != spacePath && strings.HasPrefix(spacePath, requestPath) { + // aggregate child metadata + aggregatedChildSize += info.Size + if mostRecentChildInfo == nil { + mostRecentChildInfo = info + continue + } + if mostRecentChildInfo.Mtime == nil || (info.Mtime != nil && utils.TSToUnixNano(info.Mtime) > utils.TSToUnixNano(mostRecentChildInfo.Mtime)) { + mostRecentChildInfo = info } - HandleErrorStatus(&log, w, parentRes.Status) - return nil, nil, false } - parentInfo = parentRes.Info + } - case parentInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == "1": - req := &provider.ListContainerRequest{ - Ref: ref, - ArbitraryMetadataKeys: metadataKeys, + if len(spaceInfos) == 0 || rootInfo == nil { + // TODO if we have children invent node on the fly + w.WriteHeader(http.StatusNotFound) + m := fmt.Sprintf("Resource %v not found", requestPath) + b, err := Marshal(exception{ + code: SabredavNotFound, + message: m, + }) + HandleWebdavError(&log, w, b, err) + return nil, false, false + } + if mostRecentChildInfo != nil { + if rootInfo.Mtime == nil || (mostRecentChildInfo.Mtime != nil && utils.TSToUnixNano(mostRecentChildInfo.Mtime) > utils.TSToUnixNano(rootInfo.Mtime)) { + rootInfo.Mtime = mostRecentChildInfo.Mtime + if mostRecentChildInfo.Etag != "" { + rootInfo.Etag = mostRecentChildInfo.Etag + } } - res, err := client.ListContainer(ctx, req) - if err != nil { - log.Error().Err(err).Msg("error sending list container grpc request") - w.WriteHeader(http.StatusInternalServerError) - return nil, nil, false + if rootInfo.Etag == "" { + rootInfo.Etag = mostRecentChildInfo.Etag } + } - if res.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, res.Status) - return nil, nil, false - } - resourceInfos = append(resourceInfos, res.Infos...) - - case depth == "infinity": - // FIXME: doesn't work cross-storage as the results will have the wrong paths! - // use a stack to explore sub-containers breadth-first - stack := []string{parentInfo.Path} - for len(stack) > 0 { - // retrieve path on top of stack - path := stack[len(stack)-1] - - var nRef *provider.Reference - if spacesPropfind { - nRef = &provider.Reference{ - ResourceId: ref.ResourceId, - Path: path, + // add size of children + rootInfo.Size += aggregatedChildSize + + resourceInfos := []*provider.ResourceInfo{ + rootInfo, // PROPFIND always includes the root resource + } + childInfos := map[string]*provider.ResourceInfo{} + + // then add children + for _, spaceInfo := range spaceInfos { + switch { + case !spacesPropfind && spaceInfo.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth != "infinity": + // The propfind is requested for a file that exists + + childPath := strings.TrimPrefix(spaceInfo.Path, requestPath) + childName, tail := router.ShiftPath(childPath) + if tail != "/" { + spaceInfo.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER + spaceInfo.Checksum = nil + // TODO unset opaque checksum + } + spaceInfo.Path = path.Join(requestPath, childName) + if existingChild, ok := childInfos[childName]; ok { + // use most recent child + if existingChild.Mtime == nil || (spaceInfo.Mtime != nil && utils.TSToUnixNano(spaceInfo.Mtime) > utils.TSToUnixNano(existingChild.Mtime)) { + childInfos[childName] = spaceInfo } } else { - nRef = &provider.Reference{Path: path} - } - req := &provider.ListContainerRequest{ - Ref: nRef, - ArbitraryMetadataKeys: metadataKeys, - } - res, err := client.ListContainer(ctx, req) - if err != nil { - log.Error().Err(err).Str("path", path).Msg("error sending list container grpc request") - w.WriteHeader(http.StatusInternalServerError) - return nil, nil, false - } - if res.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, res.Status) - return nil, nil, false + childInfos[childName] = spaceInfo } - stack = stack[:len(stack)-1] + case spaceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == "1": + spacePath := spaceInfo.Path + + switch { + case strings.HasPrefix(requestPath, spacePath): + req := &provider.ListContainerRequest{ + Ref: &provider.Reference{ + ResourceId: spaceInfo.Id, + Path: ".", + }, + ArbitraryMetadataKeys: metadataKeys, + } + res, err := client.ListContainer(ctx, req) + if err != nil { + log.Error().Err(err).Msg("error sending list container grpc request") + w.WriteHeader(http.StatusInternalServerError) + return nil, false, false + } - // check sub-containers in reverse order and add them to the stack - // the reversed order here will produce a more logical sorting of results - for i := len(res.Infos) - 1; i >= 0; i-- { - if spacesPropfind { - res.Infos[i].Path = utils.MakeRelativePath(filepath.Join(nRef.Path, res.Infos[i].Path)) + if res.Status.Code != rpc.Code_CODE_OK { + log.Debug().Interface("status", res.Status).Msg("List Container not ok, skipping") + continue + } + if !spacesPropfind { + for _, info := range res.Infos { + info.Path = path.Join(requestPath, info.Path) + } } - if res.Infos[i].Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - stack = append(stack, res.Infos[i].Path) + resourceInfos = append(resourceInfos, res.Infos...) + case strings.HasPrefix(spacePath, requestPath): // space is a child of the requested path + childPath := strings.TrimPrefix(spacePath, requestPath) + childName, tail := router.ShiftPath(childPath) + if tail != "/" { + spaceInfo.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER + spaceInfo.Checksum = nil + // TODO unset opaque checksum + } + spaceInfo.Path = path.Join(requestPath, childName) + if existingChild, ok := childInfos[childName]; ok { + // use most recent child + if existingChild.Mtime == nil || (spaceInfo.Mtime != nil && utils.TSToUnixNano(spaceInfo.Mtime) > utils.TSToUnixNano(existingChild.Mtime)) { + childInfos[childName] = spaceInfo + } + } else { + childInfos[childName] = spaceInfo } + default: + log.Debug().Msg("unhandled") } - resourceInfos = append(resourceInfos, res.Infos...) + case depth == "infinity": + // use a stack to explore sub-containers breadth-first + if spaceInfo != rootInfo { + resourceInfos = append(resourceInfos, spaceInfo) + } + stack := []*provider.ResourceInfo{spaceInfo} + for len(stack) != 0 { + info := stack[0] + stack = stack[1:] + + req := &provider.ListContainerRequest{ + Ref: &provider.Reference{ + ResourceId: spaceInfo.Id, + Path: utils.MakeRelativePath(strings.TrimPrefix(info.Path, spaceInfo.Path)), + }, + ArbitraryMetadataKeys: metadataKeys, + } + res, err := client.ListContainer(ctx, req) // FIXME public link depth infinity -> "gateway: could not find provider: gateway: error calling ListStorageProviders: rpc error: code = PermissionDenied desc = auth: core access token is invalid" + if err != nil { + log.Error().Err(err).Interface("info", info).Msg("error sending list container grpc request") + w.WriteHeader(http.StatusInternalServerError) + return nil, false, false + } + if res.Status.Code != rpc.Code_CODE_OK { + log.Debug().Interface("status", res.Status).Msg("List Container not ok, skipping") + continue + } - if depth != "infinity" { - break + // check sub-containers in reverse order and add them to the stack + // the reversed order here will produce a more logical sorting of results + for i := len(res.Infos) - 1; i >= 0; i-- { + // add path to resource + res.Infos[i].Path = filepath.Join(info.Path, res.Infos[i].Path) + if spacesPropfind { + res.Infos[i].Path = utils.MakeRelativePath(filepath.Join(info.Path, res.Infos[i].Path)) + } + if res.Infos[i].Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + stack = append(stack, res.Infos[i]) + } + } + + resourceInfos = append(resourceInfos, res.Infos...) + // TODO: stream response to avoid storing too many results in memory + // we can do that after having stated the root. } + } + } + + // now add all aggregated child infos + for _, childInfo := range childInfos { + resourceInfos = append(resourceInfos, childInfo) + } - // TODO: stream response to avoid storing too many results in memory + sendTusHeaders := true + // let clients know this collection supports tus.io POST requests to start uploads + if rootInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + if rootInfo.Opaque != nil { + _, ok := rootInfo.Opaque.Map["disable_tus"] + sendTusHeaders = !ok } } - return parentInfo, resourceInfos, true + return resourceInfos, sendTusHeaders, true } func requiresExplicitFetching(n *xml.Name) bool { @@ -724,7 +816,8 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // TODO we cannot find out if md.Size is set or not because ints in go default to 0 // TODO what is the difference to d:quota-used-bytes (which only exists for collections)? // oc:size is available on files and folders and behaves like d:getcontentlength or d:quota-used-bytes respectively - if ls == nil { + // The hasPrefix is a workaround to make children of the link root show a size if they have 0 bytes + if ls == nil || strings.HasPrefix(md.Path, "/"+ls.Token+"/") { propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size)) } else { // link share root collection has no size diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 77c39673f7..7349586891 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -64,9 +64,18 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str return } - ref := &provider.Reference{Path: fn} + space, rpcStatus, err := s.lookUpStorageSpaceForPath(ctx, fn) + if err != nil { + sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if rpcStatus.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, rpcStatus) + return + } // check if resource exists - statReq := &provider.StatRequest{Ref: ref} + statReq := &provider.StatRequest{Ref: makeRelativeReference(space, fn, false)} statRes, err := c.Stat(ctx, statReq) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc stat request") @@ -88,7 +97,7 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str return } - acceptedProps, removedProps, ok := s.handleProppatch(ctx, w, r, ref, pp, sublog) + acceptedProps, removedProps, ok := s.handleProppatch(ctx, w, r, makeRelativeReference(space, fn, false), pp, sublog) if !ok { // handleProppatch handles responses in error cases so we can just return return @@ -117,7 +126,7 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index b7a62d8bc7..183291ccd5 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -21,8 +21,8 @@ package ocdav import ( "net/http" "path" + "path/filepath" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" @@ -50,10 +50,6 @@ func (h *PublicFileHandler) Handler(s *svc) http.Handler { if relativePath != "" && relativePath != "/" { // accessing the file - // PROPFIND has an implicit call - if r.Method != MethodPropfind && !s.adjustResourcePathInURL(w, r) { - return - } switch r.Method { case MethodPropfind: @@ -85,44 +81,6 @@ func (h *PublicFileHandler) Handler(s *svc) http.Handler { }) } -func (s *svc) adjustResourcePathInURL(w http.ResponseWriter, r *http.Request) bool { - ctx, span := rtrace.Provider.Tracer("ocdav").Start(r.Context(), "adjustResourcePathInURL") - defer span.End() - - // find actual file name - tokenStatInfo := ctx.Value(tokenStatInfoKey{}).(*provider.ResourceInfo) - sublog := appctx.GetLogger(ctx).With().Interface("tokenStatInfo", tokenStatInfo).Logger() - - client, err := s.getClient() - if err != nil { - sublog.Error().Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) - return false - } - pathRes, err := client.GetPath(ctx, &provider.GetPathRequest{ - ResourceId: tokenStatInfo.GetId(), - }) - if err != nil { - sublog.Error().Msg("Could not get path of resource") - w.WriteHeader(http.StatusInternalServerError) - return false - } - if pathRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, pathRes.Status) - return false - } - if path.Base(r.URL.Path) != path.Base(pathRes.Path) { - sublog.Debug(). - Str("requestbase", path.Base(r.URL.Path)). - Str("pathbase", path.Base(pathRes.Path)). - Msg("base paths don't match") - w.WriteHeader(http.StatusConflict) - return false - } - - return true -} - // ns is the namespace that is prefixed to the path in the cs3 namespace func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns string, onContainer bool) { ctx, span := rtrace.Provider.Tracer("ocdav").Start(r.Context(), "token_propfind") @@ -151,32 +109,9 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s return } - client, err := s.getClient() - if err != nil { - sublog.Error().Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) - return - } - - // find actual file name - pathRes, err := client.GetPath(ctx, &provider.GetPathRequest{ - ResourceId: tokenStatInfo.GetId(), - }) - if err != nil { - sublog.Warn().Msg("Could not get path of resource") - w.WriteHeader(http.StatusInternalServerError) - return - } - if pathRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, pathRes.Status) - return - } + // prefix tokenStatInfo.Path with token + tokenStatInfo.Path = filepath.Join(r.URL.Path, tokenStatInfo.Path) - if !onContainer && path.Base(r.URL.Path) != path.Base(pathRes.Path) { - // if queried on the wrong path, return not found - w.WriteHeader(http.StatusNotFound) - return - } infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo) propRes, err := s.multistatusResponse(ctx, &pf, infos, ns, nil) diff --git a/internal/http/services/owncloud/ocdav/put.go b/internal/http/services/owncloud/ocdav/put.go index f14cbafab4..f7b0dae4e1 100644 --- a/internal/http/services/owncloud/ocdav/put.go +++ b/internal/http/services/owncloud/ocdav/put.go @@ -112,10 +112,18 @@ func (s *svc) handlePathPut(w http.ResponseWriter, r *http.Request, ns string) { fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() + space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + if err != nil { + sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return + } - ref := &provider.Reference{Path: fn} - - s.handlePut(ctx, w, r, ref, sublog) + s.handlePut(ctx, w, r, makeRelativeReference(space, fn, false), sublog) } func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -298,9 +306,11 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusInternalServerError) return } - sReq = &provider.StatRequest{Ref: &provider.Reference{Path: chunk.Path}} + sReq = &provider.StatRequest{Ref: &provider.Reference{ + ResourceId: ref.ResourceId, + Path: chunk.Path, + }} } - // } // stat again to check the new file's metadata sRes, err = client.Stat(ctx, sReq) @@ -341,7 +351,7 @@ func (s *svc) handleSpacesPut(w http.ResponseWriter, r *http.Request, spaceID st sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path) + spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/spaces.go b/internal/http/services/owncloud/ocdav/spaces.go index eba6736a2e..7695cf33b3 100644 --- a/internal/http/services/owncloud/ocdav/spaces.go +++ b/internal/http/services/owncloud/ocdav/spaces.go @@ -22,20 +22,29 @@ import ( "context" "fmt" "net/http" + "path" + "strconv" + "strings" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/utils" ) // SpacesHandler handles trashbin requests type SpacesHandler struct { - gatewaySvc string + gatewaySvc string + namespace string + useLoggedInUserNS bool } func (h *SpacesHandler) init(c *Config) error { h.gatewaySvc = c.GatewaySvc + h.namespace = path.Join("/", c.WebdavNamespace) + h.useLoggedInUserNS = true return nil } @@ -94,15 +103,88 @@ func (h *SpacesHandler) Handler(s *svc) http.Handler { }) } -func (s *svc) lookUpStorageSpaceReference(ctx context.Context, spaceID string, relativePath string) (*storageProvider.Reference, *rpc.Status, error) { +// lookUpStorageSpacesForPath returns: +// th storage spaces responsible for a path +// the status and error for the lookup +func (s *svc) lookUpStorageSpaceForPath(ctx context.Context, path string) (*storageProvider.StorageSpace, *rpc.Status, error) { // Get the getway client gatewayClient, err := s.getClient() if err != nil { return nil, nil, err } + // TODO add filter to only fetch spaces changed in the last 30 sec? + // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? + // TODO use ListContainerStream to listen for changes // retrieve a specific storage space lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte(path), + }, + "unique": { + Decoder: "plain", + Value: []byte(strconv.FormatBool(true)), + }, + }, + }, + } + + lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) + if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { + return nil, lSSRes.Status, err + } + switch len(lSSRes.StorageSpaces) { + case 0: + return nil, status.NewNotFound(ctx, "no space found"), nil + case 1: + return lSSRes.StorageSpaces[0], lSSRes.Status, nil + } + + return nil, status.NewInternal(ctx, "too many spaces returned"), nil +} + +// lookUpStorageSpacesForPathWithChildren returns: +// the list of storage spaces responsible for a path +// the status and error for the lookup +func (s *svc) lookUpStorageSpacesForPathWithChildren(ctx context.Context, path string) ([]*storageProvider.StorageSpace, *rpc.Status, error) { + // Get the getway client + gatewayClient, err := s.getClient() + if err != nil { + return nil, nil, err + } + + // TODO add filter to only fetch spaces changed in the last 30 sec? + // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? + // TODO use ListContainerStream to listen for changes + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": {Decoder: "plain", Value: []byte(path)}, + "withChildMounts": {Decoder: "plain", Value: []byte("true")}, + }}, + } + + lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) + if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { + return nil, lSSRes.Status, err + } + + return lSSRes.StorageSpaces, lSSRes.Status, nil +} +func (s *svc) lookUpStorageSpaceByID(ctx context.Context, spaceID string) (*storageProvider.StorageSpace, *rpc.Status, error) { + // Get the getway client + gatewayClient, err := s.getClient() + if err != nil { + return nil, nil, err + } + + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{}, Filters: []*storageProvider.ListStorageSpacesRequest_Filter{ { Type: storageProvider.ListStorageSpacesRequest_Filter_TYPE_ID, @@ -123,10 +205,30 @@ func (s *svc) lookUpStorageSpaceReference(ctx context.Context, spaceID string, r if len(lSSRes.StorageSpaces) != 1 { return nil, nil, fmt.Errorf("unexpected number of spaces %d", len(lSSRes.StorageSpaces)) } - space := lSSRes.StorageSpaces[0] + return lSSRes.StorageSpaces[0], lSSRes.Status, nil +} +func (s *svc) lookUpStorageSpaceReference(ctx context.Context, spaceID string, relativePath string, spacesDavRequest bool) (*storageProvider.Reference, *rpc.Status, error) { + space, status, err := s.lookUpStorageSpaceByID(ctx, spaceID) + if err != nil { + return nil, status, err + } + return makeRelativeReference(space, relativePath, spacesDavRequest), status, err +} + +func makeRelativeReference(space *storageProvider.StorageSpace, relativePath string, spacesDavRequest bool) *storageProvider.Reference { + if space.Opaque == nil || space.Opaque.Map == nil || space.Opaque.Map["path"] == nil || space.Opaque.Map["path"].Decoder != "plain" { + return nil // not mounted + } + spacePath := string(space.Opaque.Map["path"].Value) + relativeSpacePath := "." + if strings.HasPrefix(relativePath, spacePath) { + relativeSpacePath = utils.MakeRelativePath(strings.TrimPrefix(relativePath, spacePath)) + } else if spacesDavRequest { + relativeSpacePath = utils.MakeRelativePath(relativePath) + } return &storageProvider.Reference{ ResourceId: space.Root, - Path: utils.MakeRelativePath(relativePath), - }, lSSRes.Status, nil + Path: relativeSpacePath, + } } diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 1e71e30e9c..1e3787ecd8 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -216,8 +216,20 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } + space, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, basePath) + if err != nil { + sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if rpcstatus.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, rpcstatus) + return + } + ref := makeRelativeReference(space, basePath, false) + // ask gateway for recycle items - getRecycleRes, err := gc.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: &provider.Reference{Path: basePath}, Key: path.Join(key, itemPath)}) + getRecycleRes, err := gc.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: path.Join(key, itemPath)}) if err != nil { sublog.Error().Err(err).Msg("error calling ListRecycle") @@ -245,7 +257,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s for len(stack) > 0 { key := stack[len(stack)-1] - getRecycleRes, err := gc.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: &provider.Reference{Path: basePath}, Key: key}) + getRecycleRes, err := gc.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: key}) if err != nil { sublog.Error().Err(err).Msg("error calling ListRecycle") w.WriteHeader(http.StatusInternalServerError) @@ -469,10 +481,17 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc w.WriteHeader(http.StatusInternalServerError) return } - - dstRef := &provider.Reference{ - Path: dst, + space, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, dst) + if err != nil { + sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return } + if rpcstatus.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, rpcstatus) + return + } + dstRef := makeRelativeReference(space, dst, false) dstStatReq := &provider.StatRequest{ Ref: dstRef, @@ -494,7 +513,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc // restore location exists, and if it doesn't returns a conflict error code. if dstStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND && isNested(dst) { parentStatReq := &provider.StatRequest{ - Ref: &provider.Reference{Path: filepath.Dir(dst)}, + Ref: makeRelativeReference(space, filepath.Dir(dst), false), } parentStatResponse, err := client.Stat(ctx, parentStatReq) @@ -540,16 +559,24 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } } + sourceSpace, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, basePath) + if err != nil { + sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if rpcstatus.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, rpcstatus) + return + } req := &provider.RestoreRecycleItemRequest{ // use the target path to find the storage provider // this means we can only undelete on the same storage, not to a different folder // use the key which is prefixed with the StoragePath to lookup the correct storage ... // TODO currently limited to the home storage - Ref: &provider.Reference{ - Path: basePath, - }, + Ref: makeRelativeReference(sourceSpace, basePath, false), Key: path.Join(key, itemPath), - RestoreRef: &provider.Reference{Path: dst}, + RestoreRef: dstRef, } res, err := client.RestoreRecycleItem(ctx, req) @@ -608,11 +635,19 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, // set key as opaque id, the storageprovider will use it as the key for the // storage drives PurgeRecycleItem key call + space, status, err := s.lookUpStorageSpaceForPath(ctx, basePath) + if err != nil { + sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + if status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, status) + return + } req := &provider.PurgeRecycleRequest{ - Ref: &provider.Reference{ - Path: basePath, - }, + Ref: makeRelativeReference(space, basePath, false), Key: path.Join(key, itemPath), } diff --git a/internal/http/services/owncloud/ocdav/tus.go b/internal/http/services/owncloud/ocdav/tus.go index b795d482d4..f5ddb67ec8 100644 --- a/internal/http/services/owncloud/ocdav/tus.go +++ b/internal/http/services/owncloud/ocdav/tus.go @@ -60,7 +60,10 @@ func (s *svc) handlePathTusPost(w http.ResponseWriter, r *http.Request, ns strin sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() // check tus headers? - ref := &provider.Reference{Path: fn} + ref := &provider.Reference{ + // FIXME ResourceId? + Path: fn, + } s.handleTusPost(ctx, w, r, meta, ref, sublog) } @@ -77,7 +80,7 @@ func (s *svc) handleSpacesTusPost(w http.ResponseWriter, r *http.Request, spaceI sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, path.Join(r.URL.Path, meta["filename"])) + spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, path.Join(r.URL.Path, meta["filename"]), true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/mocks/GatewayClient.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/mocks/GatewayClient.go index d068b24597..d802734e9e 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/mocks/GatewayClient.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/mocks/GatewayClient.go @@ -16,7 +16,7 @@ // 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. +// Code generated by mockery v1.1.2. DO NOT EDIT. package mocks @@ -163,6 +163,36 @@ func (_m *GatewayClient) GetGroupByClaim(ctx context.Context, in *groupv1beta1.G return r0, r1 } +// GetPath provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetPath(ctx context.Context, in *providerv1beta1.GetPathRequest, opts ...grpc.CallOption) (*providerv1beta1.GetPathResponse, 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.GetPathResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.GetPathRequest, ...grpc.CallOption) *providerv1beta1.GetPathResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetPathResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.GetPathRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetShare provides a mock function with given fields: ctx, in, opts func (_m *GatewayClient) GetShare(ctx context.Context, in *collaborationv1beta1.GetShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetShareResponse, error) { _va := make([]interface{}, len(opts)) 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 b73615de5f..9bc66874a2 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 @@ -24,10 +24,7 @@ import ( "net/http" "path" "sort" - "strconv" - "strings" - 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" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -95,11 +92,14 @@ func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) { sort.Strings(mountPoints) // 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)) + // FIXME: adjust logic + /* + for i, mp := range mountPoints { + if mp == mount { + mount = fmt.Sprintf("%s (%s)", base, strconv.Itoa(i+1)) + } } - } + */ for id := range sharesToAccept { h.updateReceivedShare(w, r, id, false, mount) @@ -163,16 +163,6 @@ func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, sh return } - // cut off configured home namespace, paths in ocs shares are relative to it - identifier := h.mustGetIdentifiers(ctx, client, info.Owner.OpaqueId, false) - u := &userpb.User{ - Id: info.Owner, - Username: identifier.Username, - DisplayName: identifier.DisplayName, - Mail: identifier.Mail, - } - info.Path = strings.TrimPrefix(info.Path, h.getHomeNamespace(u)) - data, err := conversions.CS3Share2ShareData(r.Context(), rs.Share) if err != nil { logger.Debug().Interface("share", rs.Share).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping") diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index 36f16a6164..a5f0ab4bf0 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -51,7 +51,6 @@ import ( "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" - revactx "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/share" @@ -114,6 +113,7 @@ type GatewayClient interface { Stat(ctx context.Context, in *provider.StatRequest, opts ...grpc.CallOption) (*provider.StatResponse, error) ListContainer(ctx context.Context, in *provider.ListContainerRequest, opts ...grpc.CallOption) (*provider.ListContainerResponse, error) + GetPath(ctx context.Context, in *provider.GetPathRequest, opts ...grpc.CallOption) (*provider.GetPathResponse, error) ListShares(ctx context.Context, in *collaboration.ListSharesRequest, opts ...grpc.CallOption) (*collaboration.ListSharesResponse, error) GetShare(ctx context.Context, in *collaboration.GetShareRequest, opts ...grpc.CallOption) (*collaboration.GetShareResponse, error) @@ -175,7 +175,10 @@ func (h *Handler) extractReference(r *http.Request) (provider.Reference, error) var ref provider.Reference if p := r.FormValue("path"); p != "" { u := ctxpkg.ContextMustGetUser(r.Context()) - ref = provider.Reference{Path: path.Join(h.getHomeNamespace(u), p)} + ref = provider.Reference{ + // FIXME ResourceId? + Path: path.Join(h.getHomeNamespace(u), p), + } } else if spaceRef := r.FormValue("space_ref"); spaceRef != "" { var err error ref, err = utils.ParseStorageSpaceReference(spaceRef) @@ -267,10 +270,6 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) { return } - // cut off configured home namespace, paths in ocs shares are relative to it - currentUser := ctxpkg.ContextMustGetUser(ctx) - statRes.Info.Path = strings.TrimPrefix(statRes.Info.Path, h.getHomeNamespace(currentUser)) - err = h.addFileInfo(ctx, s, statRes.Info) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error adding fileinfo to share", err) @@ -299,7 +298,7 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) { } // Get auth - granteeCtx := revactx.ContextSetUser(context.Background(), res.User) + granteeCtx := ctxpkg.ContextSetUser(context.Background(), res.User) authRes, err := client.Authenticate(granteeCtx, &gateway.AuthenticateRequest{ Type: "machine", @@ -314,7 +313,7 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "machine authentication failed", nil) return } - granteeCtx = metadata.AppendToOutgoingContext(granteeCtx, revactx.TokenHeader, authRes.Token) + granteeCtx = metadata.AppendToOutgoingContext(granteeCtx, ctxpkg.TokenHeader, authRes.Token) lrs, ocsResponse := getSharesList(granteeCtx, client) if ocsResponse != nil { @@ -569,9 +568,6 @@ func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) { return } - // cut off configured home namespace, paths in ocs shares are relative to it - info.Path = strings.TrimPrefix(info.Path, h.getHomeNamespace(revactx.ContextMustGetUser(ctx))) - err = h.addFileInfo(ctx, share, info) if err != nil { log.Error().Err(err).Msg("error mapping share data") @@ -679,9 +675,6 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, shareID st return } - // cut off configured home namespace, paths in ocs shares are relative to it - statRes.Info.Path = strings.TrimPrefix(statRes.Info.Path, h.getHomeNamespace(revactx.ContextMustGetUser(ctx))) - err = h.addFileInfo(r.Context(), share, statRes.Info) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, err.Error(), err) @@ -747,7 +740,7 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { // we need to lookup the resource id so we can filter the list of shares later if p != "" { // prefix the path with the owners home, because ocs share requests are relative to the home dir - target := path.Join(h.getHomeNamespace(revactx.ContextMustGetUser(ctx)), r.FormValue("path")) + target := path.Join(h.getHomeNamespace(ctxpkg.ContextMustGetUser(ctx)), r.FormValue("path")) var status *rpc.Status pinfo, status, err = h.getResourceInfoByPath(ctx, client, target) @@ -812,27 +805,6 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { return } - // in a jailed namespace we have to point to the mount point in the users /Shares jail - // to do that we have to list the /Shares jail and use those paths instead of stating the shared resources - // The stat results would start with a path outside the jail and thus be inaccessible - - var shareJailInfos []*provider.ResourceInfo - - if h.sharePrefix != "/" { - // we only need the path from the share jail for accepted shares - if stateFilter == collaboration.ShareState_SHARE_STATE_ACCEPTED || stateFilter == ocsStateUnknown { - // only log errors. They may happen but we can continue trying to at least list the shares - lcRes, err := client.ListContainer(ctx, &provider.ListContainerRequest{ - Ref: &provider.Reference{Path: path.Join(h.getHomeNamespace(revactx.ContextMustGetUser(ctx)), h.sharePrefix)}, - }) - if err != nil || lcRes.Status.Code != rpc.Code_CODE_OK { - h.logProblems(lcRes.GetStatus(), err, "could not list container, continuing without share jail path info") - } else { - shareJailInfos = lcRes.Infos - } - } - } - shares := make([]*conversions.ShareData, 0, len(lrsRes.GetShares())) // TODO(refs) filter out "invalid" shares @@ -851,10 +823,20 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { info = pinfo } else { var status *rpc.Status - info, status, err = h.getResourceInfoByID(ctx, client, rs.Share.ResourceId) + // FIXME the ResourceID is the id of the resource, but we want the id of the mount point so we can fetch that path, well we have the mountpoint path in the receivedshare + // first stat mount point + mountID := &provider.ResourceId{ + StorageId: utils.ShareStorageProviderID, + OpaqueId: rs.Share.Id.OpaqueId, + } + info, status, err = h.getResourceInfoByID(ctx, client, mountID) if err != nil || status.Code != rpc.Code_CODE_OK { - h.logProblems(status, err, "could not stat, skipping") - continue + // fallback to unmounted resource + info, status, err = h.getResourceInfoByID(ctx, client, rs.Share.ResourceId) + if err != nil || status.Code != rpc.Code_CODE_OK { + h.logProblems(status, err, "could not stat, skipping") + continue + } } } @@ -864,16 +846,6 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { continue } - // cut off configured home namespace, paths in ocs shares are relative to it - identifier := h.mustGetIdentifiers(ctx, client, info.Owner.OpaqueId, false) - u := &userpb.User{ - Id: info.Owner, - Username: identifier.Username, - DisplayName: identifier.DisplayName, - Mail: identifier.Mail, - } - info.Path = strings.TrimPrefix(info.Path, h.getHomeNamespace(u)) - data.State = mapState(rs.GetState()) if err := h.addFileInfo(ctx, data, info); err != nil { @@ -906,11 +878,11 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { // Needed because received shares can be jailed in a folder in the users home if h.sharePrefix != "/" { - // if we have share jail infos use them to build the path - if sji := findMatch(shareJailInfos, rs.Share.ResourceId); sji != nil { + // if we have a mount point use it to build the path + if rs.MountPoint != nil && rs.MountPoint.Path != "" { // override path with info from share jail - data.FileTarget = path.Join(h.sharePrefix, path.Base(sji.Path)) - data.Path = path.Join(h.sharePrefix, path.Base(sji.Path)) + data.FileTarget = path.Join(h.sharePrefix, rs.MountPoint.Path) + data.Path = path.Join(h.sharePrefix, rs.MountPoint.Path) } else { data.FileTarget = path.Join(h.sharePrefix, path.Base(info.Path)) data.Path = path.Join(h.sharePrefix, path.Base(info.Path)) @@ -935,15 +907,6 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { response.WriteOCSSuccess(w, r, shares) } -func findMatch(shareJailInfos []*provider.ResourceInfo, id *provider.ResourceId) *provider.ResourceInfo { - for i := range shareJailInfos { - if shareJailInfos[i].Id != nil && shareJailInfos[i].Id.StorageId == id.StorageId && shareJailInfos[i].Id.OpaqueId == id.OpaqueId { - return shareJailInfos[i] - } - } - return nil -} - func (h *Handler) listSharesWithOthers(w http.ResponseWriter, r *http.Request) { shares := make([]*conversions.ShareData, 0) @@ -955,7 +918,7 @@ func (h *Handler) listSharesWithOthers(w http.ResponseWriter, r *http.Request) { p := r.URL.Query().Get("path") if p != "" { // prefix the path with the owners home, because ocs share requests are relative to the home dir - filters, linkFilters, e = h.addFilters(w, r, h.getHomeNamespace(revactx.ContextMustGetUser(r.Context()))) + filters, linkFilters, e = h.addFilters(w, r, h.getHomeNamespace(ctxpkg.ContextMustGetUser(r.Context()))) if e != nil { // result has been written as part of addFilters return @@ -1080,12 +1043,53 @@ func (h *Handler) addFileInfo(ctx context.Context, s *conversions.ShareData, inf case h.sharePrefix == "/": s.FileTarget = info.Path s.Path = info.Path + client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err == nil { + gpRes, err := client.GetPath(ctx, &provider.GetPathRequest{ + ResourceId: info.Id, + }) + if err == nil && gpRes.Status.Code == rpc.Code_CODE_OK { + // TODO log error? + s.Path = gpRes.Path + + // cut off configured home namespace, paths in ocs shares are relative to it + identifier := h.mustGetIdentifiers(ctx, client, info.GetOwner().GetOpaqueId(), false) + u := &userpb.User{ + Id: info.Owner, + Username: identifier.Username, + DisplayName: identifier.DisplayName, + Mail: identifier.Mail, + } + s.Path = strings.TrimPrefix(s.Path, h.getHomeNamespace(u)) + } + } + case s.ShareType == conversions.ShareTypePublicLink: s.FileTarget = path.Join("/", path.Base(info.Path)) s.Path = path.Join("/", path.Base(info.Path)) default: s.FileTarget = path.Join(h.sharePrefix, path.Base(info.Path)) s.Path = info.Path + client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err == nil { + gpRes, err := client.GetPath(ctx, &provider.GetPathRequest{ + ResourceId: info.Id, + }) + if err == nil && gpRes.Status.Code == rpc.Code_CODE_OK { + // TODO log error? + s.Path = gpRes.Path + } + + // cut off configured home namespace, paths in ocs shares are relative to it + identifier := h.mustGetIdentifiers(ctx, client, info.GetOwner().GetOpaqueId(), false) + u := &userpb.User{ + Id: info.Owner, + Username: identifier.Username, + DisplayName: identifier.DisplayName, + Mail: identifier.Mail, + } + s.Path = strings.TrimPrefix(s.Path, h.getHomeNamespace(u)) + } } s.StorageID = storageIDPrefix + s.FileTarget // TODO FileParent: @@ -1230,12 +1234,13 @@ func (h *Handler) getAdditionalInfoAttribute(ctx context.Context, u *userIdentif func (h *Handler) getResourceInfoByPath(ctx context.Context, client GatewayClient, path string) (*provider.ResourceInfo, *rpc.Status, error) { return h.getResourceInfo(ctx, client, path, &provider.Reference{ + // FIXME ResourceId? Path: path, }) } func (h *Handler) getResourceInfoByID(ctx context.Context, client GatewayClient, id *provider.ResourceId) (*provider.ResourceInfo, *rpc.Status, error) { - return h.getResourceInfo(ctx, client, resourceid.OwnCloudResourceIDWrap(id), &provider.Reference{ResourceId: id}) + return h.getResourceInfo(ctx, client, resourceid.OwnCloudResourceIDWrap(id), &provider.Reference{ResourceId: id, Path: "."}) } // getResourceInfo retrieves the resource info to a target. diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go index 6c20a7a9e7..98c723936e 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go @@ -103,9 +103,10 @@ var _ = Describe("The ocs API", func() { client.On("Stat", mock.Anything, mock.Anything).Return(&provider.StatResponse{ Status: status.NewOK(context.Background()), Info: &provider.ResourceInfo{ - Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, - Path: "/newshare", - Id: resID, + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: "/newshare", + Id: resID, + Owner: user.Id, PermissionSet: &provider.ResourcePermissions{ Stat: true, AddGrant: true, diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index a5405efc30..492ad807ea 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -20,7 +20,6 @@ package shares import ( "net/http" - "strings" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -30,7 +29,6 @@ import ( "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" - ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) @@ -185,7 +183,6 @@ func (h *Handler) removeUserShare(w http.ResponseWriter, r *http.Request, shareI func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.Filter) ([]*conversions.ShareData, *rpc.Status, error) { ctx := r.Context() log := appctx.GetLogger(ctx) - u := ctxpkg.ContextMustGetUser(ctx) lsUserSharesRequest := collaboration.ListSharesRequest{ Filters: filters, @@ -222,9 +219,6 @@ func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.Filte continue } - // cut off configured home namespace, paths in ocs shares are relative to it - info.Path = strings.TrimPrefix(info.Path, h.getHomeNamespace(u)) - if err := h.addFileInfo(ctx, data, info); err != nil { log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") continue diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go index 38436d20a9..2c0b204201 100644 --- a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go +++ b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go @@ -118,7 +118,10 @@ func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) { var relative float32 // lightweight accounts don't have access to their storage space if u.Id.Type != userpb.UserType_USER_TYPE_LIGHTWEIGHT { - getQuotaRes, err := gc.GetQuota(ctx, &gateway.GetQuotaRequest{Ref: &provider.Reference{Path: getHomeRes.Path}}) + getQuotaRes, err := gc.GetQuota(ctx, &gateway.GetQuotaRequest{Ref: &provider.Reference{ + // FIXME ResourceId? + Path: getHomeRes.Path, + }}) if err != nil { sublog.Error().Err(err).Msg("error calling GetQuota") w.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/auth/scope/publicshare.go b/pkg/auth/scope/publicshare.go index 1960148688..61a230f970 100644 --- a/pkg/auth/scope/publicshare.go +++ b/pkg/auth/scope/publicshare.go @@ -125,8 +125,14 @@ func checkStorageRef(ctx context.Context, s *link.PublicShare, r *provider.Refer } // r: path:$path> - if id := r.GetResourceId(); id.GetStorageId() == PublicStorageProviderID && id.GetOpaqueId() == s.Token+"/"+s.GetResourceId().GetOpaqueId() { - return true + if id := r.GetResourceId(); id.GetStorageId() == PublicStorageProviderID { + if id.GetOpaqueId() == PublicStorageProviderID && strings.HasPrefix(r.Path, "./"+s.Token) { + return true + } + // r: path:$path> + if strings.HasPrefix(id.GetOpaqueId(), s.Token+"/") { + return true + } } return false } diff --git a/pkg/auth/scope/resourceinfo.go b/pkg/auth/scope/resourceinfo.go index 1a6c41a40f..2f4a3977b9 100644 --- a/pkg/auth/scope/resourceinfo.go +++ b/pkg/auth/scope/resourceinfo.go @@ -44,6 +44,29 @@ func resourceinfoScope(_ context.Context, scope *authpb.Scope, resource interfac // Viewer role case *registry.GetStorageProvidersRequest: return checkResourceInfo(&r, v.GetRef()), nil + case *registry.ListStorageProvidersRequest: + // the call will only return spaces the current user has access to + ref := &provider.Reference{} + if v.Opaque != nil && v.Opaque.Map != nil { + if e, ok := v.Opaque.Map["storage_id"]; ok { + ref.ResourceId = &provider.ResourceId{ + StorageId: string(e.Value), + } + } + if e, ok := v.Opaque.Map["opaque_id"]; ok { + if ref.ResourceId == nil { + ref.ResourceId = &provider.ResourceId{} + } + ref.ResourceId.OpaqueId = string(e.Value) + } + if e, ok := v.Opaque.Map["path"]; ok { + ref.Path = string(e.Value) + } + } + return checkResourceInfo(&r, ref), nil + case *provider.ListStorageSpacesRequest: + // the call will only return spaces the current user has access to + return true, nil case *provider.StatRequest: return checkResourceInfo(&r, v.GetRef()), nil case *provider.ListContainerRequest: diff --git a/pkg/storage/registry/spaces/spaces.go b/pkg/storage/registry/spaces/spaces.go index 53eaeec051..42802bcb71 100644 --- a/pkg/storage/registry/spaces/spaces.go +++ b/pkg/storage/registry/spaces/spaces.go @@ -22,14 +22,15 @@ import ( "bytes" "context" "encoding/json" + "errors" "path/filepath" "regexp" + "strconv" "strings" "text/template" "github.com/Masterminds/sprig" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registrypb "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" @@ -37,7 +38,6 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/logger" - "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/storage" @@ -105,10 +105,12 @@ func (c *config) init() { c.Providers = map[string]*Provider{ sharedconf.GetGatewaySVC(""): { Spaces: map[string]*spaceConfig{ - "personal": {MountPoint: "/users", PathTemplate: "/users/{{.Space.Owner.Id.OpaqueId}}"}, - "project": {MountPoint: "/projects", PathTemplate: "/projects/{{.Space.Name}}"}, - "share": {MountPoint: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", PathTemplate: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}"}, - "public": {MountPoint: "/public"}, + "personal": {MountPoint: "/users", PathTemplate: "/users/{{.Space.Owner.Id.OpaqueId}}"}, + "project": {MountPoint: "/projects", PathTemplate: "/projects/{{.Space.Name}}"}, + "virtual": {MountPoint: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares"}, + "grant": {MountPoint: "."}, + "mountpoint": {MountPoint: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", PathTemplate: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}"}, + "public": {MountPoint: "/public"}, }, }, } @@ -271,24 +273,29 @@ func (r *registry) GetProvider(ctx context.Context, space *providerpb.StorageSpa // matches = /foo/bar <=> /foo/bar -> list(spaceid, .) // below = /foo/bar/bif <=> /foo/bar -> list(spaceid, ./bif) func (r *registry) ListProviders(ctx context.Context, filters map[string]string) ([]*registrypb.ProviderInfo, error) { + b, _ := strconv.ParseBool(filters["unique"]) switch { case filters["storage_id"] != "" && filters["opaque_id"] != "": - return r.findProvidersForResource(ctx, filters["storage_id"]+"!"+filters["opaque_id"]), nil + findMountpoint := filters["type"] == "mountpoint" + findGrant := !findMountpoint && filters["path"] == "" // relvative references, by definition, occur in the correct storage, so do not look for grants + return r.findProvidersForResource(ctx, filters["storage_id"]+"!"+filters["opaque_id"], findMountpoint, findGrant), nil case filters["path"] != "": - return r.findProvidersForAbsolutePathReference(ctx, filters["path"]), nil + return r.findProvidersForAbsolutePathReference(ctx, filters["path"], b), nil // TODO add filter for all spaces the user can manage? case len(filters) == 0: // return all providers return r.findAllProviders(ctx), nil + default: + return nil, errors.New("filters misconfigured") } - return []*registrypb.ProviderInfo{}, nil } // findProvidersForResource looks up storage providers based on a resource id // for the root of a space the res.StorageId is the same as the res.OpaqueId // for share spaces the res.StorageId tells the registry the spaceid and res.OpaqueId is a node in that space -func (r *registry) findProvidersForResource(ctx context.Context, id string) []*registrypb.ProviderInfo { +func (r *registry) findProvidersForResource(ctx context.Context, id string, findMoundpoint, findGrant bool) []*registrypb.ProviderInfo { currentUser := ctxpkg.ContextMustGetUser(ctx) + providerInfos := []*registrypb.ProviderInfo{} for address, provider := range r.c.Providers { p := ®istrypb.ProviderInfo{ Address: address, @@ -302,12 +309,21 @@ func (r *registry) findProvidersForResource(ctx context.Context, id string) []*r }, }, }} - for spaceType := range provider.Spaces { - // add filter to id based request if it is configured + if findMoundpoint { + // when listing by id return also grants and mountpoints filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{ Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE, Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{ - SpaceType: spaceType, + SpaceType: "+mountpoint", + }, + }) + } + if findGrant { + // when listing by id return also grants and mountpoints + filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{ + Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE, + Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{ + SpaceType: "+grant", }, }) } @@ -322,37 +338,52 @@ func (r *registry) findProvidersForResource(ctx context.Context, id string) []*r // nothing to do, will continue with next provider case 1: space := spaces[0] - for spaceType, sc := range provider.Spaces { - if spaceType == space.SpaceType { - providerPath, err := sc.SpacePath(currentUser, space) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("space", space).Msg("failed to execute template, continuing") - continue - } - spacePaths := map[string]string{ - space.Id.OpaqueId: providerPath, - } - p.Opaque, err = spacePathsToOpaque(spacePaths) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Msg("marshaling space paths map failed, continuing") - continue - } - // we can stop after we found the first space - // TODO to improve lookup time the registry could cache which provider last was responsible for a space? could be invalidated by simple ttl? would that work for shares? - return []*registrypb.ProviderInfo{p} + + var sc *spaceConfig + var ok bool + var spacePath string + + if space.SpaceType == "grant" { + spacePath = "." // a . indicates a grant, the gateway will do a findMountpoint for it + } else { + if findMoundpoint && space.SpaceType != "mountpoint" { + continue + } + // filter unwanted space types. type mountpoint is not explicitly configured but requested by the gateway + if sc, ok = provider.Spaces[space.SpaceType]; !ok && space.SpaceType != "mountpoint" { + continue + } + + spacePath, err = sc.SpacePath(currentUser, space) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("space", space).Msg("failed to execute template, continuing") + continue } } + + spacePaths := map[string]string{ + space.Id.OpaqueId: spacePath, + } + p.Opaque, err = spacePathsToOpaque(spacePaths) + if err != nil { + appctx.GetLogger(ctx).Debug().Err(err).Msg("marshaling space paths map failed, continuing") + continue + } + // we can stop after we found the first space + // TODO to improve lookup time the registry could cache which provider last was responsible for a space? could be invalidated by simple ttl? would that work for shares? + // return []*registrypb.ProviderInfo{p} + providerInfos = append(providerInfos, p) // hm we need to query all providers ... or the id based lookup might only see the spaces storage provider default: // there should not be multiple spaces with the same id per provider appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("spaces", spaces).Msg("multiple spaces returned, ignoring") } } - return []*registrypb.ProviderInfo{} + return providerInfos } // findProvidersForAbsolutePathReference takes a path and returns the storage provider with the longest matching path prefix // FIXME use regex to return the correct provider when multiple are configured -func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, path string) []*registrypb.ProviderInfo { +func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, path string, unique bool) []*registrypb.ProviderInfo { currentUser := ctxpkg.ContextMustGetUser(ctx) deepestMountPath := "" @@ -366,31 +397,13 @@ func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, pa var spaces []*providerpb.StorageSpace var err error filters := []*providerpb.ListStorageSpacesRequest_Filter{} - for spaceType, sc := range provider.Spaces { - - filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{ - Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE, - Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{ - SpaceType: spaceType, - }, - }) - if sc.OwnerIsCurrentUser { - filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{ - Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_OWNER, - Term: &providerpb.ListStorageSpacesRequest_Filter_Owner{ - Owner: currentUser.Id, - }, - }) - } - if sc.ID != "" { - filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{ - Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_ID, - Term: &providerpb.ListStorageSpacesRequest_Filter_Id{ - Id: &providerpb.StorageSpaceId{OpaqueId: sc.ID}, - }, - }) - } - } + // when listing paths also return mountpoints + filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{ + Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE, + Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{ + SpaceType: "+mountpoint", + }, + }) spaces, err = r.findStorageSpaceOnProvider(ctx, p.Address, filters) if err != nil { @@ -402,6 +415,13 @@ func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, pa for _, space := range spaces { var sc *spaceConfig var ok bool + + if space.SpaceType == "grant" { + spacePaths[space.Id.OpaqueId] = "." // a . indicates a grant, the gateway will do a findMountpoint for it + continue + } + + // filter unwanted space types. type mountpoint is not explicitly configured but requested by the gateway if sc, ok = provider.Spaces[space.SpaceType]; !ok { continue } @@ -412,7 +432,14 @@ func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, pa } switch { - case strings.HasPrefix(spacePath, path): + case spacePath == path && unique: + spacePaths[space.Id.OpaqueId] = spacePath + + deepestMountPath = spacePath + deepestMountSpace = space + deepestMountPathProvider = p + + case strings.HasPrefix(spacePath, path) && !unique: // and add all providers below and exactly matching the path // requested /foo, mountPath /foo/sub spacePaths[space.Id.OpaqueId] = spacePath @@ -421,6 +448,7 @@ func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, pa deepestMountSpace = space deepestMountPathProvider = p } + case strings.HasPrefix(path, spacePath) && len(spacePath) > len(deepestMountPath): // eg. three providers: /foo, /foo/sub, /foo/sub/bar // requested /foo/sub/mob @@ -496,10 +524,8 @@ func (r *registry) findStorageSpaceOnProvider(ctx context.Context, addr string, res, err := c.ListStorageSpaces(ctx, req) if err != nil { - return nil, err - } - if res.Status.Code != rpc.Code_CODE_OK && res.Status.Code != rpc.Code_CODE_NOT_FOUND { - return nil, status.NewErrorFromCode(res.Status.Code, "spaces registry") + // ignore errors + return nil, nil } return res.StorageSpaces, nil } diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index 84b5637391..89f8c008b2 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -948,14 +948,14 @@ func (n *Node) FindStorageSpaceRoot() error { var err error // remember the node we ask for and use parent to climb the tree parent := n - for parent.ParentID != "" { - if parent, err = parent.Parent(); err != nil { - return err - } + for { if IsSpaceRoot(parent) { n.SpaceRoot = parent break } + if parent, err = parent.Parent(); err != nil { + return err + } } return nil } @@ -963,10 +963,8 @@ func (n *Node) FindStorageSpaceRoot() error { // IsSpaceRoot checks if the node is a space root func IsSpaceRoot(r *Node) bool { path := r.InternalPath() - if spaceNameBytes, err := xattr.Get(path, xattrs.SpaceNameAttr); err == nil { - if string(spaceNameBytes) != "" { - return true - } + if _, err := xattr.Get(path, xattrs.SpaceNameAttr); err == nil { + return true } return false } diff --git a/pkg/storage/utils/decomposedfs/node/permissions.go b/pkg/storage/utils/decomposedfs/node/permissions.go index d0ba6582df..6605160044 100644 --- a/pkg/storage/utils/decomposedfs/node/permissions.go +++ b/pkg/storage/utils/decomposedfs/node/permissions.go @@ -251,7 +251,8 @@ func (p *Permissions) HasPermission(ctx context.Context, n *Node, check func(*pr } } - appctx.GetLogger(ctx).Debug().Interface("permissions", NoPermissions()).Interface("node", n).Interface("user", u).Msg("no grant found, returning default permissions") + // NOTE: this log is being printed 1 million times on a simple test. TODO: Check if this is expected + // appctx.GetLogger(ctx).Debug().Interface("permissions", NoPermissions()).Interface("node", n).Interface("user", u).Msg("no grant found, returning default permissions") return false, nil } diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 91d54febd4..1a5b9ad814 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -185,7 +185,14 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide for i := range filter { switch filter[i].Type { case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: - spaceTypes = append(spaceTypes, filter[i].GetSpaceType()) + switch filter[i].GetSpaceType() { + case "+mountpoint": + // TODO include mount poits + case "+grant": + // TODO include grants + default: + spaceTypes = append(spaceTypes, filter[i].GetSpaceType()) + } case provider.ListStorageSpacesRequest_Filter_TYPE_ID: spaceID, nodeID, _ = utils.SplitStorageSpaceID(filter[i].GetId().OpaqueId) } @@ -226,6 +233,7 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // an efficient lookup would be possible if we received a spaceid&opaqueid in the request // the personal spaces must also use the nodeid and not the name + numShares := 0 for i := range matches { // always read link in case storage space id != node id if target, err := os.Readlink(matches[i]); err != nil { @@ -247,6 +255,7 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide } if spaceType == "share" && utils.UserEqual(u.Id, owner) { + numShares++ // do not list shares as spaces for the owner continue } @@ -262,23 +271,20 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide spaces = append(spaces, space) } } - if len(matches) == 0 && nodeID != spaceID { + // if there are no matches (or they happened to be spaces for the owner) and the node is a child return a space + if len(matches) <= numShares && nodeID != spaceID { // try node id target := filepath.Join(fs.o.Root, "nodes", nodeID) n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) if err != nil { return nil, err } - space, err := fs.storageSpaceFromNode(ctx, n, n.InternalPath(), permissions) - if err != nil { - return nil, err - } - if space.Id.OpaqueId == spaceID { + if n.Exists { + space, err := fs.storageSpaceFromNode(ctx, n, n.InternalPath(), permissions) + if err != nil { + return nil, err + } spaces = append(spaces, space) - } else { - appctx.GetLogger(ctx).Debug().Err(err). - Str("spaceid", spaceID).Str("nodeid", nodeID). - Interface("space", space).Msg("mismatching spaceid, skipping") } } @@ -441,7 +447,7 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, Id: &provider.StorageSpaceId{OpaqueId: n.SpaceRoot.ID}, Root: &provider.ResourceId{ StorageId: n.SpaceRoot.ID, - OpaqueId: n.ID, + OpaqueId: n.SpaceRoot.ID, }, Name: sname, SpaceType: spaceType, diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index b105ddd0f9..890aac74da 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -270,20 +270,11 @@ func (t *Tree) CreateDir(ctx context.Context, n *node.Node) (err error) { return } - e := t.Delete(ctx, n) - switch { - case e != nil: - appctx.GetLogger(ctx).Debug().Err(e).Msg("cannot move to trashcan") - default: - _, rm, e := t.PurgeRecycleItemFunc(ctx, n.SpaceRoot.ID, n.ID, "") - if e == nil { - e = rm() - if e != nil { - appctx.GetLogger(ctx).Debug().Err(e).Msg("cannot purge from trashbin") - } - } + // try to remove the node + e := os.RemoveAll(n.InternalPath()) + if e != nil { + appctx.GetLogger(ctx).Debug().Err(e).Msg("cannot delete node") } - return errtypes.AlreadyExists(err.Error()) } return t.Propagate(ctx, n) diff --git a/tests/integration/grpc/gateway_storageprovider_test.go b/tests/integration/grpc/gateway_storageprovider_test.go index baf20e188e..0de1bd45af 100644 --- a/tests/integration/grpc/gateway_storageprovider_test.go +++ b/tests/integration/grpc/gateway_storageprovider_test.go @@ -631,9 +631,9 @@ var _ = Describe("gateway", func() { Describe("Move", func() { It("moves a directory", func() { - sourceRef := &storagep.Reference{ResourceId: &storagep.ResourceId{StorageId: homeSpace.Id.OpaqueId, OpaqueId: homeSpace.Id.OpaqueId}, Path: "/source"} - targetRef := &storagep.Reference{ResourceId: &storagep.ResourceId{StorageId: homeSpace.Id.OpaqueId, OpaqueId: homeSpace.Id.OpaqueId}, Path: "/destination"} - dstRef := &storagep.Reference{ResourceId: &storagep.ResourceId{StorageId: homeSpace.Id.OpaqueId}, Path: "/destination/source"} + sourceRef := &storagep.Reference{ResourceId: &storagep.ResourceId{StorageId: homeSpace.Id.OpaqueId, OpaqueId: homeSpace.Id.OpaqueId}, Path: "./source"} + targetRef := &storagep.Reference{ResourceId: &storagep.ResourceId{StorageId: homeSpace.Id.OpaqueId, OpaqueId: homeSpace.Id.OpaqueId}, Path: "./destination"} + dstRef := &storagep.Reference{ResourceId: &storagep.ResourceId{StorageId: homeSpace.Id.OpaqueId, OpaqueId: homeSpace.Id.OpaqueId}, Path: "./destination/source"} err := fs.CreateDir(ctx, sourceRef) Expect(err).ToNot(HaveOccurred()) diff --git a/tests/oc-integration-tests/drone/gateway.toml b/tests/oc-integration-tests/drone/gateway.toml index e33ee39dc6..82ebbb5ec4 100644 --- a/tests/oc-integration-tests/drone/gateway.toml +++ b/tests/oc-integration-tests/drone/gateway.toml @@ -66,7 +66,9 @@ home_template = "/users/{{.Id.OpaqueId}}" ## the virtual /Shares folder of every user is routed like this: ## whenever the path matches the pattern /users/{{.CurrentUser.Id.OpaqueId}}/Shares we forward requests to the sharesstorageprovider [grpc.services.storageregistry.drivers.spaces.providers."localhost:14000".spaces] -"share" = { "mount_point" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", "path_template" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}" } +"virtual" = { "mount_point" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares" } +"grant" = { "mount_point" = "." } +"mountpoint" = { "mount_point" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", "path_template" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}" } ## An alternative would be used to mount shares outside of the users home: #"localhost:14000" = {"mount_path" = "/shares", "space_type" = "share", "path_template" = "/shares/{{.Space.Name}}", "description" = "shares"} diff --git a/tests/oc-integration-tests/local/gateway.toml b/tests/oc-integration-tests/local/gateway.toml index 108dcb3410..ae69da4a2f 100644 --- a/tests/oc-integration-tests/local/gateway.toml +++ b/tests/oc-integration-tests/local/gateway.toml @@ -72,7 +72,9 @@ home_template = "/users/{{.Id.OpaqueId}}" ## the virtual /Shares folder of every user is routed like this: ## whenever the path matches the pattern /users/{{.CurrentUser.Id.OpaqueId}}/Shares we forward requests to the sharesstorageprovider [grpc.services.storageregistry.drivers.spaces.providers."localhost:14000".spaces] -"share" = { "mount_point" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", "path_template" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}" } +"virtual" = { "mount_point" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares" } +"grant" = { "mount_point" = "." } +"mountpoint" = { "mount_point" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", "path_template" = "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}" } ## An alternative would be used to mount shares outside of the users home: #"localhost:14000" = {"mount_path" = "/shares", "space_type" = "share", "path_template" = "/shares/{{.Space.Name}}", "description" = "shares"}