Skip to content

Commit

Permalink
Fix Restore file to read-only node (DecomposedFS) (#1913)
Browse files Browse the repository at this point in the history
  • Loading branch information
refs authored Aug 20, 2021
1 parent 7f7fab2 commit a965121
Show file tree
Hide file tree
Showing 17 changed files with 162 additions and 53 deletions.
7 changes: 7 additions & 0 deletions changelog/unreleased/restore-readonly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Bugfix: Logic to restore files to readonly nodes

This impacts solely the DecomposedFS. Prior to these changes there was no validation when a user tried to restore a file from the trashbin to a share location (i.e any folder under `/Shares`).

With this patch if the user restoring the resource has write permissions on the share, restore is possible.

https://github.com/cs3org/reva/pull/1913
2 changes: 1 addition & 1 deletion pkg/errtypes/errtypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ type IsChecksumMismatch interface {
}

// IsInsufficientStorage is the interface to implement
// to specify that a there is insufficient storage.
// to specify that there is insufficient storage.
type IsInsufficientStorage interface {
IsInsufficientStorage()
}
9 changes: 9 additions & 0 deletions pkg/rgrpc/status/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ func NewInvalidArg(ctx context.Context, msg string) *rpc.Status {
}
}

// NewConflict returns a Status with Code_CODE_ABORTED and logs the msg.
func NewConflict(ctx context.Context, err error, msg string) *rpc.Status {
return &rpc.Status{
Code: rpc.Code_CODE_ABORTED,
Message: msg,
Trace: getTrace(ctx),
}
}

// NewStatusFromErrType returns a status that corresponds to the given errtype
func NewStatusFromErrType(ctx context.Context, msg string, err error) *rpc.Status {
switch e := err.(type) {
Expand Down
6 changes: 3 additions & 3 deletions pkg/storage/utils/decomposedfs/decomposedfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,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, trashPath, restorePath string) (*node.Node, func() error, error) // FIXME REFERENCE use ref instead of path
RestoreRecycleItemFunc(ctx context.Context, key, trashPath, restorePath string) (*node.Node, *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
Expand Down Expand Up @@ -191,7 +191,7 @@ func (fs *Decomposedfs) CreateHome(ctx context.Context) (err error) {
if n, err = fs.lu.RootNode(ctx); err != nil {
return
}
h, err = fs.lu.WalkPath(ctx, n, fs.lu.mustGetUserLayout(ctx), func(ctx context.Context, n *node.Node) error {
h, err = fs.lu.WalkPath(ctx, n, fs.lu.mustGetUserLayout(ctx), false, func(ctx context.Context, n *node.Node) error {
if !n.Exists {
if err := fs.tp.CreateDir(ctx, n); err != nil {
return err
Expand Down Expand Up @@ -344,7 +344,7 @@ func (fs *Decomposedfs) CreateReference(ctx context.Context, p string, targetURI

// create Shares folder if it does not exist
var n *node.Node
if n, err = fs.lu.NodeFromPath(ctx, fs.o.ShareFolder); err != nil {
if n, err = fs.lu.NodeFromPath(ctx, fs.o.ShareFolder, false); err != nil {
return errtypes.InternalError(err.Error())
} else if !n.Exists {
if err = fs.tp.CreateDir(ctx, n); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/utils/decomposedfs/grants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ var _ = Describe("Grants", func() {

Describe("AddGrant", func() {
It("adds grants", func() {
n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1")
n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1", false)
Expect(err).ToNot(HaveOccurred())

err = env.Fs.AddGrant(env.Ctx, ref, grant)
Expand Down
36 changes: 27 additions & 9 deletions pkg/storage/utils/decomposedfs/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import (
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/options"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs"
"github.com/cs3org/reva/pkg/storage/utils/templates"
"github.com/pkg/xattr"
)

// Lookup implements transformations from filepath to node and back
Expand All @@ -51,7 +53,7 @@ func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference)
p := filepath.Clean(ref.Path)
if p != "." {
// walk the relative path
n, err = lu.WalkPath(ctx, n, p, func(ctx context.Context, n *node.Node) error {
n, err = lu.WalkPath(ctx, n, p, false, func(ctx context.Context, n *node.Node) error {
return nil
})
if err != nil {
Expand All @@ -64,15 +66,15 @@ func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference)
}

if ref.Path != "" {
return lu.NodeFromPath(ctx, ref.GetPath())
return lu.NodeFromPath(ctx, ref.GetPath(), false)
}

// reference is invalid
return nil, fmt.Errorf("invalid reference %+v. at least resource_id or path must be set", ref)
}

// NodeFromPath converts a filename into a Node
func (lu *Lookup) NodeFromPath(ctx context.Context, fn string) (*node.Node, error) {
func (lu *Lookup) NodeFromPath(ctx context.Context, fn string, followReferences bool) (*node.Node, error) {
log := appctx.GetLogger(ctx)
log.Debug().Interface("fn", fn).Msg("NodeFromPath()")

Expand All @@ -84,7 +86,7 @@ func (lu *Lookup) NodeFromPath(ctx context.Context, fn string) (*node.Node, erro
// TODO collect permissions of the current user on every segment
fn = filepath.Clean(fn)
if fn != "/" && fn != "." {
n, err = lu.WalkPath(ctx, n, fn, func(ctx context.Context, n *node.Node) error {
n, err = lu.WalkPath(ctx, n, fn, followReferences, func(ctx context.Context, n *node.Node) error {
log.Debug().Interface("node", n).Msg("NodeFromPath() walk")
return nil
})
Expand Down Expand Up @@ -139,20 +141,36 @@ func (lu *Lookup) HomeNode(ctx context.Context) (node *node.Node, err error) {
if node, err = lu.RootNode(ctx); err != nil {
return
}
node, err = lu.WalkPath(ctx, node, lu.mustGetUserLayout(ctx), nil)
node, err = lu.WalkPath(ctx, node, lu.mustGetUserLayout(ctx), false, nil)
return
}

// WalkPath calls n.Child(segment) on every path segment in p starting at the node r
// If a function f is given it will be executed for every segment node, but not the root node r
func (lu *Lookup) WalkPath(ctx context.Context, r *node.Node, p string, f func(ctx context.Context, n *node.Node) error) (*node.Node, error) {
// WalkPath calls n.Child(segment) on every path segment in p starting at the node r.
// If a function f is given it will be executed for every segment node, but not the root node r.
// If followReferences is given the current visited reference node is replaced by the referenced node.
func (lu *Lookup) WalkPath(ctx context.Context, r *node.Node, p string, followReferences bool, f func(ctx context.Context, n *node.Node) error) (*node.Node, error) {
segments := strings.Split(strings.Trim(p, "/"), "/")
var err error
for i := range segments {
if r, err = r.Child(ctx, segments[i]); err != nil {
return r, err
}
// if an intermediate node is missing return not found

if followReferences {
if attrBytes, err := xattr.Get(r.InternalPath(), xattrs.ReferenceAttr); err == nil {
realNodeID := attrBytes
ref, err := xattrs.ReferenceFromAttr(realNodeID)
if err != nil {
return nil, err
}

r, err = lu.NodeFromID(ctx, ref.ResourceId)
if err != nil {
return nil, err
}
}
}

if !r.Exists && i < len(segments)-1 {
return r, errtypes.NotFound(segments[i])
}
Expand Down
15 changes: 14 additions & 1 deletion pkg/storage/utils/decomposedfs/lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package decomposedfs_test

import (
helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -40,16 +41,28 @@ var _ = Describe("Lookup", func() {
if env != nil {
env.Cleanup()
}

})

Describe("Path", func() {
It("returns the path including a leading slash", func() {
n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1")
n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1", false)
Expect(err).ToNot(HaveOccurred())

path, err := env.Lookup.Path(env.Ctx, n)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("/dir1/file1"))
})
})

Describe("Reference Parsing", func() {
It("parses a valid cs3 reference", func() {
in := []byte("cs3:bede11a0-ea3d-11eb-a78b-bf907adce8ed/c402d01c-ea3d-11eb-a0fc-c32f9d32528f")
ref, err := xattrs.ReferenceFromAttr(in)

Expect(err).ToNot(HaveOccurred())
Expect(ref.ResourceId.StorageId).To(Equal("bede11a0-ea3d-11eb-a78b-bf907adce8ed"))
Expect(ref.ResourceId.OpaqueId).To(Equal("c402d01c-ea3d-11eb-a0fc-c32f9d32528f"))
})
})
})
17 changes: 17 additions & 0 deletions pkg/storage/utils/decomposedfs/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@ func New(id, parentID, name string, blobsize int64, blobID string, owner *userpb
}
}

// ChangeOwner sets the owner of n to newOwner
func (n *Node) ChangeOwner(new *userpb.UserId) (err error) {
nodePath := n.InternalPath()
n.owner = new
if err = xattr.Set(nodePath, xattrs.OwnerIDAttr, []byte(new.OpaqueId)); err != nil {
return errors.Wrap(err, "Decomposedfs: could not reset owner id attribute")
}
if err = xattr.Set(nodePath, xattrs.OwnerIDPAttr, []byte(new.Idp)); err != nil {
return errors.Wrap(err, "Decomposedfs: could not reset owner idp attribute")
}
if err = xattr.Set(nodePath, xattrs.OwnerTypeAttr, []byte(utils.UserTypeToString(new.Type))); err != nil {
return errors.Wrap(err, "Decomposedfs: could not reset owner idp attribute")
}

return
}

// WriteMetadata writes the Node metadata to disk
func (n *Node) WriteMetadata(owner *userpb.UserId) (err error) {
nodePath := n.InternalPath()
Expand Down
12 changes: 6 additions & 6 deletions pkg/storage/utils/decomposedfs/node/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ var _ = Describe("Node", func() {

Describe("ReadNode", func() {
It("reads the blobID from the xattrs", func() {
lookupNode, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1")
lookupNode, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1", false)
Expect(err).ToNot(HaveOccurred())

n, err := node.ReadNode(env.Ctx, env.Lookup, lookupNode.ID)
Expand All @@ -75,7 +75,7 @@ var _ = Describe("Node", func() {

Describe("WriteMetadata", func() {
It("writes all xattrs", func() {
n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1")
n, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1", false)
Expect(err).ToNot(HaveOccurred())

blobsize := 239485734
Expand All @@ -90,7 +90,7 @@ var _ = Describe("Node", func() {

err = n.WriteMetadata(owner)
Expect(err).ToNot(HaveOccurred())
n2, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1")
n2, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1", false)
Expect(err).ToNot(HaveOccurred())
Expect(n2.Name).To(Equal("TestName"))
Expect(n2.BlobID).To(Equal("TestBlobID"))
Expand All @@ -100,7 +100,7 @@ var _ = Describe("Node", func() {

Describe("Parent", func() {
It("returns the parent node", func() {
child, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/subdir1")
child, err := env.Lookup.NodeFromPath(env.Ctx, "/dir1/subdir1", false)
Expect(err).ToNot(HaveOccurred())
Expect(child).ToNot(BeNil())

Expand All @@ -118,7 +118,7 @@ var _ = Describe("Node", func() {

BeforeEach(func() {
var err error
parent, err = env.Lookup.NodeFromPath(env.Ctx, "/dir1")
parent, err = env.Lookup.NodeFromPath(env.Ctx, "/dir1", false)
Expect(err).ToNot(HaveOccurred())
Expect(parent).ToNot(BeNil())
})
Expand Down Expand Up @@ -165,7 +165,7 @@ var _ = Describe("Node", func() {

BeforeEach(func() {
var err error
n, err = env.Lookup.NodeFromPath(env.Ctx, "dir1/file1")
n, err = env.Lookup.NodeFromPath(env.Ctx, "dir1/file1", false)
Expect(err).ToNot(HaveOccurred())
})

Expand Down
12 changes: 11 additions & 1 deletion pkg/storage/utils/decomposedfs/recycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key, path string
if restoreRef == nil {
restoreRef = &provider.Reference{}
}
rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, key, path, restoreRef.Path)
rn, p, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, key, path, restoreRef.Path)
if err != nil {
return err
}
Expand All @@ -278,6 +278,16 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key, path string
return errtypes.PermissionDenied(key)
}

ps, err := fs.p.AssemblePermissions(ctx, p)
if err != nil {
return errtypes.InternalError(err.Error())
}

// share receiver cannot restore to a shared resource to which she does not have write permissions.
if !ps.InitiateFileUpload {
return errtypes.PermissionDenied(key)
}

// Run the restore func
return restoreFunc()
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/utils/decomposedfs/testhelpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ func (t *TestEnv) CreateTestDir(name string) (*node.Node, error) {
if err != nil {
return nil, err
}
n, err := t.Lookup.NodeFromPath(t.Ctx, name)
n, err := t.Lookup.NodeFromPath(t.Ctx, name, false)
if err != nil {
return nil, err
}
Expand Down
39 changes: 32 additions & 7 deletions pkg/storage/utils/decomposedfs/tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type Blobstore interface {

// PathLookup defines the interface for the lookup component
type PathLookup interface {
NodeFromPath(ctx context.Context, fn string) (*node.Node, error)
NodeFromPath(ctx context.Context, fn string, followReferences bool) (*node.Node, error)
NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error)
RootNode(ctx context.Context) (node *node.Node, err error)
HomeOrRootNode(ctx context.Context) (node *node.Node, err error)
Expand Down Expand Up @@ -446,25 +446,35 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) {
return t.Propagate(ctx, n)
}

// RestoreRecycleItemFunc returns a node and a function to restore it from the trash
func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, trashPath, restorePath string) (*node.Node, func() error, error) {
// RestoreRecycleItemFunc returns a node and a function to restore it from the trash.
func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, trashPath, restorePath string) (*node.Node, *node.Node, func() error, error) {
rn, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, key, trashPath)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

if restorePath == "" {
restorePath = origin
}

var target *node.Node
target, err = t.lookup.NodeFromPath(ctx, restorePath, true)
if err != nil {
return nil, nil, nil, err
}

p, err := target.Parent()
if err != nil {
return nil, nil, nil, err
}

fn := func() error {
// link to origin
var n *node.Node
n, err = t.lookup.NodeFromPath(ctx, restorePath)
n, err = t.lookup.NodeFromPath(ctx, restorePath, true)
if err != nil {
return err
}

if n.Exists {
return errtypes.AlreadyExists("origin already exists")
}
Expand All @@ -486,6 +496,21 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, trashPath, resto
}
}

// the new node will inherit the permissions of its parent
p, err := n.Parent()
if err != nil {
return err
}

po, err := p.Owner()
if err != nil {
return err
}

if err := rn.ChangeOwner(po); err != nil {
return err
}

n.Exists = true
// update name attribute
if err := xattr.Set(nodePath, xattrs.NameAttr, []byte(n.Name)); err != nil {
Expand All @@ -505,7 +530,7 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, trashPath, resto
}
return t.Propagate(ctx, n)
}
return rn, fn, nil
return rn, p, fn, nil
}

// PurgeRecycleItemFunc returns a node and a function to purge it from the trash
Expand Down
Loading

0 comments on commit a965121

Please sign in to comment.