Skip to content

Commit

Permalink
Features for favorites xattrs in EOS, cache for scope expansion (#2686)
Browse files Browse the repository at this point in the history
  • Loading branch information
ishank011 authored Mar 28, 2022
1 parent 8950933 commit d8593c2
Show file tree
Hide file tree
Showing 10 changed files with 495 additions and 279 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/eos-scope-devs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Features for favorites xattrs in EOS, cache for scope expansion

https://github.com/cs3org/reva/pull/2686
26 changes: 16 additions & 10 deletions internal/grpc/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
)

var userGroupsCache gcache.Cache
var scopeExpansionCache gcache.Cache

type config struct {
// TODO(labkode): access a map is more performant as uri as fixed in length
Expand Down Expand Up @@ -75,6 +76,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI
conf.GatewayAddr = sharedconf.GetGatewaySVC(conf.GatewayAddr)

userGroupsCache = gcache.New(1000000).LFU().Build()
scopeExpansionCache = gcache.New(1000000).LFU().Build()

h, ok := tokenmgr.NewFuncs[conf.TokenManager]
if !ok {
Expand All @@ -96,7 +98,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI
// to decide the storage provider.
tkn, ok := ctxpkg.ContextGetToken(ctx)
if ok {
u, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr, false)
u, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr, true)
if err == nil {
ctx = ctxpkg.ContextSetUser(ctx, u)
}
Expand All @@ -112,7 +114,7 @@ func NewUnary(m map[string]interface{}, unprotected []string) (grpc.UnaryServerI
}

// validate the token and ensure access to the resource is allowed
u, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr, true)
u, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr, false)
if err != nil {
log.Warn().Err(err).Msg("access token is invalid")
return nil, status.Errorf(codes.PermissionDenied, "auth: core access token is invalid")
Expand All @@ -137,6 +139,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe
}

userGroupsCache = gcache.New(1000000).LFU().Build()
scopeExpansionCache = gcache.New(1000000).LFU().Build()

h, ok := tokenmgr.NewFuncs[conf.TokenManager]
if !ok {
Expand All @@ -159,7 +162,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe
// to decide the storage provider.
tkn, ok := ctxpkg.ContextGetToken(ctx)
if ok {
u, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr, false)
u, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr, true)
if err == nil {
ctx = ctxpkg.ContextSetUser(ctx, u)
ss = newWrappedServerStream(ctx, ss)
Expand All @@ -177,7 +180,7 @@ func NewStream(m map[string]interface{}, unprotected []string) (grpc.StreamServe
}

// validate the token and ensure access to the resource is allowed
u, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr, true)
u, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr, false)
if err != nil {
log.Warn().Err(err).Msg("access token is invalid")
return status.Errorf(codes.PermissionDenied, "auth: core access token is invalid")
Expand All @@ -204,18 +207,21 @@ func (ss *wrappedServerStream) Context() context.Context {
return ss.newCtx
}

func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.Manager, gatewayAddr string, fetchUserGroups bool) (*userpb.User, error) {
func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.Manager, gatewayAddr string, unprotected bool) (*userpb.User, error) {
u, tokenScope, err := mgr.DismantleToken(ctx, tkn)
if err != nil {
return nil, err
}

client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return nil, err
if unprotected {
return u, nil
}

if sharedconf.SkipUserGroupsInToken() && fetchUserGroups {
if sharedconf.SkipUserGroupsInToken() {
client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return nil, err
}
groups, err := getUserGroups(ctx, u, client)
if err != nil {
return nil, err
Expand All @@ -232,7 +238,7 @@ func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.
return u, nil
}

if err = expandAndVerifyScope(ctx, req, tokenScope, gatewayAddr, mgr); err != nil {
if err = expandAndVerifyScope(ctx, req, tokenScope, u, gatewayAddr, mgr); err != nil {
return nil, err
}

Expand Down
192 changes: 112 additions & 80 deletions internal/grpc/interceptors/auth/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package auth
import (
"context"
"strings"
"time"

appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
Expand All @@ -40,10 +41,16 @@ import (
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/token"
"github.com/cs3org/reva/pkg/utils"
"github.com/cs3org/reva/pkg/utils/resourceid"
"google.golang.org/grpc/metadata"
)

func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, gatewayAddr string, mgr token.Manager) error {
const (
scopeDelimiter = "#"
scopeCacheExpiration = 3600
)

func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[string]*authpb.Scope, user *userpb.User, gatewayAddr string, mgr token.Manager) error {
log := appctx.GetLogger(ctx)
client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
Expand All @@ -52,108 +59,63 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s

hasEditorRole := false
for _, v := range tokenScope {
if v.Role == authpb.Role_ROLE_EDITOR {
if v.Role == authpb.Role_ROLE_OWNER || v.Role == authpb.Role_ROLE_EDITOR {
hasEditorRole = true
break
}
}

if ref, ok := extractRef(req, hasEditorRole); ok {
// Check if req is of type *provider.Reference_Path
// If yes, the request might be coming from a share where the accessor is
// trying to impersonate the owner, since the share manager doesn't know the
// share path.
if ref.GetPath() != "" {
log.Info().Msgf("resolving path reference to ID to check token scope %+v", ref.GetPath())
for k := range tokenScope {
switch {
case strings.HasPrefix(k, "publicshare"):
var share link.PublicShare
err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share)
if err != nil {
continue
}
if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil && ok {
return nil
}
// The request is for a storage reference. This can be the case for multiple scenarios:
// - If the path is not empty, the request might be coming from a share where the accessor is
// trying to impersonate the owner, since the share manager doesn't know the
// share path.
// - If the ID not empty, the request might be coming from
// - a resource present inside a shared folder, or
// - a share created for a lightweight account after the token was minted.
log.Info().Msgf("resolving storage reference to check token scope %s", ref.String())
for k := range tokenScope {
switch {
case strings.HasPrefix(k, "publicshare"):
if err = resolvePublicShare(ctx, ref, tokenScope[k], client, mgr); err == nil {
return nil
}

case strings.HasPrefix(k, "share"):
var share collaboration.Share
err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share)
if err != nil {
continue
}
if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil && ok {
return nil
}
case strings.HasPrefix(k, "lightweight"):
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 _, share := range shares.Shares {
if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok {
return nil
}
}
case strings.HasPrefix(k, "share"):
if err = resolveUserShare(ctx, ref, tokenScope[k], client, mgr); err == nil {
return nil
}
}
} else {
// ref has ID present
// The request might be coming from
// - a resource present inside a shared folder, or
// - a share created for a lightweight account after the token was minted.

client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return err
}
for k := range tokenScope {
if strings.HasPrefix(k, "lightweight") {
log.Info().Msgf("resolving ID reference against received shares to verify token scope %+v", ref.GetResourceId())
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 _, share := range shares.Shares {
if utils.ResourceIDEqual(share.Share.ResourceId, ref.GetResourceId()) {
return nil
}
if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok {
return nil
}
}
} else if strings.HasPrefix(k, "publicshare") {
var share link.PublicShare
err := utils.UnmarshalJSONToProtoV1(tokenScope[k].Resource.Value, &share)
if err != nil {
continue
}
if ok, err := checkIfNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil && ok {
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)
client, err := pool.GetGatewayServiceClient(gatewayAddr)
if err != nil {
return err
}
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
}
Expand All @@ -169,6 +131,69 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
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
}

shares, err := client.ListReceivedShares(ctx, &collaboration.ListReceivedSharesRequest{})
if err != nil || shares.Status.Code != rpc.Code_CODE_OK {
return errtypes.InternalError("error listing received shares")
}

for _, share := range shares.Shares {
shareKey := "lw:" + user.Id.OpaqueId + scopeDelimiter + resourceid.OwnCloudResourceIDWrap(share.Share.ResourceId)
_ = scopeExpansionCache.SetWithExpire(shareKey, nil, scopeCacheExpiration*time.Second)

if ref.ResourceId != nil && utils.ResourceIDEqual(share.Share.ResourceId, ref.ResourceId) {
return nil
}
if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok {
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
return nil
}
}

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

func resolvePublicShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
var share link.PublicShare
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return err
}

return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
}

func resolveUserShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
var share collaboration.Share
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return err
}

return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
}

func checkCacheForNestedResource(ctx context.Context, ref *provider.Reference, resource *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) error {
// Check if this ref is cached
key := resourceid.OwnCloudResourceIDWrap(resource) + scopeDelimiter + getRefKey(ref)
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
}

if ok, err := checkIfNestedResource(ctx, ref, resource, client, mgr); err == nil && ok {
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
return nil
}

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

func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) (bool, error) {
// Since the resource ID is obtained from the scope, the current token
// has access to it.
Expand Down Expand Up @@ -270,3 +295,10 @@ func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) {
}
return nil, false
}

func getRefKey(ref *provider.Reference) string {
if ref.Path != "" {
return ref.Path
}
return resourceid.OwnCloudResourceIDWrap(ref.ResourceId)
}
3 changes: 1 addition & 2 deletions internal/grpc/services/gateway/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token)

// Commenting out as the token size can get too big
// For now, we'll try to resolve all resources on every request
// TODO(ishank011): Add a cache for these
// For now, we'll try to resolve all resources on every request and cache those
/* scope, err := s.expandScopes(ctx, res.TokenScope)
if err != nil {
err = errors.Wrap(err, "authsvc: error expanding token scope")
Expand Down
Loading

0 comments on commit d8593c2

Please sign in to comment.