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  }