Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Restore file to read-only node (DecomposedFS) #1913

Merged
merged 11 commits into from
Aug 20, 2021
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