From ee69f2faaab84f0dbfeb642edfa74ebbf8febc34 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 25 Jun 2021 10:56:25 +0200 Subject: [PATCH 1/8] implement depth handling for propfinds on the trash-bin --- go.mod | 2 +- go.sum | 4 +- .../grpc/services/gateway/storageprovider.go | 1 + .../storageprovider/storageprovider.go | 8 +- .../http/services/owncloud/ocdav/trashbin.go | 62 +++++++- pkg/storage/fs/owncloud/owncloud.go | 2 +- pkg/storage/fs/owncloudsql/owncloudsql.go | 2 +- pkg/storage/fs/s3/s3.go | 2 +- pkg/storage/storage.go | 2 +- pkg/storage/utils/decomposedfs/recycle.go | 148 ++++++++++++++++-- pkg/storage/utils/eosfs/eosfs.go | 2 +- pkg/storage/utils/localfs/localfs.go | 2 +- 12 files changed, 212 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index b02feb08c9..09ededa618 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20210614143420-5ee2eb1e7887 + github.com/cs3org/go-cs3apis v0.0.0-20210702091910-85a56bfd027f github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 diff --git a/go.sum b/go.sum index f9984dd5f0..f63843c651 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20210614143420-5ee2eb1e7887 h1:X5Se3M/kbh9w6LZQvyLS7djAGKcWGzmaY6IOa7Talpk= -github.com/cs3org/go-cs3apis v0.0.0-20210614143420-5ee2eb1e7887/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20210702091910-85a56bfd027f h1:l09QSEPO8DI3V2hBnc6KhTsrNg/DTyBYjCTwSb/HR6Q= +github.com/cs3org/go-cs3apis v0.0.0-20210702091910-85a56bfd027f/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 80fa004610..1ce15afe34 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -1928,6 +1928,7 @@ func (s *svc) ListRecycle(ctx context.Context, req *gateway.ListRecycleRequest) Opaque: req.Opaque, FromTs: req.FromTs, ToTs: req.ToTs, + Ref: req.Ref, }) if err != nil { return nil, errors.Wrap(err, "gateway: error calling ListRecycleRequest") diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 9c8529971e..e73ef6191c 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -793,7 +793,7 @@ func (s *service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p ctx := ss.Context() log := appctx.GetLogger(ctx) - items, err := s.storage.ListRecycle(ctx) + items, err := s.storage.ListRecycle(ctx, nil) if err != nil { var st *rpc.Status switch err.(type) { @@ -829,7 +829,11 @@ func (s *service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p } func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) (*provider.ListRecycleResponse, error) { - items, err := s.storage.ListRecycle(ctx) + ref, err := s.unwrap(ctx, req.Ref) + if err != nil { + return nil, err + } + items, err := s.storage.ListRecycle(ctx, ref) // TODO(labkode): CRITICAL: fill recycle info with storage provider. if err != nil { var st *rpc.Status diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index d19c086631..24cf6b21ce 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -107,8 +107,8 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { // return //} - if key == "" && r.Method == "PROPFIND" { - h.listTrashbin(w, r, s, u) + if r.Method == "PROPFIND" { + h.listTrashbin(w, r, s, u, path.Join(key, r.URL.Path)) return } if key != "" && r.Method == "MOVE" { @@ -142,13 +142,25 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { }) } -func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User) { +func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "listTrashbin") defer span.End() + depth := r.Header.Get("Depth") + if depth == "" { + depth = "1" + } + sublog := appctx.GetLogger(ctx).With().Logger() + // see https://tools.ietf.org/html/rfc4918#section-9.1 + if depth != "0" && depth != "1" && depth != "infinity" { + sublog.Debug().Str("depth", depth).Msgf("invalid Depth header value") + w.WriteHeader(http.StatusBadRequest) + return + } + pf, status, err := readPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") @@ -179,7 +191,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s // ask gateway for recycle items // TODO(labkode): add Reference to ListRecycleRequest - getRecycleRes, err := gc.ListRecycle(ctx, &gateway.ListRecycleRequest{Ref: &provider.Reference{Path: getHomeRes.Path}}) + getRecycleRes, err := gc.ListRecycle(ctx, &gateway.ListRecycleRequest{Ref: &provider.Reference{Path: filepath.Join(getHomeRes.Path, key)}}) if err != nil { sublog.Error().Err(err).Msg("error calling ListRecycle") @@ -192,7 +204,47 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } - propRes, err := h.formatTrashPropfind(ctx, s, u, &pf, getRecycleRes.RecycleItems) + items := getRecycleRes.RecycleItems + + if depth == "infinity" { + var stack []string + // 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(items) - 1; i >= 0; i-- { + // for i := range res.Infos { + if items[i].Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + stack = append(stack, items[i].Key) + } + } + + for len(stack) > 0 { + key := stack[len(stack)-1] + getRecycleRes, err := gc.ListRecycle(ctx, &gateway.ListRecycleRequest{Ref: &provider.Reference{Path: path.Join(getHomeRes.Path, key)}}) + if err != nil { + sublog.Error().Err(err).Msg("error calling ListRecycle") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if getRecycleRes.Status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, getRecycleRes.Status) + return + } + items = append(items, getRecycleRes.RecycleItems...) + + stack = stack[:len(stack)-1] + // 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(getRecycleRes.RecycleItems) - 1; i >= 0; i-- { + // for i := range res.Infos { + if getRecycleRes.RecycleItems[i].Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + stack = append(stack, getRecycleRes.RecycleItems[i].Key) + } + } + } + } + + propRes, err := h.formatTrashPropfind(ctx, s, u, &pf, items) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 27c202aa5b..8348282da0 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2153,7 +2153,7 @@ func (fs *ocfs) convertToRecycleItem(ctx context.Context, rp string, md os.FileI } } -func (fs *ocfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) { +func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { // TODO check permission? on what? user must be the owner? rp, err := fs.getRecyclePath(ctx) if err != nil { diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index 589338da11..553f612b49 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1972,7 +1972,7 @@ func (fs *ocfs) convertToRecycleItem(ctx context.Context, md os.FileInfo) *provi } } -func (fs *ocfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) { +func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { // TODO check permission? on what? user must be the owner? rp, err := fs.getRecyclePath(ctx) if err != nil { diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index 38c3b6b4f1..bae99f7ed4 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -654,7 +654,7 @@ func (fs *s3FS) EmptyRecycle(ctx context.Context) error { return errtypes.NotSupported("empty recycle") } -func (fs *s3FS) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) { +func (fs *s3FS) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { return nil, errtypes.NotSupported("list recycle") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 627a649a00..acb4cac988 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -42,7 +42,7 @@ type FS interface { ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error - ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) + ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error PurgeRecycleItem(ctx context.Context, key string) error EmptyRecycle(ctx context.Context) error diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index 28f3bfedcc..a6cc999aa9 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -21,6 +21,7 @@ package decomposedfs import ( "context" "os" + "path" "path/filepath" "strings" "time" @@ -45,12 +46,10 @@ import ( // contain a directory with symlinks to trash files for every userid/"root" // ListRecycle returns the list of available recycle items -func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.RecycleItem, err error) { +func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { log := appctx.GetLogger(ctx) - trashRoot := fs.getRecycleRoot(ctx) - - items = make([]*provider.RecycleItem, 0) + items := make([]*provider.RecycleItem, 0) // TODO how do we check if the storage allows listing the recycle for the current user? check owner of the root of the storage? // use permissions ReadUserPermissions? @@ -66,6 +65,121 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy } } + if ref.Path == "/" { + return fs.listTrashRoot(ctx) + } + + trashRoot := fs.getRecycleRoot(ctx) + f, err := os.Open(filepath.Join(trashRoot, ref.Path)) + if err != nil { + if os.IsNotExist(err) { + return items, nil + } + return nil, errors.Wrapf(err, "tree: error listing %s", trashRoot) + } + defer f.Close() + + root, tail := shiftPath(ref.Path) + parentNode, err := os.Readlink(filepath.Join(trashRoot, root)) + if err != nil { + log.Error().Err(err).Str("trashRoot", trashRoot).Msg("error reading trash link, skipping") + return nil, err + } + + if md, err := f.Stat(); err != nil { + return nil, err + } else if !md.IsDir() { + // this is the case when we want to directly list a file in the trashbin + if item, err := fs.createTrashItem(ctx, parentNode, filepath.Dir(tail), filepath.Join(trashRoot, ref.Path)); err != nil { + return items, err + } else { + items = append(items, item) + return items, err + } + } + + names, err := f.Readdirnames(0) + if err != nil { + return nil, err + } + for i := range names { + if item, err := fs.createTrashItem(ctx, parentNode, tail, filepath.Join(trashRoot, ref.Path, names[i])); err == nil { + items = append(items, item) + } + } + return items, nil +} + +func (fs *Decomposedfs) createTrashItem(ctx context.Context, parentNode, intermediatePath, itemPath string) (*provider.RecycleItem, error) { + log := appctx.GetLogger(ctx) + trashRoot := fs.getRecycleRoot(ctx) + trashnode, err := os.Readlink(itemPath) + if err != nil { + log.Error().Err(err).Str("trashRoot", trashRoot).Msg("error reading trash link, skipping") + return nil, err + } + parts := strings.SplitN(filepath.Base(parentNode), ".T.", 2) + if len(parts) != 2 { + log.Error().Str("trashRoot", trashRoot).Str("trashnode", trashnode).Interface("parts", parts).Msg("malformed trash link, skipping") + return nil, errors.New("malformed trash link") + } + + nodePath := fs.lu.InternalPath(filepath.Base(trashnode)) + md, err := os.Stat(nodePath) + if err != nil { + log.Error().Err(err).Str("trashRoot", trashRoot).Str("trashnode", trashnode).Msg("could not stat trash item, skipping") + return nil, err + } + + item := &provider.RecycleItem{ + Type: getResourceType(md.IsDir()), + Size: uint64(md.Size()), + Key: path.Join(parts[0], intermediatePath, filepath.Base(itemPath)), + } + if deletionTime, err := time.Parse(time.RFC3339Nano, parts[1]); err == nil { + item.DeletionTime = &types.Timestamp{ + Seconds: uint64(deletionTime.Unix()), + // TODO nanos + } + } else { + log.Error().Err(err).Str("trashRoot", trashRoot).Str("link", trashnode).Interface("parts", parts).Msg("could parse time format, ignoring") + } + + // lookup origin path in extended attributes + parentPath := fs.lu.InternalPath(filepath.Base(parentNode)) + if attrBytes, err := xattr.Get(parentPath, xattrs.TrashOriginAttr); err == nil { + item.Ref = &provider.Reference{Path: filepath.Join(string(attrBytes), intermediatePath, filepath.Base(itemPath))} + } else { + log.Error().Err(err).Str("trashRoot", trashRoot).Str("link", trashnode).Msg("could not read origin path, skipping") + return nil, err + } + // TODO filter results by permission ... on the original parent? or the trashed node? + // if it were on the original parent it would be possible to see files that were trashed before the current user got access + // so -> check the trash node itself + // hmm listing trash currently lists the current users trash or the 'root' trash. from ocs only the home storage is queried for trash items. + // for now we can only really check if the current user is the owner + if attrBytes, err := xattr.Get(nodePath, xattrs.OwnerIDAttr); err == nil { + if fs.o.EnableHome { + u := user.ContextMustGetUser(ctx) + if u.Id.OpaqueId != string(attrBytes) { + log.Warn().Str("trashRoot", trashRoot).Str("link", trashnode).Msg("trash item not owned by current user, skipping") + // continue + return nil, errors.New("trash item not owned by current user") + } + } + } else { + log.Error().Err(err).Str("trashRoot", trashRoot).Str("link", trashnode).Msg("could not read owner, skipping") + return nil, err + } + + return item, nil +} + +func (fs *Decomposedfs) listTrashRoot(ctx context.Context) ([]*provider.RecycleItem, error) { + log := appctx.GetLogger(ctx) + items := make([]*provider.RecycleItem, 0) + + trashRoot := fs.getRecycleRoot(ctx) f, err := os.Open(trashRoot) if err != nil { if os.IsNotExist(err) { @@ -79,12 +193,11 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy if err != nil { return nil, err } + for i := range names { - var trashnode string - trashnode, err = os.Readlink(filepath.Join(trashRoot, names[i])) + trashnode, err := os.Readlink(filepath.Join(trashRoot, names[i])) if err != nil { log.Error().Err(err).Str("trashRoot", trashRoot).Str("name", names[i]).Msg("error reading trash link, skipping") - err = nil continue } parts := strings.SplitN(filepath.Base(trashnode), ".T.", 2) @@ -96,7 +209,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy nodePath := fs.lu.InternalPath(filepath.Base(trashnode)) md, err := os.Stat(nodePath) if err != nil { - log.Error().Err(err).Str("trashRoot", trashRoot).Str("name", names[i]).Str("trashnode", trashnode).Interface("parts", parts).Msg("could not stat trash item, skipping") + log.Error().Err(err).Str("trashRoot", trashRoot).Str("name", names[i]).Str("trashnode", trashnode). /*.Interface("parts", parts)*/ Msg("could not stat trash item, skipping") continue } @@ -142,7 +255,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy items = append(items, item) } - return + return items, nil } // RestoreRecycleItem restores the specified item @@ -220,3 +333,20 @@ func (fs *Decomposedfs) getRecycleRoot(ctx context.Context) string { } return filepath.Join(fs.o.Root, "trash", "root") } + +// shiftPath splits off the first component of p, which will be cleaned of +// relative components before processing. head will never contain a slash and +// tail will always be a rooted path without trailing slash. +// see https://blog.merovius.de/2017/06/18/how-not-to-use-an-http-router.html +// and https://gist.github.com/weatherglass/62bd8a704d4dfdc608fe5c5cb5a6980c#gistcomment-2161690 for the zero alloc code below +func shiftPath(p string) (head, tail string) { + if p == "" { + return "", "/" + } + p = strings.TrimPrefix(path.Clean(p), "/") + i := strings.Index(p, "/") + if i < 0 { + return p, "/" + } + return p[:i], p[i:] +} diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 52d724a64f..3a60368026 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1380,7 +1380,7 @@ func (fs *eosfs) EmptyRecycle(ctx context.Context) error { return fs.c.PurgeDeletedEntries(ctx, uid, gid) } -func (fs *eosfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) { +func (fs *eosfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { u, err := getUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index 5416f6fa39..9eeb1167b6 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1181,7 +1181,7 @@ func (fs *localfs) convertToRecycleItem(ctx context.Context, rp string, md os.Fi } } -func (fs *localfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error) { +func (fs *localfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { rp := fs.wrapRecycleBin(ctx, "/") From 4adfa446eb7540c5da524ccbe54bcbc292603c77 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 25 Jun 2021 14:37:03 +0200 Subject: [PATCH 2/8] implement purging of specific files in the trash-bin --- .../storageprovider/storageprovider.go | 2 +- .../http/services/owncloud/ocdav/trashbin.go | 5 +-- pkg/storage/fs/owncloud/owncloud.go | 8 ++--- pkg/storage/fs/owncloudsql/owncloudsql.go | 8 ++--- pkg/storage/fs/s3/s3.go | 2 +- pkg/storage/storage.go | 2 +- .../utils/decomposedfs/decomposedfs.go | 2 +- pkg/storage/utils/decomposedfs/recycle.go | 6 ++-- pkg/storage/utils/decomposedfs/tree/tree.go | 32 +++++++++++-------- .../utils/decomposedfs/tree/tree_test.go | 4 +-- pkg/storage/utils/eosfs/eosfs.go | 2 +- pkg/storage/utils/localfs/localfs.go | 4 +-- 12 files changed, 41 insertions(+), 36 deletions(-) diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index e73ef6191c..fd7320f3cd 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -883,7 +883,7 @@ func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreR func (s *service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) { // if a key was sent as opaque id purge only that item if req.GetRef().GetResourceId() != nil && req.GetRef().GetResourceId().OpaqueId != "" { - if err := s.storage.PurgeRecycleItem(ctx, req.GetRef().GetResourceId().OpaqueId); err != nil { + if err := s.storage.PurgeRecycleItem(ctx, req.GetRef()); err != nil { var st *rpc.Status switch err.(type) { case errtypes.IsNotFound: diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 24cf6b21ce..a6a99f4013 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -134,7 +134,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { } if r.Method == "DELETE" { - h.delete(w, r, s, u, key) + h.delete(w, r, s, u, key, r.URL.Path) return } @@ -555,7 +555,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } // delete has only a key -func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key string) { +func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key, path string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "erase") defer span.End() @@ -599,6 +599,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, StorageId: sRes.Info.Id.StorageId, OpaqueId: key, }, + Path: utils.MakeRelativePath(path), }, } diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 8348282da0..b2333c53c2 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2060,12 +2060,12 @@ func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, re return fs.propagate(ctx, ip) } -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key string) error { +func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "ocfs: error resolving recycle path") } - ip := filepath.Join(rp, filepath.Clean(key)) + ip := filepath.Join(rp, filepath.Clean(ref.ResourceId.OpaqueId)) // TODO check permission? // check permissions @@ -2086,7 +2086,7 @@ func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key string) error { if err != nil { return errors.Wrap(err, "ocfs: error deleting recycle item") } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(key))) + err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(ref.ResourceId.OpaqueId))) if err != nil { return errors.Wrap(err, "ocfs: error deleting recycle item versions") } @@ -2161,7 +2161,7 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr } // list files folder - mds, err := ioutil.ReadDir(rp) + mds, err := ioutil.ReadDir(filepath.Join(rp, ref.Path)) if err != nil { log := appctx.GetLogger(ctx) log.Debug().Err(err).Str("path", rp).Msg("trash not readable") diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index 553f612b49..aee9f2129b 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1864,12 +1864,12 @@ func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, re return fs.propagate(ctx, ip) } -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key string) error { +func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "owncloudsql: error resolving recycle path") } - ip := filepath.Join(rp, filepath.Clean(key)) + ip := filepath.Join(rp, filepath.Clean(ref.ResourceId.OpaqueId)) // TODO check permission? // check permissions @@ -1890,12 +1890,12 @@ func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key string) error { if err != nil { return errors.Wrap(err, "owncloudsql: error deleting recycle item") } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(key))) + err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(ref.ResourceId.OpaqueId))) if err != nil { return errors.Wrap(err, "owncloudsql: error deleting recycle item versions") } - base, ttime, err := splitTrashKey(key) + base, ttime, err := splitTrashKey(ref.ResourceId.OpaqueId) if err != nil { return err } diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index bae99f7ed4..24c150ec26 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -646,7 +646,7 @@ func (fs *s3FS) RestoreRevision(ctx context.Context, ref *provider.Reference, re return errtypes.NotSupported("restore revision") } -func (fs *s3FS) PurgeRecycleItem(ctx context.Context, key string) error { +func (fs *s3FS) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { return errtypes.NotSupported("purge recycle item") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index acb4cac988..08f5dbf8c3 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -44,7 +44,7 @@ type FS interface { RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error - PurgeRecycleItem(ctx context.Context, key string) error + PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error EmptyRecycle(ctx context.Context) error GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index eb31b6eb97..0fa149de10 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -69,7 +69,7 @@ type Tree interface { Move(ctx context.Context, oldNode *node.Node, newNode *node.Node) (err error) Delete(ctx context.Context, node *node.Node) (err error) RestoreRecycleItemFunc(ctx context.Context, key, restorePath string) (*node.Node, func() error, error) // FIXME REFERENCE use ref instead of path - PurgeRecycleItemFunc(ctx context.Context, key string) (*node.Node, func() error, error) + PurgeRecycleItemFunc(ctx context.Context, key, purgePath string) (*node.Node, func() error, error) WriteBlob(key string, reader io.Reader) error ReadBlob(key string) (io.ReadCloser, error) diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index a6cc999aa9..d3ed9938bb 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -284,8 +284,8 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key string, rest } // PurgeRecycleItem purges the specified item -func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, key string) error { - rn, purgeFunc, err := fs.tp.PurgeRecycleItemFunc(ctx, key) +func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { + rn, purgeFunc, err := fs.tp.PurgeRecycleItemFunc(ctx, ref.ResourceId.OpaqueId, ref.Path) if err != nil { return err } @@ -298,7 +298,7 @@ func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, key string) error case err != nil: return errtypes.InternalError(err.Error()) case !ok: - return errtypes.PermissionDenied(key) + return errtypes.PermissionDenied(ref.ResourceId.OpaqueId) } // Run the purge func diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index af74301078..ca4df1afe1 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -25,7 +25,6 @@ import ( "os" "path/filepath" "strconv" - "strings" "time" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -411,7 +410,7 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { // RestoreRecycleItemFunc returns a node and a function to restore it from the trash func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, restorePath string) (*node.Node, func() error, error) { - rn, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, key) + rn, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, key, "") if err != nil { return nil, nil, err } @@ -461,8 +460,8 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, restorePath stri } // PurgeRecycleItemFunc returns a node and a function to purge it from the trash -func (t *Tree) PurgeRecycleItemFunc(ctx context.Context, key string) (*node.Node, func() error, error) { - rn, trashItem, deletedNodePath, _, err := t.readRecycleItem(ctx, key) +func (t *Tree) PurgeRecycleItemFunc(ctx context.Context, key string, path string) (*node.Node, func() error, error) { + rn, trashItem, deletedNodePath, _, err := t.readRecycleItem(ctx, key, path) if err != nil { return nil, nil, err } @@ -696,13 +695,13 @@ func (t *Tree) createNode(n *node.Node, owner *userpb.UserId) (err error) { } // TODO refactor the returned params into Node properties? would make all the path transformations go away... -func (t *Tree) readRecycleItem(ctx context.Context, key string) (n *node.Node, trashItem string, deletedNodePath string, origin string, err error) { +func (t *Tree) readRecycleItem(ctx context.Context, key, path string) (n *node.Node, trashItem string, deletedNodePath string, origin string, err error) { if key == "" { return nil, "", "", "", errtypes.InternalError("key is empty") } u := user.ContextMustGetUser(ctx) - trashItem = filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key) + trashItem = filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key, path) var link string link, err = os.Readlink(trashItem) @@ -710,11 +709,8 @@ func (t *Tree) readRecycleItem(ctx context.Context, key string) (n *node.Node, t appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Msg("error reading trash link") return } - parts := strings.SplitN(filepath.Base(link), ".T.", 2) - if len(parts) != 2 { - appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Interface("parts", parts).Msg("malformed trash link") - return - } + + nodeID := filepath.Base(link) var attrBytes []byte deletedNodePath = t.lookup.InternalPath(filepath.Base(link)) @@ -733,7 +729,7 @@ func (t *Tree) readRecycleItem(ctx context.Context, key string) (n *node.Node, t return } - n = node.New(parts[0], "", "", 0, "", owner, t.lookup) + n = node.New(nodeID, "", "", 0, "", owner, t.lookup) // lookup blobID in extended attributes if attrBytes, err = xattr.Get(deletedNodePath, xattrs.BlobIDAttr); err == nil { n.BlobID = string(attrBytes) @@ -757,9 +753,17 @@ func (t *Tree) readRecycleItem(ctx context.Context, key string) (n *node.Node, t // get origin node origin = "/" + trashItemRoot := filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key) + rootLink, err := os.Readlink(trashItemRoot) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Msg("error reading trash link") + return + } + + deletedNodeRootPath := t.lookup.InternalPath(filepath.Base(rootLink)) // lookup origin path in extended attributes - if attrBytes, err = xattr.Get(deletedNodePath, xattrs.TrashOriginAttr); err == nil { - origin = string(attrBytes) + if attrBytes, err = xattr.Get(deletedNodeRootPath, xattrs.TrashOriginAttr); err == nil { + origin = filepath.Join(string(attrBytes), path) } else { log.Error().Err(err).Str("trashItem", trashItem).Str("link", link).Str("deletedNodePath", deletedNodePath).Msg("could not read origin path, restoring to /") } diff --git a/pkg/storage/utils/decomposedfs/tree/tree_test.go b/pkg/storage/utils/decomposedfs/tree/tree_test.go index ad83131c3a..d41d2f530a 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree_test.go +++ b/pkg/storage/utils/decomposedfs/tree/tree_test.go @@ -115,7 +115,7 @@ var _ = Describe("Tree", func() { _, err := os.Stat(trashPath) Expect(err).ToNot(HaveOccurred()) - _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.ID) + _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.ID, "") Expect(err).ToNot(HaveOccurred()) Expect(purgeFunc()).To(Succeed()) }) @@ -203,7 +203,7 @@ var _ = Describe("Tree", func() { _, err := os.Stat(trashPath) Expect(err).ToNot(HaveOccurred()) - _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.ID) + _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.ID, "") Expect(err).ToNot(HaveOccurred()) Expect(purgeFunc()).To(Succeed()) }) diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 3a60368026..d59084e601 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1362,7 +1362,7 @@ func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, r return fs.c.RollbackToVersion(ctx, uid, gid, fn, revisionKey) } -func (fs *eosfs) PurgeRecycleItem(ctx context.Context, key string) error { +func (fs *eosfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { return errtypes.NotSupported("eosfs: operation not supported") } diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index 9eeb1167b6..f07a744ac0 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1131,8 +1131,8 @@ func (fs *localfs) RestoreRevision(ctx context.Context, ref *provider.Reference, return fs.propagate(ctx, np) } -func (fs *localfs) PurgeRecycleItem(ctx context.Context, key string) error { - rp := fs.wrapRecycleBin(ctx, key) +func (fs *localfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { + rp := fs.wrapRecycleBin(ctx, ref.ResourceId.OpaqueId) if err := os.Remove(rp); err != nil { return errors.Wrap(err, "localfs: error deleting recycle item") From f0f9c7ad0a2a2f458ebb912ee5a21a83320dd47f Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 25 Jun 2021 15:22:15 +0200 Subject: [PATCH 3/8] fix lookup of recycle item --- pkg/storage/utils/decomposedfs/tree/tree.go | 26 +++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index ca4df1afe1..a976256c69 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "time" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -711,6 +712,14 @@ func (t *Tree) readRecycleItem(ctx context.Context, key, path string) (n *node.N } nodeID := filepath.Base(link) + if path == "" { + parts := strings.SplitN(filepath.Base(link), ".T.", 2) + if len(parts) != 2 { + appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Interface("parts", parts).Msg("malformed trash link") + return + } + nodeID = parts[0] + } var attrBytes []byte deletedNodePath = t.lookup.InternalPath(filepath.Base(link)) @@ -753,14 +762,17 @@ func (t *Tree) readRecycleItem(ctx context.Context, key, path string) (n *node.N // get origin node origin = "/" - trashItemRoot := filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key) - rootLink, err := os.Readlink(trashItemRoot) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Msg("error reading trash link") - return + deletedNodeRootPath := deletedNodePath + if path != "" { + trashItemRoot := filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key) + var rootLink string + rootLink, err = os.Readlink(trashItemRoot) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Msg("error reading trash link") + return + } + deletedNodeRootPath = t.lookup.InternalPath(filepath.Base(rootLink)) } - - deletedNodeRootPath := t.lookup.InternalPath(filepath.Base(rootLink)) // lookup origin path in extended attributes if attrBytes, err = xattr.Get(deletedNodeRootPath, xattrs.TrashOriginAttr); err == nil { origin = filepath.Join(string(attrBytes), path) From 986fffe106048308ddb573bcf611b616a38b52a7 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Mon, 28 Jun 2021 16:35:37 +0200 Subject: [PATCH 4/8] implement restore for specific files in the trash-bin tree --- .../services/storageprovider/storageprovider.go | 9 ++++++++- internal/http/services/owncloud/ocdav/trashbin.go | 6 +++--- pkg/storage/fs/owncloud/owncloud.go | 12 ++++++------ pkg/storage/fs/owncloudsql/owncloudsql.go | 10 +++++----- pkg/storage/fs/s3/s3.go | 2 +- pkg/storage/storage.go | 2 +- pkg/storage/utils/decomposedfs/decomposedfs.go | 2 +- pkg/storage/utils/decomposedfs/recycle.go | 6 +++--- pkg/storage/utils/decomposedfs/tree/tree.go | 14 ++++++++++---- pkg/storage/utils/decomposedfs/tree/tree_test.go | 6 +++--- pkg/storage/utils/eosfs/eosfs.go | 4 ++-- pkg/storage/utils/localfs/localfs.go | 12 ++++++------ 12 files changed, 49 insertions(+), 36 deletions(-) diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index fd7320f3cd..176285fe86 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -859,7 +859,14 @@ func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequ func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecycleItemRequest) (*provider.RestoreRecycleItemResponse, error) { // TODO(labkode): CRITICAL: fill recycle info with storage provider. - if err := s.storage.RestoreRecycleItem(ctx, req.Key, req.RestoreRef); err != nil { + ref, err := s.unwrap(ctx, req.Ref) + if err != nil { + return nil, err + } + ref.ResourceId = &provider.ResourceId{ + OpaqueId: req.Key, + } + if err := s.storage.RestoreRecycleItem(ctx, ref, req.RestoreRef); err != nil { var st *rpc.Status switch err.(type) { case errtypes.IsNotFound: diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index a6a99f4013..3a32ccc6b0 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -129,7 +129,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { log.Debug().Str("key", key).Str("dst", dst).Msg("restore") - h.restore(w, r, s, u, dst, key) + h.restore(w, r, s, u, dst, key, r.URL.Path) return } @@ -415,7 +415,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use } // restore has a destination and a key -func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, dst string, key string) { +func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, dst, key, resourcePath string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "restore") defer span.End() @@ -516,7 +516,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc // 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: getHomeRes.Path, + Path: path.Join(getHomeRes.Path, resourcePath), }, Key: key, RestoreRef: &provider.Reference{Path: dst}, diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index b2333c53c2..05e24f9719 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2180,18 +2180,18 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr return items, nil } -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error { +func (fs *ocfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { // TODO check permission? on what? user must be the owner? log := appctx.GetLogger(ctx) rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "ocfs: error resolving recycle path") } - src := filepath.Join(rp, filepath.Clean(key)) + src := filepath.Join(rp, filepath.Clean(trashRef.ResourceId.OpaqueId)) suffix := filepath.Ext(src) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - log.Error().Str("key", key).Str("path", src).Msg("invalid trash item suffix") + log.Error().Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("invalid trash item suffix") return nil } @@ -2201,20 +2201,20 @@ func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key string, restoreRef * if restoreRef.Path == "" { v, err := xattr.Get(src, trashOriginPrefix) if err != nil { - log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin") + log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("could not read origin") } restoreRef.Path = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix)) } tgt := fs.toInternalPath(ctx, restoreRef.Path) // move back to original location if err := os.Rename(src, tgt); err != nil { - log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") + log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") return errors.Wrap(err, "ocfs: could not restore item") } // unset trash origin location in metadata if err := xattr.Remove(tgt, trashOriginPrefix); err != nil { // just a warning, will be overwritten next time it is deleted - log.Warn().Err(err).Str("key", key).Str("tgt", tgt).Msg("could not unset origin") + log.Warn().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("tgt", tgt).Msg("could not unset origin") } // TODO(jfd) restore versions diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index aee9f2129b..4d9d478b65 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1999,32 +1999,32 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr return items, nil } -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error { +func (fs *ocfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { // TODO check permission? on what? user must be the owner? log := appctx.GetLogger(ctx) rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "owncloudsql: error resolving recycle path") } - src := filepath.Join(rp, filepath.Clean(key)) + src := filepath.Join(rp, filepath.Clean(trashRef.ResourceId.OpaqueId)) suffix := filepath.Ext(src) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - log.Error().Str("key", key).Str("path", src).Msg("invalid trash item suffix") + log.Error().Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("invalid trash item suffix") return nil } if restoreRef.Path == "" { v, err := xattr.Get(src, trashOriginPrefix) if err != nil { - log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin") + log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("could not read origin") } restoreRef.Path = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix)) } tgt := fs.toInternalPath(ctx, restoreRef.Path) // move back to original location if err := os.Rename(src, tgt); err != nil { - log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") + log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") return errors.Wrap(err, "owncloudsql: could not restore item") } diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index 24c150ec26..ebff312e93 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -658,7 +658,7 @@ func (fs *s3FS) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr return nil, errtypes.NotSupported("list recycle") } -func (fs *s3FS) RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error { +func (fs *s3FS) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { return errtypes.NotSupported("restore recycle") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 08f5dbf8c3..5d198f2418 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -43,7 +43,7 @@ type FS interface { DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) - RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error + RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error EmptyRecycle(ctx context.Context) error GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 0fa149de10..ccce2b4fce 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -68,7 +68,7 @@ type Tree interface { // CreateReference(ctx context.Context, node *node.Node, targetURI *url.URL) error Move(ctx context.Context, oldNode *node.Node, newNode *node.Node) (err error) Delete(ctx context.Context, node *node.Node) (err error) - RestoreRecycleItemFunc(ctx context.Context, key, restorePath string) (*node.Node, func() error, error) // FIXME REFERENCE use ref instead of path + RestoreRecycleItemFunc(ctx context.Context, key, trashPath, restorePath string) (*node.Node, func() error, error) // FIXME REFERENCE use ref instead of path PurgeRecycleItemFunc(ctx context.Context, key, purgePath string) (*node.Node, func() error, error) WriteBlob(key string, reader io.Reader) error diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index d3ed9938bb..9a91483dbe 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -259,11 +259,11 @@ func (fs *Decomposedfs) listTrashRoot(ctx context.Context) ([]*provider.RecycleI } // RestoreRecycleItem restores the specified item -func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error { +func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { if restoreRef == nil { restoreRef = &provider.Reference{} } - rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, key, restoreRef.Path) + rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, trashRef.ResourceId.OpaqueId, trashRef.Path, restoreRef.Path) if err != nil { return err } @@ -276,7 +276,7 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key string, rest case err != nil: return errtypes.InternalError(err.Error()) case !ok: - return errtypes.PermissionDenied(key) + return errtypes.PermissionDenied(trashRef.ResourceId.OpaqueId) } // Run the restore func diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index a976256c69..1724c6175d 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -410,8 +410,8 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { } // RestoreRecycleItemFunc returns a node and a function to restore it from the trash -func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, restorePath string) (*node.Node, func() error, error) { - rn, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, key, "") +func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, trashPath, restorePath string) (*node.Node, func() error, error) { + rn, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, key, trashPath) if err != nil { return nil, nil, err } @@ -451,6 +451,12 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, restorePath stri return errors.Wrap(err, "Decomposedfs: could not set name attribute") } + if trashPath != "" { + if err := xattr.Set(nodePath, xattrs.ParentidAttr, []byte(n.ParentID)); err != nil { + return errors.Wrap(err, "Decomposedfs: could not set name attribute") + } + } + // delete item link in trash if err = os.Remove(trashItem); err != nil { log.Error().Err(err).Str("trashItem", trashItem).Msg("error deleting trashitem") @@ -712,7 +718,7 @@ func (t *Tree) readRecycleItem(ctx context.Context, key, path string) (n *node.N } nodeID := filepath.Base(link) - if path == "" { + if path == "" || path == "/" { parts := strings.SplitN(filepath.Base(link), ".T.", 2) if len(parts) != 2 { appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Interface("parts", parts).Msg("malformed trash link") @@ -763,7 +769,7 @@ func (t *Tree) readRecycleItem(ctx context.Context, key, path string) (n *node.N origin = "/" deletedNodeRootPath := deletedNodePath - if path != "" { + if path != "" && path != "/" { trashItemRoot := filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key) var rootLink string rootLink, err = os.Readlink(trashItemRoot) diff --git a/pkg/storage/utils/decomposedfs/tree/tree_test.go b/pkg/storage/utils/decomposedfs/tree/tree_test.go index d41d2f530a..08c9362a7c 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree_test.go +++ b/pkg/storage/utils/decomposedfs/tree/tree_test.go @@ -139,7 +139,7 @@ var _ = Describe("Tree", func() { }) It("restores the file to its original location if the targetPath is empty", func() { - _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "") + _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "", "") Expect(err).ToNot(HaveOccurred()) Expect(restoreFunc()).To(Succeed()) @@ -150,7 +150,7 @@ var _ = Describe("Tree", func() { }) It("restores files to different locations", func() { - _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "dir1/newLocation") + _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "", "dir1/newLocation") Expect(err).ToNot(HaveOccurred()) Expect(restoreFunc()).To(Succeed()) @@ -165,7 +165,7 @@ var _ = Describe("Tree", func() { }) It("removes the file from the trash", func() { - _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "") + _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "", "") Expect(err).ToNot(HaveOccurred()) Expect(restoreFunc()).To(Succeed()) diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index d59084e601..288ba63bbd 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1411,7 +1411,7 @@ func (fs *eosfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*p return recycleEntries, nil } -func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error { +func (fs *eosfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { u, err := getUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") @@ -1422,7 +1422,7 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key string, restoreRef return err } - return fs.c.RestoreDeletedEntry(ctx, uid, gid, key) + return fs.c.RestoreDeletedEntry(ctx, uid, gid, trashRef.ResourceId.OpaqueId) } func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index f07a744ac0..fcec170d42 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1199,14 +1199,14 @@ func (fs *localfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([] return items, nil } -func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string, restoreRef *provider.Reference) error { +func (fs *localfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { - suffix := path.Ext(restoreKey) + suffix := path.Ext(trashRef.ResourceId.OpaqueId) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { return errors.New("localfs: invalid trash item suffix") } - filePath, err := fs.getRecycledEntry(ctx, restoreKey) + filePath, err := fs.getRecycledEntry(ctx, trashRef.ResourceId.OpaqueId) if err != nil { return errors.Wrap(err, "localfs: invalid key") } @@ -1225,10 +1225,10 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string, re return errors.New("localfs: can't restore - file already exists at original path") } - rp := fs.wrapRecycleBin(ctx, restoreKey) + rp := fs.wrapRecycleBin(ctx, trashRef.ResourceId.OpaqueId) if _, err = os.Stat(rp); err != nil { if os.IsNotExist(err) { - return errtypes.NotFound(restoreKey) + return errtypes.NotFound(trashRef.ResourceId.OpaqueId) } return errors.Wrap(err, "localfs: error stating "+rp) } @@ -1237,7 +1237,7 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string, re return errors.Wrap(err, "ocfs: could not restore item") } - err = fs.removeFromRecycledDB(ctx, restoreKey) + err = fs.removeFromRecycledDB(ctx, trashRef.ResourceId.OpaqueId) if err != nil { return errors.Wrap(err, "localfs: error adding entry to DB") } From 6fde82bca710b462cd7a4f55278d70a1c9f4b653 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Mon, 28 Jun 2021 20:16:30 +0200 Subject: [PATCH 5/8] resolve linter issue and update expected failures --- pkg/storage/utils/decomposedfs/recycle.go | 8 +++--- .../expected-failures-on-OCIS-storage.md | 28 ++----------------- .../expected-failures-on-S3NG-storage.md | 28 ++----------------- tests/integration/grpc/grpc_suite_test.go | 4 +-- 4 files changed, 12 insertions(+), 56 deletions(-) diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index 9a91483dbe..51b107f751 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -90,12 +90,12 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference return nil, err } else if !md.IsDir() { // this is the case when we want to directly list a file in the trashbin - if item, err := fs.createTrashItem(ctx, parentNode, filepath.Dir(tail), filepath.Join(trashRoot, ref.Path)); err != nil { - return items, err - } else { - items = append(items, item) + item, err := fs.createTrashItem(ctx, parentNode, filepath.Dir(tail), filepath.Join(trashRoot, ref.Path)) + if err != nil { return items, err } + items = append(items, item) + return items, err } names, err := f.Readdirnames(0) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 45d4aa26e1..f3902a9a39 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -3,10 +3,6 @@ ### File Basic file management like up and download, move, copy, properties, quota, trash, versions and chunking. -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) -- [apiTrashbin/trashbinDelete.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L106) -- [apiTrashbin/trashbinDelete.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L122) - #### [invalid webdav responses for unauthorized requests.](https://github.com/owncloud/product/issues/273) - [apiTrashbin/trashbinFilesFolders.feature:200](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L200) - [apiTrashbin/trashbinFilesFolders.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L201) @@ -15,28 +11,10 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiTrashbin/trashbinFilesFolders.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L230) - [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) -- [apiTrashbin/trashbinFilesFolders.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L278) -- [apiTrashbin/trashbinFilesFolders.feature:279](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L279) -- [apiTrashbinRestore/trashbinRestore.feature:459](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L459) -- [apiTrashbinRestore/trashbinRestore.feature:460](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L460) -- [apiTrashbinRestore/trashbinRestore.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L478) -- [apiTrashbinRestore/trashbinRestore.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L479) -- [apiTrashbinRestore/trashbinRestore.feature:502](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L502) -- [apiTrashbinRestore/trashbinRestore.feature:503](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L503) -- [apiTrashbinRestore/trashbinRestore.feature:521](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L521) -- [apiTrashbinRestore/trashbinRestore.feature:522](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L522) - -#### [trash-bin restore move does not send back Etag and other headers](https://github.com/owncloud/ocis/issues/1121) -- [apiTrashbinRestore/trashbinRestore.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L130) -- [apiTrashbinRestore/trashbinRestore.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L131) - -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) +#### [href in trashbin PROPFIND response is wrong](https://github.com/owncloud/ocis/issues/1120) #### [cannot restore to a different file-name](https://github.com/owncloud/ocis/issues/1122) -- [apiTrashbinRestore/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L309) -- [apiTrashbinRestore/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L310) -- [apiTrashbinRestore/trashbinRestore.feature:329](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L329) -- [apiTrashbinRestore/trashbinRestore.feature:330](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L330) +- [apiTrashbin/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L309) +- [apiTrashbin/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L310) #### [Implement Versions Feature for ocis storage](https://github.com/owncloud/product/issues/210) - [apiWebdavEtagPropagation2/restoreVersion.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreVersion.feature#L10) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 093d35c0e5..7a5c456f6b 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -3,10 +3,6 @@ ### File Basic file management like up and download, move, copy, properties, quota, trash, versions and chunking. -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) -- [apiTrashbin/trashbinDelete.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L106) -- [apiTrashbin/trashbinDelete.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L122) - #### [invalid webdav responses for unauthorized requests.](https://github.com/owncloud/product/issues/273) - [apiTrashbin/trashbinFilesFolders.feature:200](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L200) - [apiTrashbin/trashbinFilesFolders.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L201) @@ -15,28 +11,10 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiTrashbin/trashbinFilesFolders.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L230) - [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) -- [apiTrashbin/trashbinFilesFolders.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L278) -- [apiTrashbin/trashbinFilesFolders.feature:279](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L279) -- [apiTrashbinRestore/trashbinRestore.feature:459](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L459) -- [apiTrashbinRestore/trashbinRestore.feature:460](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L460) -- [apiTrashbinRestore/trashbinRestore.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L478) -- [apiTrashbinRestore/trashbinRestore.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L479) -- [apiTrashbinRestore/trashbinRestore.feature:502](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L502) -- [apiTrashbinRestore/trashbinRestore.feature:503](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L503) -- [apiTrashbinRestore/trashbinRestore.feature:521](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L521) -- [apiTrashbinRestore/trashbinRestore.feature:522](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L522) - -#### [trash-bin restore move does not send back Etag and other headers](https://github.com/owncloud/ocis/issues/1121) -- [apiTrashbinRestore/trashbinRestore.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L130) -- [apiTrashbinRestore/trashbinRestore.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L131) - -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) +#### [href in trashbin PROPFIND response is wrong](https://github.com/owncloud/ocis/issues/1120) #### [cannot restore to a different file-name](https://github.com/owncloud/ocis/issues/1122) -- [apiTrashbinRestore/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L309) -- [apiTrashbinRestore/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L310) -- [apiTrashbinRestore/trashbinRestore.feature:329](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L329) -- [apiTrashbinRestore/trashbinRestore.feature:330](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L330) +- [apiTrashbin/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L309) +- [apiTrashbin/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L310) #### [Version count is 1 more than on oC10](https://github.com/owncloud/ocis/issues/1633) - [apiVersions/fileVersionsSharingToShares.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L178) diff --git a/tests/integration/grpc/grpc_suite_test.go b/tests/integration/grpc/grpc_suite_test.go index a45338a641..ea8b14d781 100644 --- a/tests/integration/grpc/grpc_suite_test.go +++ b/tests/integration/grpc/grpc_suite_test.go @@ -41,9 +41,9 @@ const timeoutMs = 30000 var mutex = sync.Mutex{} var port = 19000 -func TestGprc(t *testing.T) { +func TestGrpc(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Gprc Suite") + RunSpecs(t, "Grpc Suite") } type cleanupFunc func(bool) error From e465080d9b4f36d4954f3ccdf819704c47494f4f Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 2 Jul 2021 12:27:54 +0200 Subject: [PATCH 6/8] update grpc tests --- tests/integration/grpc/storageprovider_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index d32a446004..642283d980 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -322,7 +322,7 @@ var _ = Describe("storage providers", func() { Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) By("listing the recycle items") - listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -337,7 +337,7 @@ var _ = Describe("storage providers", func() { restoreRes, err := serviceClient.RestoreRecycleItem(ctx, &storagep.RestoreRecycleItemRequest{ - Ref: subdirRef, + Ref: homeRef, Key: item.Key, }, ) @@ -357,7 +357,7 @@ var _ = Describe("storage providers", func() { Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) By("listing the recycle items") - listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -372,7 +372,7 @@ var _ = Describe("storage providers", func() { restoreRes, err := serviceClient.RestoreRecycleItem(ctx, &storagep.RestoreRecycleItemRequest{ - Ref: subdirRef, + Ref: homeRef, Key: item.Key, RestoreRef: &storagep.Reference{Path: "/subdirRestored"}, }, @@ -392,7 +392,7 @@ var _ = Describe("storage providers", func() { Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) By("listing recycle items") - listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) Expect(len(listRes.RecycleItems)).To(Equal(1)) @@ -402,7 +402,7 @@ var _ = Describe("storage providers", func() { Expect(err).ToNot(HaveOccurred()) Expect(purgeRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - listRes, err = serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + listRes, err = serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) Expect(len(listRes.RecycleItems)).To(Equal(0)) From 34426fecd5b26e50d7beca218bf7d3009ce9b7a0 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 2 Jul 2021 15:13:18 +0200 Subject: [PATCH 7/8] make recycle methods require a key and a path explicitly --- .../unreleased/support-recycle-subpaths.md | 6 +++ .../grpc/services/gateway/storageprovider.go | 1 - .../storageprovider/storageprovider.go | 18 ++++---- .../http/services/owncloud/ocdav/trashbin.go | 22 +++++----- pkg/storage/fs/owncloud/owncloud.go | 22 +++++----- pkg/storage/fs/owncloudsql/owncloudsql.go | 20 ++++----- pkg/storage/fs/s3/s3.go | 6 +-- pkg/storage/storage.go | 6 +-- pkg/storage/utils/decomposedfs/recycle.go | 42 ++++++------------- pkg/storage/utils/eosfs/eosfs.go | 8 ++-- pkg/storage/utils/localfs/localfs.go | 18 ++++---- 11 files changed, 79 insertions(+), 90 deletions(-) create mode 100644 changelog/unreleased/support-recycle-subpaths.md diff --git a/changelog/unreleased/support-recycle-subpaths.md b/changelog/unreleased/support-recycle-subpaths.md new file mode 100644 index 0000000000..bc413d352f --- /dev/null +++ b/changelog/unreleased/support-recycle-subpaths.md @@ -0,0 +1,6 @@ +Enhancement: Support trashbin sub paths in the recycle API + +The recycle API could only act on the root items of the trashbin. Meaning if you delete a deep tree, you couldn't restore just one file from that tree but you had to restore the whole tree. Now listing, restoring and purging work also for sub paths in the trashbin. + +https://github.com/cs3org/reva/pull/1827 + diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 1ce15afe34..33e5d7ff27 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -1954,7 +1954,6 @@ func (s *svc) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecyc } func (s *svc) PurgeRecycle(ctx context.Context, req *gateway.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) { - // lookup storage by treating the key as a path. It has been prefixed with the storage path in ListRecycle c, err := s.find(ctx, req.Ref) if err != nil { return &provider.PurgeRecycleResponse{ diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 176285fe86..cdf813b73b 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -36,6 +36,7 @@ import ( "github.com/cs3org/reva/pkg/mime" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/storage" "github.com/cs3org/reva/pkg/storage/fs/registry" "github.com/mitchellh/mapstructure" @@ -793,7 +794,12 @@ func (s *service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p ctx := ss.Context() log := appctx.GetLogger(ctx) - items, err := s.storage.ListRecycle(ctx, nil) + ref, err := s.unwrap(ctx, req.Ref) + if err != nil { + return err + } + + items, err := s.storage.ListRecycle(ctx, ref.ResourceId.OpaqueId, ref.Path) if err != nil { var st *rpc.Status switch err.(type) { @@ -833,7 +839,8 @@ func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequ if err != nil { return nil, err } - items, err := s.storage.ListRecycle(ctx, ref) + key, itemPath := router.ShiftPath(ref.Path) + items, err := s.storage.ListRecycle(ctx, key, itemPath) // TODO(labkode): CRITICAL: fill recycle info with storage provider. if err != nil { var st *rpc.Status @@ -863,10 +870,7 @@ func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreR if err != nil { return nil, err } - ref.ResourceId = &provider.ResourceId{ - OpaqueId: req.Key, - } - if err := s.storage.RestoreRecycleItem(ctx, ref, req.RestoreRef); err != nil { + if err := s.storage.RestoreRecycleItem(ctx, req.Key, ref.Path, req.RestoreRef); err != nil { var st *rpc.Status switch err.(type) { case errtypes.IsNotFound: @@ -890,7 +894,7 @@ func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreR func (s *service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) { // if a key was sent as opaque id purge only that item if req.GetRef().GetResourceId() != nil && req.GetRef().GetResourceId().OpaqueId != "" { - if err := s.storage.PurgeRecycleItem(ctx, req.GetRef()); err != nil { + if err := s.storage.PurgeRecycleItem(ctx, req.GetRef().GetResourceId().OpaqueId, req.GetRef().Path); err != nil { var st *rpc.Status switch err.(type) { case errtypes.IsNotFound: diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 3a32ccc6b0..a06a37d9c0 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -24,7 +24,6 @@ import ( "fmt" "net/http" "path" - "path/filepath" "strconv" "strings" "time" @@ -108,7 +107,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { //} if r.Method == "PROPFIND" { - h.listTrashbin(w, r, s, u, path.Join(key, r.URL.Path)) + h.listTrashbin(w, r, s, u, key, r.URL.Path) return } if key != "" && r.Method == "MOVE" { @@ -142,7 +141,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { }) } -func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key string) { +func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key, itemPath string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "listTrashbin") defer span.End() @@ -190,8 +189,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } // ask gateway for recycle items - // TODO(labkode): add Reference to ListRecycleRequest - getRecycleRes, err := gc.ListRecycle(ctx, &gateway.ListRecycleRequest{Ref: &provider.Reference{Path: filepath.Join(getHomeRes.Path, key)}}) + getRecycleRes, err := gc.ListRecycle(ctx, &gateway.ListRecycleRequest{Ref: &provider.Reference{Path: path.Join(getHomeRes.Path, key, itemPath)}}) if err != nil { sublog.Error().Err(err).Msg("error calling ListRecycle") @@ -331,7 +329,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use Prop: []*propertyXML{}, }) // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", filepath.Base(item.Ref.Path))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-datetime", dTime)) @@ -368,7 +366,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use } case "trashbin-original-filename": // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-filename", filepath.Base(item.Ref.Path))) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) case "trashbin-original-location": // TODO (jfd) double check and clarify the cs3 spec what the Key is about and if Path is only the folder that contains the file or if it includes the filename propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) @@ -415,7 +413,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use } // restore has a destination and a key -func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, dst, key, resourcePath string) { +func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, dst, key, itemPath string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "restore") defer span.End() @@ -453,7 +451,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } dstRef := &provider.Reference{ - Path: filepath.Join(getHomeRes.Path, dst), + Path: path.Join(getHomeRes.Path, dst), } dstStatReq := &provider.StatRequest{ @@ -516,7 +514,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc // 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: path.Join(getHomeRes.Path, resourcePath), + Path: path.Join(getHomeRes.Path, itemPath), }, Key: key, RestoreRef: &provider.Reference{Path: dst}, @@ -555,7 +553,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } // delete has only a key -func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key, path string) { +func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key, itemPath string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "erase") defer span.End() @@ -599,7 +597,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, StorageId: sRes.Info.Id.StorageId, OpaqueId: key, }, - Path: utils.MakeRelativePath(path), + Path: utils.MakeRelativePath(itemPath), }, } diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 05e24f9719..43da2744e2 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2060,12 +2060,12 @@ func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, re return fs.propagate(ctx, ip) } -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { +func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key, path string) error { rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "ocfs: error resolving recycle path") } - ip := filepath.Join(rp, filepath.Clean(ref.ResourceId.OpaqueId)) + ip := filepath.Join(rp, filepath.Clean(key)) // TODO check permission? // check permissions @@ -2086,7 +2086,7 @@ func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) e if err != nil { return errors.Wrap(err, "ocfs: error deleting recycle item") } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(ref.ResourceId.OpaqueId))) + err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(key))) if err != nil { return errors.Wrap(err, "ocfs: error deleting recycle item versions") } @@ -2153,7 +2153,7 @@ func (fs *ocfs) convertToRecycleItem(ctx context.Context, rp string, md os.FileI } } -func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *ocfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { // TODO check permission? on what? user must be the owner? rp, err := fs.getRecyclePath(ctx) if err != nil { @@ -2161,7 +2161,7 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr } // list files folder - mds, err := ioutil.ReadDir(filepath.Join(rp, ref.Path)) + mds, err := ioutil.ReadDir(filepath.Join(rp, key)) if err != nil { log := appctx.GetLogger(ctx) log.Debug().Err(err).Str("path", rp).Msg("trash not readable") @@ -2180,18 +2180,18 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr return items, nil } -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error { // TODO check permission? on what? user must be the owner? log := appctx.GetLogger(ctx) rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "ocfs: error resolving recycle path") } - src := filepath.Join(rp, filepath.Clean(trashRef.ResourceId.OpaqueId)) + src := filepath.Join(rp, filepath.Clean(key)) suffix := filepath.Ext(src) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - log.Error().Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("invalid trash item suffix") + log.Error().Str("key", key).Str("path", src).Msg("invalid trash item suffix") return nil } @@ -2201,20 +2201,20 @@ func (fs *ocfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Refer if restoreRef.Path == "" { v, err := xattr.Get(src, trashOriginPrefix) if err != nil { - log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("could not read origin") + log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin") } restoreRef.Path = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix)) } tgt := fs.toInternalPath(ctx, restoreRef.Path) // move back to original location if err := os.Rename(src, tgt); err != nil { - log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") + log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") return errors.Wrap(err, "ocfs: could not restore item") } // unset trash origin location in metadata if err := xattr.Remove(tgt, trashOriginPrefix); err != nil { // just a warning, will be overwritten next time it is deleted - log.Warn().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("tgt", tgt).Msg("could not unset origin") + log.Warn().Err(err).Str("key", key).Str("tgt", tgt).Msg("could not unset origin") } // TODO(jfd) restore versions diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index 4d9d478b65..6a004f46e2 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1864,12 +1864,12 @@ func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, re return fs.propagate(ctx, ip) } -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { +func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key, path string) error { rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "owncloudsql: error resolving recycle path") } - ip := filepath.Join(rp, filepath.Clean(ref.ResourceId.OpaqueId)) + ip := filepath.Join(rp, filepath.Clean(key)) // TODO check permission? // check permissions @@ -1890,12 +1890,12 @@ func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) e if err != nil { return errors.Wrap(err, "owncloudsql: error deleting recycle item") } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(ref.ResourceId.OpaqueId))) + err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(key))) if err != nil { return errors.Wrap(err, "owncloudsql: error deleting recycle item versions") } - base, ttime, err := splitTrashKey(ref.ResourceId.OpaqueId) + base, ttime, err := splitTrashKey(key) if err != nil { return err } @@ -1972,7 +1972,7 @@ func (fs *ocfs) convertToRecycleItem(ctx context.Context, md os.FileInfo) *provi } } -func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *ocfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { // TODO check permission? on what? user must be the owner? rp, err := fs.getRecyclePath(ctx) if err != nil { @@ -1999,32 +1999,32 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr return items, nil } -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error { // TODO check permission? on what? user must be the owner? log := appctx.GetLogger(ctx) rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "owncloudsql: error resolving recycle path") } - src := filepath.Join(rp, filepath.Clean(trashRef.ResourceId.OpaqueId)) + src := filepath.Join(rp, filepath.Clean(key)) suffix := filepath.Ext(src) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - log.Error().Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("invalid trash item suffix") + log.Error().Str("key", key).Str("path", src).Msg("invalid trash item suffix") return nil } if restoreRef.Path == "" { v, err := xattr.Get(src, trashOriginPrefix) if err != nil { - log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("could not read origin") + log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin") } restoreRef.Path = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix)) } tgt := fs.toInternalPath(ctx, restoreRef.Path) // move back to original location if err := os.Rename(src, tgt); err != nil { - log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") + log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") return errors.Wrap(err, "owncloudsql: could not restore item") } diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index ebff312e93..f0c8fc73c7 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -646,7 +646,7 @@ func (fs *s3FS) RestoreRevision(ctx context.Context, ref *provider.Reference, re return errtypes.NotSupported("restore revision") } -func (fs *s3FS) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { +func (fs *s3FS) PurgeRecycleItem(ctx context.Context, key, path string) error { return errtypes.NotSupported("purge recycle item") } @@ -654,11 +654,11 @@ func (fs *s3FS) EmptyRecycle(ctx context.Context) error { return errtypes.NotSupported("empty recycle") } -func (fs *s3FS) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *s3FS) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { return nil, errtypes.NotSupported("list recycle") } -func (fs *s3FS) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *s3FS) RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error { return errtypes.NotSupported("restore recycle") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 5d198f2418..1392bbfcff 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -42,9 +42,9 @@ type FS interface { ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error - ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) - RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error - PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error + ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) + RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error + PurgeRecycleItem(ctx context.Context, key, path string) error EmptyRecycle(ctx context.Context) error GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index 51b107f751..d3dcebe24a 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -46,7 +46,7 @@ import ( // contain a directory with symlinks to trash files for every userid/"root" // ListRecycle returns the list of available recycle items -func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *Decomposedfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { log := appctx.GetLogger(ctx) items := make([]*provider.RecycleItem, 0) @@ -65,12 +65,12 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference } } - if ref.Path == "/" { + if key == "" && path == "/" { return fs.listTrashRoot(ctx) } trashRoot := fs.getRecycleRoot(ctx) - f, err := os.Open(filepath.Join(trashRoot, ref.Path)) + f, err := os.Open(filepath.Join(trashRoot, key, path)) if err != nil { if os.IsNotExist(err) { return items, nil @@ -79,8 +79,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference } defer f.Close() - root, tail := shiftPath(ref.Path) - parentNode, err := os.Readlink(filepath.Join(trashRoot, root)) + parentNode, err := os.Readlink(filepath.Join(trashRoot, key)) if err != nil { log.Error().Err(err).Str("trashRoot", trashRoot).Msg("error reading trash link, skipping") return nil, err @@ -90,7 +89,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference return nil, err } else if !md.IsDir() { // this is the case when we want to directly list a file in the trashbin - item, err := fs.createTrashItem(ctx, parentNode, filepath.Dir(tail), filepath.Join(trashRoot, ref.Path)) + item, err := fs.createTrashItem(ctx, parentNode, filepath.Dir(path), filepath.Join(trashRoot, key, path)) if err != nil { return items, err } @@ -103,7 +102,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference return nil, err } for i := range names { - if item, err := fs.createTrashItem(ctx, parentNode, tail, filepath.Join(trashRoot, ref.Path, names[i])); err == nil { + if item, err := fs.createTrashItem(ctx, parentNode, path, filepath.Join(trashRoot, key, path, names[i])); err == nil { items = append(items, item) } } @@ -259,11 +258,11 @@ func (fs *Decomposedfs) listTrashRoot(ctx context.Context) ([]*provider.RecycleI } // RestoreRecycleItem restores the specified item -func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error { if restoreRef == nil { restoreRef = &provider.Reference{} } - rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, trashRef.ResourceId.OpaqueId, trashRef.Path, restoreRef.Path) + rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, key, path, restoreRef.Path) if err != nil { return err } @@ -276,7 +275,7 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, trashRef *provid case err != nil: return errtypes.InternalError(err.Error()) case !ok: - return errtypes.PermissionDenied(trashRef.ResourceId.OpaqueId) + return errtypes.PermissionDenied(key) } // Run the restore func @@ -284,8 +283,8 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, trashRef *provid } // PurgeRecycleItem purges the specified item -func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { - rn, purgeFunc, err := fs.tp.PurgeRecycleItemFunc(ctx, ref.ResourceId.OpaqueId, ref.Path) +func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, key, path string) error { + rn, purgeFunc, err := fs.tp.PurgeRecycleItemFunc(ctx, key, path) if err != nil { return err } @@ -298,7 +297,7 @@ func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, ref *provider.Refe case err != nil: return errtypes.InternalError(err.Error()) case !ok: - return errtypes.PermissionDenied(ref.ResourceId.OpaqueId) + return errtypes.PermissionDenied(key) } // Run the purge func @@ -333,20 +332,3 @@ func (fs *Decomposedfs) getRecycleRoot(ctx context.Context) string { } return filepath.Join(fs.o.Root, "trash", "root") } - -// shiftPath splits off the first component of p, which will be cleaned of -// relative components before processing. head will never contain a slash and -// tail will always be a rooted path without trailing slash. -// see https://blog.merovius.de/2017/06/18/how-not-to-use-an-http-router.html -// and https://gist.github.com/weatherglass/62bd8a704d4dfdc608fe5c5cb5a6980c#gistcomment-2161690 for the zero alloc code below -func shiftPath(p string) (head, tail string) { - if p == "" { - return "", "/" - } - p = strings.TrimPrefix(path.Clean(p), "/") - i := strings.Index(p, "/") - if i < 0 { - return p, "/" - } - return p[:i], p[i:] -} diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 288ba63bbd..81b927b8c6 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1362,7 +1362,7 @@ func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, r return fs.c.RollbackToVersion(ctx, uid, gid, fn, revisionKey) } -func (fs *eosfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { +func (fs *eosfs) PurgeRecycleItem(ctx context.Context, key, itemPath string) error { return errtypes.NotSupported("eosfs: operation not supported") } @@ -1380,7 +1380,7 @@ func (fs *eosfs) EmptyRecycle(ctx context.Context) error { return fs.c.PurgeDeletedEntries(ctx, uid, gid) } -func (fs *eosfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *eosfs) ListRecycle(ctx context.Context, key, itemPath string) ([]*provider.RecycleItem, error) { u, err := getUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") @@ -1411,7 +1411,7 @@ func (fs *eosfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*p return recycleEntries, nil } -func (fs *eosfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, restoreRef *provider.Reference) error { u, err := getUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") @@ -1422,7 +1422,7 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Refe return err } - return fs.c.RestoreDeletedEntry(ctx, uid, gid, trashRef.ResourceId.OpaqueId) + return fs.c.RestoreDeletedEntry(ctx, uid, gid, key) } func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index fcec170d42..4b07a91f20 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1131,8 +1131,8 @@ func (fs *localfs) RestoreRevision(ctx context.Context, ref *provider.Reference, return fs.propagate(ctx, np) } -func (fs *localfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { - rp := fs.wrapRecycleBin(ctx, ref.ResourceId.OpaqueId) +func (fs *localfs) PurgeRecycleItem(ctx context.Context, key, itemPath string) error { + rp := fs.wrapRecycleBin(ctx, key) if err := os.Remove(rp); err != nil { return errors.Wrap(err, "localfs: error deleting recycle item") @@ -1181,7 +1181,7 @@ func (fs *localfs) convertToRecycleItem(ctx context.Context, rp string, md os.Fi } } -func (fs *localfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *localfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { rp := fs.wrapRecycleBin(ctx, "/") @@ -1199,14 +1199,14 @@ func (fs *localfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([] return items, nil } -func (fs *localfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *localfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, restoreRef *provider.Reference) error { - suffix := path.Ext(trashRef.ResourceId.OpaqueId) + suffix := path.Ext(key) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { return errors.New("localfs: invalid trash item suffix") } - filePath, err := fs.getRecycledEntry(ctx, trashRef.ResourceId.OpaqueId) + filePath, err := fs.getRecycledEntry(ctx, key) if err != nil { return errors.Wrap(err, "localfs: invalid key") } @@ -1225,10 +1225,10 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Re return errors.New("localfs: can't restore - file already exists at original path") } - rp := fs.wrapRecycleBin(ctx, trashRef.ResourceId.OpaqueId) + rp := fs.wrapRecycleBin(ctx, key) if _, err = os.Stat(rp); err != nil { if os.IsNotExist(err) { - return errtypes.NotFound(trashRef.ResourceId.OpaqueId) + return errtypes.NotFound(key) } return errors.Wrap(err, "localfs: error stating "+rp) } @@ -1237,7 +1237,7 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Re return errors.Wrap(err, "ocfs: could not restore item") } - err = fs.removeFromRecycledDB(ctx, trashRef.ResourceId.OpaqueId) + err = fs.removeFromRecycledDB(ctx, key) if err != nil { return errors.Wrap(err, "localfs: error adding entry to DB") } From df9e2067bee87147af07b6af444153cf5685971f Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 9 Jul 2021 09:45:37 +0200 Subject: [PATCH 8/8] update expected failures --- tests/acceptance/expected-failures-on-OCIS-storage.md | 9 ++++++--- tests/acceptance/expected-failures-on-S3NG-storage.md | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index f3902a9a39..744be876c7 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -11,10 +11,13 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiTrashbin/trashbinFilesFolders.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L230) - [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) -#### [href in trashbin PROPFIND response is wrong](https://github.com/owncloud/ocis/issues/1120) +#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) +- [apiTrashbinRestore/trashbinRestore.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L478) +- [apiTrashbinRestore/trashbinRestore.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L479) + #### [cannot restore to a different file-name](https://github.com/owncloud/ocis/issues/1122) -- [apiTrashbin/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L309) -- [apiTrashbin/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L310) +- [apiTrashbinRestore/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L309) +- [apiTrashbinRestore/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L310) #### [Implement Versions Feature for ocis storage](https://github.com/owncloud/product/issues/210) - [apiWebdavEtagPropagation2/restoreVersion.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreVersion.feature#L10) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 7a5c456f6b..8db46e4b05 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -11,10 +11,13 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiTrashbin/trashbinFilesFolders.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L230) - [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) -#### [href in trashbin PROPFIND response is wrong](https://github.com/owncloud/ocis/issues/1120) +#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) +- [apiTrashbinRestore/trashbinRestore.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L478) +- [apiTrashbinRestore/trashbinRestore.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L479) + #### [cannot restore to a different file-name](https://github.com/owncloud/ocis/issues/1122) -- [apiTrashbin/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L309) -- [apiTrashbin/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L310) +- [apiTrashbinRestore/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L309) +- [apiTrashbinRestore/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L310) #### [Version count is 1 more than on oC10](https://github.com/owncloud/ocis/issues/1633) - [apiVersions/fileVersionsSharingToShares.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L178)