github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/usershareprovider/usershareprovider_test.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_test
    20  
    21  import (
    22  	"context"
    23  	"path/filepath"
    24  	"regexp"
    25  
    26  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    27  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    28  	permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
    29  	rpcpb "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    30  	collaborationpb "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
    31  	providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    32  	. "github.com/onsi/ginkgo/v2"
    33  	. "github.com/onsi/gomega"
    34  	"github.com/pkg/errors"
    35  	"github.com/stretchr/testify/mock"
    36  	"google.golang.org/grpc"
    37  	"google.golang.org/protobuf/types/known/fieldmaskpb"
    38  
    39  	"github.com/cs3org/reva/v2/internal/grpc/services/usershareprovider"
    40  	"github.com/cs3org/reva/v2/pkg/conversions"
    41  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    42  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    43  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    44  	"github.com/cs3org/reva/v2/pkg/share"
    45  	_ "github.com/cs3org/reva/v2/pkg/share/manager/loader"
    46  	"github.com/cs3org/reva/v2/pkg/share/manager/registry"
    47  	"github.com/cs3org/reva/v2/pkg/share/mocks"
    48  	"github.com/cs3org/reva/v2/pkg/utils"
    49  	cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
    50  )
    51  
    52  var _ = Describe("user share provider service", func() {
    53  	var (
    54  		ctx                      context.Context
    55  		provider                 collaborationpb.CollaborationAPIServer
    56  		manager                  *mocks.Manager
    57  		gatewayClient            *cs3mocks.GatewayAPIClient
    58  		gatewaySelector          pool.Selectable[gateway.GatewayAPIClient]
    59  		checkPermissionResponse  *permissions.CheckPermissionResponse
    60  		statResourceResponse     *providerpb.StatResponse
    61  		cs3permissionsNoAddGrant *providerpb.ResourcePermissions
    62  		getShareResponse         *collaborationpb.Share
    63  	)
    64  	cs3permissionsNoAddGrant = conversions.RoleFromName("manager").CS3ResourcePermissions()
    65  	cs3permissionsNoAddGrant.AddGrant = false
    66  
    67  	BeforeEach(func() {
    68  		manager = &mocks.Manager{}
    69  
    70  		registry.Register("mockManager", func(m map[string]interface{}) (share.Manager, error) {
    71  			return manager, nil
    72  		})
    73  		manager.On("UpdateShare", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&collaborationpb.Share{}, nil)
    74  
    75  		gatewayClient = &cs3mocks.GatewayAPIClient{}
    76  		pool.RemoveSelector("GatewaySelector" + "any")
    77  		gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient](
    78  			"GatewaySelector",
    79  			"any",
    80  			func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient {
    81  				return gatewayClient
    82  			},
    83  		)
    84  		checkPermissionResponse = &permissions.CheckPermissionResponse{
    85  			Status: status.NewOK(ctx),
    86  		}
    87  		gatewayClient.On("CheckPermission", mock.Anything, mock.Anything).
    88  			Return(checkPermissionResponse, nil)
    89  
    90  		statResourceResponse = &providerpb.StatResponse{
    91  			Status: status.NewOK(ctx),
    92  			Info: &providerpb.ResourceInfo{
    93  				PermissionSet: &providerpb.ResourcePermissions{},
    94  			},
    95  		}
    96  		gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResourceResponse, nil)
    97  		alice := &userpb.User{
    98  			Id: &userpb.UserId{
    99  				OpaqueId: "alice",
   100  			},
   101  			Username: "alice",
   102  		}
   103  
   104  		getShareResponse = &collaborationpb.Share{
   105  			Id: &collaborationpb.ShareId{
   106  				OpaqueId: "shareid",
   107  			},
   108  			ResourceId: &providerpb.ResourceId{
   109  				StorageId: "storageid",
   110  				SpaceId:   "spaceid",
   111  				OpaqueId:  "opaqueid",
   112  			},
   113  			Owner:   alice.Id,
   114  			Creator: alice.Id,
   115  		}
   116  		manager.On("GetShare", mock.Anything, mock.Anything).Return(getShareResponse, nil)
   117  
   118  		rgrpcService := usershareprovider.New(gatewaySelector, manager, []*regexp.Regexp{})
   119  
   120  		provider = rgrpcService.(collaborationpb.CollaborationAPIServer)
   121  		Expect(provider).ToNot(BeNil())
   122  
   123  		ctx = ctxpkg.ContextSetUser(context.Background(), alice)
   124  	})
   125  
   126  	Describe("UpdateReceivedShare", func() {
   127  		DescribeTable("validates the share update request",
   128  			func(
   129  				req *collaborationpb.UpdateReceivedShareRequest,
   130  				expectedStatus *rpcpb.Status,
   131  				expectedError error,
   132  			) {
   133  
   134  				res, err := provider.UpdateReceivedShare(ctx, req)
   135  
   136  				switch expectedError {
   137  				case nil:
   138  					Expect(err).To(BeNil())
   139  				}
   140  
   141  				Expect(res.GetStatus().GetCode()).To(Equal(expectedStatus.GetCode()))
   142  				Expect(res.GetStatus().GetMessage()).To(ContainSubstring(expectedStatus.GetMessage()))
   143  			},
   144  			Entry(
   145  				"no share opaque id",
   146  				&collaborationpb.UpdateReceivedShareRequest{
   147  					Share: &collaborationpb.ReceivedShare{
   148  						Share: &collaborationpb.Share{
   149  							Id: &collaborationpb.ShareId{},
   150  						},
   151  					},
   152  				},
   153  				status.NewInvalid(ctx, "share id empty"),
   154  				nil,
   155  			),
   156  		)
   157  
   158  		DescribeTable("fails if getting the share fails",
   159  			func(
   160  				req *collaborationpb.UpdateReceivedShareRequest,
   161  				expectedStatus *rpcpb.Status,
   162  				expectedError error,
   163  			) {
   164  				gatewayClient.EXPECT().
   165  					GetReceivedShare(mock.Anything, mock.Anything, mock.Anything).
   166  					RunAndReturn(func(ctx context.Context, request *collaborationpb.GetReceivedShareRequest, option ...grpc.CallOption) (*collaborationpb.GetReceivedShareResponse, error) {
   167  						return &collaborationpb.GetReceivedShareResponse{
   168  							Status: expectedStatus,
   169  						}, expectedError
   170  					})
   171  
   172  				res, err := provider.UpdateReceivedShare(ctx, req)
   173  
   174  				switch expectedError {
   175  				case nil:
   176  					Expect(err).To(BeNil())
   177  				default:
   178  					Expect(err).To(MatchError(expectedError))
   179  				}
   180  
   181  				switch expectedStatus {
   182  				case nil:
   183  					Expect(expectedStatus).To(BeNil())
   184  				default:
   185  					Expect(res.GetStatus().GetCode()).To(Equal(expectedStatus.GetCode()))
   186  					Expect(res.GetStatus().GetMessage()).To(ContainSubstring(expectedStatus.GetMessage()))
   187  				}
   188  			},
   189  			Entry(
   190  				"requesting the share errors",
   191  				&collaborationpb.UpdateReceivedShareRequest{
   192  					UpdateMask: &fieldmaskpb.FieldMask{
   193  						Paths: []string{"state"},
   194  					},
   195  					Share: &collaborationpb.ReceivedShare{
   196  						State: collaborationpb.ShareState_SHARE_STATE_ACCEPTED,
   197  						Share: &collaborationpb.Share{
   198  							Id: &collaborationpb.ShareId{
   199  								OpaqueId: "1",
   200  							},
   201  						},
   202  					},
   203  				},
   204  				nil,
   205  				errors.New("some"),
   206  			),
   207  			Entry(
   208  				"requesting the share fails",
   209  				&collaborationpb.UpdateReceivedShareRequest{
   210  					UpdateMask: &fieldmaskpb.FieldMask{
   211  						Paths: []string{"state"},
   212  					},
   213  					Share: &collaborationpb.ReceivedShare{
   214  						State: collaborationpb.ShareState_SHARE_STATE_ACCEPTED,
   215  						Share: &collaborationpb.Share{
   216  							Id: &collaborationpb.ShareId{
   217  								OpaqueId: "1",
   218  							},
   219  						},
   220  					},
   221  				},
   222  				status.NewInvalid(ctx, "something"),
   223  				nil,
   224  			),
   225  		)
   226  
   227  		DescribeTable("fails if the resource stat fails",
   228  			func(
   229  				req *collaborationpb.UpdateReceivedShareRequest,
   230  				expectedStatus *rpcpb.Status,
   231  				expectedError error,
   232  			) {
   233  				gatewayClient.EXPECT().
   234  					GetReceivedShare(mock.Anything, mock.Anything, mock.Anything).
   235  					RunAndReturn(func(ctx context.Context, request *collaborationpb.GetReceivedShareRequest, option ...grpc.CallOption) (*collaborationpb.GetReceivedShareResponse, error) {
   236  						return &collaborationpb.GetReceivedShareResponse{
   237  							Status: status.NewOK(ctx),
   238  						}, nil
   239  					})
   240  				gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything, mock.Anything).Unset()
   241  				gatewayClient.EXPECT().
   242  					Stat(mock.Anything, mock.Anything, mock.Anything).
   243  					RunAndReturn(func(ctx context.Context, request *providerpb.StatRequest, option ...grpc.CallOption) (*providerpb.StatResponse, error) {
   244  						return &providerpb.StatResponse{
   245  							Status: expectedStatus,
   246  						}, expectedError
   247  					})
   248  
   249  				res, err := provider.UpdateReceivedShare(ctx, req)
   250  
   251  				switch expectedError {
   252  				case nil:
   253  					Expect(err).To(BeNil())
   254  				default:
   255  					Expect(err).To(MatchError(expectedError))
   256  				}
   257  
   258  				switch expectedStatus {
   259  				case nil:
   260  					Expect(expectedStatus).To(BeNil())
   261  				default:
   262  					Expect(res.GetStatus().GetCode()).To(Equal(expectedStatus.GetCode()))
   263  					Expect(res.GetStatus().GetMessage()).To(ContainSubstring(expectedStatus.GetMessage()))
   264  				}
   265  			},
   266  			Entry(
   267  				"stat the resource errors",
   268  				&collaborationpb.UpdateReceivedShareRequest{
   269  					UpdateMask: &fieldmaskpb.FieldMask{
   270  						Paths: []string{"state"},
   271  					},
   272  					Share: &collaborationpb.ReceivedShare{
   273  						State: collaborationpb.ShareState_SHARE_STATE_ACCEPTED,
   274  						Share: &collaborationpb.Share{
   275  							Id: &collaborationpb.ShareId{
   276  								OpaqueId: "1",
   277  							},
   278  						},
   279  					},
   280  				},
   281  				nil,
   282  				errors.New("some"),
   283  			),
   284  			Entry(
   285  				"stat the resource fails",
   286  				&collaborationpb.UpdateReceivedShareRequest{
   287  					UpdateMask: &fieldmaskpb.FieldMask{
   288  						Paths: []string{"state"},
   289  					},
   290  					Share: &collaborationpb.ReceivedShare{
   291  						State: collaborationpb.ShareState_SHARE_STATE_ACCEPTED,
   292  						Share: &collaborationpb.Share{
   293  							Id: &collaborationpb.ShareId{
   294  								OpaqueId: "1",
   295  							},
   296  						},
   297  					},
   298  				},
   299  				status.NewInvalid(ctx, "something"),
   300  				nil,
   301  			),
   302  		)
   303  	})
   304  
   305  	Describe("CreateShare", func() {
   306  		DescribeTable("only requests with sufficient permissions get passed to the manager",
   307  			func(
   308  				resourceInfoPermissions *providerpb.ResourcePermissions,
   309  				grantPermissions *providerpb.ResourcePermissions,
   310  				checkPermissionStatusCode rpcpb.Code,
   311  				expectedCode rpcpb.Code,
   312  				expectedCalls int,
   313  			) {
   314  				manager.On("Share", mock.Anything, mock.Anything, mock.Anything).Return(&collaborationpb.Share{}, nil)
   315  				checkPermissionResponse.Status.Code = checkPermissionStatusCode
   316  
   317  				statResourceResponse.Info.PermissionSet = resourceInfoPermissions
   318  
   319  				createShareResponse, err := provider.CreateShare(ctx, &collaborationpb.CreateShareRequest{
   320  					ResourceInfo: &providerpb.ResourceInfo{
   321  						PermissionSet: resourceInfoPermissions,
   322  					},
   323  					Grant: &collaborationpb.ShareGrant{
   324  						Permissions: &collaborationpb.SharePermissions{
   325  							Permissions: grantPermissions,
   326  						},
   327  					},
   328  				})
   329  
   330  				Expect(err).ToNot(HaveOccurred())
   331  				Expect(createShareResponse.Status.Code).To(Equal(expectedCode))
   332  
   333  				manager.AssertNumberOfCalls(GinkgoT(), "Share", expectedCalls)
   334  			},
   335  			Entry(
   336  				"insufficient permissions",
   337  				conversions.RoleFromName("spaceeditor").CS3ResourcePermissions(),
   338  				conversions.RoleFromName("manager").CS3ResourcePermissions(),
   339  				rpcpb.Code_CODE_OK,
   340  				rpcpb.Code_CODE_INVALID_ARGUMENT,
   341  				0,
   342  			),
   343  			Entry(
   344  				"sufficient permissions",
   345  				conversions.RoleFromName("manager").CS3ResourcePermissions(),
   346  				conversions.RoleFromName("spaceeditor").CS3ResourcePermissions(),
   347  				rpcpb.Code_CODE_OK,
   348  				rpcpb.Code_CODE_OK,
   349  				1,
   350  			),
   351  			Entry(
   352  				"no AddGrant permission on resource",
   353  				cs3permissionsNoAddGrant,
   354  				conversions.RoleFromName("spaceeditor").CS3ResourcePermissions(),
   355  				rpcpb.Code_CODE_OK,
   356  				rpcpb.Code_CODE_PERMISSION_DENIED,
   357  				0,
   358  			),
   359  			Entry(
   360  				"no WriteShare permission on user role",
   361  				conversions.RoleFromName("manager").CS3ResourcePermissions(),
   362  				conversions.RoleFromName("mspaceeditor").CS3ResourcePermissions(),
   363  				rpcpb.Code_CODE_PERMISSION_DENIED,
   364  				rpcpb.Code_CODE_PERMISSION_DENIED,
   365  				0,
   366  			),
   367  		)
   368  		Context("resharing is not allowed", func() {
   369  			JustBeforeEach(func() {
   370  				rgrpcService := usershareprovider.New(gatewaySelector, manager, []*regexp.Regexp{})
   371  
   372  				provider = rgrpcService.(collaborationpb.CollaborationAPIServer)
   373  				Expect(provider).ToNot(BeNil())
   374  
   375  				// user has list grants access
   376  				statResourceResponse.Info.PermissionSet = &providerpb.ResourcePermissions{
   377  					AddGrant:   true,
   378  					ListGrants: true,
   379  				}
   380  			})
   381  			DescribeTable("rejects shares with any grant changing permissions",
   382  				func(
   383  					resourceInfoPermissions *providerpb.ResourcePermissions,
   384  					grantPermissions *providerpb.ResourcePermissions,
   385  					responseCode rpcpb.Code,
   386  					expectedCalls int,
   387  				) {
   388  					manager.On("Share", mock.Anything, mock.Anything, mock.Anything).Return(&collaborationpb.Share{}, nil)
   389  
   390  					createShareResponse, err := provider.CreateShare(ctx, &collaborationpb.CreateShareRequest{
   391  						ResourceInfo: &providerpb.ResourceInfo{
   392  							PermissionSet: resourceInfoPermissions,
   393  						},
   394  						Grant: &collaborationpb.ShareGrant{
   395  							Permissions: &collaborationpb.SharePermissions{
   396  								Permissions: grantPermissions,
   397  							},
   398  						},
   399  					})
   400  
   401  					Expect(err).ToNot(HaveOccurred())
   402  					Expect(createShareResponse.Status.Code).To(Equal(responseCode))
   403  
   404  					manager.AssertNumberOfCalls(GinkgoT(), "Share", expectedCalls)
   405  				},
   406  				Entry("AddGrant", conversions.RoleFromName("manager").CS3ResourcePermissions(), &providerpb.ResourcePermissions{AddGrant: true}, rpcpb.Code_CODE_INVALID_ARGUMENT, 0),
   407  				Entry("UpdateGrant", conversions.RoleFromName("manager").CS3ResourcePermissions(), &providerpb.ResourcePermissions{UpdateGrant: true}, rpcpb.Code_CODE_INVALID_ARGUMENT, 0),
   408  				Entry("RemoveGrant", conversions.RoleFromName("manager").CS3ResourcePermissions(), &providerpb.ResourcePermissions{RemoveGrant: true}, rpcpb.Code_CODE_INVALID_ARGUMENT, 0),
   409  				Entry("DenyGrant", conversions.RoleFromName("manager").CS3ResourcePermissions(), &providerpb.ResourcePermissions{DenyGrant: true}, rpcpb.Code_CODE_INVALID_ARGUMENT, 0),
   410  				Entry("ListGrants", conversions.RoleFromName("manager").CS3ResourcePermissions(), &providerpb.ResourcePermissions{ListGrants: true}, rpcpb.Code_CODE_OK, 1),
   411  			)
   412  		})
   413  	})
   414  	Describe("UpdateShare", func() {
   415  		It("fails without WriteShare permission in user role", func() {
   416  			checkPermissionResponse.Status.Code = rpcpb.Code_CODE_PERMISSION_DENIED
   417  
   418  			updateShareResponse, err := provider.UpdateShare(ctx, &collaborationpb.UpdateShareRequest{
   419  				Ref: &collaborationpb.ShareReference{
   420  					Spec: &collaborationpb.ShareReference_Id{
   421  						Id: &collaborationpb.ShareId{
   422  							OpaqueId: "shareid",
   423  						},
   424  					},
   425  				},
   426  				Share: &collaborationpb.Share{},
   427  				UpdateMask: &fieldmaskpb.FieldMask{
   428  					Paths: []string{"permissions"},
   429  				},
   430  			})
   431  
   432  			Expect(err).ToNot(HaveOccurred())
   433  			Expect(updateShareResponse.Status.Code).To(Equal(rpcpb.Code_CODE_PERMISSION_DENIED))
   434  
   435  			manager.AssertNumberOfCalls(GinkgoT(), "UpdateShare", 0)
   436  		})
   437  		It("fails when the user tries to share with elevated permissions", func() {
   438  			// user has only read access
   439  			statResourceResponse.Info.PermissionSet = &providerpb.ResourcePermissions{
   440  				InitiateFileDownload: true,
   441  				Stat:                 true,
   442  			}
   443  
   444  			// user tries to update a share to give write permissions
   445  			updateShareResponse, err := provider.UpdateShare(ctx, &collaborationpb.UpdateShareRequest{
   446  				Ref: &collaborationpb.ShareReference{
   447  					Spec: &collaborationpb.ShareReference_Id{
   448  						Id: &collaborationpb.ShareId{
   449  							OpaqueId: "shareid",
   450  						},
   451  					},
   452  				},
   453  				Share: &collaborationpb.Share{
   454  					Permissions: &collaborationpb.SharePermissions{
   455  						Permissions: &providerpb.ResourcePermissions{
   456  							Stat:                 true,
   457  							InitiateFileDownload: true,
   458  							InitiateFileUpload:   true,
   459  						},
   460  					},
   461  				},
   462  				UpdateMask: &fieldmaskpb.FieldMask{
   463  					Paths: []string{"permissions"},
   464  				},
   465  			})
   466  
   467  			Expect(err).ToNot(HaveOccurred())
   468  			Expect(updateShareResponse.Status.Code).To(Equal(rpcpb.Code_CODE_PERMISSION_DENIED))
   469  
   470  			manager.AssertNumberOfCalls(GinkgoT(), "UpdateShare", 0)
   471  		})
   472  		It("succeeds when the user is not the owner/creator and does not have the UpdateGrant permissions", func() {
   473  			// user has only read access
   474  			statResourceResponse.Info.PermissionSet = &providerpb.ResourcePermissions{
   475  				InitiateFileDownload: true,
   476  				Stat:                 true,
   477  			}
   478  			bobId := &userpb.UserId{OpaqueId: "bob"}
   479  			getShareResponse.Owner = bobId
   480  			getShareResponse.Creator = bobId
   481  
   482  			// user tries to update a share to give write permissions
   483  			updateShareResponse, err := provider.UpdateShare(ctx, &collaborationpb.UpdateShareRequest{
   484  				Ref: &collaborationpb.ShareReference{
   485  					Spec: &collaborationpb.ShareReference_Id{
   486  						Id: &collaborationpb.ShareId{
   487  							OpaqueId: "shareid",
   488  						},
   489  					},
   490  				},
   491  				Share: &collaborationpb.Share{
   492  					Permissions: &collaborationpb.SharePermissions{
   493  						Permissions: &providerpb.ResourcePermissions{
   494  							Stat:                 true,
   495  							InitiateFileDownload: true,
   496  						},
   497  					},
   498  				},
   499  				UpdateMask: &fieldmaskpb.FieldMask{
   500  					Paths: []string{"permissions"},
   501  				},
   502  			})
   503  
   504  			Expect(err).ToNot(HaveOccurred())
   505  			Expect(updateShareResponse.Status.Code).To(Equal(rpcpb.Code_CODE_PERMISSION_DENIED))
   506  
   507  			manager.AssertNumberOfCalls(GinkgoT(), "UpdateShare", 0)
   508  		})
   509  		It("succeeds when the user is the owner/creator", func() {
   510  			// user has only read access
   511  			statResourceResponse.Info.PermissionSet = &providerpb.ResourcePermissions{
   512  				InitiateFileDownload: true,
   513  				Stat:                 true,
   514  			}
   515  
   516  			// user tries to update a share to give write permissions
   517  			updateShareResponse, err := provider.UpdateShare(ctx, &collaborationpb.UpdateShareRequest{
   518  				Ref: &collaborationpb.ShareReference{
   519  					Spec: &collaborationpb.ShareReference_Id{
   520  						Id: &collaborationpb.ShareId{
   521  							OpaqueId: "shareid",
   522  						},
   523  					},
   524  				},
   525  				Share: &collaborationpb.Share{
   526  					Permissions: &collaborationpb.SharePermissions{
   527  						Permissions: &providerpb.ResourcePermissions{
   528  							Stat:                 true,
   529  							InitiateFileDownload: true,
   530  						},
   531  					},
   532  				},
   533  				UpdateMask: &fieldmaskpb.FieldMask{
   534  					Paths: []string{"permissions"},
   535  				},
   536  			})
   537  
   538  			Expect(err).ToNot(HaveOccurred())
   539  			Expect(updateShareResponse.Status.Code).To(Equal(rpcpb.Code_CODE_OK))
   540  
   541  			manager.AssertNumberOfCalls(GinkgoT(), "UpdateShare", 1)
   542  		})
   543  		It("succeeds when the user is not the owner/creator but has the UpdateGrant permissions", func() {
   544  			// user has only read access
   545  			statResourceResponse.Info.PermissionSet = &providerpb.ResourcePermissions{
   546  				UpdateGrant:          true,
   547  				InitiateFileDownload: true,
   548  				Stat:                 true,
   549  			}
   550  			bobId := &userpb.UserId{OpaqueId: "bob"}
   551  			getShareResponse.Owner = bobId
   552  			getShareResponse.Creator = bobId
   553  
   554  			// user tries to update a share to give write permissions
   555  			updateShareResponse, err := provider.UpdateShare(ctx, &collaborationpb.UpdateShareRequest{
   556  				Ref: &collaborationpb.ShareReference{
   557  					Spec: &collaborationpb.ShareReference_Id{
   558  						Id: &collaborationpb.ShareId{
   559  							OpaqueId: "shareid",
   560  						},
   561  					},
   562  				},
   563  				Share: &collaborationpb.Share{
   564  					Permissions: &collaborationpb.SharePermissions{
   565  						Permissions: &providerpb.ResourcePermissions{
   566  							Stat:                 true,
   567  							InitiateFileDownload: true,
   568  						},
   569  					},
   570  				},
   571  				UpdateMask: &fieldmaskpb.FieldMask{
   572  					Paths: []string{"permissions"},
   573  				},
   574  			})
   575  
   576  			Expect(err).ToNot(HaveOccurred())
   577  			Expect(updateShareResponse.Status.Code).To(Equal(rpcpb.Code_CODE_OK))
   578  
   579  			manager.AssertNumberOfCalls(GinkgoT(), "UpdateShare", 1)
   580  		})
   581  	})
   582  })
   583  
   584  var _ = Describe("helpers", func() {
   585  	type GetMountpointAndUnmountedSharesArgs struct {
   586  		withName                   string
   587  		withResourceId             *providerpb.ResourceId
   588  		listReceivedSharesResponse *collaborationpb.ListReceivedSharesResponse
   589  		listReceivedSharesError    error
   590  		expectedName               string
   591  	}
   592  	DescribeTable("GetMountpointAndUnmountedShares",
   593  		func(args GetMountpointAndUnmountedSharesArgs) {
   594  			gatewayClient := cs3mocks.NewGatewayAPIClient(GinkgoT())
   595  
   596  			gatewayClient.EXPECT().
   597  				ListReceivedShares(mock.Anything, mock.Anything).
   598  				RunAndReturn(func(ctx context.Context, request *collaborationpb.ListReceivedSharesRequest, option ...grpc.CallOption) (*collaborationpb.ListReceivedSharesResponse, error) {
   599  					return args.listReceivedSharesResponse, args.listReceivedSharesError
   600  				})
   601  
   602  			statCallCount := 0
   603  
   604  			for _, s := range args.listReceivedSharesResponse.GetShares() {
   605  				if s.GetState() != collaborationpb.ShareState_SHARE_STATE_ACCEPTED {
   606  					continue
   607  				}
   608  
   609  				// add one for every accepted share where the resource id matches
   610  				if utils.ResourceIDEqual(s.GetShare().GetResourceId(), args.withResourceId) {
   611  					statCallCount++
   612  				}
   613  
   614  				// add one for every accepted share where the mountpoint patch matches
   615  				if s.GetMountPoint().GetPath() == filepath.Clean(args.withName) {
   616  					statCallCount++
   617  				}
   618  			}
   619  
   620  			if statCallCount > 0 {
   621  				gatewayClient.EXPECT().
   622  					Stat(mock.Anything, mock.Anything, mock.Anything).
   623  					RunAndReturn(func(ctx context.Context, request *providerpb.StatRequest, option ...grpc.CallOption) (*providerpb.StatResponse, error) {
   624  						return &providerpb.StatResponse{
   625  							Status: status.NewOK(ctx),
   626  						}, nil
   627  					}).Times(statCallCount)
   628  			}
   629  
   630  			availableMountpoint, _, err := usershareprovider.GetMountpointAndUnmountedShares(context.Background(), gatewayClient, args.withResourceId, args.withName, nil)
   631  
   632  			if args.listReceivedSharesError != nil {
   633  				Expect(err).To(HaveOccurred(), "expected error, got none")
   634  				return
   635  			}
   636  
   637  			Expect(availableMountpoint).To(Equal(args.expectedName), "expected mountpoint %s, got %s", args.expectedName, availableMountpoint)
   638  
   639  			gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything, mock.Anything).Unset()
   640  		},
   641  		Entry(
   642  			"listing received shares errors",
   643  			GetMountpointAndUnmountedSharesArgs{
   644  				listReceivedSharesError: errors.New("some error"),
   645  			},
   646  		),
   647  		Entry(
   648  			"returns the given name if no shares are found",
   649  			GetMountpointAndUnmountedSharesArgs{
   650  				withName: "name1",
   651  				listReceivedSharesResponse: &collaborationpb.ListReceivedSharesResponse{
   652  					Status: status.NewOK(context.Background()),
   653  				},
   654  				expectedName: "name1",
   655  			},
   656  		),
   657  		Entry(
   658  			"returns the path as name if a share with the same resourceId is found",
   659  			GetMountpointAndUnmountedSharesArgs{
   660  				withName: "name",
   661  				withResourceId: &providerpb.ResourceId{
   662  					StorageId: "1",
   663  					OpaqueId:  "2",
   664  					SpaceId:   "3",
   665  				},
   666  				listReceivedSharesResponse: &collaborationpb.ListReceivedSharesResponse{
   667  					Status: status.NewOK(context.Background()),
   668  					Shares: []*collaborationpb.ReceivedShare{
   669  						{
   670  							State: collaborationpb.ShareState_SHARE_STATE_ACCEPTED,
   671  							MountPoint: &providerpb.Reference{
   672  								Path: "path",
   673  							},
   674  							Share: &collaborationpb.Share{
   675  								ResourceId: &providerpb.ResourceId{
   676  									StorageId: "1",
   677  									OpaqueId:  "2",
   678  									SpaceId:   "3",
   679  								},
   680  							},
   681  						},
   682  					},
   683  				},
   684  				expectedName: "path",
   685  			},
   686  		),
   687  		Entry(
   688  			"enumerates the name if a share with the same path already exists",
   689  			GetMountpointAndUnmountedSharesArgs{
   690  				withName: "some name",
   691  				listReceivedSharesResponse: &collaborationpb.ListReceivedSharesResponse{
   692  					Status: status.NewOK(context.Background()),
   693  					Shares: []*collaborationpb.ReceivedShare{
   694  						{
   695  							State: collaborationpb.ShareState_SHARE_STATE_ACCEPTED,
   696  							MountPoint: &providerpb.Reference{
   697  								Path: "some name",
   698  							},
   699  							Share: &collaborationpb.Share{
   700  								ResourceId: &providerpb.ResourceId{
   701  									StorageId: "1",
   702  									OpaqueId:  "2",
   703  									SpaceId:   "3",
   704  								},
   705  							},
   706  						},
   707  					},
   708  				},
   709  				expectedName: "some name (1)",
   710  			},
   711  		),
   712  	)
   713  })