diff --git a/changelog/unreleased/eos-scope-devs.md b/changelog/unreleased/eos-scope-devs.md new file mode 100644 index 0000000000..a5eeee678d --- /dev/null +++ b/changelog/unreleased/eos-scope-devs.md @@ -0,0 +1,3 @@ +Enhancement: Features for favorites xattrs in EOS, cache for scope expansion + +https://github.com/cs3org/reva/pull/2686 \ No newline at end of file diff --git a/internal/grpc/interceptors/auth/auth.go b/internal/grpc/interceptors/auth/auth.go index 1db27bb2b1..b31f96014c 100644 --- a/internal/grpc/interceptors/auth/auth.go +++ b/internal/grpc/interceptors/auth/auth.go @@ -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 @@ -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 { @@ -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) } @@ -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") @@ -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 { @@ -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) @@ -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") @@ -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 @@ -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 } diff --git a/internal/grpc/interceptors/auth/scope.go b/internal/grpc/interceptors/auth/scope.go index a18ac245cf..5f1c96cb91 100644 --- a/internal/grpc/interceptors/auth/scope.go +++ b/internal/grpc/interceptors/auth/scope.go @@ -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" @@ -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 { @@ -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 } @@ -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. @@ -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) +} diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 28eddaa1ac..b526188b13 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -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") diff --git a/pkg/cbox/storage/eoswrapper/eoswrapper.go b/pkg/cbox/storage/eoswrapper/eoswrapper.go index bc4467ba5d..5cbfc0feed 100644 --- a/pkg/cbox/storage/eoswrapper/eoswrapper.go +++ b/pkg/cbox/storage/eoswrapper/eoswrapper.go @@ -21,12 +21,14 @@ package eoswrapper import ( "bytes" "context" + "io" "strings" "text/template" "github.com/Masterminds/sprig" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/storage" "github.com/cs3org/reva/pkg/storage/fs/registry" "github.com/cs3org/reva/pkg/storage/utils/eosfs" @@ -67,6 +69,7 @@ func parseConfig(m map[string]interface{}) (*eosfs.Config, string, error) { // allow recycle operations for project spaces if !c.EnableHome && strings.HasPrefix(c.Namespace, eosProjectsNamespace) { c.AllowPathRecycleOperations = true + c.ImpersonateOwnerforRevisions = true } t, ok := m["mount_id_template"].(string) @@ -135,6 +138,30 @@ func (w *wrapper) ListFolder(ctx context.Context, ref *provider.Reference, mdKey return res, nil } +func (w *wrapper) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { + if err := w.userIsProjectAdmin(ctx, ref); err != nil { + return nil, err + } + + return w.FS.ListRevisions(ctx, ref) +} + +func (w *wrapper) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) { + if err := w.userIsProjectAdmin(ctx, ref); err != nil { + return nil, err + } + + return w.FS.DownloadRevision(ctx, ref, revisionKey) +} + +func (w *wrapper) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { + if err := w.userIsProjectAdmin(ctx, ref); err != nil { + return err + } + + return w.FS.RestoreRevision(ctx, ref, revisionKey) +} + func (w *wrapper) getMountID(ctx context.Context, r *provider.ResourceInfo) string { if r == nil { return "" @@ -173,3 +200,33 @@ func (w *wrapper) setProjectSharingPermissions(ctx context.Context, r *provider. } return nil } + +func (w *wrapper) userIsProjectAdmin(ctx context.Context, ref *provider.Reference) error { + // Check if this storage provider corresponds to a project spaces instance + if !strings.HasPrefix(w.conf.Namespace, eosProjectsNamespace) { + return nil + } + + res, err := w.FS.GetMD(ctx, ref, nil) + if err != nil { + return err + } + + // Extract project name from the path resembling /c/cernbox or /c/cernbox/minutes/.. + parts := strings.SplitN(res.Path, "/", 4) + if len(parts) != 4 && len(parts) != 3 { + // The request might be for / or /$letter + // Nothing to do in that case + return nil + } + adminGroup := projectSpaceGroupsPrefix + parts[2] + projectSpaceAdminGroupsSuffix + user := ctxpkg.ContextMustGetUser(ctx) + + for _, g := range user.Groups { + if g == adminGroup { + return nil + } + } + + return errtypes.PermissionDenied("eosfs: project spaces revisions can only be accessed by admins") +} diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index e77bf8472b..a74fce777f 100644 --- a/pkg/eosclient/eosbinary/eosbinary.go +++ b/pkg/eosclient/eosbinary/eosbinary.go @@ -34,6 +34,7 @@ import ( "time" "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/eosclient" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/storage/utils/acl" @@ -45,6 +46,8 @@ import ( const ( versionPrefix = ".sys.v#." lwShareAttrKey = "reva.lwshare" + userACLEvalKey = "eval.useracl" + favoritesKey = "http://owncloud.org/ns/favorite" ) const ( @@ -289,7 +292,7 @@ func (c *Client) executeEOS(ctx context.Context, cmdArgs []string, auth eosclien // AddACL adds an new acl to EOS with the given aclType. func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, pos uint, a *acl.Entry) error { - finfo, err := c.GetFileInfoByPath(ctx, auth, path) + finfo, err := c.getRawFileInfoByPath(ctx, auth, path) if err != nil { return err } @@ -342,7 +345,7 @@ func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorizat // RemoveACL removes the acl from EOS. func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error { - finfo, err := c.GetFileInfoByPath(ctx, auth, path) + finfo, err := c.getRawFileInfoByPath(ctx, auth, path) if err != nil { return err } @@ -436,7 +439,7 @@ func (c *Client) GetFileInfoByInode(ctx context.Context, auth eosclient.Authoriz if err != nil { return nil, err } - info, err := c.parseFileInfo(stdout) + info, err := c.parseFileInfo(ctx, stdout, true) if err != nil { return nil, err } @@ -449,7 +452,7 @@ func (c *Client) GetFileInfoByInode(ctx context.Context, auth eosclient.Authoriz info.Inode = inode } - return c.mergeParentACLsForFiles(ctx, auth, info), nil + return c.mergeACLsAndAttrsForFiles(ctx, auth, info), nil } // GetFileInfoByFXID returns the FileInfo by the given file id in hexadecimal @@ -460,12 +463,12 @@ func (c *Client) GetFileInfoByFXID(ctx context.Context, auth eosclient.Authoriza return nil, err } - info, err := c.parseFileInfo(stdout) + info, err := c.parseFileInfo(ctx, stdout, true) if err != nil { return nil, err } - return c.mergeParentACLsForFiles(ctx, auth, info), nil + return c.mergeACLsAndAttrsForFiles(ctx, auth, info), nil } // GetFileInfoByPath returns the FilInfo at the given path @@ -475,7 +478,7 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authoriza if err != nil { return nil, err } - info, err := c.parseFileInfo(stdout) + info, err := c.parseFileInfo(ctx, stdout, true) if err != nil { return nil, err } @@ -486,18 +489,38 @@ func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authoriza } } - return c.mergeParentACLsForFiles(ctx, auth, info), nil + return c.mergeACLsAndAttrsForFiles(ctx, auth, info), nil +} + +func (c *Client) getRawFileInfoByPath(ctx context.Context, auth eosclient.Authorization, path string) (*eosclient.FileInfo, error) { + args := []string{"file", "info", path, "-m"} + stdout, _, err := c.executeEOS(ctx, args, auth) + if err != nil { + return nil, err + } + return c.parseFileInfo(ctx, stdout, false) } -func (c *Client) mergeParentACLsForFiles(ctx context.Context, auth eosclient.Authorization, info *eosclient.FileInfo) *eosclient.FileInfo { +func (c *Client) mergeACLsAndAttrsForFiles(ctx context.Context, auth eosclient.Authorization, info *eosclient.FileInfo) *eosclient.FileInfo { // We need to inherit the ACLs for the parent directory as these are not available for files + // And the attributes from the version folders if !info.IsDir { - parentInfo, err := c.GetFileInfoByPath(ctx, auth, path.Dir(info.File)) + parentInfo, err := c.getRawFileInfoByPath(ctx, auth, path.Dir(info.File)) // Even if this call fails, at least return the current file object if err == nil { info.SysACL.Entries = append(info.SysACL.Entries, parentInfo.SysACL.Entries...) } + + // We need to merge attrs set for the version folders, so get those resolved for the current user + versionFolderInfo, err := c.GetFileInfoByPath(ctx, auth, getVersionFolder(info.File)) + if err == nil { + info.SysACL.Entries = append(info.SysACL.Entries, versionFolderInfo.SysACL.Entries...) + for k, v := range versionFolderInfo.Attrs { + info.Attrs[k] = v + } + } } + return info } @@ -506,6 +529,30 @@ func (c *Client) SetAttr(ctx context.Context, auth eosclient.Authorization, attr if !isValidAttribute(attr) { return errors.New("eos: attr is invalid: " + serializeAttribute(attr)) } + + var info *eosclient.FileInfo + var err error + // We need to set the attrs on the version folder as they are not persisted across writes + // Except for the sys.eval.useracl attr as EOS uses that to determine if it needs to obey + // the user ACLs set on the file + if !(attr.Type == SystemAttr && attr.Key == userACLEvalKey) { + info, err = c.getRawFileInfoByPath(ctx, auth, path) + if err != nil { + return err + } + if !info.IsDir { + path = getVersionFolder(path) + } + } + + // Favorites need to be stored per user so handle these separately + if attr.Type == UserAttr && attr.Key == favoritesKey { + return c.handleFavAttr(ctx, auth, attr, recursive, path, info, true) + } + return c.setEOSAttr(ctx, auth, attr, recursive, path) +} + +func (c *Client) setEOSAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string) error { var args []string if recursive { args = []string{"attr", "-r", "set", serializeAttribute(attr), path} @@ -520,18 +567,65 @@ func (c *Client) SetAttr(ctx context.Context, auth eosclient.Authorization, attr return nil } +func (c *Client) handleFavAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string, info *eosclient.FileInfo, set bool) error { + var err error + u := ctxpkg.ContextMustGetUser(ctx) + if info == nil { + info, err = c.getRawFileInfoByPath(ctx, auth, path) + if err != nil { + return err + } + } + favStr := info.Attrs[favoritesKey] + favs, err := acl.Parse(favStr, acl.ShortTextForm) + if err != nil { + return err + } + if set { + err = favs.SetEntry(acl.TypeUser, u.Id.OpaqueId, "1") + if err != nil { + return err + } + } else { + favs.DeleteEntry(acl.TypeUser, u.Id.OpaqueId) + } + attr.Val = favs.Serialize() + return c.setEOSAttr(ctx, auth, attr, recursive, path) +} + // UnsetAttr unsets an extended attribute on a path. func (c *Client) UnsetAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string) error { if !isValidAttribute(attr) { return errors.New("eos: attr is invalid: " + serializeAttribute(attr)) } + + var info *eosclient.FileInfo + var err error + // We need to set the attrs on the version folder as they are not persisted across writes + // Except for the sys.eval.useracl attr as EOS uses that to determine if it needs to obey + // the user ACLs set on the file + if !(attr.Type == SystemAttr && attr.Key == userACLEvalKey) { + info, err = c.getRawFileInfoByPath(ctx, auth, path) + if err != nil { + return err + } + if !info.IsDir { + path = getVersionFolder(path) + } + } + + // Favorites need to be stored per user so handle these separately + if attr.Type == UserAttr && attr.Key == favoritesKey { + return c.handleFavAttr(ctx, auth, attr, recursive, path, info, false) + } + var args []string if recursive { args = []string{"attr", "-r", "rm", fmt.Sprintf("%s.%s", attrTypeToString(attr.Type), attr.Key), path} } else { args = []string{"attr", "rm", fmt.Sprintf("%s.%s", attrTypeToString(attr.Type), attr.Key), path} } - _, _, err := c.executeEOS(ctx, args, auth) + _, _, err = c.executeEOS(ctx, args, auth) if err != nil { return err } @@ -764,12 +858,12 @@ func (c *Client) GenerateToken(ctx context.Context, auth eosclient.Authorization func (c *Client) getVersionFolderInode(ctx context.Context, auth eosclient.Authorization, p string) (uint64, error) { versionFolder := getVersionFolder(p) - md, err := c.GetFileInfoByPath(ctx, auth, versionFolder) + md, err := c.getRawFileInfoByPath(ctx, auth, versionFolder) if err != nil { if err = c.CreateDir(ctx, auth, versionFolder); err != nil { return 0, err } - md, err = c.GetFileInfoByPath(ctx, auth, versionFolder) + md, err = c.getRawFileInfoByPath(ctx, auth, versionFolder) if err != nil { return 0, err } @@ -877,7 +971,7 @@ func (c *Client) parseFind(ctx context.Context, auth eosclient.Authorization, di if rl == "" { continue } - fi, err := c.parseFileInfo(rl) + fi, err := c.parseFileInfo(ctx, rl, true) if err != nil { return nil, err } @@ -908,8 +1002,13 @@ func (c *Client) parseFind(ctx context.Context, auth eosclient.Authorization, di versionFolderPath := getVersionFolder(fi.File) if vf, ok := versionFolders[versionFolderPath]; ok { fi.Inode = vf.Inode - } else if err := c.CreateDir(ctx, auth, versionFolderPath); err == nil { - if md, err := c.GetFileInfoByPath(ctx, auth, versionFolderPath); err == nil { + fi.SysACL.Entries = append(fi.SysACL.Entries, vf.SysACL.Entries...) + for k, v := range vf.Attrs { + fi.Attrs[k] = v + } + + } else if err := c.CreateDir(ctx, auth, versionFolderPath); err == nil { // Create the version folder if it doesn't exist + if md, err := c.getRawFileInfoByPath(ctx, auth, versionFolderPath); err == nil { fi.Inode = md.Inode } } @@ -963,7 +1062,7 @@ func (c *Client) parseQuota(path, raw string) (*eosclient.QuotaInfo, error) { } // TODO(labkode): better API to access extended attributes. -func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) { +func (c *Client) parseFileInfo(ctx context.Context, raw string, parseFavoriteKey bool) (*eosclient.FileInfo, error) { line := raw[15:] index := strings.Index(line, " file=/") @@ -1005,7 +1104,7 @@ func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) { } } } - fi, err := c.mapToFileInfo(kv, attrs) + fi, err := c.mapToFileInfo(ctx, kv, attrs, parseFavoriteKey) if err != nil { return nil, err } @@ -1015,7 +1114,7 @@ func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) { // mapToFileInfo converts the dictionary to an usable structure. // The kv has format: // map[sys.forced.space:default files:0 mode:42555 ino:5 sys.forced.blocksize:4k sys.forced.layout:replica uid:0 fid:5 sys.forced.blockchecksum:crc32c sys.recycle:/eos/backup/proc/recycle/ fxid:00000005 pid:1 etag:5:0.000 keylength.file:4 file:/eos treesize:1931593933849913 container:3 gid:0 mtime:1498571294.108614409 ctime:1460121992.294326762 pxid:00000001 sys.forced.checksum:adler sys.forced.nstripes:2] -func (c *Client) mapToFileInfo(kv, attrs map[string]string) (*eosclient.FileInfo, error) { +func (c *Client) mapToFileInfo(ctx context.Context, kv, attrs map[string]string, parseFavoriteKey bool) (*eosclient.FileInfo, error) { inode, err := strconv.ParseUint(kv["ino"], 10, 64) if err != nil { return nil, err @@ -1121,6 +1220,11 @@ func (c *Client) mapToFileInfo(kv, attrs map[string]string) (*eosclient.FileInfo } } + // Read the favorite attr + if parseFavoriteKey { + parseAndSetFavoriteAttr(ctx, attrs) + } + fi := &eosclient.FileInfo{ File: kv["file"], Inode: inode, @@ -1142,3 +1246,26 @@ func (c *Client) mapToFileInfo(kv, attrs map[string]string) (*eosclient.FileInfo return fi, nil } + +func parseAndSetFavoriteAttr(ctx context.Context, attrs map[string]string) { + // Read and correctly set the favorite attr + if user, ok := ctxpkg.ContextGetUser(ctx); ok { + if favAttrStr, ok := attrs[favoritesKey]; ok { + favUsers, err := acl.Parse(favAttrStr, acl.ShortTextForm) + if err != nil { + return + } + for _, u := range favUsers.Entries { + // Check if the current user has favorited this resource + if u.Qualifier == user.Id.OpaqueId { + // Set attr val to 1 + attrs[favoritesKey] = "1" + return + } + } + } + } + + // Delete the favorite attr from the response + delete(attrs, favoritesKey) +} diff --git a/pkg/storage/utils/acl/acl.go b/pkg/storage/utils/acl/acl.go index 311ee1a454..802bcf3a1a 100644 --- a/pkg/storage/utils/acl/acl.go +++ b/pkg/storage/utils/acl/acl.go @@ -67,10 +67,6 @@ func Parse(acls string, delimiter string) (*ACLs, error) { if err != nil { return nil, err } - // for now we ignore default / empty qualifiers - // if entry.Qualifier == "" { - // continue - // } entries = append(entries, entry) } @@ -129,7 +125,14 @@ type Entry struct { func ParseEntry(singleSysACL string) (*Entry, error) { tokens := strings.Split(singleSysACL, ":") if len(tokens) != 3 { - return nil, errInvalidACL + if len(tokens) == 2 { + // The ACL entries might be stored as type:qualifier=permissions + // Handle that case separately + parts := (strings.Split(tokens[1], "=")) + tokens = []string{tokens[0], parts[0], parts[1]} + } else { + return nil, errInvalidACL + } } return &Entry{ diff --git a/pkg/storage/utils/eosfs/config.go b/pkg/storage/utils/eosfs/config.go index 53cc6a3992..5b6838e696 100644 --- a/pkg/storage/utils/eosfs/config.go +++ b/pkg/storage/utils/eosfs/config.go @@ -140,6 +140,10 @@ type Config struct { // Only considered when EnableHome is false. AllowPathRecycleOperations bool `mapstructure:"allow_path_recycle_operations"` + // Whether we should impersonate the owner of a resource when trying to perform + // revisions-related operations. + ImpersonateOwnerforRevisions bool `mapstructure:"impersonate_owner_for_revisions"` + // HTTP connections to EOS: max number of idle conns MaxIdleConns int `mapstructure:"max_idle_conns"` diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 59b0fa8b88..288bdd206d 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -365,6 +365,47 @@ func (fs *eosfs) unwrapInternal(ctx context.Context, ns, np, layout string) (str return external, nil } +func (fs *eosfs) resolveRefForbidShareFolder(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) { + p, err := fs.resolve(ctx, ref) + if err != nil { + return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") + } + if fs.isShareFolder(ctx, p) { + return "", eosclient.Authorization{}, errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder") + } + fn := fs.wrap(ctx, p) + + u, err := getUser(ctx) + if err != nil { + return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return "", eosclient.Authorization{}, err + } + + return fn, auth, nil +} + +func (fs *eosfs) resolveRefAndGetAuth(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) { + p, err := fs.resolve(ctx, ref) + if err != nil { + return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference") + } + fn := fs.wrap(ctx, p) + + u, err := getUser(ctx) + if err != nil { + return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx") + } + auth, err := fs.getUserAuth(ctx, u, fn) + if err != nil { + return "", eosclient.Authorization{}, err + } + + return fn, auth, nil +} + // resolve takes in a request path or request id and returns the unwrapped path. func (fs *eosfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) { if ref.ResourceId != nil { @@ -459,19 +500,9 @@ func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Referen return errtypes.BadRequest("eosfs: no metadata set") } - p, err := fs.resolve(ctx, ref) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return errors.Wrap(err, "eosfs: error getting uid and gid for user") + return err } for k, v := range md.Metadata { @@ -501,19 +532,9 @@ func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Refer return errtypes.BadRequest("eosfs: no keys set") } - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { - return errors.Wrap(err, "eosfs: error getting uid and gid for user") + return err } for _, k := range keys { @@ -556,18 +577,7 @@ func (fs *eosfs) Unlock(ctx context.Context, ref *provider.Reference) error { } func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return err } @@ -594,13 +604,11 @@ func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provi } func (fs *eosfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { - p, err := fs.resolve(ctx, ref) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") + return err } - fn := fs.wrap(ctx, p) - position := eosclient.EndPosition rootAuth, err := fs.getRootAuth(ctx) @@ -614,16 +622,6 @@ func (fs *eosfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *prov Permissions: &provider.ResourcePermissions{}, } - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return err - } - eosACL, err := fs.getEosACL(ctx, grant) if err != nil { return err @@ -702,17 +700,7 @@ func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *pr Type: eosACLType, } - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return err } @@ -734,17 +722,7 @@ func (fs *eosfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *pr } func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error resolving reference") - } - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return nil, err - } - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return nil, err } @@ -1025,7 +1003,7 @@ func (fs *eosfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, // lightweight accounts don't have quota nodes, so we're passing an empty string as path auth, err := fs.getUserAuth(ctx, u, "") if err != nil { - return 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user") + return 0, 0, err } rootAuth, err := fs.getRootAuth(ctx) @@ -1209,50 +1187,41 @@ func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, func (fs *eosfs) CreateDir(ctx context.Context, ref *provider.Reference) error { log := appctx.GetLogger(ctx) - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } p, err := fs.resolve(ctx, ref) if err != nil { - return nil + return errors.Wrap(err, "eosfs: error resolving reference") + } + if fs.isShareFolder(ctx, p) { + return errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder") } + fn := fs.wrap(ctx, p) - auth, err := fs.getUserAuth(ctx, u, p) + u, err := getUser(ctx) if err != nil { - return err + return errors.Wrap(err, "eosfs: no user in ctx") } - log.Info().Msgf("eosfs: createdir: path=%s", p) - - if fs.isShareFolder(ctx, p) { - return errtypes.PermissionDenied("eosfs: cannot create folder under the share folder") + // We need the auth corresponding to the parent directory + // as the file might not exist at the moment + auth, err := fs.getUserAuth(ctx, u, path.Dir(fn)) + if err != nil { + return err } - fn := fs.wrap(ctx, p) + log.Info().Msgf("eosfs: createdir: path=%s", fn) return fs.c.CreateDir(ctx, auth, fn) } // TouchFile as defined in the storage.FS interface func (fs *eosfs) TouchFile(ctx context.Context, ref *provider.Reference) error { log := appctx.GetLogger(ctx) - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil - } - auth, err := fs.getUserAuth(ctx, u, p) + fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref) if err != nil { return err } + log.Info().Msgf("eosfs: touch file: path=%s", fn) - log.Info().Msgf("eosfs: touch file: path=%s", p) - - fn := fs.wrap(ctx, p) return fs.c.Touch(ctx, auth, fn) } @@ -1409,22 +1378,7 @@ func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error } func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error resolving reference") - } - - if fs.isShareFolder(ctx, p) { - return nil, errtypes.PermissionDenied("eosfs: cannot download under the virtual share folder") - } - - fn := fs.wrap(ctx, p) - - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) + fn, auth, err := fs.resolveRefForbidShareFolder(ctx, ref) if err != nil { return nil, err } @@ -1433,24 +1387,33 @@ func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.Read } func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error resolving reference") - } - - if fs.isShareFolder(ctx, p) { - return nil, errtypes.PermissionDenied("eosfs: cannot list revisions under the virtual share folder") - } + var auth eosclient.Authorization + var fn string + var err error - fn := fs.wrap(ctx, p) + if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { + // We need to access the revisions for a non-home reference. + // We'll get the owner of the particular resource and impersonate them + // if we have access to it. + md, err := fs.GetMD(ctx, ref, nil) + if err != nil { + return nil, err + } + fn = fs.wrap(ctx, md.Path) - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return nil, err + if md.PermissionSet.ListFileVersions { + auth, err = fs.getUIDGateway(ctx, md.Owner) + if err != nil { + return nil, err + } + } else { + return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to list revisions") + } + } else { + fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + if err != nil { + return nil, err + } } eosRevisions, err := fs.c.ListVersions(ctx, auth, fn) @@ -1467,48 +1430,66 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([] } func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) { - p, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "eosfs: error resolving reference") - } - - if fs.isShareFolder(ctx, p) { - return nil, errtypes.PermissionDenied("eosfs: cannot download revision under the virtual share folder") - } + var auth eosclient.Authorization + var fn string + var err error - fn := fs.wrap(ctx, p) + if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { + // We need to access the revisions for a non-home reference. + // We'll get the owner of the particular resource and impersonate them + // if we have access to it. + md, err := fs.GetMD(ctx, ref, nil) + if err != nil { + return nil, err + } + fn = fs.wrap(ctx, md.Path) - u, err := getUser(ctx) - if err != nil { - return nil, errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return nil, err + if md.PermissionSet.InitiateFileDownload { + auth, err = fs.getUIDGateway(ctx, md.Owner) + if err != nil { + return nil, err + } + } else { + return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to download revisions") + } + } else { + fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + if err != nil { + return nil, err + } } return fs.c.ReadVersion(ctx, auth, fn, revisionKey) } func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { - p, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "eosfs: error resolving reference") - } - - if fs.isShareFolder(ctx, p) { - return errtypes.PermissionDenied("eosfs: cannot restore revision under the virtual share folder") - } + var auth eosclient.Authorization + var fn string + var err error - fn := fs.wrap(ctx, p) + if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions { + // We need to access the revisions for a non-home reference. + // We'll get the owner of the particular resource and impersonate them + // if we have access to it. + md, err := fs.GetMD(ctx, ref, nil) + if err != nil { + return err + } + fn = fs.wrap(ctx, md.Path) - u, err := getUser(ctx) - if err != nil { - return errors.Wrap(err, "eosfs: no user in ctx") - } - auth, err := fs.getUserAuth(ctx, u, fn) - if err != nil { - return err + if md.PermissionSet.RestoreFileVersion { + auth, err = fs.getUIDGateway(ctx, md.Owner) + if err != nil { + return err + } + } else { + return errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore revisions") + } + } else { + fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref) + if err != nil { + return err + } } return fs.c.RollbackToVersion(ctx, auth, fn, revisionKey) @@ -1988,7 +1969,7 @@ func (fs *eosfs) getEOSToken(ctx context.Context, u *userpb.User, fn string) (eo } info, err := fs.c.GetFileInfoByPath(ctx, rootAuth, fn) if err != nil { - return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting file info by path") + return eosclient.Authorization{}, err } auth := eosclient.Authorization{ Role: eosclient.Role{ diff --git a/pkg/storage/utils/eosfs/upload.go b/pkg/storage/utils/eosfs/upload.go index 9d20e01194..6a926f8ceb 100644 --- a/pkg/storage/utils/eosfs/upload.go +++ b/pkg/storage/utils/eosfs/upload.go @@ -22,6 +22,7 @@ import ( "context" "io" "os" + "path" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/errtypes" @@ -67,7 +68,10 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC if err != nil { return errors.Wrap(err, "eos: no user in ctx") } - auth, err := fs.getUserAuth(ctx, u, fn) + + // We need the auth corresponding to the parent directory + // as the file might not exist at the moment + auth, err := fs.getUserAuth(ctx, u, path.Dir(fn)) if err != nil { return err }