github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/usershareprovider/usershareprovider.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 usershareprovider
    20  
    21  import (
    22  	"context"
    23  	"path/filepath"
    24  	"regexp"
    25  	"slices"
    26  	"strconv"
    27  	"strings"
    28  
    29  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    30  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    31  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    32  	collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
    33  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    34  	"github.com/mitchellh/mapstructure"
    35  	"github.com/pkg/errors"
    36  	"github.com/rs/zerolog"
    37  	"google.golang.org/grpc"
    38  
    39  	"github.com/cs3org/reva/v2/pkg/appctx"
    40  	"github.com/cs3org/reva/v2/pkg/conversions"
    41  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    42  	"github.com/cs3org/reva/v2/pkg/errtypes"
    43  	"github.com/cs3org/reva/v2/pkg/permission"
    44  	"github.com/cs3org/reva/v2/pkg/rgrpc"
    45  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    46  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    47  	"github.com/cs3org/reva/v2/pkg/share"
    48  	"github.com/cs3org/reva/v2/pkg/share/manager/registry"
    49  	"github.com/cs3org/reva/v2/pkg/sharedconf"
    50  	"github.com/cs3org/reva/v2/pkg/utils"
    51  )
    52  
    53  const (
    54  	_fieldMaskPathMountPoint  = "mount_point"
    55  	_fieldMaskPathPermissions = "permissions"
    56  	_fieldMaskPathState       = "state"
    57  )
    58  
    59  func init() {
    60  	rgrpc.Register("usershareprovider", NewDefault)
    61  }
    62  
    63  type config struct {
    64  	Driver                string                            `mapstructure:"driver"`
    65  	Drivers               map[string]map[string]interface{} `mapstructure:"drivers"`
    66  	GatewayAddr           string                            `mapstructure:"gateway_addr"`
    67  	AllowedPathsForShares []string                          `mapstructure:"allowed_paths_for_shares"`
    68  }
    69  
    70  func (c *config) init() {
    71  	if c.Driver == "" {
    72  		c.Driver = "json"
    73  	}
    74  }
    75  
    76  type service struct {
    77  	sm                    share.Manager
    78  	gatewaySelector       pool.Selectable[gateway.GatewayAPIClient]
    79  	allowedPathsForShares []*regexp.Regexp
    80  }
    81  
    82  func getShareManager(c *config) (share.Manager, error) {
    83  	if f, ok := registry.NewFuncs[c.Driver]; ok {
    84  		return f(c.Drivers[c.Driver])
    85  	}
    86  	return nil, errtypes.NotFound("driver not found: " + c.Driver)
    87  }
    88  
    89  // TODO(labkode): add ctx to Close.
    90  func (s *service) Close() error {
    91  	return nil
    92  }
    93  
    94  func (s *service) UnprotectedEndpoints() []string {
    95  	return []string{}
    96  }
    97  
    98  func (s *service) Register(ss *grpc.Server) {
    99  	collaboration.RegisterCollaborationAPIServer(ss, s)
   100  }
   101  
   102  func parseConfig(m map[string]interface{}) (*config, error) {
   103  	c := &config{}
   104  	if err := mapstructure.Decode(m, c); err != nil {
   105  		err = errors.Wrap(err, "error decoding conf")
   106  		return nil, err
   107  	}
   108  	return c, nil
   109  }
   110  
   111  // New creates a new user share provider svc initialized from defaults
   112  func NewDefault(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
   113  
   114  	c, err := parseConfig(m)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	c.init()
   120  
   121  	sm, err := getShareManager(c)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	allowedPathsForShares := make([]*regexp.Regexp, 0, len(c.AllowedPathsForShares))
   127  	for _, s := range c.AllowedPathsForShares {
   128  		regex, err := regexp.Compile(s)
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		allowedPathsForShares = append(allowedPathsForShares, regex)
   133  	}
   134  
   135  	gatewaySelector, err := pool.GatewaySelector(sharedconf.GetGatewaySVC(c.GatewayAddr))
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	return New(gatewaySelector, sm, allowedPathsForShares), nil
   141  }
   142  
   143  // New creates a new user share provider svc
   144  func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sm share.Manager, allowedPathsForShares []*regexp.Regexp) rgrpc.Service {
   145  	service := &service{
   146  		sm:                    sm,
   147  		gatewaySelector:       gatewaySelector,
   148  		allowedPathsForShares: allowedPathsForShares,
   149  	}
   150  
   151  	return service
   152  }
   153  
   154  func (s *service) isPathAllowed(path string) bool {
   155  	if len(s.allowedPathsForShares) == 0 {
   156  		return true
   157  	}
   158  	for _, reg := range s.allowedPathsForShares {
   159  		if reg.MatchString(path) {
   160  			return true
   161  		}
   162  	}
   163  	return false
   164  }
   165  
   166  func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShareRequest) (*collaboration.CreateShareResponse, error) {
   167  	log := appctx.GetLogger(ctx)
   168  	user := ctxpkg.ContextMustGetUser(ctx)
   169  
   170  	// Grants must not allow grant permissions
   171  	if HasGrantPermissions(req.GetGrant().GetPermissions().GetPermissions()) {
   172  		return &collaboration.CreateShareResponse{
   173  			Status: status.NewInvalidArg(ctx, "resharing not supported"),
   174  		}, nil
   175  	}
   176  
   177  	gatewayClient, err := s.gatewaySelector.Next()
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	// check if the user has the permission to create shares at all
   183  	ok, err := utils.CheckPermission(ctx, permission.WriteShare, gatewayClient)
   184  	if err != nil {
   185  		return &collaboration.CreateShareResponse{
   186  			Status: status.NewInternal(ctx, "failed check user permission to write public link"),
   187  		}, err
   188  	}
   189  	if !ok {
   190  		return &collaboration.CreateShareResponse{
   191  			Status: status.NewPermissionDenied(ctx, nil, "no permission to create public links"),
   192  		}, nil
   193  	}
   194  
   195  	if req.GetGrant().GetGrantee().GetType() == provider.GranteeType_GRANTEE_TYPE_USER && req.GetGrant().GetGrantee().GetUserId().GetIdp() == "" {
   196  		// use logged in user Idp as default.
   197  		req.GetGrant().GetGrantee().Id = &provider.Grantee_UserId{
   198  			UserId: &userpb.UserId{
   199  				OpaqueId: req.GetGrant().GetGrantee().GetUserId().GetOpaqueId(),
   200  				Idp:      user.GetId().GetIdp(),
   201  				Type:     userpb.UserType_USER_TYPE_PRIMARY},
   202  		}
   203  	}
   204  
   205  	sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: req.GetResourceInfo().GetId()}})
   206  	if err != nil {
   207  		log.Err(err).Interface("resource_id", req.GetResourceInfo().GetId()).Msg("failed to stat resource to share")
   208  		return &collaboration.CreateShareResponse{
   209  			Status: status.NewInternal(ctx, "failed to stat shared resource"),
   210  		}, err
   211  	}
   212  	// the user needs to have the AddGrant permissions on the Resource to be able to create a share
   213  	if !sRes.GetInfo().GetPermissionSet().AddGrant {
   214  		return &collaboration.CreateShareResponse{
   215  			Status: status.NewPermissionDenied(ctx, nil, "no permission to add grants on shared resource"),
   216  		}, err
   217  	}
   218  	// check if the share creator has sufficient permissions to do so.
   219  	if shareCreationAllowed := conversions.SufficientCS3Permissions(
   220  		sRes.GetInfo().GetPermissionSet(),
   221  		req.GetGrant().GetPermissions().GetPermissions(),
   222  	); !shareCreationAllowed {
   223  		return &collaboration.CreateShareResponse{
   224  			Status: status.NewPermissionDenied(ctx, nil, "insufficient permissions to create that kind of share"),
   225  		}, nil
   226  	}
   227  	// check if the requested permission are plausible for the Resource
   228  	if sRes.GetInfo().GetType() == provider.ResourceType_RESOURCE_TYPE_FILE {
   229  		if newPermissions := req.GetGrant().GetPermissions().GetPermissions(); newPermissions.GetCreateContainer() || newPermissions.GetMove() || newPermissions.GetDelete() {
   230  			return &collaboration.CreateShareResponse{
   231  				Status: status.NewInvalid(ctx, "cannot set the requested permissions on that type of resource"),
   232  			}, nil
   233  		}
   234  	}
   235  
   236  	if !s.isPathAllowed(req.GetResourceInfo().GetPath()) {
   237  		return &collaboration.CreateShareResponse{
   238  			Status: status.NewFailedPrecondition(ctx, nil, "share creation is not allowed for the specified path"),
   239  		}, nil
   240  	}
   241  
   242  	createdShare, err := s.sm.Share(ctx, req.GetResourceInfo(), req.GetGrant())
   243  	if err != nil {
   244  		return &collaboration.CreateShareResponse{
   245  			Status: status.NewStatusFromErrType(ctx, "error creating share", err),
   246  		}, nil
   247  	}
   248  
   249  	return &collaboration.CreateShareResponse{
   250  		Status: status.NewOK(ctx),
   251  		Share:  createdShare,
   252  		Opaque: utils.AppendPlainToOpaque(nil, "resourcename", sRes.GetInfo().GetName()),
   253  	}, nil
   254  }
   255  
   256  func HasGrantPermissions(p *provider.ResourcePermissions) bool {
   257  	return p.GetAddGrant() || p.GetUpdateGrant() || p.GetRemoveGrant() || p.GetDenyGrant()
   258  }
   259  
   260  func (s *service) RemoveShare(ctx context.Context, req *collaboration.RemoveShareRequest) (*collaboration.RemoveShareResponse, error) {
   261  	log := appctx.GetLogger(ctx)
   262  	user := ctxpkg.ContextMustGetUser(ctx)
   263  	share, err := s.sm.GetShare(ctx, req.Ref)
   264  	if err != nil {
   265  		return &collaboration.RemoveShareResponse{
   266  			Status: status.NewInternal(ctx, "error getting share"),
   267  		}, nil
   268  	}
   269  
   270  	gatewayClient, err := s.gatewaySelector.Next()
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  	sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: share.GetResourceId()}})
   275  	if err != nil {
   276  		log.Err(err).Interface("resource_id", share.GetResourceId()).Msg("failed to stat shared resource")
   277  		return &collaboration.RemoveShareResponse{
   278  			Status: status.NewInternal(ctx, "failed to stat shared resource"),
   279  		}, err
   280  	}
   281  	// the requesting user needs to be either the Owner/Creator of the share or have the RemoveGrant permissions on the Resource
   282  	switch {
   283  	case utils.UserEqual(user.GetId(), share.GetCreator()) || utils.UserEqual(user.GetId(), share.GetOwner()):
   284  		fallthrough
   285  	case sRes.GetInfo().GetPermissionSet().RemoveGrant:
   286  		break
   287  	default:
   288  		return &collaboration.RemoveShareResponse{
   289  			Status: status.NewPermissionDenied(ctx, nil, "no permission to remove grants on shared resource"),
   290  		}, err
   291  	}
   292  
   293  	err = s.sm.Unshare(ctx, req.Ref)
   294  	if err != nil {
   295  		return &collaboration.RemoveShareResponse{
   296  			Status: status.NewInternal(ctx, "error removing share"),
   297  		}, nil
   298  	}
   299  
   300  	o := utils.AppendJSONToOpaque(nil, "resourceid", share.GetResourceId())
   301  	o = utils.AppendPlainToOpaque(o, "resourcename", sRes.GetInfo().GetName())
   302  	if user := share.GetGrantee().GetUserId(); user != nil {
   303  		o = utils.AppendJSONToOpaque(o, "granteeuserid", user)
   304  	} else {
   305  		o = utils.AppendJSONToOpaque(o, "granteegroupid", share.GetGrantee().GetGroupId())
   306  	}
   307  
   308  	return &collaboration.RemoveShareResponse{
   309  		Opaque: o,
   310  		Status: status.NewOK(ctx),
   311  	}, nil
   312  }
   313  
   314  func (s *service) GetShare(ctx context.Context, req *collaboration.GetShareRequest) (*collaboration.GetShareResponse, error) {
   315  	share, err := s.sm.GetShare(ctx, req.Ref)
   316  	if err != nil {
   317  		var st *rpc.Status
   318  		switch err.(type) {
   319  		case errtypes.IsNotFound:
   320  			st = status.NewNotFound(ctx, err.Error())
   321  		default:
   322  			st = status.NewInternal(ctx, err.Error())
   323  		}
   324  		return &collaboration.GetShareResponse{
   325  			Status: st,
   326  		}, nil
   327  	}
   328  
   329  	return &collaboration.GetShareResponse{
   330  		Status: status.NewOK(ctx),
   331  		Share:  share,
   332  	}, nil
   333  }
   334  
   335  func (s *service) ListShares(ctx context.Context, req *collaboration.ListSharesRequest) (*collaboration.ListSharesResponse, error) {
   336  	shares, err := s.sm.ListShares(ctx, req.Filters) // TODO(labkode): add filter to share manager
   337  	if err != nil {
   338  		return &collaboration.ListSharesResponse{
   339  			Status: status.NewInternal(ctx, "error listing shares"),
   340  		}, nil
   341  	}
   342  
   343  	res := &collaboration.ListSharesResponse{
   344  		Status: status.NewOK(ctx),
   345  		Shares: shares,
   346  	}
   347  	return res, nil
   348  }
   349  
   350  func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShareRequest) (*collaboration.UpdateShareResponse, error) {
   351  	log := appctx.GetLogger(ctx)
   352  	user := ctxpkg.ContextMustGetUser(ctx)
   353  
   354  	// Grants must not allow grant permissions
   355  	if HasGrantPermissions(req.GetShare().GetPermissions().GetPermissions()) {
   356  		return &collaboration.UpdateShareResponse{
   357  			Status: status.NewInvalidArg(ctx, "resharing not supported"),
   358  		}, nil
   359  	}
   360  
   361  	gatewayClient, err := s.gatewaySelector.Next()
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	// check if the user has the permission to create shares at all
   367  	ok, err := utils.CheckPermission(ctx, permission.WriteShare, gatewayClient)
   368  	if err != nil {
   369  		return &collaboration.UpdateShareResponse{
   370  			Status: status.NewInternal(ctx, "failed check user permission to write share"),
   371  		}, err
   372  	}
   373  	if !ok {
   374  		return &collaboration.UpdateShareResponse{
   375  			Status: status.NewPermissionDenied(ctx, nil, "no permission to create user share"),
   376  		}, nil
   377  	}
   378  
   379  	// Read share from backend. We need the shared resource's id for STATing it, it might not be in
   380  	// the incoming request
   381  	currentShare, err := s.sm.GetShare(ctx,
   382  		&collaboration.ShareReference{
   383  			Spec: &collaboration.ShareReference_Id{
   384  				Id: req.GetShare().GetId(),
   385  			},
   386  		},
   387  	)
   388  	if err != nil {
   389  		var st *rpc.Status
   390  		switch err.(type) {
   391  		case errtypes.IsNotFound:
   392  			st = status.NewNotFound(ctx, err.Error())
   393  		default:
   394  			st = status.NewInternal(ctx, err.Error())
   395  		}
   396  		return &collaboration.UpdateShareResponse{
   397  			Status: st,
   398  		}, nil
   399  	}
   400  
   401  	sRes, err := gatewayClient.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: currentShare.GetResourceId()}})
   402  	if err != nil {
   403  		log.Err(err).Interface("resource_id", req.GetShare().GetResourceId()).Msg("failed to stat resource to share")
   404  		return &collaboration.UpdateShareResponse{
   405  			Status: status.NewInternal(ctx, "failed to stat shared resource"),
   406  		}, err
   407  	}
   408  	// the requesting user needs to be either the Owner/Creator of the share or have the UpdateGrant permissions on the Resource
   409  	switch {
   410  	case utils.UserEqual(user.GetId(), currentShare.GetCreator()) || utils.UserEqual(user.GetId(), currentShare.GetOwner()):
   411  		fallthrough
   412  	case sRes.GetInfo().GetPermissionSet().UpdateGrant:
   413  		break
   414  	default:
   415  		return &collaboration.UpdateShareResponse{
   416  			Status: status.NewPermissionDenied(ctx, nil, "no permission to remove grants on shared resource"),
   417  		}, err
   418  	}
   419  
   420  	// If this is a permissions update, check if user's permissions on the resource are sufficient to set the desired permissions
   421  	var newPermissions *provider.ResourcePermissions
   422  	if slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathPermissions) {
   423  		newPermissions = req.GetShare().GetPermissions().GetPermissions()
   424  	} else {
   425  		newPermissions = req.GetField().GetPermissions().GetPermissions()
   426  	}
   427  	if newPermissions != nil && !conversions.SufficientCS3Permissions(sRes.GetInfo().GetPermissionSet(), newPermissions) {
   428  		return &collaboration.UpdateShareResponse{
   429  			Status: status.NewPermissionDenied(ctx, nil, "insufficient permissions to create that kind of share"),
   430  		}, nil
   431  	}
   432  
   433  	// check if the requested permission are plausible for the Resource
   434  	// do we need more here?
   435  	if sRes.GetInfo().GetType() == provider.ResourceType_RESOURCE_TYPE_FILE {
   436  		if newPermissions.GetCreateContainer() || newPermissions.GetMove() || newPermissions.GetDelete() {
   437  			return &collaboration.UpdateShareResponse{
   438  				Status: status.NewInvalid(ctx, "cannot set the requested permissions on that type of resource"),
   439  			}, nil
   440  		}
   441  	}
   442  
   443  	share, err := s.sm.UpdateShare(ctx, req.Ref, req.Field.GetPermissions(), req.Share, req.UpdateMask) // TODO(labkode): check what to update
   444  	if err != nil {
   445  		return &collaboration.UpdateShareResponse{
   446  			Status: status.NewInternal(ctx, "error updating share"),
   447  		}, nil
   448  	}
   449  
   450  	res := &collaboration.UpdateShareResponse{
   451  		Status: status.NewOK(ctx),
   452  		Share:  share,
   453  		Opaque: utils.AppendPlainToOpaque(nil, "resourcename", sRes.GetInfo().GetName()),
   454  	}
   455  	return res, nil
   456  }
   457  
   458  func (s *service) ListReceivedShares(ctx context.Context, req *collaboration.ListReceivedSharesRequest) (*collaboration.ListReceivedSharesResponse, error) {
   459  	// For the UI add a filter to not display the denial shares
   460  	foundExclude := false
   461  	for _, f := range req.Filters {
   462  		if f.Type == collaboration.Filter_TYPE_EXCLUDE_DENIALS {
   463  			foundExclude = true
   464  			break
   465  		}
   466  	}
   467  	if !foundExclude {
   468  		req.Filters = append(req.Filters, &collaboration.Filter{Type: collaboration.Filter_TYPE_EXCLUDE_DENIALS})
   469  	}
   470  
   471  	var uid userpb.UserId
   472  	_ = utils.ReadJSONFromOpaque(req.Opaque, "userid", &uid)
   473  	shares, err := s.sm.ListReceivedShares(ctx, req.Filters, &uid) // TODO(labkode): check what to update
   474  	if err != nil {
   475  		return &collaboration.ListReceivedSharesResponse{
   476  			Status: status.NewInternal(ctx, "error listing received shares"),
   477  		}, nil
   478  	}
   479  
   480  	res := &collaboration.ListReceivedSharesResponse{
   481  		Status: status.NewOK(ctx),
   482  		Shares: shares,
   483  	}
   484  	return res, nil
   485  }
   486  
   487  func (s *service) GetReceivedShare(ctx context.Context, req *collaboration.GetReceivedShareRequest) (*collaboration.GetReceivedShareResponse, error) {
   488  	log := appctx.GetLogger(ctx)
   489  
   490  	share, err := s.sm.GetReceivedShare(ctx, req.Ref)
   491  	if err != nil {
   492  		log.Debug().Err(err).Msg("error getting received share")
   493  		switch err.(type) {
   494  		case errtypes.NotFound:
   495  			return &collaboration.GetReceivedShareResponse{
   496  				Status: status.NewNotFound(ctx, "error getting received share"),
   497  			}, nil
   498  		default:
   499  			return &collaboration.GetReceivedShareResponse{
   500  				Status: status.NewInternal(ctx, "error getting received share"),
   501  			}, nil
   502  		}
   503  	}
   504  
   505  	res := &collaboration.GetReceivedShareResponse{
   506  		Status: status.NewOK(ctx),
   507  		Share:  share,
   508  	}
   509  	return res, nil
   510  }
   511  
   512  func (s *service) UpdateReceivedShare(ctx context.Context, req *collaboration.UpdateReceivedShareRequest) (*collaboration.UpdateReceivedShareResponse, error) {
   513  	if req.GetShare().GetShare().GetId().GetOpaqueId() == "" {
   514  		return &collaboration.UpdateReceivedShareResponse{
   515  			Status: status.NewInvalid(ctx, "share id empty"),
   516  		}, nil
   517  	}
   518  
   519  	isStateTransitionShareAccepted := slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathState) && req.GetShare().GetState() == collaboration.ShareState_SHARE_STATE_ACCEPTED
   520  	isMountPointSet := slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathMountPoint) && req.GetShare().GetMountPoint().GetPath() != ""
   521  	// we calculate a valid mountpoint only if the share should be accepted and the mount point is not set explicitly
   522  	if isStateTransitionShareAccepted && !isMountPointSet {
   523  		s, err := s.setReceivedShareMountPoint(ctx, req)
   524  		switch {
   525  		case err != nil:
   526  			fallthrough
   527  		case s.GetCode() != rpc.Code_CODE_OK:
   528  			return &collaboration.UpdateReceivedShareResponse{
   529  				Status: s,
   530  			}, err
   531  		}
   532  	}
   533  
   534  	var uid userpb.UserId
   535  	_ = utils.ReadJSONFromOpaque(req.Opaque, "userid", &uid)
   536  	updatedShare, err := s.sm.UpdateReceivedShare(ctx, req.Share, req.UpdateMask, &uid)
   537  	switch err.(type) {
   538  	case nil:
   539  		return &collaboration.UpdateReceivedShareResponse{
   540  			Status: status.NewOK(ctx),
   541  			Share:  updatedShare,
   542  		}, nil
   543  	case errtypes.NotFound:
   544  		return &collaboration.UpdateReceivedShareResponse{
   545  			Status: status.NewNotFound(ctx, "error getting received share"),
   546  		}, nil
   547  	default:
   548  		return &collaboration.UpdateReceivedShareResponse{
   549  			Status: status.NewInternal(ctx, "error getting received share"),
   550  		}, nil
   551  	}
   552  }
   553  
   554  func (s *service) setReceivedShareMountPoint(ctx context.Context, req *collaboration.UpdateReceivedShareRequest) (*rpc.Status, error) {
   555  	gwc, err := s.gatewaySelector.Next()
   556  	if err != nil {
   557  		return nil, err
   558  	}
   559  	receivedShare, err := gwc.GetReceivedShare(ctx, &collaboration.GetReceivedShareRequest{
   560  		Ref: &collaboration.ShareReference{
   561  			Spec: &collaboration.ShareReference_Id{
   562  				Id: req.GetShare().GetShare().GetId(),
   563  			},
   564  		},
   565  	})
   566  	switch {
   567  	case err != nil:
   568  		fallthrough
   569  	case receivedShare.GetStatus().GetCode() != rpc.Code_CODE_OK:
   570  		return receivedShare.GetStatus(), err
   571  	}
   572  
   573  	if receivedShare.GetShare().GetMountPoint().GetPath() != "" {
   574  		return status.NewOK(ctx), nil
   575  	}
   576  
   577  	gwc, err = s.gatewaySelector.Next()
   578  	if err != nil {
   579  		return nil, err
   580  	}
   581  	resourceStat, err := gwc.Stat(ctx, &provider.StatRequest{
   582  		Ref: &provider.Reference{
   583  			ResourceId: receivedShare.GetShare().GetShare().GetResourceId(),
   584  		},
   585  	})
   586  	switch {
   587  	case err != nil:
   588  		fallthrough
   589  	case resourceStat.GetStatus().GetCode() != rpc.Code_CODE_OK:
   590  		return resourceStat.GetStatus(), err
   591  	}
   592  
   593  	// handle mount point related updates
   594  	{
   595  		var userID *userpb.UserId
   596  		_ = utils.ReadJSONFromOpaque(req.Opaque, "userid", &userID)
   597  
   598  		receivedShares, err := s.sm.ListReceivedShares(ctx, []*collaboration.Filter{}, userID)
   599  		if err != nil {
   600  			return nil, err
   601  		}
   602  
   603  		// check if the requested mount point is available and if not, find a suitable one
   604  		availableMountpoint, _, err := getMountpointAndUnmountedShares(ctx, receivedShares, s.gatewaySelector, nil,
   605  			resourceStat.GetInfo().GetId(),
   606  			resourceStat.GetInfo().GetName(),
   607  		)
   608  		if err != nil {
   609  			return status.NewInternal(ctx, err.Error()), nil
   610  		}
   611  
   612  		if !slices.Contains(req.GetUpdateMask().GetPaths(), _fieldMaskPathMountPoint) {
   613  			req.GetUpdateMask().Paths = append(req.GetUpdateMask().GetPaths(), _fieldMaskPathMountPoint)
   614  		}
   615  
   616  		req.GetShare().MountPoint = &provider.Reference{
   617  			Path: availableMountpoint,
   618  		}
   619  	}
   620  
   621  	return status.NewOK(ctx), nil
   622  }
   623  
   624  // GetMountpointAndUnmountedShares returns a new or existing mountpoint for the given info and produces a list of unmounted received shares for the same resource
   625  func GetMountpointAndUnmountedShares(ctx context.Context, gwc gateway.GatewayAPIClient, id *provider.ResourceId, name string, userId *userpb.UserId) (string, []*collaboration.ReceivedShare, error) {
   626  	listReceivedSharesReq := &collaboration.ListReceivedSharesRequest{}
   627  	if userId != nil {
   628  		listReceivedSharesReq.Opaque = utils.AppendJSONToOpaque(nil, "userid", userId)
   629  	}
   630  	listReceivedSharesRes, err := gwc.ListReceivedShares(ctx, listReceivedSharesReq)
   631  	if err != nil {
   632  		return "", nil, errtypes.InternalError("grpc list received shares request failed")
   633  	}
   634  
   635  	if err := errtypes.NewErrtypeFromStatus(listReceivedSharesRes.GetStatus()); err != nil {
   636  		return "", nil, err
   637  	}
   638  
   639  	return getMountpointAndUnmountedShares(ctx, listReceivedSharesRes.GetShares(), nil, gwc, id, name)
   640  }
   641  
   642  // GetMountpointAndUnmountedShares returns a new or existing mountpoint for the given info and produces a list of unmounted received shares for the same resource
   643  func getMountpointAndUnmountedShares(ctx context.Context, receivedShares []*collaboration.ReceivedShare, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], gwc gateway.GatewayAPIClient, id *provider.ResourceId, name string) (string, []*collaboration.ReceivedShare, error) {
   644  
   645  	unmountedShares := []*collaboration.ReceivedShare{}
   646  	base := filepath.Clean(name)
   647  	mount := base
   648  	existingMountpoint := ""
   649  	mountedShares := make([]string, 0, len(receivedShares))
   650  	var pathExists bool
   651  	var err error
   652  
   653  	for _, s := range receivedShares {
   654  		resourceIDEqual := utils.ResourceIDEqual(s.GetShare().GetResourceId(), id)
   655  
   656  		if resourceIDEqual && s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
   657  			if gatewaySelector != nil {
   658  				gwc, err = gatewaySelector.Next()
   659  				if err != nil {
   660  					return "", nil, err
   661  				}
   662  			}
   663  			// a share to the resource already exists and is mounted, remembers the mount point
   664  			_, err := utils.GetResourceByID(ctx, s.GetShare().GetResourceId(), gwc)
   665  			if err == nil {
   666  				existingMountpoint = s.GetMountPoint().GetPath()
   667  			}
   668  		}
   669  
   670  		if resourceIDEqual && s.State != collaboration.ShareState_SHARE_STATE_ACCEPTED {
   671  			// a share to the resource already exists but is not mounted, collect the unmounted share
   672  			unmountedShares = append(unmountedShares, s)
   673  		}
   674  
   675  		if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED {
   676  			// collect all accepted mount points
   677  			mountedShares = append(mountedShares, s.GetMountPoint().GetPath())
   678  			if s.GetMountPoint().GetPath() == mount {
   679  				// does the shared resource still exist?
   680  				if gatewaySelector != nil {
   681  					gwc, err = gatewaySelector.Next()
   682  					if err != nil {
   683  						return "", nil, err
   684  					}
   685  				}
   686  				_, err := utils.GetResourceByID(ctx, s.GetShare().GetResourceId(), gwc)
   687  				if err == nil {
   688  					pathExists = true
   689  				}
   690  				// TODO we could delete shares here if the stat returns code NOT FOUND ... but listening for file deletes would be better
   691  			}
   692  		}
   693  	}
   694  
   695  	if existingMountpoint != "" {
   696  		// we want to reuse the same mountpoint for all unmounted shares to the same resource
   697  		return existingMountpoint, unmountedShares, nil
   698  	}
   699  
   700  	// If the mount point really already exists, we need to insert a number into the filename
   701  	if pathExists {
   702  		// now we have a list of shares, we want to iterate over all of them and check for name collisions agents a mount points list
   703  		for i := 1; i <= len(mountedShares)+1; i++ {
   704  			ext := filepath.Ext(base)
   705  			name := strings.TrimSuffix(base, ext)
   706  
   707  			mount = name + " (" + strconv.Itoa(i) + ")" + ext
   708  			if !slices.Contains(mountedShares, mount) {
   709  				return mount, unmountedShares, nil
   710  			}
   711  		}
   712  	}
   713  
   714  	return mount, unmountedShares, nil
   715  }