github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/gateway/storageprovider.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 gateway
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"encoding/xml"
    25  	"fmt"
    26  	"net/url"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/BurntSushi/toml"
    32  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    33  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    34  	collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
    35  	linkv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
    36  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    37  	registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
    38  	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    39  	"google.golang.org/grpc/codes"
    40  
    41  	"github.com/cs3org/reva/v2/pkg/appctx"
    42  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    43  	"github.com/cs3org/reva/v2/pkg/errtypes"
    44  	"github.com/cs3org/reva/v2/pkg/publicshare"
    45  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    46  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    47  	sdk "github.com/cs3org/reva/v2/pkg/sdk/common"
    48  	"github.com/cs3org/reva/v2/pkg/share"
    49  	"github.com/cs3org/reva/v2/pkg/storagespace"
    50  	"github.com/cs3org/reva/v2/pkg/utils"
    51  	"github.com/golang-jwt/jwt/v5"
    52  	"github.com/pkg/errors"
    53  	gstatus "google.golang.org/grpc/status"
    54  )
    55  
    56  /*  About caching
    57      The gateway is doing a lot of requests to look up the responsible storage providers for a reference.
    58      - when the reference uses an id we can use a global id -> provider cache because it is the same for all users
    59      - when the reference is an absolute path we
    60     	 - 1. look up the corresponding space in the space registry
    61       - 2. can reuse the global id -> provider cache to look up the provider
    62  	 - paths are unique per user: when a rule mounts shares at /shares/{{.Space.Name}}
    63  	   the path /shares/Documents might show different content for einstein than for marie
    64  	   -> path -> spaceid lookup needs a per user cache
    65  	When can we invalidate?
    66  	- the global cache needs to be invalidated when the provider for a space id changes.
    67  		- happens when a space is moved from one provider to another. Not yet implemented
    68  		-> should be good enough to use a TTL. daily should be good enough
    69  	- the user individual file cache is actually a cache of the mount points
    70  	    - we could do a registry.ListProviders (for user) on startup to warm up the cache ...
    71  		- when a share is granted or removed we need to invalidate that path
    72  		- when a share is renamed we need to invalidate the path
    73  		- we can use a ttl for all paths?
    74  		- the findProviders func in the gateway needs to look up in the user cache first
    75  	We want to cache the root etag of spaces
    76  	    - can be invalidated on every write or delete with fallback via TTL?
    77  */
    78  
    79  // transferClaims are custom claims for a JWT token to be used between the metadata and data gateways.
    80  type transferClaims struct {
    81  	jwt.RegisteredClaims
    82  	Target string `json:"target"`
    83  }
    84  
    85  func (s *svc) sign(_ context.Context, target string, expiresAt int64) (string, error) {
    86  	// Tus sends a separate request to the datagateway service for every chunk.
    87  	// For large files, this can take a long time, so we extend the expiration
    88  	claims := transferClaims{
    89  		RegisteredClaims: jwt.RegisteredClaims{
    90  			ExpiresAt: jwt.NewNumericDate(time.Unix(expiresAt, 0)),
    91  			Audience:  jwt.ClaimStrings{"reva"},
    92  			IssuedAt:  jwt.NewNumericDate(time.Now()),
    93  		},
    94  		Target: target,
    95  	}
    96  
    97  	t := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims)
    98  
    99  	tkn, err := t.SignedString([]byte(s.c.TransferSharedSecret))
   100  	if err != nil {
   101  		return "", errors.Wrapf(err, "error signing token with claims %+v", claims)
   102  	}
   103  
   104  	return tkn, nil
   105  }
   106  
   107  func (s *svc) CreateHome(ctx context.Context, req *provider.CreateHomeRequest) (*provider.CreateHomeResponse, error) {
   108  	u, ok := ctxpkg.ContextGetUser(ctx)
   109  	if !ok {
   110  		return &provider.CreateHomeResponse{
   111  			Status: status.NewPermissionDenied(ctx, nil, "can't create home for anonymous user"),
   112  		}, nil
   113  
   114  	}
   115  	quotaStr := utils.ReadPlainFromOpaque(req.Opaque, "quota")
   116  	var quota *provider.Quota
   117  	if quotaStr != "" {
   118  		q, err := strconv.ParseUint(quotaStr, 10, 64)
   119  		if err != nil {
   120  			return &provider.CreateHomeResponse{
   121  				Status: status.NewInvalid(ctx, fmt.Sprintf("can't parse quotaStr: %s", quotaStr)),
   122  			}, nil
   123  		}
   124  		quota = &provider.Quota{
   125  			QuotaMaxBytes: q,
   126  		}
   127  	}
   128  	createReq := &provider.CreateStorageSpaceRequest{
   129  		Type:  "personal",
   130  		Owner: u,
   131  		Name:  u.DisplayName,
   132  		Quota: quota,
   133  	}
   134  
   135  	// send the user id as the space id, makes debugging easier
   136  	if u.Id != nil && u.Id.OpaqueId != "" {
   137  		createReq.Opaque = &typesv1beta1.Opaque{
   138  			Map: map[string]*typesv1beta1.OpaqueEntry{
   139  				"space_id": {
   140  					Decoder: "plain",
   141  					Value:   []byte(u.Id.OpaqueId),
   142  				},
   143  			},
   144  		}
   145  	}
   146  	res, err := s.CreateStorageSpace(ctx, createReq)
   147  	if err != nil {
   148  		return &provider.CreateHomeResponse{
   149  			Status: status.NewStatusFromErrType(ctx, "gateway could not call CreateStorageSpace", err),
   150  		}, nil
   151  	}
   152  	return &provider.CreateHomeResponse{
   153  		Opaque: res.Opaque,
   154  		Status: res.Status,
   155  	}, nil
   156  }
   157  
   158  func (s *svc) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) {
   159  	// TODO change the CreateStorageSpaceRequest to contain a space instead of sending individual properties
   160  	space := &provider.StorageSpace{
   161  		Owner:     req.Owner,
   162  		SpaceType: req.Type,
   163  		Name:      req.Name,
   164  		Quota:     req.Quota,
   165  	}
   166  
   167  	if req.Opaque != nil && req.Opaque.Map != nil && req.Opaque.Map["id"] != nil {
   168  		if req.Opaque.Map["space_id"].Decoder == "plain" {
   169  			space.Id = &provider.StorageSpaceId{OpaqueId: string(req.Opaque.Map["id"].Value)}
   170  		}
   171  	}
   172  
   173  	srClient, err := s.getStorageRegistryClient(ctx, s.c.StorageRegistryEndpoint)
   174  	if err != nil {
   175  		return &provider.CreateStorageSpaceResponse{
   176  			Status: status.NewStatusFromErrType(ctx, "gateway could get storage registry client", err),
   177  		}, nil
   178  	}
   179  
   180  	spaceJSON, err := json.Marshal(space)
   181  	if err != nil {
   182  		return &provider.CreateStorageSpaceResponse{
   183  			Status: status.NewStatusFromErrType(ctx, "gateway could not marshal space json", err),
   184  		}, nil
   185  	}
   186  
   187  	// The registry is responsible for choosing the right provider
   188  	res, err := srClient.GetStorageProviders(ctx, &registry.GetStorageProvidersRequest{
   189  		Opaque: &typesv1beta1.Opaque{
   190  			Map: map[string]*typesv1beta1.OpaqueEntry{
   191  				"space": {
   192  					Decoder: "json",
   193  					Value:   spaceJSON,
   194  				},
   195  			},
   196  		},
   197  	})
   198  	if err != nil {
   199  		return &provider.CreateStorageSpaceResponse{
   200  			Status: status.NewStatusFromErrType(ctx, "gateway could not call GetStorageProviders", err),
   201  		}, nil
   202  	}
   203  	if res.Status.Code != rpc.Code_CODE_OK {
   204  		return &provider.CreateStorageSpaceResponse{
   205  			Status: res.Status,
   206  		}, nil
   207  	}
   208  
   209  	if len(res.Providers) == 0 {
   210  		return &provider.CreateStorageSpaceResponse{
   211  			Status: status.NewNotFound(ctx, fmt.Sprintf("gateway found no provider for space %+v", space)),
   212  		}, nil
   213  	}
   214  
   215  	// just pick the first provider, we expect only one
   216  	c, err := s.getSpacesProviderClient(ctx, res.Providers[0])
   217  	if err != nil {
   218  		return &provider.CreateStorageSpaceResponse{
   219  			Status: status.NewStatusFromErrType(ctx, "gateway could not get storage provider client", err),
   220  		}, nil
   221  	}
   222  	createRes, err := c.CreateStorageSpace(ctx, req)
   223  	if err != nil {
   224  		return &provider.CreateStorageSpaceResponse{
   225  			Status: status.NewStatusFromErrType(ctx, "gateway could not call CreateStorageSpace", err),
   226  		}, nil
   227  	}
   228  
   229  	return createRes, nil
   230  }
   231  
   232  func (s *svc) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) {
   233  	// TODO update CS3 api to forward the filters to the registry so it can filter the number of providers the gateway needs to query
   234  	filters := map[string]string{
   235  		// TODO add opaque / CS3 api to expand 'path,root,stat?' properties / field mask
   236  		"mask": "*", // fetch all properties when listing storage spaces
   237  	}
   238  
   239  	mask := utils.ReadPlainFromOpaque(req.Opaque, "mask")
   240  	if mask != "" {
   241  		// TODO check for allowed filters
   242  		filters["mask"] = mask
   243  	}
   244  	path := utils.ReadPlainFromOpaque(req.Opaque, "path")
   245  	if path != "" {
   246  		// TODO check for allowed filters
   247  		filters["path"] = path
   248  	}
   249  
   250  	for _, f := range req.Filters {
   251  		switch f.Type {
   252  		case provider.ListStorageSpacesRequest_Filter_TYPE_ID:
   253  			sid, spid, oid, err := storagespace.SplitID(f.GetId().GetOpaqueId())
   254  			if err != nil {
   255  				continue
   256  			}
   257  			filters["storage_id"], filters["space_id"], filters["opaque_id"] = sid, spid, oid
   258  		case provider.ListStorageSpacesRequest_Filter_TYPE_OWNER:
   259  			filters["owner_idp"] = f.GetOwner().GetIdp()
   260  			filters["owner_id"] = f.GetOwner().GetOpaqueId()
   261  		case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE:
   262  			filters["space_type"] = f.GetSpaceType()
   263  		case provider.ListStorageSpacesRequest_Filter_TYPE_USER:
   264  			filters["user_idp"] = f.GetUser().GetIdp()
   265  			filters["user_id"] = f.GetUser().GetOpaqueId()
   266  		default:
   267  			return &provider.ListStorageSpacesResponse{
   268  				Status: status.NewInvalid(ctx, fmt.Sprintf("unknown filter %v", f.Type)),
   269  			}, nil
   270  		}
   271  	}
   272  
   273  	c, err := s.getStorageRegistryClient(ctx, s.c.StorageRegistryEndpoint)
   274  	if err != nil {
   275  		return &provider.ListStorageSpacesResponse{
   276  			Status: status.NewStatusFromErrType(ctx, "gateway could not get storage registry client", err),
   277  		}, nil
   278  	}
   279  
   280  	listReq := &registry.ListStorageProvidersRequest{Opaque: req.Opaque}
   281  	if listReq.Opaque == nil {
   282  		listReq.Opaque = &typesv1beta1.Opaque{}
   283  	}
   284  	if len(filters) > 0 {
   285  		sdk.EncodeOpaqueMap(listReq.Opaque, filters)
   286  	}
   287  	res, err := c.ListStorageProviders(ctx, listReq)
   288  	if err != nil {
   289  		return &provider.ListStorageSpacesResponse{
   290  			Status: status.NewStatusFromErrType(ctx, "gateway could not call ListStorageSpaces", err),
   291  		}, nil
   292  	}
   293  	if res.Status.Code != rpc.Code_CODE_OK {
   294  		return &provider.ListStorageSpacesResponse{
   295  			Status: res.Status,
   296  		}, nil
   297  	}
   298  
   299  	spaces := []*provider.StorageSpace{}
   300  	for _, providerInfo := range res.Providers {
   301  		spaces = append(spaces, decodeSpaces(providerInfo)...)
   302  	}
   303  
   304  	return &provider.ListStorageSpacesResponse{
   305  		Status:        status.NewOK(ctx),
   306  		StorageSpaces: spaces,
   307  	}, nil
   308  }
   309  
   310  func (s *svc) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) {
   311  	// TODO: needs to be fixed
   312  	ref := &provider.Reference{ResourceId: req.StorageSpace.Root}
   313  	c, _, err := s.findSpacesProvider(ctx, ref)
   314  	if err != nil {
   315  		return &provider.UpdateStorageSpaceResponse{
   316  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find reference %+v", ref), err),
   317  		}, nil
   318  	}
   319  
   320  	res, err := c.UpdateStorageSpace(ctx, req)
   321  	if err != nil {
   322  		return &provider.UpdateStorageSpaceResponse{
   323  			Status: status.NewStatusFromErrType(ctx, "gateway could not call UpdateStorageSpace", err),
   324  		}, nil
   325  	}
   326  
   327  	if res.Status.Code == rpc.Code_CODE_OK {
   328  		id := res.StorageSpace.Root
   329  		s.providerCache.RemoveListStorageProviders(id)
   330  	}
   331  	return res, nil
   332  }
   333  
   334  func (s *svc) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) (*provider.DeleteStorageSpaceResponse, error) {
   335  	opaque := req.Opaque
   336  	var purge bool
   337  	// This is just a temporary hack until the CS3 API get's updated to have a dedicated purge parameter or a dedicated PurgeStorageSpace method.
   338  	if opaque != nil {
   339  		_, purge = opaque.Map["purge"]
   340  	}
   341  
   342  	rid, err := storagespace.ParseID(req.GetId().GetOpaqueId())
   343  	if err != nil {
   344  		return &provider.DeleteStorageSpaceResponse{
   345  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not parse space id %s", req.GetId().GetOpaqueId()), err),
   346  		}, nil
   347  	}
   348  
   349  	ref := &provider.Reference{ResourceId: &rid}
   350  	c, _, err := s.findSpacesProvider(ctx, ref)
   351  	if err != nil {
   352  		return &provider.DeleteStorageSpaceResponse{
   353  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find reference %+v", ref), err),
   354  		}, nil
   355  	}
   356  
   357  	dsRes, err := c.DeleteStorageSpace(ctx, req)
   358  	if err != nil {
   359  		return &provider.DeleteStorageSpaceResponse{
   360  			Status: status.NewStatusFromErrType(ctx, "gateway could not call DeleteStorageSpace", err),
   361  		}, nil
   362  	}
   363  
   364  	id := &provider.ResourceId{OpaqueId: req.GetId().GetOpaqueId()}
   365  	s.providerCache.RemoveListStorageProviders(id)
   366  
   367  	if dsRes.Status.Code != rpc.Code_CODE_OK {
   368  		return dsRes, nil
   369  	}
   370  
   371  	if !purge {
   372  		return dsRes, nil
   373  	}
   374  
   375  	log := appctx.GetLogger(ctx)
   376  	log.Debug().Msg("purging storage space")
   377  	// List all shares in this storage space
   378  	lsRes, err := s.ListShares(ctx, &collaborationv1beta1.ListSharesRequest{
   379  		Filters: []*collaborationv1beta1.Filter{share.SpaceIDFilter(id.SpaceId)},
   380  	})
   381  	switch {
   382  	case err != nil:
   383  		return &provider.DeleteStorageSpaceResponse{
   384  			Status: status.NewStatusFromErrType(ctx, "gateway could not delete shares of StorageSpace", err),
   385  		}, nil
   386  	case lsRes.Status.Code != rpc.Code_CODE_OK:
   387  		return &provider.DeleteStorageSpaceResponse{
   388  			Status: status.NewInternal(ctx, "gateway could not delete shares of StorageSpace"),
   389  		}, nil
   390  	}
   391  	for _, share := range lsRes.Shares {
   392  		rsRes, err := s.RemoveShare(ctx, &collaborationv1beta1.RemoveShareRequest{
   393  			Ref: &collaborationv1beta1.ShareReference{
   394  				Spec: &collaborationv1beta1.ShareReference_Id{Id: share.Id},
   395  			},
   396  		})
   397  		if err != nil || rsRes.Status.Code != rpc.Code_CODE_OK {
   398  			log.Error().Err(err).Interface("status", rsRes.Status).Str("share_id", share.Id.OpaqueId).Msg("failed to delete share")
   399  		}
   400  	}
   401  
   402  	// List all public shares in this storage space
   403  	lpsRes, err := s.ListPublicShares(ctx, &linkv1beta1.ListPublicSharesRequest{
   404  		Filters: []*linkv1beta1.ListPublicSharesRequest_Filter{publicshare.StorageIDFilter(id.SpaceId)}, // FIXME rename the filter? @c0rby
   405  	})
   406  	switch {
   407  	case err != nil:
   408  		return &provider.DeleteStorageSpaceResponse{
   409  			Status: status.NewStatusFromErrType(ctx, "gateway could not delete shares of StorageSpace", err),
   410  		}, nil
   411  	case lpsRes.Status.Code != rpc.Code_CODE_OK:
   412  		return &provider.DeleteStorageSpaceResponse{
   413  			Status: status.NewInternal(ctx, "gateway could not delete shares of StorageSpace"),
   414  		}, nil
   415  	}
   416  	for _, share := range lpsRes.Share {
   417  		rsRes, err := s.RemovePublicShare(ctx, &linkv1beta1.RemovePublicShareRequest{
   418  			Ref: &linkv1beta1.PublicShareReference{
   419  				Spec: &linkv1beta1.PublicShareReference_Id{Id: share.Id},
   420  			},
   421  		})
   422  		if err != nil || rsRes.Status.Code != rpc.Code_CODE_OK {
   423  			log.Error().Err(err).Interface("status", rsRes.Status).Str("share_id", share.Id.OpaqueId).Msg("failed to delete share")
   424  		}
   425  	}
   426  
   427  	return dsRes, nil
   428  }
   429  
   430  func (s *svc) GetHome(ctx context.Context, _ *provider.GetHomeRequest) (*provider.GetHomeResponse, error) {
   431  	currentUser, ok := ctxpkg.ContextGetUser(ctx)
   432  	if !ok {
   433  		return nil, errors.New("user not found in context")
   434  	}
   435  
   436  	srClient, err := s.getStorageRegistryClient(ctx, s.c.StorageRegistryEndpoint)
   437  	if err != nil {
   438  		return &provider.GetHomeResponse{
   439  			Status: status.NewStatusFromErrType(ctx, "gateway could not get storage registry client", err),
   440  		}, nil
   441  	}
   442  
   443  	spaceJSON, err := json.Marshal(&provider.StorageSpace{
   444  		Owner:     currentUser,
   445  		SpaceType: "personal",
   446  	})
   447  	if err != nil {
   448  		return &provider.GetHomeResponse{
   449  			Status: status.NewStatusFromErrType(ctx, "gateway could not marshal space", err),
   450  		}, nil
   451  	}
   452  
   453  	// The registry is responsible for choosing the right provider
   454  	// TODO fix naming GetStorageProviders calls the GetProvider functon on the registry implementation
   455  	res, err := srClient.GetStorageProviders(ctx, &registry.GetStorageProvidersRequest{
   456  		Opaque: &typesv1beta1.Opaque{
   457  			Map: map[string]*typesv1beta1.OpaqueEntry{
   458  				"space": {
   459  					Decoder: "json",
   460  					Value:   spaceJSON,
   461  				},
   462  			},
   463  		},
   464  	})
   465  	if err != nil {
   466  		return &provider.GetHomeResponse{
   467  			Status: status.NewStatusFromErrType(ctx, "gateway could not call GetStorageProviders", err),
   468  		}, nil
   469  	}
   470  	if res.Status.Code != rpc.Code_CODE_OK {
   471  		return &provider.GetHomeResponse{
   472  			Status: res.Status,
   473  		}, nil
   474  	}
   475  
   476  	if len(res.Providers) == 0 {
   477  		return &provider.GetHomeResponse{
   478  			Status: status.NewNotFound(ctx, fmt.Sprintf("error finding provider for home space of %+v", currentUser)),
   479  		}, nil
   480  	}
   481  
   482  	// NOTE: this will cause confusion if len(spaces) > 1
   483  	spaces := decodeSpaces(res.Providers[0])
   484  	for _, space := range spaces {
   485  		return &provider.GetHomeResponse{
   486  			Path:   decodePath(space),
   487  			Status: status.NewOK(ctx),
   488  		}, nil
   489  	}
   490  
   491  	return &provider.GetHomeResponse{
   492  		Status: status.NewNotFound(ctx, fmt.Sprintf("error finding home path for provider %+v with spaces %+v ", res.Providers[0], spaces)),
   493  	}, nil
   494  }
   495  
   496  func (s *svc) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*gateway.InitiateFileDownloadResponse, error) {
   497  	// TODO(ishank011): enable downloading references spread across storage providers, eg. /eos
   498  	var c provider.ProviderAPIClient
   499  	var err error
   500  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   501  	if err != nil {
   502  		return &gateway.InitiateFileDownloadResponse{
   503  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   504  		}, nil
   505  	}
   506  
   507  	storageRes, err := c.InitiateFileDownload(ctx, req)
   508  	if err != nil {
   509  		return &gateway.InitiateFileDownloadResponse{
   510  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not call InitiateFileDownload, ref=%+v", req.Ref), err),
   511  		}, nil
   512  	}
   513  
   514  	protocols := make([]*gateway.FileDownloadProtocol, len(storageRes.Protocols))
   515  	for p := range storageRes.Protocols {
   516  		protocols[p] = &gateway.FileDownloadProtocol{
   517  			Opaque:           storageRes.Protocols[p].Opaque,
   518  			Protocol:         storageRes.Protocols[p].Protocol,
   519  			DownloadEndpoint: storageRes.Protocols[p].DownloadEndpoint,
   520  		}
   521  
   522  		if !storageRes.Protocols[p].Expose {
   523  			// sign the download location and pass it to the data gateway
   524  			u, err := url.Parse(protocols[p].DownloadEndpoint)
   525  			if err != nil {
   526  				return &gateway.InitiateFileDownloadResponse{
   527  					Status: status.NewStatusFromErrType(ctx, "wrong format for download endpoint", err),
   528  				}, nil
   529  			}
   530  
   531  			// TODO(labkode): calculate signature of the whole request? we only sign the URI now. Maybe worth https://tools.ietf.org/html/draft-cavage-http-signatures-11
   532  			target := u.String()
   533  			token, err := s.sign(ctx, target, time.Now().UTC().Add(time.Duration(s.c.TransferExpires)*time.Second).Unix())
   534  			if err != nil {
   535  				return &gateway.InitiateFileDownloadResponse{
   536  					Status: status.NewStatusFromErrType(ctx, "error creating signature for download", err),
   537  				}, nil
   538  			}
   539  
   540  			protocols[p].DownloadEndpoint = s.c.DataGatewayEndpoint
   541  			protocols[p].Token = token
   542  		}
   543  	}
   544  
   545  	return &gateway.InitiateFileDownloadResponse{
   546  		Opaque:    storageRes.Opaque,
   547  		Status:    storageRes.Status,
   548  		Protocols: protocols,
   549  	}, nil
   550  }
   551  
   552  func (s *svc) InitiateFileUpload(ctx context.Context, req *provider.InitiateFileUploadRequest) (*gateway.InitiateFileUploadResponse, error) {
   553  	var c provider.ProviderAPIClient
   554  	var err error
   555  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   556  	if err != nil {
   557  		return &gateway.InitiateFileUploadResponse{
   558  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   559  		}, nil
   560  	}
   561  
   562  	storageRes, err := c.InitiateFileUpload(ctx, req)
   563  	if err != nil {
   564  		return &gateway.InitiateFileUploadResponse{
   565  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not call InitiateFileUpload, ref=%+v", req.Ref), err),
   566  		}, nil
   567  	}
   568  
   569  	if storageRes.Status.Code != rpc.Code_CODE_OK {
   570  		return &gateway.InitiateFileUploadResponse{
   571  			Status: storageRes.Status,
   572  		}, nil
   573  	}
   574  
   575  	protocols := make([]*gateway.FileUploadProtocol, len(storageRes.Protocols))
   576  	for p := range storageRes.Protocols {
   577  		protocols[p] = &gateway.FileUploadProtocol{
   578  			Opaque:             storageRes.Protocols[p].Opaque,
   579  			Protocol:           storageRes.Protocols[p].Protocol,
   580  			UploadEndpoint:     storageRes.Protocols[p].UploadEndpoint,
   581  			AvailableChecksums: storageRes.Protocols[p].AvailableChecksums,
   582  		}
   583  
   584  		if !storageRes.Protocols[p].Expose {
   585  			// sign the upload location and pass it to the data gateway
   586  			u, err := url.Parse(protocols[p].UploadEndpoint)
   587  			if err != nil {
   588  				return &gateway.InitiateFileUploadResponse{
   589  					Status: status.NewStatusFromErrType(ctx, "wrong format for upload endpoint", err),
   590  				}, nil
   591  			}
   592  
   593  			// TODO(labkode): calculate signature of the whole request? we only sign the URI now. Maybe worth https://tools.ietf.org/html/draft-cavage-http-signatures-11
   594  			target := u.String()
   595  			ttl := time.Duration(s.c.TransferExpires) * time.Second
   596  			expiresAt := time.Now().Add(ttl).Unix()
   597  			if storageRes.Protocols[p].Expiration != nil {
   598  				expiresAt = utils.TSToTime(storageRes.Protocols[p].Expiration).Unix()
   599  			}
   600  			token, err := s.sign(ctx, target, expiresAt)
   601  			if err != nil {
   602  				return &gateway.InitiateFileUploadResponse{
   603  					Status: status.NewStatusFromErrType(ctx, "error creating signature for upload", err),
   604  				}, nil
   605  			}
   606  
   607  			protocols[p].UploadEndpoint = s.c.DataGatewayEndpoint
   608  			protocols[p].Token = token
   609  		}
   610  	}
   611  
   612  	return &gateway.InitiateFileUploadResponse{
   613  		Opaque:    storageRes.Opaque,
   614  		Status:    storageRes.Status,
   615  		Protocols: protocols,
   616  	}, nil
   617  }
   618  
   619  func (s *svc) GetPath(ctx context.Context, req *provider.GetPathRequest) (*provider.GetPathResponse, error) {
   620  	c, _, ref, err := s.findAndUnwrap(ctx, &provider.Reference{ResourceId: req.ResourceId})
   621  	if err != nil {
   622  		return &provider.GetPathResponse{
   623  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find reference %+v", ref), err),
   624  		}, nil
   625  	}
   626  
   627  	req.ResourceId = ref.ResourceId
   628  	return c.GetPath(ctx, req)
   629  }
   630  
   631  func (s *svc) CreateContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) {
   632  	var c provider.ProviderAPIClient
   633  	var err error
   634  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   635  	if err != nil {
   636  		return &provider.CreateContainerResponse{
   637  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   638  		}, nil
   639  	}
   640  
   641  	res, err := c.CreateContainer(ctx, req)
   642  	if err != nil {
   643  		return &provider.CreateContainerResponse{
   644  			Status: status.NewStatusFromErrType(ctx, "gateway could not call CreateContainer", err),
   645  		}, nil
   646  	}
   647  
   648  	return res, nil
   649  }
   650  
   651  func (s *svc) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*provider.TouchFileResponse, error) {
   652  	var c provider.ProviderAPIClient
   653  	var err error
   654  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   655  	if err != nil {
   656  		return &provider.TouchFileResponse{
   657  			Status: status.NewStatusFromErrType(ctx, "TouchFile ref="+req.Ref.String(), err),
   658  		}, nil
   659  	}
   660  
   661  	res, err := c.TouchFile(ctx, req)
   662  	if err != nil {
   663  		if gstatus.Code(err) == codes.PermissionDenied {
   664  			return &provider.TouchFileResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil
   665  		}
   666  		return nil, errors.Wrap(err, "gateway: error calling TouchFile")
   667  	}
   668  
   669  	return res, nil
   670  }
   671  
   672  func (s *svc) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) {
   673  	// TODO(ishank011): enable deleting references spread across storage providers, eg. /eos
   674  	var c provider.ProviderAPIClient
   675  	var err error
   676  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   677  	if err != nil {
   678  		return &provider.DeleteResponse{
   679  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   680  		}, nil
   681  	}
   682  
   683  	res, err := c.Delete(ctx, req)
   684  	if err != nil {
   685  		return &provider.DeleteResponse{
   686  			Status: status.NewStatusFromErrType(ctx, "gateway could not call Delete", err),
   687  		}, nil
   688  	}
   689  
   690  	return res, nil
   691  }
   692  
   693  func (s *svc) Move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) {
   694  	c, sourceProviderInfo, sref, err := s.findAndUnwrap(ctx, req.Source)
   695  	if err != nil {
   696  		return &provider.MoveResponse{
   697  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Source), err),
   698  		}, nil
   699  	}
   700  
   701  	_, destProviderInfo, dref, err := s.findAndUnwrap(ctx, req.Destination)
   702  	if err != nil {
   703  		return &provider.MoveResponse{
   704  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Source), err),
   705  		}, nil
   706  	}
   707  
   708  	if sourceProviderInfo.Address != destProviderInfo.Address {
   709  		return &provider.MoveResponse{
   710  			Status: status.NewUnimplemented(ctx, nil, "cross storage moves are not supported, use copy and delete"),
   711  		}, nil
   712  	}
   713  
   714  	req.Source = sref
   715  	req.Destination = dref
   716  	return c.Move(ctx, req)
   717  }
   718  
   719  func (s *svc) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest) (*provider.SetArbitraryMetadataResponse, error) {
   720  	// TODO(ishank011): enable for references spread across storage providers, eg. /eos
   721  	var c provider.ProviderAPIClient
   722  	var err error
   723  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   724  	if err != nil {
   725  		return &provider.SetArbitraryMetadataResponse{
   726  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   727  		}, nil
   728  	}
   729  
   730  	res, err := c.SetArbitraryMetadata(ctx, req)
   731  	if err != nil {
   732  		if gstatus.Code(err) == codes.PermissionDenied {
   733  			return &provider.SetArbitraryMetadataResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil
   734  		}
   735  		return nil, errors.Wrap(err, "gateway: error calling SetArbitraryMetadata")
   736  	}
   737  
   738  	return res, nil
   739  }
   740  
   741  func (s *svc) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest) (*provider.UnsetArbitraryMetadataResponse, error) {
   742  	// TODO(ishank011): enable for references spread across storage providers, eg. /eos
   743  	var c provider.ProviderAPIClient
   744  	var err error
   745  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   746  	if err != nil {
   747  		return &provider.UnsetArbitraryMetadataResponse{
   748  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   749  		}, nil
   750  	}
   751  
   752  	res, err := c.UnsetArbitraryMetadata(ctx, req)
   753  	if err != nil {
   754  		if gstatus.Code(err) == codes.PermissionDenied {
   755  			return &provider.UnsetArbitraryMetadataResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil
   756  		}
   757  		return nil, errors.Wrap(err, "gateway: error calling UnsetArbitraryMetadata")
   758  	}
   759  
   760  	return res, nil
   761  }
   762  
   763  // SetLock puts a lock on the given reference
   764  func (s *svc) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provider.SetLockResponse, error) {
   765  	var c provider.ProviderAPIClient
   766  	var err error
   767  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   768  	if err != nil {
   769  		return &provider.SetLockResponse{
   770  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   771  		}, nil
   772  	}
   773  
   774  	res, err := c.SetLock(ctx, req)
   775  	if err != nil {
   776  		if gstatus.Code(err) == codes.PermissionDenied {
   777  			return &provider.SetLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil
   778  		}
   779  		return nil, errors.Wrap(err, "gateway: error calling SetLock")
   780  	}
   781  
   782  	return res, nil
   783  }
   784  
   785  // GetLock returns an existing lock on the given reference
   786  func (s *svc) GetLock(ctx context.Context, req *provider.GetLockRequest) (*provider.GetLockResponse, error) {
   787  	c, _, err := s.find(ctx, req.Ref)
   788  	if err != nil {
   789  		return &provider.GetLockResponse{
   790  			Status: status.NewStatusFromErrType(ctx, "GetLock ref="+req.Ref.String(), err),
   791  		}, nil
   792  	}
   793  
   794  	res, err := c.GetLock(ctx, req)
   795  	if err != nil {
   796  		if gstatus.Code(err) == codes.PermissionDenied {
   797  			return &provider.GetLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil
   798  		}
   799  		return nil, errors.Wrap(err, "gateway: error calling GetLock")
   800  	}
   801  
   802  	return res, nil
   803  }
   804  
   805  // RefreshLock refreshes an existing lock on the given reference
   806  func (s *svc) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) (*provider.RefreshLockResponse, error) {
   807  	c, _, err := s.find(ctx, req.Ref)
   808  	if err != nil {
   809  		return &provider.RefreshLockResponse{
   810  			Status: status.NewStatusFromErrType(ctx, "RefreshLock ref="+req.Ref.String(), err),
   811  		}, nil
   812  	}
   813  
   814  	res, err := c.RefreshLock(ctx, req)
   815  	if err != nil {
   816  		if gstatus.Code(err) == codes.PermissionDenied {
   817  			return &provider.RefreshLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil
   818  		}
   819  		return nil, errors.Wrap(err, "gateway: error calling RefreshLock")
   820  	}
   821  
   822  	return res, nil
   823  }
   824  
   825  // Unlock removes an existing lock from the given reference
   826  func (s *svc) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provider.UnlockResponse, error) {
   827  	c, _, err := s.find(ctx, req.Ref)
   828  	if err != nil {
   829  		return &provider.UnlockResponse{
   830  			Status: status.NewStatusFromErrType(ctx, "Unlock ref="+req.Ref.String(), err),
   831  		}, nil
   832  	}
   833  
   834  	res, err := c.Unlock(ctx, req)
   835  	if err != nil {
   836  		if gstatus.Code(err) == codes.PermissionDenied {
   837  			return &provider.UnlockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil
   838  		}
   839  		return nil, errors.Wrap(err, "gateway: error calling Unlock")
   840  	}
   841  
   842  	return res, nil
   843  }
   844  
   845  // Stat returns the Resoure info for a given resource by forwarding the request to the responsible provider.
   846  func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) {
   847  	c, _, ref, err := s.findAndUnwrap(ctx, req.Ref)
   848  	if err != nil {
   849  		return &provider.StatResponse{
   850  			Status: status.NewNotFound(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref)),
   851  		}, nil
   852  	}
   853  
   854  	return c.Stat(ctx, &provider.StatRequest{
   855  		Opaque:                req.Opaque,
   856  		Ref:                   ref,
   857  		ArbitraryMetadataKeys: req.ArbitraryMetadataKeys,
   858  		FieldMask:             req.FieldMask,
   859  	})
   860  }
   861  
   862  func (s *svc) ListContainerStream(_ *provider.ListContainerStreamRequest, _ gateway.GatewayAPI_ListContainerStreamServer) error {
   863  	return errtypes.NotSupported("Unimplemented")
   864  }
   865  
   866  // ListContainer lists the Resoure infos for a given resource by forwarding the request to the responsible provider.
   867  func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) {
   868  	c, _, ref, err := s.findAndUnwrap(ctx, req.Ref)
   869  	if err != nil {
   870  		// we have no provider -> not found
   871  		return &provider.ListContainerResponse{
   872  			Status: status.NewNotFound(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref)),
   873  		}, nil
   874  	}
   875  
   876  	return c.ListContainer(ctx, &provider.ListContainerRequest{
   877  		Opaque:                req.Opaque,
   878  		Ref:                   ref,
   879  		ArbitraryMetadataKeys: req.ArbitraryMetadataKeys,
   880  		FieldMask:             req.FieldMask,
   881  	})
   882  }
   883  
   884  func (s *svc) CreateSymlink(ctx context.Context, req *provider.CreateSymlinkRequest) (*provider.CreateSymlinkResponse, error) {
   885  	return &provider.CreateSymlinkResponse{
   886  		Status: status.NewUnimplemented(ctx, errtypes.NotSupported("CreateSymlink not implemented"), "CreateSymlink not implemented"),
   887  	}, nil
   888  }
   889  
   890  func (s *svc) ListFileVersions(ctx context.Context, req *provider.ListFileVersionsRequest) (*provider.ListFileVersionsResponse, error) {
   891  	var c provider.ProviderAPIClient
   892  	var err error
   893  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   894  	if err != nil {
   895  		return &provider.ListFileVersionsResponse{
   896  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   897  		}, nil
   898  	}
   899  
   900  	return c.ListFileVersions(ctx, req)
   901  }
   902  
   903  func (s *svc) RestoreFileVersion(ctx context.Context, req *provider.RestoreFileVersionRequest) (*provider.RestoreFileVersionResponse, error) {
   904  	var c provider.ProviderAPIClient
   905  	var err error
   906  	c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref)
   907  	if err != nil {
   908  		return &provider.RestoreFileVersionResponse{
   909  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   910  		}, nil
   911  	}
   912  
   913  	res, err := c.RestoreFileVersion(ctx, req)
   914  	if err != nil {
   915  		return &provider.RestoreFileVersionResponse{
   916  			Status: status.NewStatusFromErrType(ctx, "gateway could not call RestoreFileVersion", err),
   917  		}, nil
   918  	}
   919  
   920  	return res, nil
   921  }
   922  
   923  func (s *svc) ListRecycleStream(_ *provider.ListRecycleStreamRequest, _ gateway.GatewayAPI_ListRecycleStreamServer) error {
   924  	return errtypes.NotSupported("ListRecycleStream unimplemented")
   925  }
   926  
   927  // TODO use the ListRecycleRequest.Ref to only list the trash of a specific storage
   928  func (s *svc) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) (*provider.ListRecycleResponse, error) {
   929  	c, _, ref, err := s.findAndUnwrap(ctx, req.Ref)
   930  	if err != nil {
   931  		return &provider.ListRecycleResponse{
   932  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   933  		}, nil
   934  	}
   935  	return c.ListRecycle(ctx, &provider.ListRecycleRequest{
   936  		Opaque: req.Opaque,
   937  		FromTs: req.FromTs,
   938  		ToTs:   req.ToTs,
   939  		Ref:    ref,
   940  		Key:    req.Key,
   941  	})
   942  }
   943  
   944  func (s *svc) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecycleItemRequest) (*provider.RestoreRecycleItemResponse, error) {
   945  	c, si, ref, err := s.findAndUnwrap(ctx, req.Ref)
   946  	if err != nil {
   947  		return &provider.RestoreRecycleItemResponse{
   948  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   949  		}, nil
   950  	}
   951  
   952  	_, di, rref, err := s.findAndUnwrap(ctx, req.RestoreRef)
   953  	if err != nil {
   954  		return &provider.RestoreRecycleItemResponse{
   955  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   956  		}, nil
   957  	}
   958  
   959  	if si.Address != di.Address {
   960  		return &provider.RestoreRecycleItemResponse{
   961  			// TODO in Move() we return an unimplemented / supported ... align?
   962  			Status: status.NewPermissionDenied(ctx, err, "gateway: cross-storage restores are forbidden"),
   963  		}, nil
   964  	}
   965  
   966  	req.Ref = ref
   967  	req.RestoreRef = rref
   968  	res, err := c.RestoreRecycleItem(ctx, req)
   969  	if err != nil {
   970  		return &provider.RestoreRecycleItemResponse{
   971  			Status: status.NewStatusFromErrType(ctx, "gateway could not call RestoreRecycleItem", err),
   972  		}, nil
   973  	}
   974  
   975  	return res, nil
   976  }
   977  
   978  func (s *svc) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) {
   979  	c, _, relativeReference, err := s.findAndUnwrap(ctx, req.Ref)
   980  	if err != nil {
   981  		return &provider.PurgeRecycleResponse{
   982  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
   983  		}, nil
   984  	}
   985  
   986  	res, err := c.PurgeRecycle(ctx, &provider.PurgeRecycleRequest{
   987  		Opaque: req.GetOpaque(),
   988  		Ref:    relativeReference,
   989  		Key:    req.Key,
   990  	})
   991  	if err != nil {
   992  		return &provider.PurgeRecycleResponse{
   993  			Status: status.NewStatusFromErrType(ctx, "gateway could not call PurgeRecycle", err),
   994  		}, nil
   995  	}
   996  
   997  	return res, nil
   998  }
   999  
  1000  func (s *svc) GetQuota(ctx context.Context, req *gateway.GetQuotaRequest) (*provider.GetQuotaResponse, error) {
  1001  	c, _, relativeReference, err := s.findAndUnwrap(ctx, req.Ref)
  1002  	if err != nil {
  1003  		return &provider.GetQuotaResponse{
  1004  			Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err),
  1005  		}, nil
  1006  	}
  1007  
  1008  	res, err := c.GetQuota(ctx, &provider.GetQuotaRequest{
  1009  		Opaque: req.GetOpaque(),
  1010  		Ref:    relativeReference,
  1011  	})
  1012  	if err != nil {
  1013  		return &provider.GetQuotaResponse{
  1014  			Status: status.NewStatusFromErrType(ctx, "gateway could not call GetQuota", err),
  1015  		}, nil
  1016  	}
  1017  	return res, nil
  1018  }
  1019  
  1020  // find looks up the provider that is responsible for the given request
  1021  // It will return a client that the caller can use to make the call, as well as the ProviderInfo. It:
  1022  // - contains the provider path, which is the mount point of the provider
  1023  // - may contain a list of storage spaces with their id and space path
  1024  func (s *svc) find(ctx context.Context, ref *provider.Reference) (provider.ProviderAPIClient, *registry.ProviderInfo, error) {
  1025  	p, err := s.findSingleSpace(ctx, ref)
  1026  	if err != nil {
  1027  		return nil, nil, err
  1028  	}
  1029  
  1030  	client, err := s.getStorageProviderClient(ctx, p[0])
  1031  	return client, p[0], err
  1032  }
  1033  
  1034  // findSpacesProvider looks up the spaces provider that is responsible for the given request
  1035  // It will return a client that the caller can use to make the call, as well as the ProviderInfo. It:
  1036  // - contains the provider path, which is the mount point of the provider
  1037  // - may contain a list of storage spaces with their id and space path
  1038  func (s *svc) findSpacesProvider(ctx context.Context, ref *provider.Reference) (provider.SpacesAPIClient, *registry.ProviderInfo, error) {
  1039  	p, err := s.findSingleSpace(ctx, ref)
  1040  	if err != nil {
  1041  		return nil, nil, err
  1042  	}
  1043  
  1044  	client, err := s.getSpacesProviderClient(ctx, p[0])
  1045  	return client, p[0], err
  1046  }
  1047  
  1048  func (s *svc) findAndUnwrap(ctx context.Context, ref *provider.Reference) (provider.ProviderAPIClient, *registry.ProviderInfo, *provider.Reference, error) {
  1049  	c, p, err := s.find(ctx, ref)
  1050  	if err != nil {
  1051  		return nil, nil, nil, err
  1052  	}
  1053  
  1054  	var (
  1055  		root      *provider.ResourceId
  1056  		mountPath string
  1057  	)
  1058  	for _, space := range decodeSpaces(p) {
  1059  		mountPath = decodePath(space)
  1060  		root = space.Root
  1061  		break // TODO can there be more than one space for a path?
  1062  	}
  1063  
  1064  	relativeReference := unwrap(ref, mountPath, root)
  1065  
  1066  	return c, p, relativeReference, nil
  1067  }
  1068  
  1069  func (s *svc) getSpacesProviderClient(_ context.Context, p *registry.ProviderInfo) (provider.SpacesAPIClient, error) {
  1070  	c, err := pool.GetSpacesProviderServiceClient(p.Address)
  1071  	if err != nil {
  1072  		return nil, err
  1073  	}
  1074  
  1075  	return &cachedSpacesAPIClient{
  1076  		c:                        c,
  1077  		createPersonalSpaceCache: s.createPersonalSpaceCache,
  1078  	}, nil
  1079  }
  1080  
  1081  func (s *svc) getStorageProviderClient(_ context.Context, p *registry.ProviderInfo) (provider.ProviderAPIClient, error) {
  1082  	c, err := pool.GetStorageProviderServiceClient(p.Address)
  1083  	if err != nil {
  1084  		return nil, err
  1085  	}
  1086  
  1087  	return &cachedAPIClient{
  1088  		c:                        c,
  1089  		createPersonalSpaceCache: s.createPersonalSpaceCache,
  1090  	}, nil
  1091  }
  1092  
  1093  func (s *svc) getStorageRegistryClient(_ context.Context, address string) (registry.RegistryAPIClient, error) {
  1094  	c, err := pool.GetStorageRegistryClient(address)
  1095  	if err != nil {
  1096  		return nil, err
  1097  	}
  1098  	return &cachedRegistryClient{
  1099  		c:     c,
  1100  		cache: s.providerCache,
  1101  	}, nil
  1102  }
  1103  
  1104  func (s *svc) findSingleSpace(ctx context.Context, ref *provider.Reference) ([]*registry.ProviderInfo, error) {
  1105  	switch {
  1106  	case ref == nil:
  1107  		return nil, errtypes.BadRequest("missing reference")
  1108  	case ref.ResourceId != nil:
  1109  		if ref.ResourceId.OpaqueId == "" {
  1110  			ref.ResourceId.OpaqueId = ref.ResourceId.SpaceId
  1111  		}
  1112  	case ref.Path != "": //  TODO implement a mount path cache in the registry?
  1113  		// nothing to do here either
  1114  	default:
  1115  		return nil, errtypes.BadRequest("invalid reference, at least path or id must be set")
  1116  	}
  1117  
  1118  	filters := map[string]string{
  1119  		"mask":   "root", // FIXME replace with fieldmask, here we only want to get the root resourceid
  1120  		"path":   ref.Path,
  1121  		"unique": "true",
  1122  	}
  1123  	if ref.ResourceId != nil {
  1124  		filters["storage_id"] = ref.ResourceId.StorageId
  1125  		filters["space_id"] = ref.ResourceId.SpaceId
  1126  		filters["opaque_id"] = ref.ResourceId.OpaqueId
  1127  	}
  1128  
  1129  	listReq := &registry.ListStorageProvidersRequest{
  1130  		Opaque: &typesv1beta1.Opaque{},
  1131  	}
  1132  	sdk.EncodeOpaqueMap(listReq.Opaque, filters)
  1133  
  1134  	return s.findProvider(ctx, listReq)
  1135  }
  1136  
  1137  func (s *svc) findProvider(ctx context.Context, listReq *registry.ListStorageProvidersRequest) ([]*registry.ProviderInfo, error) {
  1138  	// lookup
  1139  	c, err := pool.GetStorageRegistryClient(s.c.StorageRegistryEndpoint)
  1140  	if err != nil {
  1141  		return nil, errors.Wrap(err, "gateway: error getting storage registry client")
  1142  	}
  1143  	res, err := c.ListStorageProviders(ctx, listReq)
  1144  	if err != nil {
  1145  		return nil, errors.Wrap(err, "gateway: error calling ListStorageProviders")
  1146  	}
  1147  
  1148  	if res.Status.Code != rpc.Code_CODE_OK {
  1149  		switch res.Status.Code {
  1150  		case rpc.Code_CODE_NOT_FOUND:
  1151  			// TODO use tombstone cache item?
  1152  			return nil, errtypes.NotFound("gateway: storage provider not found for reference:" + listReq.String())
  1153  		case rpc.Code_CODE_PERMISSION_DENIED:
  1154  			return nil, errtypes.PermissionDenied("gateway: " + res.Status.Message + " for " + listReq.String() + " with code " + res.Status.Code.String())
  1155  		case rpc.Code_CODE_INVALID_ARGUMENT, rpc.Code_CODE_FAILED_PRECONDITION, rpc.Code_CODE_OUT_OF_RANGE:
  1156  			return nil, errtypes.BadRequest("gateway: " + res.Status.Message + " for " + listReq.String() + " with code " + res.Status.Code.String())
  1157  		case rpc.Code_CODE_UNIMPLEMENTED:
  1158  			return nil, errtypes.NotSupported("gateway: " + res.Status.Message + " for " + listReq.String() + " with code " + res.Status.Code.String())
  1159  		default:
  1160  			return nil, status.NewErrorFromCode(res.Status.Code, "gateway")
  1161  		}
  1162  	}
  1163  
  1164  	if res.Providers == nil {
  1165  		return nil, errtypes.NotFound("gateway: provider is nil")
  1166  	}
  1167  
  1168  	return res.Providers, nil
  1169  }
  1170  
  1171  // unwrap takes a reference and builds a reference for the provider. can be absolute or relative to a root node
  1172  func unwrap(ref *provider.Reference, mountPoint string, root *provider.ResourceId) *provider.Reference {
  1173  	if utils.IsAbsolutePathReference(ref) {
  1174  		providerRef := &provider.Reference{
  1175  			Path: strings.TrimPrefix(ref.Path, mountPoint),
  1176  		}
  1177  		// if we have a root use it and make the path relative
  1178  		if root != nil {
  1179  			providerRef.ResourceId = root
  1180  			providerRef.Path = utils.MakeRelativePath(providerRef.Path)
  1181  		}
  1182  		return providerRef
  1183  	}
  1184  
  1185  	return &provider.Reference{
  1186  		ResourceId: &provider.ResourceId{
  1187  			StorageId: ref.GetResourceId().GetStorageId(),
  1188  			SpaceId:   ref.GetResourceId().GetSpaceId(),
  1189  			OpaqueId:  ref.GetResourceId().GetOpaqueId(),
  1190  		},
  1191  		Path: ref.GetPath(),
  1192  	}
  1193  }
  1194  
  1195  func decodeSpaces(r *registry.ProviderInfo) []*provider.StorageSpace {
  1196  	spaces := []*provider.StorageSpace{}
  1197  	if r.Opaque != nil {
  1198  		if entry, ok := r.Opaque.Map["spaces"]; ok {
  1199  			switch entry.Decoder {
  1200  			case "json":
  1201  				_ = json.Unmarshal(entry.Value, &spaces)
  1202  			case "toml":
  1203  				_ = toml.Unmarshal(entry.Value, &spaces)
  1204  			case "xml":
  1205  				_ = xml.Unmarshal(entry.Value, &spaces)
  1206  			}
  1207  		}
  1208  	}
  1209  	if len(spaces) == 0 {
  1210  		// we need to convert the provider into a space, needed for the static registry
  1211  		spaces = append(spaces, &provider.StorageSpace{
  1212  			Opaque: &typesv1beta1.Opaque{Map: map[string]*typesv1beta1.OpaqueEntry{
  1213  				"path": {
  1214  					Decoder: "plain",
  1215  					Value:   []byte(r.ProviderPath),
  1216  				},
  1217  			}},
  1218  		})
  1219  	}
  1220  	return spaces
  1221  }
  1222  
  1223  func decodePath(s *provider.StorageSpace) (path string) {
  1224  	if s.Opaque != nil {
  1225  		if entry, ok := s.Opaque.Map["path"]; ok {
  1226  			switch entry.Decoder {
  1227  			case "plain":
  1228  				path = string(entry.Value)
  1229  			case "json":
  1230  				_ = json.Unmarshal(entry.Value, &path)
  1231  			case "toml":
  1232  				_ = toml.Unmarshal(entry.Value, &path)
  1233  			case "xml":
  1234  				_ = xml.Unmarshal(entry.Value, &path)
  1235  			}
  1236  		}
  1237  	}
  1238  	return
  1239  }