github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/eosfs/eosfs.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package eosfs
    20  
    21  import (
    22  	"context"
    23  	"database/sql"
    24  	b64 "encoding/base64"
    25  	"encoding/json"
    26  	"fmt"
    27  	"io"
    28  	"net/url"
    29  	"os"
    30  	"path"
    31  	"path/filepath"
    32  	"regexp"
    33  	"strconv"
    34  	"strings"
    35  	"time"
    36  
    37  	"github.com/bluele/gcache"
    38  	grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
    39  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    40  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    41  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    42  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    43  	"github.com/cs3org/reva/v2/pkg/appctx"
    44  	"github.com/cs3org/reva/v2/pkg/conversions"
    45  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    46  	"github.com/cs3org/reva/v2/pkg/eosclient"
    47  	"github.com/cs3org/reva/v2/pkg/eosclient/eosbinary"
    48  	"github.com/cs3org/reva/v2/pkg/eosclient/eosgrpc"
    49  	"github.com/cs3org/reva/v2/pkg/errtypes"
    50  	"github.com/cs3org/reva/v2/pkg/mime"
    51  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    52  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    53  	"github.com/cs3org/reva/v2/pkg/sharedconf"
    54  	"github.com/cs3org/reva/v2/pkg/storage"
    55  	"github.com/cs3org/reva/v2/pkg/storage/utils/acl"
    56  	"github.com/cs3org/reva/v2/pkg/storage/utils/chunking"
    57  	"github.com/cs3org/reva/v2/pkg/storage/utils/grants"
    58  	"github.com/cs3org/reva/v2/pkg/storage/utils/templates"
    59  	"github.com/cs3org/reva/v2/pkg/utils"
    60  	"github.com/jellydator/ttlcache/v2"
    61  	"github.com/pkg/errors"
    62  )
    63  
    64  const (
    65  	refTargetAttrKey = "reva.target"
    66  )
    67  
    68  const (
    69  	// SystemAttr is the system extended attribute.
    70  	SystemAttr eosclient.AttrType = iota
    71  	// UserAttr is the user extended attribute.
    72  	UserAttr
    73  )
    74  
    75  // LockPayloadKey is the key in the xattr for lock payload
    76  const LockPayloadKey = "reva.lock.payload"
    77  
    78  // LockExpirationKey is the key in the xattr for lock expiration
    79  const LockExpirationKey = "reva.lock.expiration"
    80  
    81  // LockTypeKey is the key in the xattr for lock payload
    82  const LockTypeKey = "reva.lock.type"
    83  
    84  var hiddenReg = regexp.MustCompile(`\.sys\..#.`)
    85  
    86  func (c *Config) init() {
    87  	c.Namespace = path.Clean(c.Namespace)
    88  	if !strings.HasPrefix(c.Namespace, "/") {
    89  		c.Namespace = "/"
    90  	}
    91  
    92  	if c.ShadowNamespace == "" {
    93  		c.ShadowNamespace = path.Join(c.Namespace, ".shadow")
    94  	}
    95  
    96  	// Quota node defaults to namespace if empty
    97  	if c.QuotaNode == "" {
    98  		c.QuotaNode = c.Namespace
    99  	}
   100  
   101  	if c.DefaultQuotaBytes == 0 {
   102  		c.DefaultQuotaBytes = 1000000000000 // 1 TB
   103  	}
   104  	if c.DefaultQuotaFiles == 0 {
   105  		c.DefaultQuotaFiles = 1000000 // 1 Million
   106  	}
   107  
   108  	if c.ShareFolder == "" {
   109  		c.ShareFolder = "/MyShares"
   110  	}
   111  	// ensure share folder always starts with slash
   112  	c.ShareFolder = path.Join("/", c.ShareFolder)
   113  
   114  	if c.EosBinary == "" {
   115  		c.EosBinary = "/usr/bin/eos"
   116  	}
   117  
   118  	if c.XrdcopyBinary == "" {
   119  		c.XrdcopyBinary = "/opt/eos/xrootd/bin/xrdcopy"
   120  	}
   121  
   122  	if c.MasterURL == "" {
   123  		c.MasterURL = "root://eos-example.org"
   124  	}
   125  
   126  	if c.SlaveURL == "" {
   127  		c.SlaveURL = c.MasterURL
   128  	}
   129  
   130  	if c.CacheDirectory == "" {
   131  		c.CacheDirectory = os.TempDir()
   132  	}
   133  
   134  	if c.UserLayout == "" {
   135  		c.UserLayout = "{{.Username}}" // TODO set better layout
   136  	}
   137  
   138  	if c.UserIDCacheSize == 0 {
   139  		c.UserIDCacheSize = 1000000
   140  	}
   141  
   142  	if c.UserIDCacheWarmupDepth == 0 {
   143  		c.UserIDCacheWarmupDepth = 2
   144  	}
   145  
   146  	if c.TokenExpiry == 0 {
   147  		c.TokenExpiry = 3600
   148  	}
   149  
   150  	c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc)
   151  }
   152  
   153  type eosfs struct {
   154  	c              eosclient.EOSClient
   155  	conf           *Config
   156  	chunkHandler   *chunking.ChunkHandler
   157  	spacesDB       *sql.DB
   158  	singleUserAuth eosclient.Authorization
   159  	userIDCache    *ttlcache.Cache
   160  	tokenCache     gcache.Cache
   161  	spacesCache    gcache.Cache
   162  }
   163  
   164  // NewEOSFS returns a storage.FS interface implementation that connects to an EOS instance
   165  func NewEOSFS(c *Config) (storage.FS, error) {
   166  	c.init()
   167  
   168  	// bail out if keytab is not found.
   169  	if c.UseKeytab {
   170  		if _, err := os.Stat(c.Keytab); err != nil {
   171  			err = errors.Wrapf(err, "eosfs: keytab not accessible at location: %s", err)
   172  			return nil, err
   173  		}
   174  	}
   175  
   176  	var eosClient eosclient.EOSClient
   177  	var err error
   178  	if c.UseGRPC {
   179  		eosClientOpts := &eosgrpc.Options{
   180  			XrdcopyBinary:      c.XrdcopyBinary,
   181  			URL:                c.MasterURL,
   182  			GrpcURI:            c.GrpcURI,
   183  			CacheDirectory:     c.CacheDirectory,
   184  			UseKeytab:          c.UseKeytab,
   185  			Keytab:             c.Keytab,
   186  			Authkey:            c.GRPCAuthkey,
   187  			SecProtocol:        c.SecProtocol,
   188  			VersionInvariant:   c.VersionInvariant,
   189  			ReadUsesLocalTemp:  c.ReadUsesLocalTemp,
   190  			WriteUsesLocalTemp: c.WriteUsesLocalTemp,
   191  		}
   192  		eosHTTPOpts := &eosgrpc.HTTPOptions{
   193  			BaseURL:             c.MasterURL,
   194  			MaxIdleConns:        c.MaxIdleConns,
   195  			MaxConnsPerHost:     c.MaxConnsPerHost,
   196  			MaxIdleConnsPerHost: c.MaxIdleConnsPerHost,
   197  			IdleConnTimeout:     c.IdleConnTimeout,
   198  			ClientCertFile:      c.ClientCertFile,
   199  			ClientKeyFile:       c.ClientKeyFile,
   200  			ClientCADirs:        c.ClientCADirs,
   201  			ClientCAFiles:       c.ClientCAFiles,
   202  		}
   203  		eosClient, err = eosgrpc.New(eosClientOpts, eosHTTPOpts)
   204  	} else {
   205  		eosClientOpts := &eosbinary.Options{
   206  			XrdcopyBinary:       c.XrdcopyBinary,
   207  			URL:                 c.MasterURL,
   208  			EosBinary:           c.EosBinary,
   209  			CacheDirectory:      c.CacheDirectory,
   210  			ForceSingleUserMode: c.ForceSingleUserMode,
   211  			SingleUsername:      c.SingleUsername,
   212  			UseKeytab:           c.UseKeytab,
   213  			Keytab:              c.Keytab,
   214  			SecProtocol:         c.SecProtocol,
   215  			VersionInvariant:    c.VersionInvariant,
   216  			TokenExpiry:         c.TokenExpiry,
   217  		}
   218  		eosClient, err = eosbinary.New(eosClientOpts)
   219  	}
   220  
   221  	if err != nil {
   222  		return nil, errors.Wrap(err, "error initializing eosclient")
   223  	}
   224  
   225  	var db *sql.DB
   226  	if c.SpacesConfig.Enabled {
   227  		db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.SpacesConfig.DbUsername, c.SpacesConfig.DbPassword, c.SpacesConfig.DbHost, c.SpacesConfig.DbPort, c.SpacesConfig.DbName))
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  	}
   232  
   233  	eosfs := &eosfs{
   234  		c:            eosClient,
   235  		conf:         c,
   236  		spacesDB:     db,
   237  		chunkHandler: chunking.NewChunkHandler(c.CacheDirectory),
   238  		userIDCache:  ttlcache.NewCache(),
   239  		tokenCache:   gcache.New(c.UserIDCacheSize).LFU().Build(),
   240  		spacesCache:  gcache.New(c.UserIDCacheSize).LFU().Build(),
   241  	}
   242  
   243  	eosfs.userIDCache.SetCacheSizeLimit(c.UserIDCacheSize)
   244  	eosfs.userIDCache.SetExpirationReasonCallback(func(key string, reason ttlcache.EvictionReason, value interface{}) {
   245  		// We only set those keys with TTL which we weren't able to retrieve the last time
   246  		// For those keys, try to contact the userprovider service again when they expire
   247  		if reason == ttlcache.Expired {
   248  			_, _ = eosfs.getUserIDGateway(context.Background(), key)
   249  		}
   250  	})
   251  
   252  	go eosfs.userIDcacheWarmup()
   253  
   254  	return eosfs, nil
   255  }
   256  
   257  func (fs *eosfs) userIDcacheWarmup() {
   258  	if !fs.conf.EnableHome {
   259  		time.Sleep(2 * time.Second)
   260  		ctx := context.Background()
   261  		paths := []string{fs.wrap(ctx, "/")}
   262  		auth, _ := fs.getRootAuth(ctx)
   263  
   264  		for i := 0; i < fs.conf.UserIDCacheWarmupDepth; i++ {
   265  			var newPaths []string
   266  			for _, fn := range paths {
   267  				if eosFileInfos, err := fs.c.List(ctx, auth, fn); err == nil {
   268  					for _, f := range eosFileInfos {
   269  						_, _ = fs.getUserIDGateway(ctx, strconv.FormatUint(f.UID, 10))
   270  						newPaths = append(newPaths, f.File)
   271  					}
   272  				}
   273  			}
   274  			paths = newPaths
   275  		}
   276  	}
   277  }
   278  
   279  func (fs *eosfs) Shutdown(ctx context.Context) error {
   280  	// TODO(labkode): in a grpc implementation we can close connections.
   281  	return nil
   282  }
   283  
   284  func getUser(ctx context.Context) (*userpb.User, error) {
   285  	u, ok := ctxpkg.ContextGetUser(ctx)
   286  	if !ok {
   287  		err := errors.Wrap(errtypes.UserRequired(""), "eosfs: error getting user from ctx")
   288  		return nil, err
   289  	}
   290  	return u, nil
   291  }
   292  
   293  func (fs *eosfs) getLayout(ctx context.Context) (layout string) {
   294  	if fs.conf.EnableHome {
   295  		u, err := getUser(ctx)
   296  		if err != nil {
   297  			panic(err)
   298  		}
   299  		layout = templates.WithUser(u, fs.conf.UserLayout)
   300  	}
   301  	return
   302  }
   303  
   304  func (fs *eosfs) getInternalHome(ctx context.Context) (string, error) {
   305  	if !fs.conf.EnableHome {
   306  		return "", errtypes.NotSupported("eos: get home not supported")
   307  	}
   308  
   309  	u, err := getUser(ctx)
   310  	if err != nil {
   311  		err = errors.Wrap(err, "eosfs: wrap: no user in ctx and home is enabled")
   312  		return "", err
   313  	}
   314  
   315  	relativeHome := templates.WithUser(u, fs.conf.UserLayout)
   316  	return relativeHome, nil
   317  }
   318  
   319  func (fs *eosfs) wrapShadow(ctx context.Context, fn string) (internal string) {
   320  	if fs.conf.EnableHome {
   321  		layout, err := fs.getInternalHome(ctx)
   322  		if err != nil {
   323  			panic(err)
   324  		}
   325  		internal = path.Join(fs.conf.ShadowNamespace, layout, fn)
   326  	} else {
   327  		internal = path.Join(fs.conf.ShadowNamespace, fn)
   328  	}
   329  	return
   330  }
   331  
   332  func (fs *eosfs) wrap(ctx context.Context, fn string) (internal string) {
   333  	fn = strings.TrimPrefix(fn, fs.conf.MountPath)
   334  	if fs.conf.EnableHome {
   335  		layout, err := fs.getInternalHome(ctx)
   336  		if err != nil {
   337  			panic(err)
   338  		}
   339  		internal = path.Join(fs.conf.Namespace, layout, fn)
   340  	} else {
   341  		internal = path.Join(fs.conf.Namespace, fn)
   342  	}
   343  	log := appctx.GetLogger(ctx)
   344  	log.Debug().Msg("eosfs: wrap external=" + fn + " internal=" + internal)
   345  	return
   346  }
   347  
   348  func (fs *eosfs) unwrap(ctx context.Context, internal string) (string, error) {
   349  	log := appctx.GetLogger(ctx)
   350  	layout := fs.getLayout(ctx)
   351  	ns, err := fs.getNsMatch(internal, []string{fs.conf.Namespace, fs.conf.ShadowNamespace})
   352  	if err != nil {
   353  		return "", err
   354  	}
   355  	external, err := fs.unwrapInternal(ctx, ns, internal, layout)
   356  	if err != nil {
   357  		return "", err
   358  	}
   359  	log.Debug().Msgf("eosfs: unwrap: internal=%s external=%s", internal, external)
   360  	return external, nil
   361  }
   362  
   363  func (fs *eosfs) getNsMatch(internal string, nss []string) (string, error) {
   364  	var match string
   365  
   366  	for _, ns := range nss {
   367  		if strings.HasPrefix(internal, ns) && len(ns) > len(match) {
   368  			match = ns
   369  		}
   370  	}
   371  
   372  	if match == "" {
   373  		return "", errtypes.NotFound(fmt.Sprintf("eosfs: path is outside namespaces: path=%s namespaces=%+v", internal, nss))
   374  	}
   375  
   376  	return match, nil
   377  }
   378  
   379  func (fs *eosfs) unwrapInternal(ctx context.Context, ns, np, layout string) (string, error) {
   380  	trim := path.Join(ns, layout)
   381  
   382  	if !strings.HasPrefix(np, trim) {
   383  		return "", errtypes.NotFound(fmt.Sprintf("eosfs: path is outside the directory of the logged-in user: internal=%s trim=%s namespace=%+v", np, trim, ns))
   384  	}
   385  
   386  	external := strings.TrimPrefix(np, trim)
   387  
   388  	if external == "" {
   389  		external = "/"
   390  	}
   391  
   392  	return external, nil
   393  }
   394  
   395  func (fs *eosfs) resolveRefForbidShareFolder(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) {
   396  	p, err := fs.resolve(ctx, ref)
   397  	if err != nil {
   398  		return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference")
   399  	}
   400  	if fs.isShareFolder(ctx, p) {
   401  		return "", eosclient.Authorization{}, errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder")
   402  	}
   403  	fn := fs.wrap(ctx, p)
   404  
   405  	u, err := getUser(ctx)
   406  	if err != nil {
   407  		return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx")
   408  	}
   409  	auth, err := fs.getUserAuth(ctx, u, fn)
   410  	if err != nil {
   411  		return "", eosclient.Authorization{}, err
   412  	}
   413  
   414  	return fn, auth, nil
   415  }
   416  
   417  func (fs *eosfs) resolveRefAndGetAuth(ctx context.Context, ref *provider.Reference) (string, eosclient.Authorization, error) {
   418  	p, err := fs.resolve(ctx, ref)
   419  	if err != nil {
   420  		return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: error resolving reference")
   421  	}
   422  	fn := fs.wrap(ctx, p)
   423  
   424  	u, err := getUser(ctx)
   425  	if err != nil {
   426  		return "", eosclient.Authorization{}, errors.Wrap(err, "eosfs: no user in ctx")
   427  	}
   428  	auth, err := fs.getUserAuth(ctx, u, fn)
   429  	if err != nil {
   430  		return "", eosclient.Authorization{}, err
   431  	}
   432  
   433  	return fn, auth, nil
   434  }
   435  
   436  // resolve takes in a request path or request id and returns the unwrapped path.
   437  func (fs *eosfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) {
   438  	if ref.ResourceId != nil {
   439  		p, err := fs.getPath(ctx, ref.ResourceId)
   440  		if err != nil {
   441  			return "", err
   442  		}
   443  		p = path.Join(p, ref.Path)
   444  		return p, nil
   445  	}
   446  	if ref.Path != "" {
   447  		return ref.Path, nil
   448  	}
   449  
   450  	// reference is invalid
   451  	return "", fmt.Errorf("invalid reference %+v. at least resource_id or path must be set", ref)
   452  }
   453  
   454  func (fs *eosfs) getPath(ctx context.Context, id *provider.ResourceId) (string, error) {
   455  	fid, err := strconv.ParseUint(id.OpaqueId, 10, 64)
   456  	if err != nil {
   457  		return "", fmt.Errorf("error converting string to int for eos fileid: %s", id.OpaqueId)
   458  	}
   459  
   460  	auth, err := fs.getRootAuth(ctx)
   461  	if err != nil {
   462  		return "", err
   463  	}
   464  
   465  	eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid)
   466  	if err != nil {
   467  		return "", errors.Wrap(err, "eosfs: error getting file info by inode")
   468  	}
   469  
   470  	return fs.unwrap(ctx, eosFileInfo.File)
   471  }
   472  
   473  func (fs *eosfs) isShareFolder(ctx context.Context, p string) bool {
   474  	return strings.HasPrefix(p, fs.conf.ShareFolder)
   475  }
   476  
   477  func (fs *eosfs) isShareFolderRoot(ctx context.Context, p string) bool {
   478  	return path.Clean(p) == fs.conf.ShareFolder
   479  }
   480  
   481  func (fs *eosfs) isShareFolderChild(ctx context.Context, p string) bool {
   482  	p = path.Clean(p)
   483  	vals := strings.Split(p, fs.conf.ShareFolder+"/")
   484  	return len(vals) > 1 && vals[1] != ""
   485  }
   486  
   487  func (fs *eosfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) {
   488  	fid, err := strconv.ParseUint(id.OpaqueId, 10, 64)
   489  	if err != nil {
   490  		return "", errors.Wrap(err, "eosfs: error parsing fileid string")
   491  	}
   492  
   493  	u, err := getUser(ctx)
   494  	if err != nil {
   495  		return "", errors.Wrap(err, "eosfs: no user in ctx")
   496  	}
   497  	if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT || u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED {
   498  		auth, err := fs.getRootAuth(ctx)
   499  		if err != nil {
   500  			return "", err
   501  		}
   502  		eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid)
   503  		if err != nil {
   504  			return "", errors.Wrap(err, "eosfs: error getting file info by inode")
   505  		}
   506  		if perm := fs.permissionSet(ctx, eosFileInfo, nil); perm.GetPath {
   507  			return fs.unwrap(ctx, eosFileInfo.File)
   508  		}
   509  		return "", errtypes.PermissionDenied("eosfs: getting path for id not allowed")
   510  	}
   511  
   512  	auth, err := fs.getUserAuth(ctx, u, "")
   513  	if err != nil {
   514  		return "", err
   515  	}
   516  
   517  	eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid)
   518  	if err != nil {
   519  		return "", errors.Wrap(err, "eosfs: error getting file info by inode")
   520  	}
   521  
   522  	p, err := fs.unwrap(ctx, eosFileInfo.File)
   523  	if err != nil {
   524  		return "", err
   525  	}
   526  	return path.Join(fs.conf.MountPath, p), nil
   527  }
   528  
   529  func (fs *eosfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error {
   530  	if len(md.Metadata) == 0 {
   531  		return errtypes.BadRequest("eosfs: no metadata set")
   532  	}
   533  
   534  	fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref)
   535  	if err != nil {
   536  		return err
   537  	}
   538  
   539  	for k, v := range md.Metadata {
   540  		if k == "" || v == "" {
   541  			return errtypes.BadRequest(fmt.Sprintf("eosfs: key or value is empty: key:%s, value:%s", k, v))
   542  		}
   543  
   544  		// do not allow to set a lock key attr
   545  		if k == LockPayloadKey || k == LockExpirationKey || k == LockTypeKey {
   546  			return errtypes.BadRequest(fmt.Sprintf("eosfs: key %s not allowed", k))
   547  		}
   548  
   549  		attr := &eosclient.Attribute{
   550  			Type: UserAttr,
   551  			Key:  k,
   552  			Val:  v,
   553  		}
   554  
   555  		// TODO(labkode): SetArbitraryMetadata does not have semantics for recursivity.
   556  		// We set it to false
   557  		err := fs.c.SetAttr(ctx, auth, attr, false, false, fn)
   558  		if err != nil {
   559  			return errors.Wrap(err, "eosfs: error setting xattr in eos driver")
   560  		}
   561  
   562  	}
   563  	return nil
   564  }
   565  
   566  func (fs *eosfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error {
   567  	if len(keys) == 0 {
   568  		return errtypes.BadRequest("eosfs: no keys set")
   569  	}
   570  
   571  	fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref)
   572  	if err != nil {
   573  		return err
   574  	}
   575  
   576  	for _, k := range keys {
   577  		if k == "" {
   578  			return errtypes.BadRequest("eosfs: key is empty")
   579  		}
   580  
   581  		attr := &eosclient.Attribute{
   582  			Type: UserAttr,
   583  			Key:  k,
   584  		}
   585  
   586  		err := fs.c.UnsetAttr(ctx, auth, attr, false, fn)
   587  		if err != nil {
   588  			return errors.Wrap(err, "eosfs: error unsetting xattr in eos driver")
   589  		}
   590  
   591  	}
   592  	return nil
   593  }
   594  
   595  func (fs *eosfs) getLockExpiration(ctx context.Context, auth eosclient.Authorization, path string) (*types.Timestamp, bool, error) {
   596  	expiration, err := fs.c.GetAttr(ctx, auth, "sys."+LockExpirationKey, path)
   597  	if err != nil {
   598  		// since the expiration is optional, if we do not find it in the attr
   599  		// just return a nil value, without reporting the error
   600  		if _, ok := err.(errtypes.NotFound); ok {
   601  			return nil, true, nil
   602  		}
   603  		return nil, false, err
   604  	}
   605  	// the expiration value should be unix time encoded
   606  	unixTime, err := strconv.ParseInt(expiration.Val, 10, 64)
   607  	if err != nil {
   608  		return nil, false, errors.Wrap(err, "eosfs: error converting unix time")
   609  	}
   610  	t := time.Unix(unixTime, 0)
   611  	timestamp := &types.Timestamp{
   612  		Seconds: uint64(unixTime),
   613  	}
   614  	return timestamp, t.After(time.Now()), nil
   615  }
   616  
   617  func (fs *eosfs) getLockContent(ctx context.Context, auth eosclient.Authorization, path string, expiration *types.Timestamp) (*provider.Lock, error) {
   618  	t, err := fs.c.GetAttr(ctx, auth, "sys."+LockTypeKey, path)
   619  	if err != nil {
   620  		return nil, err
   621  	}
   622  	lockType, err := strconv.ParseInt(t.Val, 10, 32)
   623  	if err != nil {
   624  		return nil, errors.Wrap(err, "eosfs: error decoding lock type")
   625  	}
   626  
   627  	d, err := fs.c.GetAttr(ctx, auth, "sys."+LockPayloadKey, path)
   628  	if err != nil {
   629  		return nil, err
   630  	}
   631  
   632  	data, err := b64.StdEncoding.DecodeString(d.Val)
   633  	if err != nil {
   634  		return nil, err
   635  	}
   636  	l := new(provider.Lock)
   637  	err = json.Unmarshal(data, l)
   638  	if err != nil {
   639  		return nil, err
   640  	}
   641  
   642  	l.Type = provider.LockType(lockType)
   643  	l.Expiration = expiration
   644  
   645  	return l, nil
   646  
   647  }
   648  
   649  func (fs *eosfs) removeLockAttrs(ctx context.Context, auth eosclient.Authorization, path string) error {
   650  	err := fs.c.UnsetAttr(ctx, auth, &eosclient.Attribute{
   651  		Type: SystemAttr,
   652  		Key:  LockExpirationKey,
   653  	}, false, path)
   654  	if err != nil {
   655  		// as the expiration time in the lock is optional
   656  		// we will discard the error if the attr is not set
   657  		if !errors.Is(err, eosclient.AttrNotExistsError) {
   658  			return errors.Wrap(err, "eosfs: error unsetting the lock expiration")
   659  		}
   660  	}
   661  
   662  	err = fs.c.UnsetAttr(ctx, auth, &eosclient.Attribute{
   663  		Type: SystemAttr,
   664  		Key:  LockTypeKey,
   665  	}, false, path)
   666  	if err != nil {
   667  		return errors.Wrap(err, "eosfs: error unsetting the lock type")
   668  	}
   669  
   670  	err = fs.c.UnsetAttr(ctx, auth, &eosclient.Attribute{
   671  		Type: SystemAttr,
   672  		Key:  LockPayloadKey,
   673  	}, false, path)
   674  	if err != nil {
   675  		return errors.Wrap(err, "eosfs: error unsetting the lock payload")
   676  	}
   677  
   678  	return nil
   679  }
   680  
   681  func (fs *eosfs) getLock(ctx context.Context, auth eosclient.Authorization, user *userpb.User, path string, ref *provider.Reference) (*provider.Lock, error) {
   682  	// the cs3apis require to have the read permission on the resource
   683  	// to get the eventual lock.
   684  	has, err := fs.userHasReadAccess(ctx, user, ref)
   685  	if err != nil {
   686  		return nil, errors.Wrap(err, "eosfs: error checking read access to resource")
   687  	}
   688  	if !has {
   689  		return nil, errtypes.BadRequest("user has not read access on resource")
   690  	}
   691  
   692  	expiration, valid, err := fs.getLockExpiration(ctx, auth, path)
   693  	if err != nil {
   694  		return nil, err
   695  	}
   696  
   697  	if !valid {
   698  		// the previous lock expired
   699  		if err := fs.removeLockAttrs(ctx, auth, path); err != nil {
   700  			return nil, err
   701  		}
   702  		return nil, errtypes.NotFound("lock not found for ref")
   703  	}
   704  
   705  	l, err := fs.getLockContent(ctx, auth, path, expiration)
   706  	if err != nil {
   707  		if !errors.Is(err, eosclient.AttrNotExistsError) {
   708  			return nil, errtypes.NotFound("lock not found for ref")
   709  		}
   710  	}
   711  	return l, nil
   712  }
   713  
   714  // GetLock returns an existing lock on the given reference
   715  func (fs *eosfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) {
   716  	path, err := fs.resolve(ctx, ref)
   717  	if err != nil {
   718  		return nil, errors.Wrap(err, "eosfs: error resolving reference")
   719  	}
   720  	path = fs.wrap(ctx, path)
   721  
   722  	user, err := getUser(ctx)
   723  	if err != nil {
   724  		return nil, errors.Wrap(err, "eosfs: no user in ctx")
   725  	}
   726  	auth, err := fs.getUserAuth(ctx, user, path)
   727  	if err != nil {
   728  		return nil, errors.Wrap(err, "eosfs: error getting uid and gid for user")
   729  	}
   730  
   731  	return fs.getLock(ctx, auth, user, path, ref)
   732  }
   733  
   734  func (fs *eosfs) setLock(ctx context.Context, lock *provider.Lock, path string, check bool) error {
   735  	auth, err := fs.getRootAuth(ctx)
   736  	if err != nil {
   737  		return err
   738  	}
   739  
   740  	encodedLock, err := encodeLock(lock)
   741  	if err != nil {
   742  		return errors.Wrap(err, "eosfs: error encoding lock")
   743  	}
   744  
   745  	if lock.Expiration != nil {
   746  		// set expiration
   747  		err = fs.c.SetAttr(ctx, auth, &eosclient.Attribute{
   748  			Type: SystemAttr,
   749  			Key:  LockExpirationKey,
   750  			Val:  strconv.FormatUint(lock.Expiration.Seconds, 10),
   751  		}, check, false, path)
   752  		switch {
   753  		case errors.Is(err, eosclient.AttrAlreadyExistsError):
   754  			return errtypes.BadRequest("lock already set")
   755  		case err != nil:
   756  			return err
   757  		}
   758  	}
   759  
   760  	// set lock type
   761  	err = fs.c.SetAttr(ctx, auth, &eosclient.Attribute{
   762  		Type: SystemAttr,
   763  		Key:  LockTypeKey,
   764  		Val:  strconv.FormatUint(uint64(lock.Type), 10),
   765  	}, false, false, path)
   766  	if err != nil {
   767  		return errors.Wrap(err, "eosfs: error setting lock type")
   768  	}
   769  
   770  	// set payload
   771  	err = fs.c.SetAttr(ctx, auth, &eosclient.Attribute{
   772  		Type: SystemAttr,
   773  		Key:  LockPayloadKey,
   774  		Val:  encodedLock,
   775  	}, false, false, path)
   776  	if err != nil {
   777  		return errors.Wrap(err, "eosfs: error setting lock payload")
   778  	}
   779  	return nil
   780  }
   781  
   782  // SetLock puts a lock on the given reference
   783  func (fs *eosfs) SetLock(ctx context.Context, ref *provider.Reference, l *provider.Lock) error {
   784  	if l.Type == provider.LockType_LOCK_TYPE_SHARED {
   785  		return errtypes.NotSupported("shared lock not yet implemented")
   786  	}
   787  
   788  	path, err := fs.resolve(ctx, ref)
   789  	if err != nil {
   790  		return errors.Wrap(err, "eosfs: error resolving reference")
   791  	}
   792  	path = fs.wrap(ctx, path)
   793  
   794  	user, err := getUser(ctx)
   795  	if err != nil {
   796  		return errors.Wrap(err, "eosfs: no user in ctx")
   797  	}
   798  	auth, err := fs.getUserAuth(ctx, user, path)
   799  	if err != nil {
   800  		return errors.Wrap(err, "eosfs: error getting uid and gid for user")
   801  	}
   802  
   803  	_, err = fs.getLock(ctx, auth, user, path, ref)
   804  	if err != nil {
   805  		// if the err is NotFound it is fine, otherwise we have to return
   806  		if _, ok := err.(errtypes.NotFound); !ok {
   807  			return err
   808  		}
   809  	}
   810  	if err == nil {
   811  		// the resource is already locked
   812  		return errtypes.BadRequest("resource already locked")
   813  	}
   814  
   815  	// the cs3apis require to have the write permission on the resource
   816  	// to set a lock. because in eos we can set attrs even if the user does
   817  	// not have the write permission, we need to check if the user that made
   818  	// the request has it
   819  	has, err := fs.userHasWriteAccess(ctx, user, ref)
   820  	if err != nil {
   821  		return errors.Wrap(err, fmt.Sprintf("eosfs: cannot check if user %s has write access on resource", user.Username))
   822  	}
   823  	if !has {
   824  		return errtypes.PermissionDenied(fmt.Sprintf("user %s has not write access on resource", user.Username))
   825  	}
   826  
   827  	// the user in the lock could differ from the user in the context
   828  	// in that case, also the user in the lock MUST have the write permission
   829  	if l.User != nil && !utils.UserEqual(user.Id, l.User) {
   830  		has, err := fs.userIDHasWriteAccess(ctx, l.User, ref)
   831  		if err != nil {
   832  			return errors.Wrap(err, "eosfs: cannot check if user has write access on resource")
   833  		}
   834  		if !has {
   835  			return errtypes.PermissionDenied(fmt.Sprintf("user %s has not write access on resource", user.Username))
   836  		}
   837  	}
   838  
   839  	return fs.setLock(ctx, l, path, true)
   840  }
   841  
   842  func (fs *eosfs) getUserFromID(ctx context.Context, userID *userpb.UserId) (*userpb.User, error) {
   843  	client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc)
   844  	if err != nil {
   845  		return nil, err
   846  	}
   847  	res, err := client.GetUser(ctx, &userpb.GetUserRequest{
   848  		UserId: userID,
   849  	})
   850  
   851  	if err != nil {
   852  		return nil, err
   853  	}
   854  	if res.Status.Code != rpc.Code_CODE_OK {
   855  		return nil, errtypes.InternalError(res.Status.Message)
   856  	}
   857  	return res.User, nil
   858  }
   859  
   860  func (fs *eosfs) userHasWriteAccess(ctx context.Context, user *userpb.User, ref *provider.Reference) (bool, error) {
   861  	ctx = ctxpkg.ContextSetUser(ctx, user)
   862  	resInfo, err := fs.GetMD(ctx, ref, nil, nil)
   863  	if err != nil {
   864  		return false, err
   865  	}
   866  	return resInfo.PermissionSet.InitiateFileUpload, nil
   867  }
   868  
   869  func (fs *eosfs) userIDHasWriteAccess(ctx context.Context, userID *userpb.UserId, ref *provider.Reference) (bool, error) {
   870  	user, err := fs.getUserFromID(ctx, userID)
   871  	if err != nil {
   872  		return false, nil
   873  	}
   874  	return fs.userHasWriteAccess(ctx, user, ref)
   875  }
   876  
   877  func (fs *eosfs) userHasReadAccess(ctx context.Context, user *userpb.User, ref *provider.Reference) (bool, error) {
   878  	ctx = ctxpkg.ContextSetUser(ctx, user)
   879  	resInfo, err := fs.GetMD(ctx, ref, nil, nil)
   880  	if err != nil {
   881  		return false, err
   882  	}
   883  	return resInfo.PermissionSet.InitiateFileDownload, nil
   884  }
   885  
   886  func encodeLock(l *provider.Lock) (string, error) {
   887  	data, err := json.Marshal(l)
   888  	if err != nil {
   889  		return "", err
   890  	}
   891  	return b64.StdEncoding.EncodeToString(data), nil
   892  }
   893  
   894  // RefreshLock refreshes an existing lock on the given reference
   895  // TODO: use existingLockId. See https://github.com/cs3org/reva/pull/3286
   896  func (fs *eosfs) RefreshLock(ctx context.Context, ref *provider.Reference, newLock *provider.Lock, _ string) error {
   897  	// TODO (gdelmont): check if the new lock is already expired?
   898  
   899  	if newLock.Type == provider.LockType_LOCK_TYPE_SHARED {
   900  		return errtypes.NotSupported("shared lock not yet implemented")
   901  	}
   902  
   903  	oldLock, err := fs.GetLock(ctx, ref)
   904  	if err != nil {
   905  		switch err.(type) {
   906  		case errtypes.NotFound:
   907  			// the lock does not exist
   908  			return errtypes.BadRequest("file was not locked")
   909  		default:
   910  			return err
   911  		}
   912  	}
   913  
   914  	user, err := getUser(ctx)
   915  	if err != nil {
   916  		return errors.Wrap(err, "eosfs: error getting user")
   917  	}
   918  
   919  	// check if the holder is the same of the new lock
   920  	if !sameHolder(oldLock, newLock) {
   921  		return errtypes.BadRequest("caller does not hold the lock")
   922  	}
   923  
   924  	path, err := fs.resolve(ctx, ref)
   925  	if err != nil {
   926  		return errors.Wrap(err, "eosfs: error resolving reference")
   927  	}
   928  	path = fs.wrap(ctx, path)
   929  
   930  	// the cs3apis require to have the write permission on the resource
   931  	// to set a lock
   932  	has, err := fs.userHasWriteAccess(ctx, user, ref)
   933  	if err != nil {
   934  		return errors.Wrap(err, "eosfs: cannot check if user has write access on resource")
   935  	}
   936  	if !has {
   937  		return errtypes.PermissionDenied(fmt.Sprintf("user %s has not write access on resource", user.Username))
   938  	}
   939  
   940  	return fs.setLock(ctx, newLock, path, false)
   941  }
   942  
   943  func sameHolder(l1, l2 *provider.Lock) bool {
   944  	same := true
   945  	if l1.User != nil || l2.User != nil {
   946  		same = utils.UserEqual(l1.User, l2.User)
   947  	}
   948  	if l1.AppName != "" || l2.AppName != "" {
   949  		same = l1.AppName == l2.AppName
   950  	}
   951  	return same
   952  }
   953  
   954  // Unlock removes an existing lock from the given reference
   955  func (fs *eosfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error {
   956  	if lock.Type == provider.LockType_LOCK_TYPE_SHARED {
   957  		return errtypes.NotSupported("shared lock not yet implemented")
   958  	}
   959  
   960  	oldLock, err := fs.GetLock(ctx, ref)
   961  	if err != nil {
   962  		switch err.(type) {
   963  		case errtypes.NotFound:
   964  			// the lock does not exist
   965  			return errtypes.BadRequest("file was not locked")
   966  		default:
   967  			return err
   968  		}
   969  	}
   970  
   971  	// check if the lock id of the lock corresponds to the stored lock
   972  	if oldLock.LockId != lock.LockId {
   973  		return errtypes.BadRequest("lock id does not match")
   974  	}
   975  
   976  	if !sameHolder(oldLock, lock) {
   977  		return errtypes.BadRequest("caller does not hold the lock")
   978  	}
   979  
   980  	user, err := getUser(ctx)
   981  	if err != nil {
   982  		return errors.Wrap(err, "eosfs: error getting user")
   983  	}
   984  
   985  	// the cs3apis require to have the write permission on the resource
   986  	// to remove the lock
   987  	has, err := fs.userHasWriteAccess(ctx, user, ref)
   988  	if err != nil {
   989  		return errors.Wrap(err, "eosfs: cannot check if user has write access on resource")
   990  	}
   991  	if !has {
   992  		return errtypes.PermissionDenied(fmt.Sprintf("user %s has not write access on resource", user.Username))
   993  	}
   994  
   995  	path, err := fs.resolve(ctx, ref)
   996  	if err != nil {
   997  		return errors.Wrap(err, "eosfs: error resolving reference")
   998  	}
   999  	path = fs.wrap(ctx, path)
  1000  
  1001  	auth, err := fs.getRootAuth(ctx)
  1002  	if err != nil {
  1003  		return errors.Wrap(err, "eosfs: error getting uid and gid for user")
  1004  	}
  1005  	return fs.removeLockAttrs(ctx, auth, path)
  1006  }
  1007  
  1008  func (fs *eosfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
  1009  	fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref)
  1010  	if err != nil {
  1011  		return err
  1012  	}
  1013  
  1014  	rootAuth, err := fs.getRootAuth(ctx)
  1015  	if err != nil {
  1016  		return err
  1017  	}
  1018  
  1019  	// position where put the ACL
  1020  	position := eosclient.StartPosition
  1021  
  1022  	eosACL, err := fs.getEosACL(ctx, g)
  1023  	if err != nil {
  1024  		return err
  1025  	}
  1026  
  1027  	err = fs.c.AddACL(ctx, auth, rootAuth, fn, position, eosACL)
  1028  	if err != nil {
  1029  		return errors.Wrap(err, "eosfs: error adding acl")
  1030  	}
  1031  	return nil
  1032  
  1033  }
  1034  
  1035  func (fs *eosfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error {
  1036  	fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref)
  1037  	if err != nil {
  1038  		return err
  1039  	}
  1040  
  1041  	position := eosclient.EndPosition
  1042  
  1043  	rootAuth, err := fs.getRootAuth(ctx)
  1044  	if err != nil {
  1045  		return err
  1046  	}
  1047  
  1048  	// empty permissions => deny
  1049  	grant := &provider.Grant{
  1050  		Grantee:     g,
  1051  		Permissions: &provider.ResourcePermissions{},
  1052  	}
  1053  
  1054  	eosACL, err := fs.getEosACL(ctx, grant)
  1055  	if err != nil {
  1056  		return err
  1057  	}
  1058  
  1059  	err = fs.c.AddACL(ctx, auth, rootAuth, fn, position, eosACL)
  1060  	if err != nil {
  1061  		return errors.Wrap(err, "eosfs: error adding acl")
  1062  	}
  1063  	return nil
  1064  }
  1065  
  1066  func (fs *eosfs) getEosACL(ctx context.Context, g *provider.Grant) (*acl.Entry, error) {
  1067  	permissions, err := grants.GetACLPerm(g.Permissions)
  1068  	if err != nil {
  1069  		return nil, err
  1070  	}
  1071  	t, err := grants.GetACLType(g.Grantee.Type)
  1072  	if err != nil {
  1073  		return nil, err
  1074  	}
  1075  
  1076  	var qualifier string
  1077  	if t == acl.TypeUser {
  1078  		// if the grantee is a lightweight account, we need to set it accordingly
  1079  		if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT ||
  1080  			g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_FEDERATED {
  1081  			t = acl.TypeLightweight
  1082  			qualifier = g.Grantee.GetUserId().OpaqueId
  1083  		} else {
  1084  			// since EOS Citrine ACLs are stored with uid, we need to convert username to
  1085  			// uid only for users.
  1086  			auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId())
  1087  			if err != nil {
  1088  				return nil, err
  1089  			}
  1090  			qualifier = auth.Role.UID
  1091  		}
  1092  	} else {
  1093  		qualifier = g.Grantee.GetGroupId().OpaqueId
  1094  	}
  1095  
  1096  	eosACL := &acl.Entry{
  1097  		Qualifier:   qualifier,
  1098  		Permissions: permissions,
  1099  		Type:        t,
  1100  	}
  1101  	return eosACL, nil
  1102  }
  1103  
  1104  func (fs *eosfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
  1105  	eosACLType, err := grants.GetACLType(g.Grantee.Type)
  1106  	if err != nil {
  1107  		return err
  1108  	}
  1109  
  1110  	var recipient string
  1111  	if eosACLType == acl.TypeUser {
  1112  		// if the grantee is a lightweight account, we need to set it accordingly
  1113  		if g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT ||
  1114  			g.Grantee.GetUserId().Type == userpb.UserType_USER_TYPE_FEDERATED {
  1115  			eosACLType = acl.TypeLightweight
  1116  			recipient = g.Grantee.GetUserId().OpaqueId
  1117  		} else {
  1118  			// since EOS Citrine ACLs are stored with uid, we need to convert username to uid
  1119  			auth, err := fs.getUIDGateway(ctx, g.Grantee.GetUserId())
  1120  			if err != nil {
  1121  				return err
  1122  			}
  1123  			recipient = auth.Role.UID
  1124  		}
  1125  	} else {
  1126  		recipient = g.Grantee.GetGroupId().OpaqueId
  1127  	}
  1128  
  1129  	eosACL := &acl.Entry{
  1130  		Qualifier: recipient,
  1131  		Type:      eosACLType,
  1132  	}
  1133  
  1134  	fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref)
  1135  	if err != nil {
  1136  		return err
  1137  	}
  1138  
  1139  	rootAuth, err := fs.getRootAuth(ctx)
  1140  	if err != nil {
  1141  		return err
  1142  	}
  1143  
  1144  	err = fs.c.RemoveACL(ctx, auth, rootAuth, fn, eosACL)
  1145  	if err != nil {
  1146  		return errors.Wrap(err, "eosfs: error removing acl")
  1147  	}
  1148  	return nil
  1149  }
  1150  
  1151  func (fs *eosfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error {
  1152  	return fs.AddGrant(ctx, ref, g)
  1153  }
  1154  
  1155  func (fs *eosfs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) {
  1156  	fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref)
  1157  	if err != nil {
  1158  		return nil, err
  1159  	}
  1160  
  1161  	acls, err := fs.c.ListACLs(ctx, auth, fn)
  1162  	if err != nil {
  1163  		return nil, err
  1164  	}
  1165  
  1166  	grantList := []*provider.Grant{}
  1167  	for _, a := range acls {
  1168  		var grantee *provider.Grantee
  1169  		switch {
  1170  		case a.Type == acl.TypeUser:
  1171  			// EOS Citrine ACLs are stored with uid for users.
  1172  			// This needs to be resolved to the user opaque ID.
  1173  			qualifier, err := fs.getUserIDGateway(ctx, a.Qualifier)
  1174  			if err != nil {
  1175  				return nil, err
  1176  			}
  1177  			grantee = &provider.Grantee{
  1178  				Id:   &provider.Grantee_UserId{UserId: qualifier},
  1179  				Type: grants.GetGranteeType(a.Type),
  1180  			}
  1181  		case a.Type == acl.TypeLightweight:
  1182  			a.Type = acl.TypeUser
  1183  			grantee = &provider.Grantee{
  1184  				Id:   &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: a.Qualifier}},
  1185  				Type: grants.GetGranteeType(a.Type),
  1186  			}
  1187  		default:
  1188  			grantee = &provider.Grantee{
  1189  				Id:   &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: a.Qualifier}},
  1190  				Type: grants.GetGranteeType(a.Type),
  1191  			}
  1192  		}
  1193  
  1194  		grantList = append(grantList, &provider.Grant{
  1195  			Grantee:     grantee,
  1196  			Permissions: grants.GetGrantPermissionSet(a.Permissions),
  1197  		})
  1198  	}
  1199  
  1200  	return grantList, nil
  1201  }
  1202  
  1203  func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) {
  1204  	log := appctx.GetLogger(ctx)
  1205  	log.Info().Msg("eosfs: get md for ref:" + ref.String())
  1206  
  1207  	u, err := getUser(ctx)
  1208  	if err != nil {
  1209  		return nil, err
  1210  	}
  1211  
  1212  	fn := ""
  1213  	p := ref.Path
  1214  
  1215  	if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT ||
  1216  		u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED {
  1217  		p, err := fs.resolve(ctx, ref)
  1218  		if err != nil {
  1219  			return nil, errors.Wrap(err, "eosfs: error resolving reference")
  1220  		}
  1221  
  1222  		fn = fs.wrap(ctx, p)
  1223  	}
  1224  
  1225  	auth, err := fs.getUserAuth(ctx, u, fn)
  1226  	if err != nil {
  1227  		return nil, err
  1228  	}
  1229  
  1230  	// We handle the case when resource ID is set to avoid making duplicate calls to EOS.
  1231  	// In the previous workflow, we would have called the resolve() method which would return
  1232  	// the path and then we'll stat the path.
  1233  	if ref.ResourceId != nil {
  1234  		fid, err := strconv.ParseUint(ref.ResourceId.OpaqueId, 10, 64)
  1235  		if err != nil {
  1236  			return nil, fmt.Errorf("error converting string to int for eos fileid: %s", ref.ResourceId.OpaqueId)
  1237  		}
  1238  
  1239  		eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid)
  1240  		if err != nil {
  1241  			return nil, err
  1242  		}
  1243  
  1244  		// If it's not a relative reference, return now, else we need to append the path
  1245  		if !utils.IsRelativeReference(ref) {
  1246  			return fs.convertToResourceInfo(ctx, eosFileInfo)
  1247  		}
  1248  
  1249  		parent, err := fs.unwrap(ctx, eosFileInfo.File)
  1250  		if err != nil {
  1251  			return nil, err
  1252  		}
  1253  
  1254  		p = path.Join(parent, p)
  1255  	}
  1256  
  1257  	// if path is home we need to add in the response any shadow folder in the shadow homedirectory.
  1258  	if fs.conf.EnableHome {
  1259  		if fs.isShareFolder(ctx, p) {
  1260  			return fs.getMDShareFolder(ctx, p, mdKeys)
  1261  		}
  1262  	}
  1263  
  1264  	fn = fs.wrap(ctx, p)
  1265  	eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn)
  1266  	if err != nil {
  1267  		return nil, err
  1268  	}
  1269  
  1270  	return fs.convertToResourceInfo(ctx, eosFileInfo)
  1271  }
  1272  
  1273  func (fs *eosfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string) (*provider.ResourceInfo, error) {
  1274  	fn := fs.wrapShadow(ctx, p)
  1275  
  1276  	u, err := getUser(ctx)
  1277  	if err != nil {
  1278  		return nil, err
  1279  	}
  1280  
  1281  	// lightweight accounts don't have share folders, so we're passing an empty string as path
  1282  	auth, err := fs.getUserAuth(ctx, u, "")
  1283  	if err != nil {
  1284  		return nil, err
  1285  	}
  1286  
  1287  	eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn)
  1288  	if err != nil {
  1289  		return nil, err
  1290  	}
  1291  
  1292  	if fs.isShareFolderRoot(ctx, p) {
  1293  		return fs.convertToResourceInfo(ctx, eosFileInfo)
  1294  	}
  1295  	return fs.convertToFileReference(ctx, eosFileInfo)
  1296  }
  1297  
  1298  func (fs *eosfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) {
  1299  	p, err := fs.resolve(ctx, ref)
  1300  	if err != nil {
  1301  		return nil, errors.Wrap(err, "eosfs: error resolving reference")
  1302  	}
  1303  
  1304  	// if path is home we need to add in the response any shadow folder in the shadow homedirectory.
  1305  	if fs.conf.EnableHome {
  1306  		return fs.listWithHome(ctx, p)
  1307  	}
  1308  
  1309  	return fs.listWithNominalHome(ctx, p)
  1310  }
  1311  
  1312  func (fs *eosfs) listWithNominalHome(ctx context.Context, p string) (finfos []*provider.ResourceInfo, err error) {
  1313  	log := appctx.GetLogger(ctx)
  1314  	fn := fs.wrap(ctx, p)
  1315  
  1316  	u, err := getUser(ctx)
  1317  	if err != nil {
  1318  		return nil, errors.Wrap(err, "eosfs: no user in ctx")
  1319  	}
  1320  	auth, err := fs.getUserAuth(ctx, u, fn)
  1321  	if err != nil {
  1322  		return nil, err
  1323  	}
  1324  
  1325  	eosFileInfos, err := fs.c.List(ctx, auth, fn)
  1326  	if err != nil {
  1327  		return nil, errors.Wrap(err, "eosfs: error listing")
  1328  	}
  1329  
  1330  	for _, eosFileInfo := range eosFileInfos {
  1331  		// filter out sys files
  1332  		if !fs.conf.ShowHiddenSysFiles {
  1333  			base := path.Base(eosFileInfo.File)
  1334  			if hiddenReg.MatchString(base) {
  1335  				log.Debug().Msgf("eosfs: path is filtered because is considered hidden: path=%s hiddenReg=%s", base, hiddenReg)
  1336  				continue
  1337  			}
  1338  		}
  1339  
  1340  		// Remove the hidden folders in the topmost directory
  1341  		if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo); err == nil && finfo.Path != "/" && !strings.HasPrefix(finfo.Path, "/.") {
  1342  			finfos = append(finfos, finfo)
  1343  		}
  1344  	}
  1345  
  1346  	return finfos, nil
  1347  }
  1348  
  1349  func (fs *eosfs) listWithHome(ctx context.Context, p string) ([]*provider.ResourceInfo, error) {
  1350  	if p == "/" {
  1351  		return fs.listHome(ctx)
  1352  	}
  1353  
  1354  	if fs.isShareFolderRoot(ctx, p) {
  1355  		return fs.listShareFolderRoot(ctx, p)
  1356  	}
  1357  
  1358  	if fs.isShareFolderChild(ctx, p) {
  1359  		return nil, errtypes.PermissionDenied("eosfs: error listing folders inside the shared folder, only file references are stored inside")
  1360  	}
  1361  
  1362  	// path points to a resource in the nominal home
  1363  	return fs.listWithNominalHome(ctx, p)
  1364  }
  1365  
  1366  func (fs *eosfs) listHome(ctx context.Context) ([]*provider.ResourceInfo, error) {
  1367  	fns := []string{fs.wrap(ctx, "/"), fs.wrapShadow(ctx, "/")}
  1368  
  1369  	u, err := getUser(ctx)
  1370  	if err != nil {
  1371  		return nil, errors.Wrap(err, "eosfs: no user in ctx")
  1372  	}
  1373  	// lightweight accounts don't have home folders, so we're passing an empty string as path
  1374  	auth, err := fs.getUserAuth(ctx, u, "")
  1375  	if err != nil {
  1376  		return nil, err
  1377  	}
  1378  
  1379  	finfos := []*provider.ResourceInfo{}
  1380  	for _, fn := range fns {
  1381  		eosFileInfos, err := fs.c.List(ctx, auth, fn)
  1382  		if err != nil {
  1383  			return nil, errors.Wrap(err, "eosfs: error listing")
  1384  		}
  1385  
  1386  		for _, eosFileInfo := range eosFileInfos {
  1387  			// filter out sys files
  1388  			if !fs.conf.ShowHiddenSysFiles {
  1389  				base := path.Base(eosFileInfo.File)
  1390  				if hiddenReg.MatchString(base) {
  1391  					continue
  1392  				}
  1393  			}
  1394  
  1395  			if finfo, err := fs.convertToResourceInfo(ctx, eosFileInfo); err == nil && finfo.Path != "/" && !strings.HasPrefix(finfo.Path, "/.") {
  1396  				finfos = append(finfos, finfo)
  1397  			}
  1398  		}
  1399  
  1400  	}
  1401  	return finfos, nil
  1402  }
  1403  
  1404  func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*provider.ResourceInfo, err error) {
  1405  	fn := fs.wrapShadow(ctx, p)
  1406  
  1407  	u, err := getUser(ctx)
  1408  	if err != nil {
  1409  		return nil, errors.Wrap(err, "eosfs: no user in ctx")
  1410  	}
  1411  	// lightweight accounts don't have share folders, so we're passing an empty string as path
  1412  	auth, err := fs.getUserAuth(ctx, u, "")
  1413  	if err != nil {
  1414  		return nil, err
  1415  	}
  1416  
  1417  	eosFileInfos, err := fs.c.List(ctx, auth, fn)
  1418  	if err != nil {
  1419  		return nil, errors.Wrap(err, "eosfs: error listing")
  1420  	}
  1421  
  1422  	for _, eosFileInfo := range eosFileInfos {
  1423  		// filter out sys files
  1424  		if !fs.conf.ShowHiddenSysFiles {
  1425  			base := path.Base(eosFileInfo.File)
  1426  			if hiddenReg.MatchString(base) {
  1427  				continue
  1428  			}
  1429  		}
  1430  
  1431  		if finfo, err := fs.convertToFileReference(ctx, eosFileInfo); err == nil {
  1432  			finfos = append(finfos, finfo)
  1433  		}
  1434  	}
  1435  
  1436  	return finfos, nil
  1437  }
  1438  
  1439  func (fs *eosfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) {
  1440  	// Check if the quota request is for the user's home or a project space
  1441  	u, err := getUser(ctx)
  1442  	if err != nil {
  1443  		return 0, 0, 0, errors.Wrap(err, "eosfs: no user in ctx")
  1444  	}
  1445  
  1446  	// If the quota request is for a resource different than the user home,
  1447  	// we impersonate the owner in that case
  1448  	uid := strconv.FormatInt(u.UidNumber, 10)
  1449  	if ref.ResourceId != nil {
  1450  		fid, err := strconv.ParseUint(ref.ResourceId.OpaqueId, 10, 64)
  1451  		if err != nil {
  1452  			return 0, 0, 0, fmt.Errorf("error converting string to int for eos fileid: %s", ref.ResourceId.OpaqueId)
  1453  		}
  1454  
  1455  		// lightweight accounts don't have quota nodes, so we're passing an empty string as path
  1456  		auth, err := fs.getUserAuth(ctx, u, "")
  1457  		if err != nil {
  1458  			return 0, 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user")
  1459  		}
  1460  		eosFileInfo, err := fs.c.GetFileInfoByInode(ctx, auth, fid)
  1461  		if err != nil {
  1462  			return 0, 0, 0, err
  1463  		}
  1464  		uid = strconv.FormatUint(eosFileInfo.UID, 10)
  1465  	}
  1466  
  1467  	rootAuth, err := fs.getRootAuth(ctx)
  1468  	if err != nil {
  1469  		return 0, 0, 0, err
  1470  	}
  1471  
  1472  	qi, err := fs.c.GetQuota(ctx, uid, rootAuth, fs.conf.QuotaNode)
  1473  	if err != nil {
  1474  		err := errors.Wrap(err, "eosfs: error getting quota")
  1475  		return 0, 0, 0, err
  1476  	}
  1477  
  1478  	remaining := qi.AvailableBytes - qi.UsedBytes
  1479  
  1480  	return qi.AvailableBytes, qi.UsedBytes, remaining, nil
  1481  }
  1482  
  1483  func (fs *eosfs) GetHome(ctx context.Context) (string, error) {
  1484  	if !fs.conf.EnableHome {
  1485  		return "", errtypes.NotSupported("eosfs: get home not supported")
  1486  	}
  1487  
  1488  	// eos drive for homes assumes root(/) points to the user home.
  1489  	return "/", nil
  1490  }
  1491  
  1492  func (fs *eosfs) createShadowHome(ctx context.Context, home string) error {
  1493  	u, err := getUser(ctx)
  1494  	if err != nil {
  1495  		return errors.Wrap(err, "eosfs: no user in ctx")
  1496  	}
  1497  	rootAuth, err := fs.getRootAuth(ctx)
  1498  	if err != nil {
  1499  		return nil
  1500  	}
  1501  	shadowFolders := []string{fs.conf.ShareFolder}
  1502  
  1503  	for _, sf := range shadowFolders {
  1504  		fn := path.Join(home, sf)
  1505  		_, err = fs.c.GetFileInfoByPath(ctx, rootAuth, fn)
  1506  		if err != nil {
  1507  			if _, ok := err.(errtypes.IsNotFound); !ok {
  1508  				return errors.Wrap(err, "eosfs: error verifying if shadow directory exists")
  1509  			}
  1510  			err = fs.createUserDir(ctx, u, fn, false)
  1511  			if err != nil {
  1512  				return err
  1513  			}
  1514  		}
  1515  	}
  1516  
  1517  	return nil
  1518  }
  1519  
  1520  func (fs *eosfs) createNominalHome(ctx context.Context, home string) error {
  1521  	u, err := getUser(ctx)
  1522  	if err != nil {
  1523  		return errors.Wrap(err, "eosfs: no user in ctx")
  1524  	}
  1525  	auth, err := fs.getUserAuth(ctx, u, "")
  1526  	if err != nil {
  1527  		return err
  1528  	}
  1529  
  1530  	rootAuth, err := fs.getRootAuth(ctx)
  1531  	if err != nil {
  1532  		return nil
  1533  	}
  1534  
  1535  	_, err = fs.c.GetFileInfoByPath(ctx, rootAuth, home)
  1536  	if err == nil { // home already exists
  1537  		return nil
  1538  	}
  1539  
  1540  	if _, ok := err.(errtypes.IsNotFound); !ok {
  1541  		return errors.Wrap(err, "eosfs: error verifying if user home directory exists")
  1542  	}
  1543  
  1544  	err = fs.createUserDir(ctx, u, home, false)
  1545  	if err != nil {
  1546  		err := errors.Wrap(err, "eosfs: error creating user dir")
  1547  		return err
  1548  	}
  1549  
  1550  	// set quota for user
  1551  	quotaInfo := &eosclient.SetQuotaInfo{
  1552  		Username:  u.Username,
  1553  		UID:       auth.Role.UID,
  1554  		GID:       auth.Role.GID,
  1555  		MaxBytes:  fs.conf.DefaultQuotaBytes,
  1556  		MaxFiles:  fs.conf.DefaultQuotaFiles,
  1557  		QuotaNode: fs.conf.QuotaNode,
  1558  	}
  1559  
  1560  	err = fs.c.SetQuota(ctx, rootAuth, quotaInfo)
  1561  	if err != nil {
  1562  		err := errors.Wrap(err, "eosfs: error setting quota")
  1563  		return err
  1564  	}
  1565  
  1566  	return err
  1567  }
  1568  
  1569  func (fs *eosfs) CreateHome(ctx context.Context) error {
  1570  	if !fs.conf.EnableHome {
  1571  		return errtypes.NotSupported("eosfs: create home not supported")
  1572  	}
  1573  
  1574  	if err := fs.createNominalHome(ctx, fs.wrap(ctx, "/")); err != nil {
  1575  		return errors.Wrap(err, "eosfs: error creating nominal home")
  1576  	}
  1577  
  1578  	if err := fs.createShadowHome(ctx, fs.wrapShadow(ctx, "/")); err != nil {
  1579  		return errors.Wrap(err, "eosfs: error creating shadow home")
  1580  	}
  1581  
  1582  	return nil
  1583  }
  1584  
  1585  func (fs *eosfs) createUserDir(ctx context.Context, u *userpb.User, path string, recursiveAttr bool) error {
  1586  	rootAuth, err := fs.getRootAuth(ctx)
  1587  	if err != nil {
  1588  		return nil
  1589  	}
  1590  
  1591  	chownAuth, err := fs.getUserAuth(ctx, u, "")
  1592  	if err != nil {
  1593  		return err
  1594  	}
  1595  
  1596  	err = fs.c.CreateDir(ctx, rootAuth, path)
  1597  	if err != nil {
  1598  		// EOS will return success on mkdir over an existing directory.
  1599  		return errors.Wrap(err, "eosfs: error creating dir")
  1600  	}
  1601  
  1602  	err = fs.c.Chown(ctx, rootAuth, chownAuth, path)
  1603  	if err != nil {
  1604  		return errors.Wrap(err, "eosfs: error chowning directory")
  1605  	}
  1606  
  1607  	err = fs.c.Chmod(ctx, rootAuth, "2770", path)
  1608  	if err != nil {
  1609  		return errors.Wrap(err, "eosfs: error chmoding directory")
  1610  	}
  1611  
  1612  	attrs := []*eosclient.Attribute{
  1613  		{
  1614  			Type: SystemAttr,
  1615  			Key:  "mask",
  1616  			Val:  "700",
  1617  		},
  1618  		{
  1619  			Type: SystemAttr,
  1620  			Key:  "allow.oc.sync",
  1621  			Val:  "1",
  1622  		},
  1623  		{
  1624  			Type: SystemAttr,
  1625  			Key:  "mtime.propagation",
  1626  			Val:  "1",
  1627  		},
  1628  		{
  1629  			Type: SystemAttr,
  1630  			Key:  "forced.atomic",
  1631  			Val:  "1",
  1632  		},
  1633  	}
  1634  
  1635  	for _, attr := range attrs {
  1636  		err = fs.c.SetAttr(ctx, rootAuth, attr, false, recursiveAttr, path)
  1637  		if err != nil {
  1638  			return errors.Wrap(err, "eosfs: error setting attribute")
  1639  		}
  1640  	}
  1641  
  1642  	return nil
  1643  }
  1644  
  1645  func (fs *eosfs) CreateDir(ctx context.Context, ref *provider.Reference) error {
  1646  	log := appctx.GetLogger(ctx)
  1647  	p, err := fs.resolve(ctx, ref)
  1648  	if err != nil {
  1649  		return errors.Wrap(err, "eosfs: error resolving reference")
  1650  	}
  1651  	if fs.isShareFolder(ctx, p) {
  1652  		return errtypes.PermissionDenied("eosfs: cannot perform operation under the virtual share folder")
  1653  	}
  1654  	fn := fs.wrap(ctx, p)
  1655  
  1656  	u, err := getUser(ctx)
  1657  	if err != nil {
  1658  		return errors.Wrap(err, "eosfs: no user in ctx")
  1659  	}
  1660  
  1661  	// We need the auth corresponding to the parent directory
  1662  	// as the file might not exist at the moment
  1663  	auth, err := fs.getUserAuth(ctx, u, path.Dir(fn))
  1664  	if err != nil {
  1665  		return err
  1666  	}
  1667  
  1668  	log.Info().Msgf("eosfs: createdir: path=%s", fn)
  1669  	return fs.c.CreateDir(ctx, auth, fn)
  1670  }
  1671  
  1672  // TouchFile as defined in the storage.FS interface
  1673  func (fs *eosfs) TouchFile(ctx context.Context, ref *provider.Reference, _ bool, _ string) error {
  1674  	log := appctx.GetLogger(ctx)
  1675  
  1676  	fn, auth, err := fs.resolveRefAndGetAuth(ctx, ref)
  1677  	if err != nil {
  1678  		return err
  1679  	}
  1680  	log.Info().Msgf("eosfs: touch file: path=%s", fn)
  1681  
  1682  	return fs.c.Touch(ctx, auth, fn)
  1683  }
  1684  
  1685  func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error {
  1686  	// TODO(labkode): for the time being we only allow creating references
  1687  	// in the virtual share folder to not pollute the nominal user tree.
  1688  	if !fs.isShareFolder(ctx, p) {
  1689  		return errtypes.PermissionDenied("eosfs: cannot create references outside the share folder: share_folder=" + fs.conf.ShareFolder + " path=" + p)
  1690  	}
  1691  	u, err := getUser(ctx)
  1692  	if err != nil {
  1693  		return errors.Wrap(err, "eosfs: no user in ctx")
  1694  	}
  1695  
  1696  	fn := fs.wrapShadow(ctx, p)
  1697  
  1698  	// TODO(labkode): with the grpc plugin we can create a file touching with xattrs.
  1699  	// Current mechanism is: touch to hidden dir, set xattr, rename.
  1700  	dir, base := path.Split(fn)
  1701  	tmp := path.Join(dir, fmt.Sprintf(".sys.reva#.%s", base))
  1702  	rootAuth, err := fs.getRootAuth(ctx)
  1703  	if err != nil {
  1704  		return nil
  1705  	}
  1706  
  1707  	if err := fs.createUserDir(ctx, u, tmp, false); err != nil {
  1708  		err = errors.Wrapf(err, "eosfs: error creating temporary ref file")
  1709  		return err
  1710  	}
  1711  
  1712  	// set xattr on ref
  1713  	attr := &eosclient.Attribute{
  1714  		Type: UserAttr,
  1715  		Key:  refTargetAttrKey,
  1716  		Val:  targetURI.String(),
  1717  	}
  1718  
  1719  	if err := fs.c.SetAttr(ctx, rootAuth, attr, false, false, tmp); err != nil {
  1720  		err = errors.Wrapf(err, "eosfs: error setting reva.ref attr on file: %q", tmp)
  1721  		return err
  1722  	}
  1723  
  1724  	// rename to have the file visible in user space.
  1725  	if err := fs.c.Rename(ctx, rootAuth, tmp, fn); err != nil {
  1726  		err = errors.Wrapf(err, "eosfs: error renaming from: %q to %q", tmp, fn)
  1727  		return err
  1728  	}
  1729  
  1730  	return nil
  1731  }
  1732  
  1733  func (fs *eosfs) Delete(ctx context.Context, ref *provider.Reference) error {
  1734  	p, err := fs.resolve(ctx, ref)
  1735  	if err != nil {
  1736  		return errors.Wrap(err, "eosfs: error resolving reference")
  1737  	}
  1738  
  1739  	if fs.isShareFolder(ctx, p) {
  1740  		return fs.deleteShadow(ctx, p)
  1741  	}
  1742  
  1743  	fn := fs.wrap(ctx, p)
  1744  
  1745  	u, err := getUser(ctx)
  1746  	if err != nil {
  1747  		return errors.Wrap(err, "eosfs: no user in ctx")
  1748  	}
  1749  	auth, err := fs.getUserAuth(ctx, u, fn)
  1750  	if err != nil {
  1751  		return err
  1752  	}
  1753  
  1754  	return fs.c.Remove(ctx, auth, fn, false)
  1755  }
  1756  
  1757  func (fs *eosfs) deleteShadow(ctx context.Context, p string) error {
  1758  	if fs.isShareFolderRoot(ctx, p) {
  1759  		return errtypes.PermissionDenied("eosfs: cannot delete the virtual share folder")
  1760  	}
  1761  
  1762  	if fs.isShareFolderChild(ctx, p) {
  1763  		fn := fs.wrapShadow(ctx, p)
  1764  
  1765  		// in order to remove the folder or the file without
  1766  		// moving it to the recycle bin, we should take
  1767  		// the privileges of the root
  1768  		auth, err := fs.getRootAuth(ctx)
  1769  		if err != nil {
  1770  			return err
  1771  		}
  1772  
  1773  		return fs.c.Remove(ctx, auth, fn, true)
  1774  	}
  1775  
  1776  	return errors.New("eosfs: shadow delete of share folder that is neither root nor child. path=" + p)
  1777  }
  1778  
  1779  func (fs *eosfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error {
  1780  	oldPath, err := fs.resolve(ctx, oldRef)
  1781  	if err != nil {
  1782  		return errors.Wrap(err, "eosfs: error resolving reference")
  1783  	}
  1784  
  1785  	newPath, err := fs.resolve(ctx, newRef)
  1786  	if err != nil {
  1787  		return errors.Wrap(err, "eosfs: error resolving reference")
  1788  	}
  1789  
  1790  	if fs.isShareFolder(ctx, oldPath) || fs.isShareFolder(ctx, newPath) {
  1791  		return fs.moveShadow(ctx, oldPath, newPath)
  1792  	}
  1793  
  1794  	oldFn := fs.wrap(ctx, oldPath)
  1795  	newFn := fs.wrap(ctx, newPath)
  1796  
  1797  	u, err := getUser(ctx)
  1798  	if err != nil {
  1799  		return errors.Wrap(err, "eosfs: no user in ctx")
  1800  	}
  1801  	auth, err := fs.getUserAuth(ctx, u, oldFn)
  1802  	if err != nil {
  1803  		return err
  1804  	}
  1805  
  1806  	return fs.c.Rename(ctx, auth, oldFn, newFn)
  1807  }
  1808  
  1809  func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error {
  1810  	if fs.isShareFolderRoot(ctx, oldPath) || fs.isShareFolderRoot(ctx, newPath) {
  1811  		return errtypes.PermissionDenied("eosfs: cannot move/rename the virtual share folder")
  1812  	}
  1813  
  1814  	// only rename of the reference is allowed, hence having the same basedir
  1815  	bold, _ := path.Split(oldPath)
  1816  	bnew, _ := path.Split(newPath)
  1817  
  1818  	if bold != bnew {
  1819  		return errtypes.PermissionDenied("eosfs: cannot move references under the virtual share folder")
  1820  	}
  1821  
  1822  	oldfn := fs.wrapShadow(ctx, oldPath)
  1823  	newfn := fs.wrapShadow(ctx, newPath)
  1824  
  1825  	u, err := getUser(ctx)
  1826  	if err != nil {
  1827  		return errors.Wrap(err, "eosfs: no user in ctx")
  1828  	}
  1829  	auth, err := fs.getUserAuth(ctx, u, "")
  1830  	if err != nil {
  1831  		return err
  1832  	}
  1833  
  1834  	return fs.c.Rename(ctx, auth, oldfn, newfn)
  1835  }
  1836  
  1837  func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
  1838  	fn, auth, err := fs.resolveRefForbidShareFolder(ctx, ref)
  1839  	if err != nil {
  1840  		return nil, nil, err
  1841  	}
  1842  
  1843  	md, err := fs.GetMD(ctx, ref, nil, nil)
  1844  	if err != nil {
  1845  		return nil, nil, err
  1846  	}
  1847  
  1848  	if !openReaderfunc(md) {
  1849  		return md, nil, nil
  1850  	}
  1851  
  1852  	reader, err := fs.c.Read(ctx, auth, fn)
  1853  	return md, reader, err
  1854  }
  1855  
  1856  func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
  1857  	var auth eosclient.Authorization
  1858  	var fn string
  1859  	var err error
  1860  
  1861  	if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions {
  1862  		// We need to access the revisions for a non-home reference.
  1863  		// We'll get the owner of the particular resource and impersonate them
  1864  		// if we have access to it.
  1865  		md, err := fs.GetMD(ctx, ref, nil, nil)
  1866  		if err != nil {
  1867  			return nil, err
  1868  		}
  1869  		fn = fs.wrap(ctx, md.Path)
  1870  
  1871  		if md.PermissionSet.ListFileVersions {
  1872  			auth, err = fs.getUIDGateway(ctx, md.Owner)
  1873  			if err != nil {
  1874  				return nil, err
  1875  			}
  1876  		} else {
  1877  			return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to list revisions")
  1878  		}
  1879  	} else {
  1880  		fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref)
  1881  		if err != nil {
  1882  			return nil, err
  1883  		}
  1884  	}
  1885  
  1886  	eosRevisions, err := fs.c.ListVersions(ctx, auth, fn)
  1887  	if err != nil {
  1888  		return nil, errors.Wrap(err, "eosfs: error listing versions")
  1889  	}
  1890  	revisions := []*provider.FileVersion{}
  1891  	for _, eosRev := range eosRevisions {
  1892  		if rev, err := fs.convertToRevision(ctx, eosRev); err == nil {
  1893  			revisions = append(revisions, rev)
  1894  		}
  1895  	}
  1896  	return revisions, nil
  1897  }
  1898  
  1899  func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
  1900  	var auth eosclient.Authorization
  1901  	var fn string
  1902  	var err error
  1903  
  1904  	md, err := fs.GetMD(ctx, ref, nil, nil)
  1905  	if err != nil {
  1906  		return nil, nil, err
  1907  	}
  1908  
  1909  	if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions {
  1910  		// We need to access the revisions for a non-home reference.
  1911  		// We'll get the owner of the particular resource and impersonate them
  1912  		// if we have access to it.
  1913  
  1914  		fn = fs.wrap(ctx, md.Path)
  1915  		if md.PermissionSet.InitiateFileDownload {
  1916  			auth, err = fs.getUIDGateway(ctx, md.Owner)
  1917  			if err != nil {
  1918  				return nil, nil, err
  1919  			}
  1920  		} else {
  1921  			return nil, nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to download revisions")
  1922  		}
  1923  	} else {
  1924  		fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref)
  1925  		if err != nil {
  1926  			return nil, nil, err
  1927  		}
  1928  	}
  1929  
  1930  	if !openReaderfunc(md) {
  1931  		return md, nil, nil
  1932  	}
  1933  
  1934  	reader, err := fs.c.ReadVersion(ctx, auth, fn, revisionKey)
  1935  	return md, reader, err
  1936  }
  1937  
  1938  func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {
  1939  	var auth eosclient.Authorization
  1940  	var fn string
  1941  	var err error
  1942  
  1943  	if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions {
  1944  		// We need to access the revisions for a non-home reference.
  1945  		// We'll get the owner of the particular resource and impersonate them
  1946  		// if we have access to it.
  1947  		md, err := fs.GetMD(ctx, ref, nil, nil)
  1948  		if err != nil {
  1949  			return err
  1950  		}
  1951  		fn = fs.wrap(ctx, md.Path)
  1952  
  1953  		if md.PermissionSet.RestoreFileVersion {
  1954  			auth, err = fs.getUIDGateway(ctx, md.Owner)
  1955  			if err != nil {
  1956  				return err
  1957  			}
  1958  		} else {
  1959  			return errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore revisions")
  1960  		}
  1961  	} else {
  1962  		fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref)
  1963  		if err != nil {
  1964  			return err
  1965  		}
  1966  	}
  1967  
  1968  	return fs.c.RollbackToVersion(ctx, auth, fn, revisionKey)
  1969  }
  1970  
  1971  func (fs *eosfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error {
  1972  	return errtypes.NotSupported("eosfs: operation not supported")
  1973  }
  1974  
  1975  func (fs *eosfs) EmptyRecycle(ctx context.Context, ref *provider.Reference) error {
  1976  	u, err := getUser(ctx)
  1977  	if err != nil {
  1978  		return errors.Wrap(err, "eosfs: no user in ctx")
  1979  	}
  1980  	auth, err := fs.getUserAuth(ctx, u, "")
  1981  	if err != nil {
  1982  		return err
  1983  	}
  1984  
  1985  	return fs.c.PurgeDeletedEntries(ctx, auth)
  1986  }
  1987  
  1988  func (fs *eosfs) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) {
  1989  	var auth eosclient.Authorization
  1990  
  1991  	if !fs.conf.EnableHome && fs.conf.AllowPathRecycleOperations && ref.Path != "/" {
  1992  		// We need to access the recycle bin for a non-home reference.
  1993  		// We'll get the owner of the particular resource and impersonate them
  1994  		// if we have access to it.
  1995  		md, err := fs.GetMD(ctx, &provider.Reference{Path: ref.Path}, nil, nil)
  1996  		if err != nil {
  1997  			return nil, err
  1998  		}
  1999  		if md.PermissionSet.ListRecycle {
  2000  			auth, err = fs.getUIDGateway(ctx, md.Owner)
  2001  			if err != nil {
  2002  				return nil, err
  2003  			}
  2004  		} else {
  2005  			return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore recycled items")
  2006  		}
  2007  	} else {
  2008  		// We just act on the logged-in user's recycle bin
  2009  		u, err := getUser(ctx)
  2010  		if err != nil {
  2011  			return nil, errors.Wrap(err, "eosfs: no user in ctx")
  2012  		}
  2013  		auth, err = fs.getUserAuth(ctx, u, "")
  2014  		if err != nil {
  2015  			return nil, err
  2016  		}
  2017  	}
  2018  
  2019  	eosDeletedEntries, err := fs.c.ListDeletedEntries(ctx, auth)
  2020  	if err != nil {
  2021  		return nil, errors.Wrap(err, "eosfs: error listing deleted entries")
  2022  	}
  2023  	recycleEntries := []*provider.RecycleItem{}
  2024  	for _, entry := range eosDeletedEntries {
  2025  		if !fs.conf.ShowHiddenSysFiles {
  2026  			base := path.Base(entry.RestorePath)
  2027  			if hiddenReg.MatchString(base) {
  2028  				continue
  2029  			}
  2030  
  2031  		}
  2032  		if recycleItem, err := fs.convertToRecycleItem(ctx, entry); err == nil {
  2033  			recycleEntries = append(recycleEntries, recycleItem)
  2034  		}
  2035  	}
  2036  	return recycleEntries, nil
  2037  }
  2038  
  2039  func (fs *eosfs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error {
  2040  	var auth eosclient.Authorization
  2041  
  2042  	if !fs.conf.EnableHome && fs.conf.AllowPathRecycleOperations && ref.Path != "/" {
  2043  		// We need to access the recycle bin for a non-home reference.
  2044  		// We'll get the owner of the particular resource and impersonate them
  2045  		// if we have access to it.
  2046  		md, err := fs.GetMD(ctx, &provider.Reference{Path: ref.Path}, nil, nil)
  2047  		if err != nil {
  2048  			return err
  2049  		}
  2050  		if md.PermissionSet.RestoreRecycleItem {
  2051  			auth, err = fs.getUIDGateway(ctx, md.Owner)
  2052  			if err != nil {
  2053  				return err
  2054  			}
  2055  		} else {
  2056  			return errtypes.PermissionDenied("eosfs: user doesn't have permissions to restore recycled items")
  2057  		}
  2058  	} else {
  2059  		// We just act on the logged-in user's recycle bin
  2060  		u, err := getUser(ctx)
  2061  		if err != nil {
  2062  			return errors.Wrap(err, "eosfs: no user in ctx")
  2063  		}
  2064  		auth, err = fs.getUserAuth(ctx, u, "")
  2065  		if err != nil {
  2066  			return err
  2067  		}
  2068  	}
  2069  
  2070  	return fs.c.RestoreDeletedEntry(ctx, auth, key)
  2071  }
  2072  
  2073  func (fs *eosfs) convertToRecycleItem(ctx context.Context, eosDeletedItem *eosclient.DeletedEntry) (*provider.RecycleItem, error) {
  2074  	path, err := fs.unwrap(ctx, eosDeletedItem.RestorePath)
  2075  	if err != nil {
  2076  		return nil, err
  2077  	}
  2078  
  2079  	recycleItem := &provider.RecycleItem{
  2080  		Ref:          &provider.Reference{Path: path},
  2081  		Key:          eosDeletedItem.RestoreKey,
  2082  		Size:         eosDeletedItem.Size,
  2083  		DeletionTime: &types.Timestamp{Seconds: eosDeletedItem.DeletionMTime},
  2084  	}
  2085  	if eosDeletedItem.IsDir {
  2086  		recycleItem.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER
  2087  	} else {
  2088  		// TODO(labkode): if eos returns more types oin the future we need to map them.
  2089  		recycleItem.Type = provider.ResourceType_RESOURCE_TYPE_FILE
  2090  	}
  2091  	return recycleItem, nil
  2092  }
  2093  
  2094  func (fs *eosfs) convertToRevision(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.FileVersion, error) {
  2095  	md, err := fs.convertToResourceInfo(ctx, eosFileInfo)
  2096  	if err != nil {
  2097  		return nil, err
  2098  	}
  2099  	revision := &provider.FileVersion{
  2100  		Key:   path.Base(md.Path),
  2101  		Size:  md.Size,
  2102  		Mtime: md.Mtime.Seconds, // TODO do we need nanos here?
  2103  		Etag:  md.Etag,
  2104  	}
  2105  	return revision, nil
  2106  }
  2107  
  2108  func (fs *eosfs) convertToResourceInfo(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) {
  2109  	return fs.convert(ctx, eosFileInfo)
  2110  }
  2111  
  2112  func (fs *eosfs) convertToFileReference(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) {
  2113  	info, err := fs.convert(ctx, eosFileInfo)
  2114  	if err != nil {
  2115  		return nil, err
  2116  	}
  2117  	info.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE
  2118  	val, ok := eosFileInfo.Attrs["reva.target"]
  2119  	if !ok || val == "" {
  2120  		return nil, errtypes.InternalError("eosfs: reference does not contain target: target=" + val + " file=" + eosFileInfo.File)
  2121  	}
  2122  	info.Target = val
  2123  	return info, nil
  2124  }
  2125  
  2126  // permissionSet returns the permission set for the current user
  2127  func (fs *eosfs) permissionSet(ctx context.Context, eosFileInfo *eosclient.FileInfo, owner *userpb.UserId) *provider.ResourcePermissions {
  2128  	u, ok := ctxpkg.ContextGetUser(ctx)
  2129  	if !ok || u.Id == nil {
  2130  		return &provider.ResourcePermissions{
  2131  			// no permissions
  2132  		}
  2133  	}
  2134  
  2135  	if owner != nil && u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp {
  2136  		// The logged-in user is the owner but we may be impersonating them
  2137  		// on behalf of a public share accessor.
  2138  
  2139  		// NOTE: This will grant the user full access when the opaque is nil
  2140  		// it is likely that this can be used for attacks
  2141  		if u.Opaque != nil {
  2142  			// FIXME: "editor" and "viewer" are not sufficient anymore, they could have different permissions
  2143  			// The role names should not be hardcoded any more as they will come from config in the future
  2144  			if publicShare, ok := u.Opaque.Map["public-share-role"]; ok {
  2145  				if string(publicShare.Value) == "editor" {
  2146  					return conversions.NewEditorRole().CS3ResourcePermissions()
  2147  				} else if string(publicShare.Value) == "uploader" {
  2148  					return conversions.NewUploaderRole().CS3ResourcePermissions()
  2149  				}
  2150  				// Default to viewer role
  2151  				return conversions.NewViewerRole().CS3ResourcePermissions()
  2152  			}
  2153  		}
  2154  
  2155  		// owner has all permissions
  2156  		return conversions.NewManagerRole().CS3ResourcePermissions()
  2157  	}
  2158  
  2159  	auth, err := fs.getUserAuth(ctx, u, eosFileInfo.File)
  2160  	if err != nil {
  2161  		return &provider.ResourcePermissions{
  2162  			// no permissions
  2163  		}
  2164  	}
  2165  
  2166  	if eosFileInfo.SysACL == nil {
  2167  		return &provider.ResourcePermissions{
  2168  			// no permissions
  2169  		}
  2170  	}
  2171  	var perm provider.ResourcePermissions
  2172  
  2173  	for _, e := range eosFileInfo.SysACL.Entries {
  2174  		var userInGroup bool
  2175  		if e.Type == acl.TypeGroup {
  2176  			for _, g := range u.Groups {
  2177  				if e.Qualifier == g {
  2178  					userInGroup = true
  2179  					break
  2180  				}
  2181  			}
  2182  		}
  2183  
  2184  		if (e.Type == acl.TypeUser && e.Qualifier == auth.Role.UID) || (e.Type == acl.TypeLightweight && e.Qualifier == u.Id.OpaqueId) || userInGroup {
  2185  			mergePermissions(&perm, grants.GetGrantPermissionSet(e.Permissions))
  2186  		}
  2187  	}
  2188  
  2189  	return &perm
  2190  }
  2191  
  2192  func mergePermissions(l *provider.ResourcePermissions, r *provider.ResourcePermissions) {
  2193  	l.AddGrant = l.AddGrant || r.AddGrant
  2194  	l.CreateContainer = l.CreateContainer || r.CreateContainer
  2195  	l.Delete = l.Delete || r.Delete
  2196  	l.GetPath = l.GetPath || r.GetPath
  2197  	l.GetQuota = l.GetQuota || r.GetQuota
  2198  	l.InitiateFileDownload = l.InitiateFileDownload || r.InitiateFileDownload
  2199  	l.InitiateFileUpload = l.InitiateFileUpload || r.InitiateFileUpload
  2200  	l.ListContainer = l.ListContainer || r.ListContainer
  2201  	l.ListFileVersions = l.ListFileVersions || r.ListFileVersions
  2202  	l.ListGrants = l.ListGrants || r.ListGrants
  2203  	l.ListRecycle = l.ListRecycle || r.ListRecycle
  2204  	l.Move = l.Move || r.Move
  2205  	l.PurgeRecycle = l.PurgeRecycle || r.PurgeRecycle
  2206  	l.RemoveGrant = l.RemoveGrant || r.RemoveGrant
  2207  	l.RestoreFileVersion = l.RestoreFileVersion || r.RestoreFileVersion
  2208  	l.RestoreRecycleItem = l.RestoreRecycleItem || r.RestoreRecycleItem
  2209  	l.Stat = l.Stat || r.Stat
  2210  	l.UpdateGrant = l.UpdateGrant || r.UpdateGrant
  2211  	l.DenyGrant = l.DenyGrant || r.DenyGrant
  2212  }
  2213  
  2214  func (fs *eosfs) convert(ctx context.Context, eosFileInfo *eosclient.FileInfo) (*provider.ResourceInfo, error) {
  2215  	path, err := fs.unwrap(ctx, eosFileInfo.File)
  2216  	if err != nil {
  2217  		return nil, err
  2218  	}
  2219  	path = filepath.Join(fs.conf.MountPath, path)
  2220  
  2221  	size := eosFileInfo.Size
  2222  	if eosFileInfo.IsDir {
  2223  		size = eosFileInfo.TreeSize
  2224  	}
  2225  
  2226  	owner, err := fs.getUserIDGateway(ctx, strconv.FormatUint(eosFileInfo.UID, 10))
  2227  	if err != nil {
  2228  		sublog := appctx.GetLogger(ctx).With().Logger()
  2229  		sublog.Warn().Uint64("uid", eosFileInfo.UID).Msg("could not lookup userid, leaving empty")
  2230  	}
  2231  
  2232  	var xs provider.ResourceChecksum
  2233  	if eosFileInfo.XS != nil {
  2234  		xs.Sum = eosFileInfo.XS.XSSum
  2235  		switch eosFileInfo.XS.XSType {
  2236  		case "adler":
  2237  			xs.Type = provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_ADLER32
  2238  		default:
  2239  			xs.Type = provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_INVALID
  2240  		}
  2241  	}
  2242  
  2243  	// filter 'sys' attrs and the reserved lock
  2244  	filteredAttrs := make(map[string]string)
  2245  	for k, v := range eosFileInfo.Attrs {
  2246  		if !strings.HasPrefix(k, "sys") {
  2247  			filteredAttrs[k] = v
  2248  		}
  2249  	}
  2250  
  2251  	info := &provider.ResourceInfo{
  2252  		Id:            &provider.ResourceId{OpaqueId: fmt.Sprintf("%d", eosFileInfo.Inode)},
  2253  		Path:          path,
  2254  		Owner:         owner,
  2255  		Etag:          fmt.Sprintf("\"%s\"", strings.Trim(eosFileInfo.ETag, "\"")),
  2256  		MimeType:      mime.Detect(eosFileInfo.IsDir, path),
  2257  		Size:          size,
  2258  		ParentId:      &provider.ResourceId{OpaqueId: fmt.Sprintf("%d", eosFileInfo.FID)},
  2259  		PermissionSet: fs.permissionSet(ctx, eosFileInfo, owner),
  2260  		Checksum:      &xs,
  2261  		Type:          getResourceType(eosFileInfo.IsDir),
  2262  		Mtime: &types.Timestamp{
  2263  			Seconds: eosFileInfo.MTimeSec,
  2264  			Nanos:   eosFileInfo.MTimeNanos,
  2265  		},
  2266  		Opaque: &types.Opaque{
  2267  			Map: map[string]*types.OpaqueEntry{
  2268  				"eos": {
  2269  					Decoder: "json",
  2270  					Value:   fs.getEosMetadata(eosFileInfo),
  2271  				},
  2272  			},
  2273  		},
  2274  		ArbitraryMetadata: &provider.ArbitraryMetadata{
  2275  			Metadata: filteredAttrs,
  2276  		},
  2277  	}
  2278  
  2279  	if eosFileInfo.IsDir {
  2280  		info.Opaque.Map["disable_tus"] = &types.OpaqueEntry{
  2281  			Decoder: "plain",
  2282  			Value:   []byte("true"),
  2283  		}
  2284  	}
  2285  
  2286  	return info, nil
  2287  }
  2288  
  2289  func getResourceType(isDir bool) provider.ResourceType {
  2290  	if isDir {
  2291  		return provider.ResourceType_RESOURCE_TYPE_CONTAINER
  2292  	}
  2293  	return provider.ResourceType_RESOURCE_TYPE_FILE
  2294  }
  2295  
  2296  func (fs *eosfs) extractUIDAndGID(u *userpb.User) (eosclient.Authorization, error) {
  2297  	if u.UidNumber == 0 {
  2298  		return eosclient.Authorization{}, errors.New("eosfs: uid missing for user")
  2299  	}
  2300  	if u.GidNumber == 0 {
  2301  		return eosclient.Authorization{}, errors.New("eosfs: gid missing for user")
  2302  	}
  2303  	return eosclient.Authorization{Role: eosclient.Role{UID: strconv.FormatInt(u.UidNumber, 10), GID: strconv.FormatInt(u.GidNumber, 10)}}, nil
  2304  }
  2305  
  2306  func (fs *eosfs) getUIDGateway(ctx context.Context, u *userpb.UserId) (eosclient.Authorization, error) {
  2307  	log := appctx.GetLogger(ctx)
  2308  	if userIDInterface, err := fs.userIDCache.Get(u.OpaqueId); err == nil {
  2309  		log.Debug().Msg("eosfs: found cached user " + u.OpaqueId)
  2310  		return fs.extractUIDAndGID(userIDInterface.(*userpb.User))
  2311  	}
  2312  
  2313  	client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc)
  2314  	if err != nil {
  2315  		return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting gateway grpc client")
  2316  	}
  2317  	getUserResp, err := client.GetUser(ctx, &userpb.GetUserRequest{
  2318  		UserId:                 u,
  2319  		SkipFetchingUserGroups: true,
  2320  	})
  2321  	if err != nil {
  2322  		_ = fs.userIDCache.SetWithTTL(u.OpaqueId, &userpb.User{}, 12*time.Hour)
  2323  		return eosclient.Authorization{}, errors.Wrap(err, "eosfs: error getting user")
  2324  	}
  2325  	if getUserResp.Status.Code != rpc.Code_CODE_OK {
  2326  		_ = fs.userIDCache.SetWithTTL(u.OpaqueId, &userpb.User{}, 12*time.Hour)
  2327  		return eosclient.Authorization{}, status.NewErrorFromCode(getUserResp.Status.Code, "eosfs")
  2328  	}
  2329  
  2330  	_ = fs.userIDCache.Set(u.OpaqueId, getUserResp.User)
  2331  	return fs.extractUIDAndGID(getUserResp.User)
  2332  }
  2333  
  2334  func (fs *eosfs) getUserIDGateway(ctx context.Context, uid string) (*userpb.UserId, error) {
  2335  	log := appctx.GetLogger(ctx)
  2336  	// Handle the case of root
  2337  	if uid == "0" {
  2338  		return nil, errtypes.BadRequest("eosfs: cannot return root user")
  2339  	}
  2340  
  2341  	if userIDInterface, err := fs.userIDCache.Get(uid); err == nil {
  2342  		log.Debug().Msg("eosfs: found cached uid " + uid)
  2343  		return userIDInterface.(*userpb.UserId), nil
  2344  	}
  2345  
  2346  	log.Debug().Msg("eosfs: retrieving user from gateway for uid " + uid)
  2347  	client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc)
  2348  	if err != nil {
  2349  		return nil, errors.Wrap(err, "eosfs: error getting gateway grpc client")
  2350  	}
  2351  	getUserResp, err := client.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{
  2352  		Claim:                  "uid",
  2353  		Value:                  uid,
  2354  		SkipFetchingUserGroups: true,
  2355  	})
  2356  	if err != nil {
  2357  		// Insert an empty object in the cache so that we don't make another call
  2358  		// for a specific amount of time
  2359  		_ = fs.userIDCache.SetWithTTL(uid, &userpb.UserId{}, 12*time.Hour)
  2360  		return nil, errors.Wrap(err, "eosfs: error getting user")
  2361  	}
  2362  	if getUserResp.Status.Code != rpc.Code_CODE_OK {
  2363  		// Insert an empty object in the cache so that we don't make another call
  2364  		// for a specific amount of time
  2365  		_ = fs.userIDCache.SetWithTTL(uid, &userpb.UserId{}, 12*time.Hour)
  2366  		return nil, status.NewErrorFromCode(getUserResp.Status.Code, "eosfs")
  2367  	}
  2368  
  2369  	_ = fs.userIDCache.Set(uid, getUserResp.User.Id)
  2370  	return getUserResp.User.Id, nil
  2371  }
  2372  
  2373  func (fs *eosfs) getUserAuth(ctx context.Context, u *userpb.User, fn string) (eosclient.Authorization, error) {
  2374  	if fs.conf.ForceSingleUserMode {
  2375  		if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" {
  2376  			return fs.singleUserAuth, nil
  2377  		}
  2378  		var err error
  2379  		fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername})
  2380  		return fs.singleUserAuth, err
  2381  	}
  2382  
  2383  	if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT ||
  2384  		u.Id.Type == userpb.UserType_USER_TYPE_FEDERATED {
  2385  		return fs.getEOSToken(ctx, u, fn)
  2386  	}
  2387  
  2388  	return fs.extractUIDAndGID(u)
  2389  }
  2390  
  2391  func (fs *eosfs) getEOSToken(ctx context.Context, u *userpb.User, fn string) (eosclient.Authorization, error) {
  2392  	if fn == "" {
  2393  		return eosclient.Authorization{}, errtypes.BadRequest("eosfs: path cannot be empty")
  2394  	}
  2395  
  2396  	rootAuth, err := fs.getRootAuth(ctx)
  2397  	if err != nil {
  2398  		return eosclient.Authorization{}, err
  2399  	}
  2400  	info, err := fs.c.GetFileInfoByPath(ctx, rootAuth, fn)
  2401  	if err != nil {
  2402  		return eosclient.Authorization{}, err
  2403  	}
  2404  	auth := eosclient.Authorization{
  2405  		Role: eosclient.Role{
  2406  			UID: strconv.FormatUint(info.UID, 10),
  2407  			GID: strconv.FormatUint(info.GID, 10),
  2408  		},
  2409  	}
  2410  
  2411  	perm := "rwx"
  2412  	for _, e := range info.SysACL.Entries {
  2413  		if e.Type == acl.TypeLightweight && e.Qualifier == u.Id.OpaqueId {
  2414  			perm = e.Permissions
  2415  			break
  2416  		}
  2417  	}
  2418  
  2419  	p := path.Clean(fn)
  2420  	for p != "." && p != fs.conf.Namespace {
  2421  		key := p + "!" + perm
  2422  		if tknIf, err := fs.tokenCache.Get(key); err == nil {
  2423  			return eosclient.Authorization{Token: tknIf.(string)}, nil
  2424  		}
  2425  		p = path.Dir(p)
  2426  	}
  2427  
  2428  	if info.IsDir {
  2429  		// EOS expects directories to have a trailing slash when generating tokens
  2430  		fn = path.Clean(fn) + "/"
  2431  	}
  2432  	tkn, err := fs.c.GenerateToken(ctx, auth, fn, &acl.Entry{Permissions: perm})
  2433  	if err != nil {
  2434  		return eosclient.Authorization{}, err
  2435  	}
  2436  
  2437  	key := path.Clean(fn) + "!" + perm
  2438  	_ = fs.tokenCache.SetWithExpire(key, tkn, time.Second*time.Duration(fs.conf.TokenExpiry))
  2439  
  2440  	return eosclient.Authorization{Token: tkn}, nil
  2441  }
  2442  
  2443  func (fs *eosfs) getRootAuth(ctx context.Context) (eosclient.Authorization, error) {
  2444  	if fs.conf.ForceSingleUserMode {
  2445  		if fs.singleUserAuth.Role.UID != "" && fs.singleUserAuth.Role.GID != "" {
  2446  			return fs.singleUserAuth, nil
  2447  		}
  2448  		var err error
  2449  		fs.singleUserAuth, err = fs.getUIDGateway(ctx, &userpb.UserId{OpaqueId: fs.conf.SingleUsername})
  2450  		return fs.singleUserAuth, err
  2451  	}
  2452  	return eosclient.Authorization{Role: eosclient.Role{UID: "0", GID: "0"}}, nil
  2453  }
  2454  
  2455  type eosSysMetadata struct {
  2456  	TreeSize  uint64 `json:"tree_size"`
  2457  	TreeCount uint64 `json:"tree_count"`
  2458  	File      string `json:"file"`
  2459  	Instance  string `json:"instance"`
  2460  }
  2461  
  2462  func (fs *eosfs) getEosMetadata(finfo *eosclient.FileInfo) []byte {
  2463  	sys := &eosSysMetadata{
  2464  		File:     finfo.File,
  2465  		Instance: finfo.Instance,
  2466  	}
  2467  
  2468  	if finfo.IsDir {
  2469  		sys.TreeCount = finfo.TreeCount
  2470  		sys.TreeSize = finfo.TreeSize
  2471  	}
  2472  
  2473  	v, _ := json.Marshal(sys)
  2474  	return v
  2475  }
  2476  
  2477  /*
  2478  	Merge shadow on requests for /home ?
  2479  
  2480  	No - GetHome(ctx context.Context) (string, error)
  2481  	No -CreateHome(ctx context.Context) error
  2482  	No - CreateDir(ctx context.Context, fn string) error
  2483  	No -Delete(ctx context.Context, ref *provider.Reference) error
  2484  	No -Move(ctx context.Context, oldRef, newRef *provider.Reference) error
  2485  	No -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error)
  2486  	Yes -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error)
  2487  	No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error
  2488  	No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error)
  2489  	No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error)
  2490  	No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error)
  2491  	No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error
  2492  	No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error)
  2493  	No RestoreRecycleItem(ctx context.Context, key string) error
  2494  	No PurgeRecycleItem(ctx context.Context, key string) error
  2495  	No EmptyRecycle(ctx context.Context) error
  2496  	? GetPathByID(ctx context.Context, id *provider.Reference) (string, error)
  2497  	No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2498  	No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2499  	No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2500  	No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error)
  2501  	No GetQuota(ctx context.Context) (int, int, error)
  2502  	No CreateReference(ctx context.Context, path string, targetURI *url.URL) error
  2503  	No Shutdown(ctx context.Context) error
  2504  	No SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error
  2505  	No UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error
  2506  */
  2507  
  2508  /*
  2509  	Merge shadow on requests for /home/MyShares ?
  2510  
  2511  	No - GetHome(ctx context.Context) (string, error)
  2512  	No -CreateHome(ctx context.Context) error
  2513  	No - CreateDir(ctx context.Context, fn string) error
  2514  	Maybe -Delete(ctx context.Context, ref *provider.Reference) error
  2515  	No -Move(ctx context.Context, oldRef, newRef *provider.Reference) error
  2516  	Yes -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error)
  2517  	Yes -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error)
  2518  	No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error
  2519  	No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error)
  2520  	No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error)
  2521  	No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error)
  2522  	No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error
  2523  	No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error)
  2524  	No RestoreRecycleItem(ctx context.Context, key string) error
  2525  	No PurgeRecycleItem(ctx context.Context, key string) error
  2526  	No EmptyRecycle(ctx context.Context) error
  2527  	?  GetPathByID(ctx context.Context, id *provider.Reference) (string, error)
  2528  	No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2529  	No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2530  	No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2531  	No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error)
  2532  	No GetQuota(ctx context.Context) (int, int, error)
  2533  	No CreateReference(ctx context.Context, path string, targetURI *url.URL) error
  2534  	No Shutdown(ctx context.Context) error
  2535  	No SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error
  2536  	No UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error
  2537  */
  2538  
  2539  /*
  2540  	Merge shadow on requests for /home/MyShares/file-reference ?
  2541  
  2542  	No - GetHome(ctx context.Context) (string, error)
  2543  	No -CreateHome(ctx context.Context) error
  2544  	No - CreateDir(ctx context.Context, fn string) error
  2545  	Maybe -Delete(ctx context.Context, ref *provider.Reference) error
  2546  	Yes -Move(ctx context.Context, oldRef, newRef *provider.Reference) error
  2547  	Yes -GetMD(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error)
  2548  	No -ListFolder(ctx context.Context, ref *provider.Reference) ([]*provider.ResourceInfo, error)
  2549  	No -Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error
  2550  	No -Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error)
  2551  	No -ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error)
  2552  	No -DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error)
  2553  	No -RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error
  2554  	No ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error)
  2555  	No RestoreRecycleItem(ctx context.Context, key string) error
  2556  	No PurgeRecycleItem(ctx context.Context, key string) error
  2557  	No EmptyRecycle(ctx context.Context) error
  2558  	?  GetPathByID(ctx context.Context, id *provider.Reference) (string, error)
  2559  	No AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2560  	No RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2561  	No UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
  2562  	No ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error)
  2563  	No GetQuota(ctx context.Context) (int, int, error)
  2564  	No CreateReference(ctx context.Context, path string, targetURI *url.URL) error
  2565  	No Shutdown(ctx context.Context) error
  2566  	Maybe SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error
  2567  	Maybe UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error
  2568  */