github.com/cs3org/reva/v2@v2.27.7/tests/integration/grpc/ocm_share_test.go (about) 1 // Copyright 2018-2023 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 grpc_test 20 21 import ( 22 "bytes" 23 "context" 24 "encoding/base64" 25 "io" 26 "net/http" 27 "path/filepath" 28 "strconv" 29 30 gatewaypb "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 31 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 32 invitev1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" 33 ocmproviderpb "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" 34 rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 35 ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" 36 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 37 storagep "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 38 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 39 "github.com/cs3org/reva/v2/internal/http/services/datagateway" 40 "github.com/cs3org/reva/v2/pkg/conversions" 41 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 42 "github.com/cs3org/reva/v2/pkg/ocm/share" 43 ocm "github.com/cs3org/reva/v2/pkg/ocm/storage/received" 44 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 45 "github.com/cs3org/reva/v2/pkg/rhttp" 46 "github.com/cs3org/reva/v2/pkg/storage/fs/ocis" 47 jwt "github.com/cs3org/reva/v2/pkg/token/manager/jwt" 48 "github.com/cs3org/reva/v2/tests/helpers" 49 "github.com/owncloud/ocis/v2/services/webdav/pkg/net" 50 "github.com/pkg/errors" 51 "github.com/studio-b12/gowebdav" 52 "google.golang.org/grpc/metadata" 53 "google.golang.org/protobuf/types/known/fieldmaskpb" 54 55 . "github.com/onsi/ginkgo/v2" 56 . "github.com/onsi/gomega" 57 ) 58 59 var ( 60 editorPermissions = &provider.ResourcePermissions{ 61 CreateContainer: true, 62 Delete: true, 63 GetPath: true, 64 GetQuota: true, 65 InitiateFileDownload: true, 66 InitiateFileUpload: true, 67 ListContainer: true, 68 ListGrants: true, 69 ListRecycle: true, 70 RestoreRecycleItem: true, 71 Move: true, 72 Stat: true, 73 } 74 viewerPermissions = &provider.ResourcePermissions{ 75 Stat: true, 76 InitiateFileDownload: true, 77 GetPath: true, 78 GetQuota: true, 79 ListContainer: true, 80 ListRecycle: true, 81 } 82 ) 83 84 var _ = Describe("ocm share", func() { 85 var ( 86 revads = map[string]*Revad{} 87 88 variables = map[string]string{} 89 90 ctxEinstein context.Context 91 ctxMarie context.Context 92 cernboxgw gatewaypb.GatewayAPIClient 93 cesnetgw gatewaypb.GatewayAPIClient 94 cernbox = &ocmproviderpb.ProviderInfo{ 95 Name: "cernbox", 96 FullName: "CERNBox", 97 Description: "CERNBox provides cloud data storage to all CERN users.", 98 Organization: "CERN", 99 Domain: "cernbox.cern.ch", 100 Homepage: "https://cernbox.web.cern.ch", 101 Services: []*ocmproviderpb.Service{ 102 { 103 Endpoint: &ocmproviderpb.ServiceEndpoint{ 104 Type: &ocmproviderpb.ServiceType{ 105 Name: "OCM", 106 Description: "CERNBox Open Cloud Mesh API", 107 }, 108 Name: "CERNBox - OCM API", 109 Path: "http://127.0.0.1:19001/ocm/", 110 IsMonitored: true, 111 }, 112 Host: "127.0.0.1:19001", 113 ApiVersion: "0.0.1", 114 }, 115 }, 116 } 117 einstein = &userpb.User{ 118 Id: &userpb.UserId{ 119 OpaqueId: "4c510ada-c86b-4815-8820-42cdf82c3d51", 120 Idp: "https://cernbox.cern.ch", 121 Type: userpb.UserType_USER_TYPE_PRIMARY, 122 }, 123 Username: "einstein", 124 Mail: "einstein@cern.ch", 125 DisplayName: "Albert Einstein", 126 } 127 federatedEinsteinID = &userpb.UserId{ 128 Type: userpb.UserType_USER_TYPE_FEDERATED, 129 Idp: "cernbox.cern.ch", 130 OpaqueId: base64.URLEncoding.EncodeToString([]byte("4c510ada-c86b-4815-8820-42cdf82c3d51@https://cernbox.cern.ch")), 131 } 132 marie = &userpb.User{ 133 Id: &userpb.UserId{ 134 OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", 135 Idp: "https://cesnet.cz", 136 Type: userpb.UserType_USER_TYPE_PRIMARY, 137 }, 138 Username: "marie", 139 Mail: "marie@cesnet.cz", 140 DisplayName: "Marie Curie", 141 } 142 federatedMarieID = &userpb.UserId{ 143 Type: userpb.UserType_USER_TYPE_FEDERATED, 144 Idp: "cesnet.cz", 145 OpaqueId: base64.URLEncoding.EncodeToString([]byte("f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c@https://cesnet.cz")), 146 } 147 ) 148 149 JustBeforeEach(func() { 150 tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"}) 151 Expect(err).ToNot(HaveOccurred()) 152 ctxEinstein = ctxWithAuthToken(tokenManager, einstein) 153 ctxMarie = ctxWithAuthToken(tokenManager, marie) 154 revads, err = startRevads([]RevadConfig{ 155 {Name: "cernboxgw", Config: "ocm-share/ocm-server-cernbox-grpc.toml", 156 Files: map[string]string{ 157 "providers": "ocm-providers.demo.json", 158 }, 159 Resources: map[string]Resource{ 160 "ocm_share_cernbox_file": File{Content: "{}"}, 161 "invite_token_file": File{Content: "{}"}, 162 }, 163 }, 164 {Name: "permissions", Config: "permissions-ocis-ci.toml"}, 165 {Name: "cernboxpublicstorage", Config: "ocm-share/cernbox-storageprovider-public.toml"}, 166 {Name: "cernboxwebdav", Config: "ocm-share/cernbox-webdav-server.toml"}, 167 {Name: "cernboxhttp", Config: "ocm-share/ocm-server-cernbox-http.toml"}, 168 {Name: "cesnetgw", Config: "ocm-share/ocm-server-cesnet-grpc.toml", 169 Files: map[string]string{ 170 "providers": "ocm-providers.demo.json", 171 }, 172 Resources: map[string]Resource{ 173 "ocm_share_cesnet_file": File{Content: "{}"}, 174 "invite_token_file": File{Content: "{}"}, 175 }, 176 }, 177 {Name: "cesnethttp", Config: "ocm-share/ocm-server-cesnet-http.toml"}, 178 {Name: "cernboxocmsharesauth", Config: "ocm-share/ocm-cernbox-ocmshares-authprovider.toml"}, 179 {Name: "cernboxmachineauth", Config: "ocm-share/cernbox-machine-authprovider.toml"}, 180 }, variables) 181 Expect(err).ToNot(HaveOccurred()) 182 cernboxgw, err = pool.GetGatewayServiceClient(revads["cernboxgw"].GrpcAddress) 183 Expect(err).ToNot(HaveOccurred()) 184 cesnetgw, err = pool.GetGatewayServiceClient(revads["cesnetgw"].GrpcAddress) 185 Expect(err).ToNot(HaveOccurred()) 186 cernbox.Services[0].Endpoint.Path = "http://" + revads["cernboxhttp"].GrpcAddress + "/ocm" 187 188 createHomeResp, err := cernboxgw.CreateHome(ctxEinstein, &provider.CreateHomeRequest{}) 189 Expect(err).ToNot(HaveOccurred()) 190 Expect(createHomeResp.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 191 }) 192 193 AfterEach(func() { 194 for _, r := range revads { 195 Expect(r.Cleanup(CurrentGinkgoTestDescription().Failed)).To(Succeed()) 196 } 197 }) 198 199 Describe("marie has already accepted the invitation workflow", func() { 200 JustBeforeEach(func() { 201 // einstein generates an invite token 202 tknRes, err := cernboxgw.GenerateInviteToken(ctxEinstein, &invitev1beta1.GenerateInviteTokenRequest{}) 203 Expect(err).ToNot(HaveOccurred()) 204 Expect(tknRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 205 206 // marie accepts it and her provider forwards the invite back to the instance of einstein 207 invRes, err := cesnetgw.ForwardInvite(ctxMarie, &invitev1beta1.ForwardInviteRequest{ 208 InviteToken: tknRes.InviteToken, 209 OriginSystemProvider: cernbox, 210 }) 211 Expect(err).ToNot(HaveOccurred()) 212 Expect(invRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 213 // Make sure the user is a federated user 214 // The user type must be a federated user 215 Expect(invRes.UserId.Type).To(Equal(userpb.UserType_USER_TYPE_FEDERATED)) 216 // Federated users use the OCM provider id which MUST NOT contain the protocol 217 Expect(invRes.UserId.Idp).To(Equal("cernbox.cern.ch")) 218 // The OpaqueId is the base64 encoded user id and the provider id to provent collisions with other users on the graph API 219 Expect(invRes.UserId.OpaqueId).To(Equal(federatedEinsteinID.OpaqueId)) 220 }) 221 222 Context("einstein shares a file with view permissions", func() { 223 It("marie is able to see the content of the file", func() { 224 fs, err := ocis.New(map[string]interface{}{ 225 "root": revads["cernboxgw"].StorageRoot, 226 "permissionssvc": revads["permissions"].GrpcAddress, 227 }, nil, nil) 228 Expect(err).ToNot(HaveOccurred()) 229 ref := &provider.Reference{ 230 ResourceId: &provider.ResourceId{ 231 SpaceId: "4c510ada-c86b-4815-8820-42cdf82c3d51", 232 }, 233 Path: "./new-file", 234 } 235 err = helpers.Upload(ctxEinstein, fs, ref, []byte("test")) 236 Expect(err).ToNot(HaveOccurred()) 237 238 By("share the file with marie") 239 info, err := stat(ctxEinstein, cernboxgw, ref) 240 Expect(err).ToNot(HaveOccurred()) 241 242 cesnet, err := cernboxgw.GetInfoByDomain(ctxEinstein, &ocmproviderpb.GetInfoByDomainRequest{ 243 Domain: "cesnet.cz", 244 }) 245 Expect(err).ToNot(HaveOccurred()) 246 Expect(cesnet.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 247 248 createShareRes, err := cernboxgw.CreateOCMShare(ctxEinstein, &ocmv1beta1.CreateOCMShareRequest{ 249 ResourceId: info.Id, 250 Grantee: &provider.Grantee{ 251 Type: provider.GranteeType_GRANTEE_TYPE_USER, 252 Id: &provider.Grantee_UserId{ 253 UserId: federatedMarieID, 254 }, 255 }, 256 AccessMethods: []*ocmv1beta1.AccessMethod{ 257 share.NewWebDavAccessMethod(conversions.NewViewerRole().CS3ResourcePermissions()), 258 }, 259 RecipientMeshProvider: cesnet.ProviderInfo, 260 }) 261 Expect(err).ToNot(HaveOccurred()) 262 Expect(createShareRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 263 264 // get auth context for ocm share 265 ocmCtx := context.Background() 266 authRes, err := cernboxgw.Authenticate(ocmCtx, &gatewaypb.AuthenticateRequest{ 267 Type: "ocmshares", 268 ClientId: createShareRes.GetShare().GetId().GetOpaqueId(), 269 ClientSecret: createShareRes.GetShare().GetToken(), 270 }) 271 Expect(err).ToNot(HaveOccurred()) 272 Expect(authRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 273 274 // create ocm context 275 ocmCtx = ctxpkg.ContextSetToken(ocmCtx, authRes.Token) 276 ocmCtx = metadata.AppendToOutgoingContext(ocmCtx, ctxpkg.TokenHeader, authRes.Token) 277 // I commented this because we currently do return a space ... but IMO we should not. the share is not accepted / synced yet 278 /* 279 // try finding the space by path 280 lssRes, err := cernboxgw.ListStorageSpaces(ocmCtx, &provider.ListStorageSpacesRequest{ 281 Opaque: &typespb.Opaque{ 282 Map: map[string]*typespb.OpaqueEntry{ 283 "path": { 284 Decoder: "plain", 285 Value: []byte("/public/" + createShareRes.GetShare().GetId().GetOpaqueId()), 286 }, 287 "metadata": { 288 Decoder: "plain", 289 Value: []byte("*"), 290 }, 291 }, 292 }}) 293 Expect(err).ToNot(HaveOccurred()) 294 Expect(lssRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 295 Expect(lssRes.StorageSpaces).To(HaveLen(0), "pending ocm share should not be listed as a space") 296 */ 297 By("marie accepts the share") 298 listRes, err := cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) 299 Expect(err).ToNot(HaveOccurred()) 300 Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 301 302 Expect(listRes.Shares).To(HaveLen(1)) 303 304 share := listRes.Shares[0] 305 Expect(share.Protocols).To(HaveLen(1)) 306 Expect(share.State).To(Equal(ocmv1beta1.ShareState_SHARE_STATE_PENDING)) 307 308 share.State = ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED 309 _, err = cesnetgw.UpdateReceivedOCMShare(ctxMarie, &ocmv1beta1.UpdateReceivedOCMShareRequest{ 310 Share: share, 311 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}}, 312 }) 313 Expect(err).ToNot(HaveOccurred()) 314 Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 315 316 By("marie accesses the share") 317 318 // try finding the space by path again 319 lssRes, err := cernboxgw.ListStorageSpaces(ocmCtx, &provider.ListStorageSpacesRequest{ 320 Opaque: &typespb.Opaque{ 321 Map: map[string]*typespb.OpaqueEntry{ 322 "path": { 323 Decoder: "plain", 324 Value: []byte("/public/" + createShareRes.GetShare().GetId().GetOpaqueId()), 325 }, 326 "metadata": { 327 Decoder: "plain", 328 Value: []byte("*"), 329 }, 330 }, 331 }}) 332 Expect(err).ToNot(HaveOccurred()) 333 Expect(lssRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 334 Expect(lssRes.StorageSpaces).To(HaveLen(1), "accepted ocm share should be listed as a space") 335 336 listRes, err = cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) 337 Expect(err).ToNot(HaveOccurred()) 338 Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 339 340 Expect(listRes.Shares).To(HaveLen(1)) 341 342 share = listRes.Shares[0] 343 Expect(share.Protocols).To(HaveLen(1)) 344 Expect(share.State).To(Equal(ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED)) 345 346 protocol := share.Protocols[0] 347 webdav, ok := protocol.Term.(*ocmv1beta1.Protocol_WebdavOptions) 348 Expect(ok).To(BeTrue()) 349 350 webdavClient := newWebDAVClient(webdav.WebdavOptions) 351 d, err := webdavClient.Read(".") 352 Expect(err).ToNot(HaveOccurred()) 353 Expect(d).To(Equal([]byte("test"))) 354 355 err = webdavClient.Write(".", []byte("will-never-be-written"), 0) 356 Expect(err).To(HaveOccurred()) 357 358 By("marie access the share using the ocm mount") 359 ref = &provider.Reference{Path: ocmPath(share.Id, "")} 360 statRes, err := cesnetgw.Stat(ctxMarie, &provider.StatRequest{Ref: ref}) 361 Expect(err).ToNot(HaveOccurred()) 362 Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 363 Expect(statRes.Info.Id).ToNot(BeNil()) 364 checkResourceInfo(statRes.Info, &provider.ResourceInfo{ 365 Name: "new-file", 366 Path: "new-file", 367 Size: 4, 368 Type: provider.ResourceType_RESOURCE_TYPE_FILE, 369 PermissionSet: viewerPermissions, 370 }) 371 372 data, err := helpers.Download(ctxMarie, cesnetgw, ref) 373 Expect(err).ToNot(HaveOccurred()) 374 Expect(data).To(Equal([]byte("test"))) 375 376 Expect(helpers.UploadGateway(ctxMarie, cesnetgw, ref, []byte("will-never-be-written"))).ToNot(Succeed()) 377 }) 378 }) 379 380 Context("einstein shares a file with editor permissions", func() { 381 It("marie is able to modify the content of the file", func() { 382 fileToShare := &provider.Reference{ 383 ResourceId: &storagep.ResourceId{ 384 SpaceId: einstein.Id.OpaqueId, 385 OpaqueId: einstein.Id.OpaqueId, 386 }, 387 Path: "./new-file", 388 } 389 By("creating a file") 390 Expect(helpers.CreateFile(ctxEinstein, cernboxgw, fileToShare, []byte("test"))).To(Succeed()) 391 392 By("share the file with marie") 393 info, err := stat(ctxEinstein, cernboxgw, fileToShare) 394 Expect(err).ToNot(HaveOccurred()) 395 396 cesnet, err := cernboxgw.GetInfoByDomain(ctxEinstein, &ocmproviderpb.GetInfoByDomainRequest{ 397 Domain: "cesnet.cz", 398 }) 399 Expect(err).ToNot(HaveOccurred()) 400 Expect(cesnet.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 401 402 createShareRes, err := cernboxgw.CreateOCMShare(ctxEinstein, &ocmv1beta1.CreateOCMShareRequest{ 403 ResourceId: info.Id, 404 Grantee: &provider.Grantee{ 405 Type: provider.GranteeType_GRANTEE_TYPE_USER, 406 Id: &provider.Grantee_UserId{ 407 UserId: federatedMarieID, 408 }, 409 }, 410 AccessMethods: []*ocmv1beta1.AccessMethod{ 411 share.NewWebDavAccessMethod(conversions.NewEditorRole().CS3ResourcePermissions()), 412 }, 413 RecipientMeshProvider: cesnet.ProviderInfo, 414 }) 415 Expect(err).ToNot(HaveOccurred()) 416 Expect(createShareRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 417 418 By("marie access the share and modify the content of the file") 419 listRes, err := cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) 420 Expect(err).ToNot(HaveOccurred()) 421 Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 422 423 Expect(listRes.Shares).To(HaveLen(1)) 424 425 share := listRes.Shares[0] 426 Expect(share.Protocols).To(HaveLen(1)) 427 428 protocol := share.Protocols[0] 429 webdav, ok := protocol.Term.(*ocmv1beta1.Protocol_WebdavOptions) 430 Expect(ok).To(BeTrue()) 431 432 data := []byte("new-content") 433 webdavClient := newWebDAVClient(webdav.WebdavOptions) 434 err = webdavClient.Write(".", data, 0) 435 Expect(err).ToNot(HaveOccurred()) 436 437 By("check that the file was modified") 438 newContent, err := download(ctxEinstein, cernboxgw, fileToShare) 439 Expect(err).ToNot(HaveOccurred()) 440 Expect(newContent).To(Equal([]byte("new-content"))) 441 442 By("marie access the share using the ocm mount") 443 ref := &provider.Reference{Path: ocmPath(share.Id, "")} 444 statRes, err := cesnetgw.Stat(ctxMarie, &provider.StatRequest{Ref: ref}) 445 Expect(err).ToNot(HaveOccurred()) 446 Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 447 checkResourceInfo(statRes.Info, &provider.ResourceInfo{ 448 Name: "new-file", 449 Path: "new-file", 450 Size: uint64(len(data)), 451 Type: provider.ResourceType_RESOURCE_TYPE_FILE, 452 PermissionSet: editorPermissions, 453 }) 454 455 data, err = helpers.Download(ctxMarie, cesnetgw, ref) 456 Expect(err).ToNot(HaveOccurred()) 457 Expect(data).To(Equal([]byte("new-content"))) 458 459 Expect(helpers.UploadGateway(ctxMarie, cesnetgw, ref, []byte("uploaded-from-ocm-mount"))).To(Succeed()) 460 newContent, err = download(ctxEinstein, cernboxgw, fileToShare) 461 Expect(err).ToNot(HaveOccurred()) 462 Expect(newContent).To(Equal([]byte("uploaded-from-ocm-mount"))) 463 }) 464 }) 465 466 Context("einstein shares a folder with view permissions", func() { 467 It("marie is able to see the content of the folder", func() { 468 structure := helpers.Folder{ 469 "foo": helpers.File{ 470 Content: "foo", 471 }, 472 "dir": helpers.Folder{ 473 "foo": helpers.File{ 474 Content: "dir/foo", 475 }, 476 "bar": helpers.Folder{}, 477 }, 478 } 479 fileToShare := &provider.Reference{ 480 ResourceId: &storagep.ResourceId{ 481 SpaceId: einstein.Id.OpaqueId, 482 OpaqueId: einstein.Id.OpaqueId, 483 }, 484 Path: "./ocm-share-folder", 485 } 486 Expect(helpers.CreateStructure(ctxEinstein, cernboxgw, fileToShare, structure)).To(Succeed()) 487 488 By("share the file with marie") 489 490 info, err := stat(ctxEinstein, cernboxgw, fileToShare) 491 Expect(err).ToNot(HaveOccurred()) 492 493 cesnet, err := cernboxgw.GetInfoByDomain(ctxEinstein, &ocmproviderpb.GetInfoByDomainRequest{ 494 Domain: "cesnet.cz", 495 }) 496 Expect(err).ToNot(HaveOccurred()) 497 Expect(cesnet.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 498 499 createShareRes, err := cernboxgw.CreateOCMShare(ctxEinstein, &ocmv1beta1.CreateOCMShareRequest{ 500 ResourceId: info.Id, 501 Grantee: &provider.Grantee{ 502 Type: provider.GranteeType_GRANTEE_TYPE_USER, 503 Id: &provider.Grantee_UserId{ 504 UserId: federatedMarieID, 505 }, 506 }, 507 AccessMethods: []*ocmv1beta1.AccessMethod{ 508 share.NewWebDavAccessMethod(conversions.NewViewerRole().CS3ResourcePermissions()), 509 }, 510 RecipientMeshProvider: cesnet.ProviderInfo, 511 }) 512 Expect(err).ToNot(HaveOccurred()) 513 Expect(createShareRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 514 515 By("marie accepts the share") 516 listRes, err := cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) 517 Expect(err).ToNot(HaveOccurred()) 518 Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 519 520 Expect(listRes.Shares).To(HaveLen(1)) 521 522 share := listRes.Shares[0] 523 Expect(share.Protocols).To(HaveLen(1)) 524 Expect(share.State).To(Equal(ocmv1beta1.ShareState_SHARE_STATE_PENDING)) 525 526 share.State = ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED 527 _, err = cesnetgw.UpdateReceivedOCMShare(ctxMarie, &ocmv1beta1.UpdateReceivedOCMShareRequest{ 528 Share: share, 529 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}}, 530 }) 531 Expect(err).ToNot(HaveOccurred()) 532 Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 533 534 // get auth context for ocm share 535 ocmCtx := context.Background() 536 authRes, err := cernboxgw.Authenticate(ocmCtx, &gatewaypb.AuthenticateRequest{ 537 Type: "ocmshares", 538 ClientId: createShareRes.GetShare().GetId().GetOpaqueId(), 539 ClientSecret: createShareRes.GetShare().GetToken(), 540 }) 541 Expect(err).ToNot(HaveOccurred()) 542 Expect(authRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 543 544 // create ocm context 545 ocmCtx = ctxpkg.ContextSetToken(ocmCtx, authRes.Token) 546 ocmCtx = metadata.AppendToOutgoingContext(ocmCtx, ctxpkg.TokenHeader, authRes.Token) 547 548 // try finding the space by path again 549 lssRes, err := cernboxgw.ListStorageSpaces(ocmCtx, &provider.ListStorageSpacesRequest{ 550 Opaque: &typespb.Opaque{ 551 Map: map[string]*typespb.OpaqueEntry{ 552 "path": { 553 Decoder: "plain", 554 Value: []byte("/public/" + createShareRes.GetShare().GetId().GetOpaqueId()), 555 }, 556 "metadata": { 557 Decoder: "plain", 558 Value: []byte("*"), 559 }, 560 }, 561 }}) 562 Expect(err).ToNot(HaveOccurred()) 563 Expect(lssRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 564 Expect(lssRes.StorageSpaces).To(HaveLen(1), "accepted ocm share should be listed as a space") 565 566 By("marie see the content of the folder") 567 listRes, err = cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) 568 Expect(err).ToNot(HaveOccurred()) 569 Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 570 571 Expect(listRes.Shares).To(HaveLen(1)) 572 573 share = listRes.Shares[0] 574 Expect(share.Protocols).To(HaveLen(1)) 575 Expect(share.State).To(Equal(ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED)) 576 577 protocol := share.Protocols[0] 578 webdav, ok := protocol.Term.(*ocmv1beta1.Protocol_WebdavOptions) 579 Expect(ok).To(BeTrue()) 580 581 webdavClient := newWebDAVClient(webdav.WebdavOptions) 582 583 ok, err = helpers.SameContentWebDAV(webdavClient, "/", structure) 584 Expect(err).ToNot(HaveOccurred()) 585 Expect(ok).To(BeTrue()) 586 587 By("check that marie does not have permissions to create files") 588 Expect(webdavClient.Write("new-file", []byte("new-file"), 0)).ToNot(Succeed()) 589 590 By("marie access the share using the ocm mount") 591 ref := &provider.Reference{Path: ocmPath(share.Id, "dir")} 592 listFolderRes, err := cesnetgw.ListContainer(ctxMarie, &provider.ListContainerRequest{ 593 Ref: ref, 594 }) 595 Expect(err).ToNot(HaveOccurred()) 596 Expect(listFolderRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 597 checkResourceInfoList(listFolderRes.Infos, []*provider.ResourceInfo{ 598 { 599 Id: &provider.ResourceId{ 600 StorageId: "984e7351-2729-4417-99b4-ab5e6d41fa97", 601 SpaceId: share.Id.OpaqueId, 602 OpaqueId: share.Id.OpaqueId, 603 }, 604 Name: "foo", 605 Path: "foo", 606 Size: 7, 607 Type: provider.ResourceType_RESOURCE_TYPE_FILE, 608 PermissionSet: viewerPermissions, 609 }, 610 { 611 Id: &provider.ResourceId{ 612 StorageId: "984e7351-2729-4417-99b4-ab5e6d41fa97", 613 SpaceId: share.Id.OpaqueId, 614 OpaqueId: share.Id.OpaqueId, 615 }, 616 Name: "bar", 617 Path: "bar", 618 Size: 0, 619 Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, 620 PermissionSet: viewerPermissions, 621 }, 622 }) 623 624 newFile := &provider.Reference{Path: ocmPath(share.Id, "dir/new")} 625 Expect(helpers.UploadGateway(ctxMarie, cesnetgw, newFile, []byte("uploaded-from-ocm-mount"))).ToNot(Succeed()) 626 }) 627 }) 628 629 Context("einstein shares a folder with editor permissions", func() { 630 It("marie is able to see the content and upload resources", func() { 631 structure := helpers.Folder{ 632 "foo": helpers.File{ 633 Content: "foo", 634 }, 635 "dir": helpers.Folder{ 636 "foo": helpers.File{ 637 Content: "dir/foo", 638 }, 639 "bar": helpers.Folder{}, 640 }, 641 } 642 fileToShare := &provider.Reference{ 643 ResourceId: &storagep.ResourceId{ 644 SpaceId: einstein.Id.OpaqueId, 645 OpaqueId: einstein.Id.OpaqueId, 646 }, 647 Path: "./ocm-share-folder", 648 } 649 650 Expect(helpers.CreateStructure(ctxEinstein, cernboxgw, fileToShare, structure)).To(Succeed()) 651 652 By("share the file with marie") 653 654 info, err := stat(ctxEinstein, cernboxgw, fileToShare) 655 Expect(err).ToNot(HaveOccurred()) 656 657 cesnet, err := cernboxgw.GetInfoByDomain(ctxEinstein, &ocmproviderpb.GetInfoByDomainRequest{ 658 Domain: "cesnet.cz", 659 }) 660 Expect(err).ToNot(HaveOccurred()) 661 Expect(cesnet.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 662 663 createShareRes, err := cernboxgw.CreateOCMShare(ctxEinstein, &ocmv1beta1.CreateOCMShareRequest{ 664 ResourceId: info.Id, 665 Grantee: &provider.Grantee{ 666 Type: provider.GranteeType_GRANTEE_TYPE_USER, 667 Id: &provider.Grantee_UserId{ 668 UserId: federatedMarieID, 669 }, 670 }, 671 AccessMethods: []*ocmv1beta1.AccessMethod{ 672 share.NewWebDavAccessMethod(conversions.NewEditorRole().CS3ResourcePermissions()), 673 }, 674 RecipientMeshProvider: cesnet.ProviderInfo, 675 }) 676 Expect(err).ToNot(HaveOccurred()) 677 Expect(createShareRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 678 679 By("marie can upload a file") 680 listRes, err := cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) 681 Expect(err).ToNot(HaveOccurred()) 682 Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 683 684 Expect(listRes.Shares).To(HaveLen(1)) 685 686 share := listRes.Shares[0] 687 Expect(share.Protocols).To(HaveLen(1)) 688 689 protocol := share.Protocols[0] 690 webdav, ok := protocol.Term.(*ocmv1beta1.Protocol_WebdavOptions) 691 Expect(ok).To(BeTrue()) 692 693 webdavClient := newWebDAVClient(webdav.WebdavOptions) 694 data := []byte("new-content") 695 Expect(webdavClient.Write("new-file", data, 0)).To(Succeed()) 696 697 data = []byte("new-file") 698 Expect(webdavClient.Write("new-file", data, 0)).To(Succeed()) 699 Expect(helpers.SameContentWebDAV(webdavClient, fileToShare.Path, helpers.Folder{ 700 "foo": helpers.File{ 701 Content: "foo", 702 }, 703 "dir": helpers.Folder{ 704 "foo": helpers.File{ 705 Content: "dir/foo", 706 }, 707 "bar": helpers.Folder{}, 708 }, 709 "new-file": helpers.File{ 710 Content: "new-file", 711 }, 712 })) 713 714 By("marie access the share using the ocm mount") 715 ref := &provider.Reference{Path: ocmPath(share.Id, "dir")} 716 listFolderRes, err := cesnetgw.ListContainer(ctxMarie, &provider.ListContainerRequest{ 717 Ref: ref, 718 }) 719 Expect(err).ToNot(HaveOccurred()) 720 Expect(listFolderRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 721 checkResourceInfoList(listFolderRes.Infos, []*provider.ResourceInfo{ 722 { 723 Id: &provider.ResourceId{ 724 StorageId: "984e7351-2729-4417-99b4-ab5e6d41fa97", 725 OpaqueId: share.Id.OpaqueId, 726 SpaceId: share.Id.OpaqueId, 727 }, 728 Name: "foo", 729 Path: "foo", 730 Size: 7, 731 Type: provider.ResourceType_RESOURCE_TYPE_FILE, 732 PermissionSet: editorPermissions, 733 }, 734 { 735 Id: &provider.ResourceId{ 736 StorageId: "984e7351-2729-4417-99b4-ab5e6d41fa97", 737 OpaqueId: share.Id.OpaqueId, 738 SpaceId: share.Id.OpaqueId, 739 }, 740 Name: "bar", 741 Path: "bar", 742 Size: 0, 743 Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, 744 PermissionSet: editorPermissions, 745 }, 746 }) 747 748 // create a new file 749 newFile := &provider.Reference{Path: ocmPath(share.Id, "dir/new-file")} 750 Expect(helpers.UploadGateway(ctxMarie, cesnetgw, newFile, []byte("uploaded-from-ocm-mount"))).To(Succeed()) 751 Expect(helpers.SameContentWebDAV(webdavClient, fileToShare.Path, helpers.Folder{ 752 "foo": helpers.File{ 753 Content: "foo", 754 }, 755 "dir": helpers.Folder{ 756 "foo": helpers.File{ 757 Content: "dir/foo", 758 }, 759 "bar": helpers.Folder{}, 760 "new-file": helpers.File{ 761 Content: "uploaded-from-ocm-mount", 762 }, 763 }, 764 "new-file": helpers.File{ 765 Content: "new-file", 766 }, 767 })) 768 769 // create a new directory 770 newDir := &provider.Reference{Path: ocmPath(share.Id, "dir/new-dir")} 771 createDirRes, err := cesnetgw.CreateContainer(ctxMarie, &provider.CreateContainerRequest{ 772 Ref: newDir, 773 }) 774 Expect(err).ToNot(HaveOccurred()) 775 Expect(createDirRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 776 Expect(helpers.SameContentWebDAV(webdavClient, fileToShare.Path, helpers.Folder{ 777 "foo": helpers.File{ 778 Content: "foo", 779 }, 780 "dir": helpers.Folder{ 781 "foo": helpers.File{ 782 Content: "dir/foo", 783 }, 784 "bar": helpers.Folder{}, 785 "new-file": helpers.File{ 786 Content: "uploaded-from-ocm-mount", 787 }, 788 "new-dir": helpers.Folder{}, 789 }, 790 "new-file": helpers.File{ 791 Content: "new-file", 792 }, 793 })) 794 }) 795 }) 796 797 Context("einstein creates twice the share to marie", func() { 798 It("fail with already existing error", func() { 799 fileToShare := &provider.Reference{ 800 ResourceId: &storagep.ResourceId{ 801 SpaceId: einstein.Id.OpaqueId, 802 OpaqueId: einstein.Id.OpaqueId, 803 }, 804 Path: "./double-share", 805 } 806 Expect(helpers.CreateFolder(ctxEinstein, cernboxgw, fileToShare)).To(Succeed()) 807 808 By("share the file with marie") 809 810 info, err := stat(ctxEinstein, cernboxgw, fileToShare) 811 Expect(err).ToNot(HaveOccurred()) 812 813 cesnet, err := cernboxgw.GetInfoByDomain(ctxEinstein, &ocmproviderpb.GetInfoByDomainRequest{ 814 Domain: "cesnet.cz", 815 }) 816 Expect(err).ToNot(HaveOccurred()) 817 Expect(cesnet.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 818 819 createShareRes, err := cernboxgw.CreateOCMShare(ctxEinstein, &ocmv1beta1.CreateOCMShareRequest{ 820 ResourceId: info.Id, 821 Grantee: &provider.Grantee{ 822 Type: provider.GranteeType_GRANTEE_TYPE_USER, 823 Id: &provider.Grantee_UserId{ 824 UserId: federatedMarieID, 825 }, 826 }, 827 AccessMethods: []*ocmv1beta1.AccessMethod{ 828 share.NewWebDavAccessMethod(conversions.NewEditorRole().CS3ResourcePermissions()), 829 }, 830 RecipientMeshProvider: cesnet.ProviderInfo, 831 }) 832 Expect(err).ToNot(HaveOccurred()) 833 Expect(createShareRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 834 }) 835 }) 836 837 Context("einstein creates a share on a not existing resource", func() { 838 It("fail with not found error", func() { 839 cesnet, err := cernboxgw.GetInfoByDomain(ctxEinstein, &ocmproviderpb.GetInfoByDomainRequest{ 840 Domain: "cesnet.cz", 841 }) 842 Expect(err).ToNot(HaveOccurred()) 843 Expect(cesnet.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) 844 845 createShareRes, err := cernboxgw.CreateOCMShare(ctxEinstein, &ocmv1beta1.CreateOCMShareRequest{ 846 ResourceId: &provider.ResourceId{StorageId: "123e4567-e89b-12d3-a456-426655440000", OpaqueId: "NON_EXISTING_FILE"}, 847 Grantee: &provider.Grantee{ 848 Type: provider.GranteeType_GRANTEE_TYPE_USER, 849 Id: &provider.Grantee_UserId{ 850 UserId: federatedMarieID, 851 }, 852 }, 853 AccessMethods: []*ocmv1beta1.AccessMethod{ 854 share.NewWebDavAccessMethod(conversions.NewEditorRole().CS3ResourcePermissions()), 855 }, 856 RecipientMeshProvider: cesnet.ProviderInfo, 857 }) 858 Expect(err).ToNot(HaveOccurred()) 859 Expect(createShareRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND)) 860 }) 861 }) 862 863 }) 864 }) 865 866 func stat(ctx context.Context, gw gatewaypb.GatewayAPIClient, ref *provider.Reference) (*provider.ResourceInfo, error) { 867 statRes, err := gw.Stat(ctx, &provider.StatRequest{Ref: ref}) 868 if err != nil { 869 return nil, err 870 } 871 if statRes.Status.Code != rpcv1beta1.Code_CODE_OK { 872 return nil, errors.New(statRes.Status.Message) 873 } 874 return statRes.Info, nil 875 } 876 877 func download(ctx context.Context, gw gatewaypb.GatewayAPIClient, ref *provider.Reference) ([]byte, error) { 878 initRes, err := gw.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{Ref: ref}) 879 if err != nil { 880 return nil, err 881 } 882 883 var token, endpoint string 884 for _, p := range initRes.Protocols { 885 // if p.Protocol == "simple" { 886 token, endpoint = p.Token, p.DownloadEndpoint 887 // } 888 } 889 httpReq, err := rhttp.NewRequest(ctx, http.MethodGet, endpoint, nil) 890 if err != nil { 891 return nil, err 892 } 893 894 httpReq.Header.Set(datagateway.TokenTransportHeader, token) 895 896 httpRes, err := http.DefaultClient.Do(httpReq) 897 if err != nil { 898 return nil, err 899 } 900 defer httpRes.Body.Close() 901 902 return io.ReadAll(httpRes.Body) 903 } 904 905 func ocmPath(id *ocmv1beta1.ShareId, p string) string { 906 return filepath.Join("/ocm", id.OpaqueId, p) 907 } 908 909 func checkResourceInfo(info, target *provider.ResourceInfo) { 910 Expect(info.Name).To(Equal(target.Name)) 911 Expect(info.Path).To(Equal(target.Path)) 912 Expect(info.Size).To(Equal(target.Size)) 913 Expect(info.Type).To(Equal(target.Type)) 914 Expect(info.PermissionSet).To(Equal(target.PermissionSet)) 915 } 916 917 func mapResourceInfos(l []*provider.ResourceInfo) map[string]*provider.ResourceInfo { 918 m := make(map[string]*provider.ResourceInfo) 919 for _, e := range l { 920 m[e.Path] = e 921 } 922 return m 923 } 924 925 func checkResourceInfoList(l1, l2 []*provider.ResourceInfo) { 926 m1, m2 := mapResourceInfos(l1), mapResourceInfos(l2) 927 Expect(l1).To(HaveLen(len(l2))) 928 929 for k, ri1 := range m1 { 930 ri2, ok := m2[k] 931 Expect(ok).To(BeTrue()) 932 checkResourceInfo(ri1, ri2) 933 } 934 } 935 936 func newWebDAVClient(options *ocmv1beta1.WebDAVProtocol) *gowebdav.Client { 937 webdavClient := gowebdav.NewAuthClient(options.Uri, gowebdav.NewPreemptiveAuth(ocm.BearerAuthenticator{Token: options.SharedSecret})) 938 webdavClient.SetInterceptor(func(method string, rq *http.Request) { 939 if rq.Body == nil { 940 return 941 } 942 943 buf := &bytes.Buffer{} 944 n, _ := io.Copy(buf, rq.Body) 945 rq.Body = io.NopCloser(buf) 946 947 rq.Header.Add(net.HeaderContentLength, strconv.Itoa(int(n))) 948 // Set the content length on the request struct directly instead of the header. 949 // The content-length header gets reset by the golang http library before 950 // sendind out the request, resulting in chunked encoding to be used which 951 // breaks the quota checks in ocdav. 952 if method == "PUT" { 953 rq.ContentLength = n 954 } 955 }) 956 return webdavClient 957 }