Skip to content

Commit

Permalink
ocis driver: enforce permissions (#1213)
Browse files Browse the repository at this point in the history
  • Loading branch information
butonic authored Oct 6, 2020
1 parent 446b26c commit 4a9be34
Show file tree
Hide file tree
Showing 16 changed files with 956 additions and 316 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/check-permissions-in-ocis-driver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: check permissions in ocis driver

We are now checking grant permissions in the ocis storage driver.

https://github.com/cs3org/reva/pull/1213
55 changes: 44 additions & 11 deletions pkg/storage/fs/ocis/grants.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,34 +34,56 @@ func (fs *ocisfs) AddGrant(ctx context.Context, ref *provider.Reference, g *prov
log := appctx.GetLogger(ctx)
log.Debug().Interface("ref", ref).Interface("grant", g).Msg("AddGrant()")
var node *Node
if node, err = fs.pw.NodeFromResource(ctx, ref); err != nil {
if node, err = fs.lu.NodeFromResource(ctx, ref); err != nil {
return
}
if !node.Exists {
err = errtypes.NotFound(filepath.Join(node.ParentID, node.Name))
return
}

np := filepath.Join(fs.pw.Root, "nodes", node.ID)
ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool {
// TODO remove AddGrant or UpdateGrant grant from CS3 api, redundant? tracked in https://github.com/cs3org/cs3apis/issues/92
return rp.AddGrant || rp.UpdateGrant
})
switch {
case err != nil:
return errtypes.InternalError(err.Error())
case !ok:
return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name))
}

np := fs.lu.toInternalPath(node.ID)
e := ace.FromGrant(g)
principal, value := e.Marshal()
if err := xattr.Set(np, sharePrefix+principal, value); err != nil {
if err := xattr.Set(np, grantPrefix+principal, value); err != nil {
return err
}
return fs.tp.Propagate(ctx, node)
}

func (fs *ocisfs) ListGrants(ctx context.Context, ref *provider.Reference) (grants []*provider.Grant, err error) {
var node *Node
if node, err = fs.pw.NodeFromResource(ctx, ref); err != nil {
if node, err = fs.lu.NodeFromResource(ctx, ref); err != nil {
return
}
if !node.Exists {
err = errtypes.NotFound(filepath.Join(node.ParentID, node.Name))
return
}

ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool {
return rp.ListGrants
})
switch {
case err != nil:
return nil, errtypes.InternalError(err.Error())
case !ok:
return nil, errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name))
}

log := appctx.GetLogger(ctx)
np := filepath.Join(fs.pw.Root, "nodes", node.ID)
np := fs.lu.toInternalPath(node.ID)
var attrs []string
if attrs, err = xattr.List(np); err != nil {
log.Error().Err(err).Msg("error listing attributes")
Expand All @@ -82,22 +104,32 @@ func (fs *ocisfs) ListGrants(ctx context.Context, ref *provider.Reference) (gran

func (fs *ocisfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) {
var node *Node
if node, err = fs.pw.NodeFromResource(ctx, ref); err != nil {
if node, err = fs.lu.NodeFromResource(ctx, ref); err != nil {
return
}
if !node.Exists {
err = errtypes.NotFound(filepath.Join(node.ParentID, node.Name))
return
}

ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool {
return rp.RemoveGrant
})
switch {
case err != nil:
return errtypes.InternalError(err.Error())
case !ok:
return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name))
}

var attr string
if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP {
attr = sharePrefix + "g:" + g.Grantee.Id.OpaqueId
attr = grantPrefix + "g:" + g.Grantee.Id.OpaqueId
} else {
attr = sharePrefix + "u:" + g.Grantee.Id.OpaqueId
attr = grantPrefix + "u:" + g.Grantee.Id.OpaqueId
}

np := filepath.Join(fs.pw.Root, "nodes", node.ID)
np := fs.lu.toInternalPath(node.ID)
if err = xattr.Remove(np, attr); err != nil {
return
}
Expand All @@ -106,6 +138,7 @@ func (fs *ocisfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *p
}

func (fs *ocisfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
// TODO remove AddGrant or UpdateGrant grant from CS3 api, redundant? tracked in https://github.com/cs3org/cs3apis/issues/92
return fs.AddGrant(ctx, ref, g)
}

Expand All @@ -114,15 +147,15 @@ func extractACEsFromAttrs(ctx context.Context, fsfn string, attrs []string) (ent
log := appctx.GetLogger(ctx)
entries = []*ace.ACE{}
for i := range attrs {
if strings.HasPrefix(attrs[i], sharePrefix) {
if strings.HasPrefix(attrs[i], grantPrefix) {
var value []byte
var err error
if value, err = xattr.Get(fsfn, attrs[i]); err != nil {
log.Error().Err(err).Str("attr", attrs[i]).Msg("could not read attribute")
continue
}
var e *ace.ACE
principal := attrs[i][len(sharePrefix):]
principal := attrs[i][len(grantPrefix):]
if e, err = ace.Unmarshal(principal, value); err != nil {
log.Error().Err(err).Str("principal", principal).Str("attr", attrs[i]).Msg("could unmarshal ace")
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import (
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
)

// TODO the different aspects of a storage: Tree, Lookup and Permissions should be able to be reusable
// Below is a start of Interfaces that needs to be worked out further

// TreePersistence is used to manage a tree hierarchy
type TreePersistence interface {
GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error)
Expand All @@ -39,8 +42,9 @@ type TreePersistence interface {
Propagate(ctx context.Context, node *Node) (err error)
}

// PathWrapper is used to encapsulate path transformations
type PathWrapper interface {
// Lookup is used to encapsulate path transformations
/*
type Lookup interface {
NodeFromResource(ctx context.Context, ref *provider.Reference) (node *Node, err error)
NodeFromID(ctx context.Context, id *provider.ResourceId) (node *Node, err error)
NodeFromPath(ctx context.Context, fn string) (node *Node, err error)
Expand All @@ -57,3 +61,4 @@ type PathWrapper interface {
// it returns the storages root node otherwise
HomeOrRootNode(ctx context.Context) (node *Node, err error)
}
*/
72 changes: 30 additions & 42 deletions pkg/storage/fs/ocis/path.go → pkg/storage/fs/ocis/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,52 +31,36 @@ import (
"github.com/cs3org/reva/pkg/user"
)

// Path implements transformations from filepath to node and back
type Path struct {
// ocis fs works on top of a dir of uuid nodes
Root string `mapstructure:"root"`

// UserLayout describes the relative path from the storage's root node to the users home node.
UserLayout string `mapstructure:"user_layout"`

// TODO NodeLayout option to save nodes as eg. nodes/1d/d8/1dd84abf-9466-4e14-bb86-02fc4ea3abcf
ShareFolder string `mapstructure:"share_folder"`

// EnableHome enables the creation of home directories.
EnableHome bool `mapstructure:"enable_home"`

// propagate mtime changes as tmtime (tree modification time) to the parent directory when user.ocis.propagation=1 is set on a node
TreeTimeAccounting bool `mapstructure:"treetime_accounting"`

// propagate size changes as treesize
TreeSizeAccounting bool `mapstructure:"treesize_accounting"`
// Lookup implements transformations from filepath to node and back
type Lookup struct {
Options *Options
}

// NodeFromResource takes in a request path or request id and converts it to a Node
func (pw *Path) NodeFromResource(ctx context.Context, ref *provider.Reference) (*Node, error) {
func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference) (*Node, error) {
if ref.GetPath() != "" {
return pw.NodeFromPath(ctx, ref.GetPath())
return lu.NodeFromPath(ctx, ref.GetPath())
}

if ref.GetId() != nil {
return pw.NodeFromID(ctx, ref.GetId())
return lu.NodeFromID(ctx, ref.GetId())
}

// reference is invalid
return nil, fmt.Errorf("invalid reference %+v", ref)
}

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

if node, err = pw.HomeOrRootNode(ctx); err != nil {
if node, err = lu.HomeOrRootNode(ctx); err != nil {
return
}

if fn != "/" {
node, err = pw.WalkPath(ctx, node, fn, func(ctx context.Context, n *Node) error {
node, err = lu.WalkPath(ctx, node, fn, func(ctx context.Context, n *Node) error {
log.Debug().Interface("node", n).Msg("NodeFromPath() walk")
return nil
})
Expand All @@ -86,17 +70,17 @@ func (pw *Path) NodeFromPath(ctx context.Context, fn string) (node *Node, err er
}

// NodeFromID returns the internal path for the id
func (pw *Path) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *Node, err error) {
func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *Node, err error) {
if id == nil || id.OpaqueId == "" {
return nil, fmt.Errorf("invalid resource id %+v", id)
}
return ReadNode(ctx, pw, id.OpaqueId)
return ReadNode(ctx, lu, id.OpaqueId)
}

// Path returns the path for node
func (pw *Path) Path(ctx context.Context, n *Node) (p string, err error) {
func (lu *Lookup) Path(ctx context.Context, n *Node) (p string, err error) {
var root *Node
if root, err = pw.HomeOrRootNode(ctx); err != nil {
if root, err = lu.HomeOrRootNode(ctx); err != nil {
return
}
for n.ID != root.ID {
Expand All @@ -114,9 +98,9 @@ func (pw *Path) Path(ctx context.Context, n *Node) (p string, err error) {
}

// RootNode returns the root node of the storage
func (pw *Path) RootNode(ctx context.Context) (node *Node, err error) {
func (lu *Lookup) RootNode(ctx context.Context) (node *Node, err error) {
return &Node{
pw: pw,
lu: lu,
ID: "root",
Name: "",
ParentID: "",
Expand All @@ -125,21 +109,21 @@ func (pw *Path) RootNode(ctx context.Context) (node *Node, err error) {
}

// HomeNode returns the home node of a user
func (pw *Path) HomeNode(ctx context.Context) (node *Node, err error) {
if !pw.EnableHome {
func (lu *Lookup) HomeNode(ctx context.Context) (node *Node, err error) {
if !lu.Options.EnableHome {
return nil, errtypes.NotSupported("ocisfs: home supported disabled")
}

if node, err = pw.RootNode(ctx); err != nil {
if node, err = lu.RootNode(ctx); err != nil {
return
}
node, err = pw.WalkPath(ctx, node, pw.mustGetUserLayout(ctx), nil)
node, err = lu.WalkPath(ctx, node, lu.mustGetUserLayout(ctx), 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 (pw *Path) WalkPath(ctx context.Context, r *Node, p string, f func(ctx context.Context, n *Node) error) (*Node, error) {
func (lu *Lookup) WalkPath(ctx context.Context, r *Node, p string, f func(ctx context.Context, n *Node) error) (*Node, error) {
segments := strings.Split(strings.Trim(p, "/"), "/")
var err error
for i := range segments {
Expand All @@ -161,14 +145,18 @@ func (pw *Path) WalkPath(ctx context.Context, r *Node, p string, f func(ctx cont

// HomeOrRootNode returns the users home node when home support is enabled.
// it returns the storages root node otherwise
func (pw *Path) HomeOrRootNode(ctx context.Context) (node *Node, err error) {
if pw.EnableHome {
return pw.HomeNode(ctx)
func (lu *Lookup) HomeOrRootNode(ctx context.Context) (node *Node, err error) {
if lu.Options.EnableHome {
return lu.HomeNode(ctx)
}
return pw.RootNode(ctx)
return lu.RootNode(ctx)
}

func (pw *Path) mustGetUserLayout(ctx context.Context) string {
func (lu *Lookup) mustGetUserLayout(ctx context.Context) string {
u := user.ContextMustGetUser(ctx)
return templates.WithUser(u, pw.UserLayout)
return templates.WithUser(u, lu.Options.UserLayout)
}

func (lu *Lookup) toInternalPath(id string) string {
return filepath.Join(lu.Options.Root, "nodes", id)
}
32 changes: 28 additions & 4 deletions pkg/storage/fs/ocis/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

func (fs *ocisfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) (err error) {
n, err := fs.pw.NodeFromResource(ctx, ref)
n, err := fs.lu.NodeFromResource(ctx, ref)
if err != nil {
return errors.Wrap(err, "ocisfs: error resolving ref")
}
Expand All @@ -39,7 +39,19 @@ func (fs *ocisfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Refere
err = errtypes.NotFound(filepath.Join(n.ParentID, n.Name))
return err
}
nodePath := filepath.Join(fs.pw.Root, "nodes", n.ID)

ok, err := fs.p.HasPermission(ctx, n, func(rp *provider.ResourcePermissions) bool {
// TODO add explicit SetArbitraryMetadata grant to CS3 api, tracked in https://github.com/cs3org/cs3apis/issues/91
return rp.InitiateFileUpload
})
switch {
case err != nil:
return errtypes.InternalError(err.Error())
case !ok:
return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name))
}

nodePath := n.lu.toInternalPath(n.ID)
for k, v := range md.Metadata {
// TODO set etag as temporary etag tmpEtagAttr
attrName := metadataPrefix + k
Expand All @@ -51,7 +63,7 @@ func (fs *ocisfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Refere
}

func (fs *ocisfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) (err error) {
n, err := fs.pw.NodeFromResource(ctx, ref)
n, err := fs.lu.NodeFromResource(ctx, ref)
if err != nil {
return errors.Wrap(err, "ocisfs: error resolving ref")
}
Expand All @@ -60,7 +72,19 @@ func (fs *ocisfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refe
err = errtypes.NotFound(filepath.Join(n.ParentID, n.Name))
return err
}
nodePath := filepath.Join(fs.pw.Root, "nodes", n.ID)

ok, err := fs.p.HasPermission(ctx, n, func(rp *provider.ResourcePermissions) bool {
// TODO use SetArbitraryMetadata grant to CS3 api, tracked in https://github.com/cs3org/cs3apis/issues/91
return rp.InitiateFileUpload
})
switch {
case err != nil:
return errtypes.InternalError(err.Error())
case !ok:
return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name))
}

nodePath := n.lu.toInternalPath(n.ID)
for i := range keys {
attrName := metadataPrefix + keys[i]
if err = xattr.Remove(nodePath, attrName); err != nil {
Expand Down
Loading

0 comments on commit 4a9be34

Please sign in to comment.