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

ocis driver: enforce permissions #1213

Merged
merged 11 commits into from
Oct 6, 2020
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