Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
Lightweight accounts (cs3org#3348)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmgigi96 authored and vascoguita committed Oct 26, 2022
1 parent 4be43dc commit 3cf7b3c
Show file tree
Hide file tree
Showing 10 changed files with 435 additions and 206 deletions.
8 changes: 8 additions & 0 deletions changelog/unreleased/lightweigh-accounts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Enhancement: Revamp lightweigth accounts

Re-implements the lighweight account scope check, making
it more efficient.
Also, the ACLs for the EOS storage driver for the lw
accounts are set atomically.

https://github.com/cs3org/reva/pull/3348
174 changes: 112 additions & 62 deletions internal/grpc/interceptors/auth/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package auth

import (
"context"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -78,77 +79,136 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
return nil
}

case strings.HasPrefix(k, "lightweight"):
if err = resolveLightweightScope(ctx, ref, tokenScope[k], user, client, mgr); err == nil {
return nil
}
}
log.Err(err).Msgf("error resolving reference %s under scope %+v", ref.String(), k)
}

} else if ref, ok := extractShareRef(req); ok {
// It's a share ref
// The request might be coming from a share created for a lightweight account
// after the token was minted.
log.Info().Msgf("resolving share reference against received shares to verify token scope %+v", ref.String())
for k := range tokenScope {
if strings.HasPrefix(k, "lightweight") {
// Check if this ID is cached
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + ref.GetId().OpaqueId
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
}
}

shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
if err != nil || shares.Status.Code != rpc.Code_CODE_OK {
log.Warn().Err(err).Msg("error listing received shares")
continue
}
for _, s := range shares.Shares {
shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + s.Share.Id.OpaqueId
_ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second)

if ref.GetId() != nil && ref.GetId().OpaqueId == s.Share.Id.OpaqueId {
return nil
}
if key := ref.GetKey(); key != nil && (utils.UserEqual(key.Owner, s.Share.Owner) || utils.UserEqual(key.Owner, s.Share.Creator)) &&
utils.ResourceIDEqual(key.ResourceId, s.Share.ResourceId) && utils.GranteeEqual(key.Grantee, s.Share.Grantee) {
return nil
}
}
}
}
if checkLightweightScope(ctx, req, tokenScope, client) {
return nil
}

return errtypes.PermissionDenied("access to resource not allowed within the assigned scope")
}

func resolveLightweightScope(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, user *userpb.User, client gateway.GatewayAPIClient, mgr token.Manager) error {
// Check if this ref is cached
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + getRefKey(ref)
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
func hasLightweightScope(tokenScope map[string]*authpb.Scope) bool {
for scope := range tokenScope {
if strings.HasPrefix(scope, "lightweight") {
return true
}
}
return false
}

shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
if err != nil || shares.Status.Code != rpc.Code_CODE_OK {
return errtypes.InternalError("error listing received shares")
func checkLightweightScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, client gateway.GatewayAPIClient) bool {
if !hasLightweightScope(tokenScope) {
return false
}

for _, share := range shares.Shares {
shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + resourceid.OwnCloudResourceIDWrap(share.Share.ResourceId)
_ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second)
switch r := req.(type) {
// Viewer role
case *registry.GetStorageProvidersRequest:
return true
case *provider.StatRequest:
return true
case *provider.ListContainerRequest:
return hasPermissions(ctx, client, r.GetRef(), &provider.ResourcePermissions{
ListContainer: true,
})
case *provider.InitiateFileDownloadRequest:
return hasPermissions(ctx, client, r.GetRef(), &provider.ResourcePermissions{
InitiateFileDownload: true,
})
case *appprovider.OpenInAppRequest:
return hasPermissions(ctx, client, &provider.Reference{ResourceId: r.ResourceInfo.Id}, &provider.ResourcePermissions{
InitiateFileDownload: true,
})
case *gateway.OpenInAppRequest:
return hasPermissions(ctx, client, r.GetRef(), &provider.ResourcePermissions{
InitiateFileDownload: true,
})

if ref.ResourceId != nil && utils.ResourceIDEqual(share.Share.ResourceId, ref.ResourceId) {
return nil
// Editor role
case *provider.CreateContainerRequest:
parent, err := parentOfResource(ctx, client, r.GetRef())
if err != nil {
return false
}
return hasPermissions(ctx, client, parent, &provider.ResourcePermissions{
CreateContainer: true,
})
case *provider.TouchFileRequest:
parent, err := parentOfResource(ctx, client, r.GetRef())
if err != nil {
return false
}
if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok {
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
return nil
return hasPermissions(ctx, client, parent, &provider.ResourcePermissions{
InitiateFileDownload: true,
})
case *provider.DeleteRequest:
return hasPermissions(ctx, client, r.GetRef(), &provider.ResourcePermissions{
InitiateFileDownload: true,
})
case *provider.MoveRequest:
return hasPermissions(ctx, client, r.Source, &provider.ResourcePermissions{
InitiateFileDownload: true,
}) && hasPermissions(ctx, client, r.Destination, &provider.ResourcePermissions{
InitiateFileUpload: true,
})
case *provider.InitiateFileUploadRequest:
parent, err := parentOfResource(ctx, client, r.GetRef())
if err != nil {
return false
}
return hasPermissions(ctx, client, parent, &provider.ResourcePermissions{
InitiateFileUpload: true,
})
}

return errtypes.PermissionDenied("request is not for a nested resource")
return false
}

func parentOfResource(ctx context.Context, client gateway.GatewayAPIClient, ref *provider.Reference) (*provider.Reference, error) {
if utils.IsAbsolutePathReference(ref) {
parent := filepath.Dir(ref.GetPath())
info, err := stat(ctx, client, &provider.Reference{Path: parent})
if err != nil {
return nil, err
}
return &provider.Reference{ResourceId: info.Id}, nil
}

info, err := stat(ctx, client, ref)
if err != nil {
return nil, err
}
return &provider.Reference{ResourceId: info.ParentId}, nil
}

func stat(ctx context.Context, client gateway.GatewayAPIClient, ref *provider.Reference) (*provider.ResourceInfo, error) {
statRes, err := client.Stat(ctx, &provider.StatRequest{
Ref: ref,
})

switch {
case err != nil:
return nil, err
case statRes.Status.Code == rpc.Code_CODE_NOT_FOUND:
return nil, errtypes.NotFound(statRes.Status.Message)
case statRes.Status.Code != rpc.Code_CODE_OK:
return nil, errtypes.InternalError(statRes.Status.Message)
}

return statRes.Info, nil
}

func hasPermissions(ctx context.Context, client gateway.GatewayAPIClient, ref *provider.Reference, permissionSet *provider.ResourcePermissions) bool {
info, err := stat(ctx, client, ref)
if err != nil {
return false
}
return utils.HasPermissions(info.PermissionSet, permissionSet)
}

func resolvePublicShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
Expand Down Expand Up @@ -329,16 +389,6 @@ func extractRef(req interface{}, tokenScope map[string]*authpb.Scope) (*provider
return nil, false
}

func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) {
switch v := req.(type) {
case *collaboration.GetReceivedShareRequest:
return v.GetRef(), true
case *collaboration.UpdateReceivedShareRequest:
return &collaboration.ShareReference{Spec: &collaboration.ShareReference_Id{Id: v.GetShare().GetShare().GetId()}}, true
}
return nil, false
}

func getRefKey(ref *provider.Reference) string {
if ref.Path != "" {
return ref.Path
Expand Down
2 changes: 2 additions & 0 deletions pkg/auth/scope/lightweight.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ func checkLightweightPath(path string) bool {
"/ocs/v1.php/cloud/user",
"/remote.php/webdav",
"/remote.php/dav/files",
"/thumbnails",
"/app/open",
"/app/new",
"/archiver",
"/dataprovider",
"/data",
"/app/open",
}
for _, p := range paths {
if strings.HasPrefix(path, p) {
Expand Down
91 changes: 28 additions & 63 deletions pkg/eosclient/eosbinary/eosbinary.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import (

const (
versionPrefix = ".sys.v#."
lwShareAttrKey = "reva.lwshare"
userACLEvalKey = "eval.useracl"
favoritesKey = "http://owncloud.org/ns/favorite"
)
Expand Down Expand Up @@ -280,30 +279,6 @@ func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorizat
return err
}

if a.Type == acl.TypeLightweight {
sysACL := ""
aclStr, ok := finfo.Attrs["sys."+lwShareAttrKey]
if ok {
acls, err := acl.Parse(aclStr, acl.ShortTextForm)
if err != nil {
return err
}
err = acls.SetEntry(a.Type, a.Qualifier, a.Permissions)
if err != nil {
return err
}
sysACL = acls.Serialize()
} else {
sysACL = a.CitrineSerialize()
}
sysACLAttr := &eosclient.Attribute{
Type: eosclient.SystemAttr,
Key: lwShareAttrKey,
Val: sysACL,
}
return c.SetAttr(ctx, auth, sysACLAttr, false, finfo.IsDir, path)
}

sysACL := a.CitrineSerialize()
args := []string{"acl", "--sys"}
if finfo.IsDir {
Expand All @@ -330,30 +305,6 @@ func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authori
return err
}

if a.Type == acl.TypeLightweight {
sysACL := ""
aclStr, ok := finfo.Attrs["sys."+lwShareAttrKey]
if ok {
acls, err := acl.Parse(aclStr, acl.ShortTextForm)
if err != nil {
return err
}
acls.DeleteEntry(a.Type, a.Qualifier)
if err != nil {
return err
}
sysACL = acls.Serialize()
} else {
sysACL = a.CitrineSerialize()
}
sysACLAttr := &eosclient.Attribute{
Type: eosclient.SystemAttr,
Key: lwShareAttrKey,
Val: sysACL,
}
return c.SetAttr(ctx, auth, sysACLAttr, false, finfo.IsDir, path)
}

sysACL := a.CitrineSerialize()
args := []string{"acl", "--sys"}
if finfo.IsDir {
Expand Down Expand Up @@ -645,6 +596,34 @@ func (c *Client) GetAttr(ctx context.Context, auth eosclient.Authorization, key,
return attr, nil
}

// GetAttrs returns all the attributes of a resource
func (c *Client) GetAttrs(ctx context.Context, auth eosclient.Authorization, path string) ([]*eosclient.Attribute, error) {
info, err := c.getRawFileInfoByPath(ctx, auth, path)
if err != nil {
return nil, err
}
if !info.IsDir {
path = getVersionFolder(path)
}

args := []string{"attr", "ls", path}
attrOut, _, err := c.executeEOS(ctx, args, auth)
if err != nil {
return nil, err
}

attrsStr := strings.Split(attrOut, "\n")
attrs := make([]*eosclient.Attribute, 0, len(attrsStr))
for _, line := range attrsStr {
attr, err := deserializeAttribute(line)
if err != nil {
return nil, err
}
attrs = append(attrs, attr)
}
return attrs, nil
}

func deserializeAttribute(attrStr string) (*eosclient.Attribute, error) {
// the string is in the form sys.forced.checksum="adler"
keyValue := strings.SplitN(strings.TrimSpace(attrStr), "=", 2) // keyValue = ["sys.forced.checksum", "\"adler\""]
Expand Down Expand Up @@ -1222,20 +1201,6 @@ func (c *Client) mapToFileInfo(ctx context.Context, kv, attrs map[string]string,
}
}

// Read lightweight ACLs recognized by the sys.reva.lwshare attr
if lwACLStr, ok := attrs["sys."+lwShareAttrKey]; ok {
lwAcls, err := acl.Parse(lwACLStr, acl.ShortTextForm)
if err != nil {
return nil, err
}
for _, e := range lwAcls.Entries {
err = sysACL.SetEntry(e.Type, e.Qualifier, e.Permissions)
if err != nil {
return nil, err
}
}
}

// Read the favorite attr
if parseFavoriteKey {
parseAndSetFavoriteAttr(ctx, attrs)
Expand Down
1 change: 1 addition & 0 deletions pkg/eosclient/eosclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type EOSClient interface {
SetAttr(ctx context.Context, auth Authorization, attr *Attribute, errorIfExists, recursive bool, path string) error
UnsetAttr(ctx context.Context, auth Authorization, attr *Attribute, recursive bool, path string) error
GetAttr(ctx context.Context, auth Authorization, key, path string) (*Attribute, error)
GetAttrs(ctx context.Context, auth Authorization, path string) ([]*Attribute, error)
GetQuota(ctx context.Context, username string, rootAuth Authorization, path string) (*QuotaInfo, error)
SetQuota(ctx context.Context, rooAuth Authorization, info *SetQuotaInfo) error
Touch(ctx context.Context, auth Authorization, path string) error
Expand Down
Loading

0 comments on commit 3cf7b3c

Please sign in to comment.