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 }