github.com/cs3org/reva/v2@v2.27.7/pkg/publicshare/manager/cs3/cs3_test.go (about) 1 // Copyright 2018-2022 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 cs3_test 20 21 import ( 22 "context" 23 "encoding/json" 24 "os" 25 "path" 26 "strings" 27 "sync" 28 "time" 29 30 . "github.com/onsi/ginkgo/v2" 31 . "github.com/onsi/gomega" 32 "github.com/stretchr/testify/mock" 33 "golang.org/x/crypto/bcrypt" 34 "google.golang.org/protobuf/proto" 35 36 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 37 link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" 38 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 39 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 40 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 41 "github.com/cs3org/reva/v2/pkg/errtypes" 42 "github.com/cs3org/reva/v2/pkg/publicshare" 43 "github.com/cs3org/reva/v2/pkg/publicshare/manager/cs3" 44 indexerpkg "github.com/cs3org/reva/v2/pkg/storage/utils/indexer" 45 indexermocks "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/mocks" 46 "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" 47 storagemocks "github.com/cs3org/reva/v2/pkg/storage/utils/metadata/mocks" 48 "github.com/cs3org/reva/v2/pkg/utils" 49 ) 50 51 var _ = Describe("Cs3", func() { 52 var ( 53 m publicshare.Manager 54 user *userpb.User 55 ctx context.Context 56 57 indexer indexerpkg.Indexer 58 storage *storagemocks.Storage 59 60 ri *provider.ResourceInfo 61 grant *link.Grant 62 share *link.PublicShare 63 64 tmpdir string 65 ) 66 67 BeforeEach(func() { 68 var err error 69 tmpdir, err = os.MkdirTemp("", "cs3-publicshare-test") 70 Expect(err).ToNot(HaveOccurred()) 71 72 ds, err := metadata.NewDiskStorage(tmpdir) 73 Expect(err).ToNot(HaveOccurred()) 74 indexer = indexerpkg.CreateIndexer(ds) 75 76 storage = &storagemocks.Storage{} 77 storage.On("Init", mock.Anything, mock.Anything).Return(nil) 78 storage.On("MakeDirIfNotExist", mock.Anything, mock.Anything).Return(nil) 79 storage.On("SimpleUpload", mock.Anything, mock.MatchedBy(func(in string) bool { 80 return strings.HasPrefix(in, "publicshares/") 81 }), mock.Anything).Return(nil) 82 user = &userpb.User{ 83 Id: &userpb.UserId{ 84 Idp: "localhost:1111", 85 OpaqueId: "1", 86 }, 87 } 88 ctx = ctxpkg.ContextSetUser(context.Background(), user) 89 90 share = &link.PublicShare{ 91 Id: &link.PublicShareId{OpaqueId: "1"}, 92 Token: "abcd", 93 } 94 95 ri = &provider.ResourceInfo{ 96 Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, 97 Path: "/share1", 98 Id: &provider.ResourceId{OpaqueId: "sharedId"}, 99 Owner: user.Id, 100 PermissionSet: &provider.ResourcePermissions{ 101 Stat: true, 102 }, 103 Size: 10, 104 } 105 grant = &link.Grant{ 106 Permissions: &link.PublicSharePermissions{ 107 Permissions: &provider.ResourcePermissions{AddGrant: true}, 108 }, 109 } 110 }) 111 112 JustBeforeEach(func() { 113 var err error 114 m, err = cs3.New(nil, storage, indexer, bcrypt.DefaultCost) 115 Expect(err).ToNot(HaveOccurred()) 116 }) 117 118 AfterEach(func() { 119 if tmpdir != "" { 120 os.RemoveAll(tmpdir) 121 } 122 }) 123 124 Describe("New", func() { 125 It("returns a new instance", func() { 126 m, err := cs3.New(nil, storage, indexer, bcrypt.DefaultCost) 127 Expect(err).ToNot(HaveOccurred()) 128 Expect(m).ToNot(BeNil()) 129 }) 130 }) 131 132 Describe("CreatePublicShare", func() { 133 It("creates a new share and adds it to the index", func() { 134 link, err := m.CreatePublicShare(ctx, user, ri, grant) 135 Expect(err).ToNot(HaveOccurred()) 136 Expect(link).ToNot(BeNil()) 137 Expect(link.Token).ToNot(Equal("")) 138 Expect(link.PasswordProtected).To(BeFalse()) 139 }) 140 141 It("sets 'PasswordProtected' and stores the password hash if a password is set", func() { 142 grant.Password = "secret123" 143 144 link, err := m.CreatePublicShare(ctx, user, ri, grant) 145 Expect(err).ToNot(HaveOccurred()) 146 Expect(link).ToNot(BeNil()) 147 Expect(link.Token).ToNot(Equal("")) 148 Expect(link.PasswordProtected).To(BeTrue()) 149 storage.AssertCalled(GinkgoT(), "SimpleUpload", mock.Anything, mock.Anything, mock.MatchedBy(func(in []byte) bool { 150 ps := publicshare.WithPassword{} 151 err = json.Unmarshal(in, &ps) 152 Expect(err).ToNot(HaveOccurred()) 153 return bcrypt.CompareHashAndPassword([]byte(ps.Password), []byte("secret123")) == nil 154 })) 155 }) 156 157 It("picks up the displayname from the metadata", func() { 158 ri.ArbitraryMetadata = &provider.ArbitraryMetadata{ 159 Metadata: map[string]string{ 160 "name": "metadata name", 161 }, 162 } 163 164 link, err := m.CreatePublicShare(ctx, user, ri, grant) 165 Expect(err).ToNot(HaveOccurred()) 166 Expect(link).ToNot(BeNil()) 167 Expect(link.DisplayName).To(Equal("metadata name")) 168 }) 169 }) 170 171 Context("with an existing share", func() { 172 var ( 173 existingShare *link.PublicShare 174 hashedPassword string 175 ) 176 177 JustBeforeEach(func() { 178 grant.Password = "foo" 179 var err error 180 existingShare, err = m.CreatePublicShare(ctx, user, ri, grant) 181 Expect(err).ToNot(HaveOccurred()) 182 183 h, err := bcrypt.GenerateFromPassword([]byte(grant.Password), bcrypt.DefaultCost) 184 Expect(err).ToNot(HaveOccurred()) 185 hashedPassword = string(h) 186 tmpShare := &publicshare.WithPassword{ 187 Password: hashedPassword, 188 } 189 proto.Merge(&tmpShare.PublicShare, existingShare) 190 shareJSON, err := json.Marshal(tmpShare) 191 Expect(err).ToNot(HaveOccurred()) 192 storage.On("SimpleDownload", mock.Anything, mock.MatchedBy(func(in string) bool { 193 return strings.HasPrefix(in, "publicshares/") 194 })).Return(shareJSON, nil) 195 }) 196 197 Describe("Load", func() { 198 It("loads shares including state and mountpoint information", func() { 199 m, err := cs3.New(nil, storage, indexer, bcrypt.DefaultCost) 200 Expect(err).ToNot(HaveOccurred()) 201 202 sharesChan := make(chan *publicshare.WithPassword) 203 204 wg := sync.WaitGroup{} 205 wg.Add(2) 206 go func() { 207 err := m.Load(ctx, sharesChan) 208 Expect(err).ToNot(HaveOccurred()) 209 wg.Done() 210 }() 211 go func() { 212 tmpShare := &publicshare.WithPassword{ 213 Password: hashedPassword, 214 } 215 proto.Merge(&tmpShare.PublicShare, existingShare) 216 sharesChan <- tmpShare 217 close(sharesChan) 218 wg.Done() 219 }() 220 wg.Wait() 221 Eventually(sharesChan).Should(BeClosed()) 222 223 expectedPath := path.Join("publicshares", existingShare.Token) 224 storage.AssertCalled(GinkgoT(), "SimpleUpload", mock.Anything, expectedPath, mock.Anything) 225 }) 226 }) 227 228 Describe("ListPublicShares", func() { 229 It("lists existing shares", func() { 230 shares, err := m.ListPublicShares(ctx, user, []*link.ListPublicSharesRequest_Filter{}, false) 231 Expect(err).ToNot(HaveOccurred()) 232 Expect(len(shares)).To(Equal(1)) 233 Expect(shares[0].Signature).To(BeNil()) 234 }) 235 236 It("adds a signature", func() { 237 shares, err := m.ListPublicShares(ctx, user, []*link.ListPublicSharesRequest_Filter{}, true) 238 Expect(err).ToNot(HaveOccurred()) 239 Expect(len(shares)).To(Equal(1)) 240 Expect(shares[0].Signature).ToNot(BeNil()) 241 }) 242 243 It("filters by id", func() { 244 shares, err := m.ListPublicShares(ctx, user, []*link.ListPublicSharesRequest_Filter{ 245 publicshare.ResourceIDFilter(&provider.ResourceId{OpaqueId: "UnknownId"}), 246 }, false) 247 Expect(err).ToNot(HaveOccurred()) 248 Expect(len(shares)).To(Equal(0)) 249 }) 250 251 It("filters by storage", func() { 252 shares, err := m.ListPublicShares(ctx, user, []*link.ListPublicSharesRequest_Filter{ 253 publicshare.StorageIDFilter("unknownstorage"), 254 }, false) 255 Expect(err).ToNot(HaveOccurred()) 256 Expect(len(shares)).To(Equal(0)) 257 }) 258 259 Context("when the share has expired", func() { 260 BeforeEach(func() { 261 t := time.Date(2022, time.January, 1, 12, 0, 0, 0, time.UTC) 262 grant.Expiration = &typespb.Timestamp{ 263 Seconds: uint64(t.Unix()), 264 } 265 storage.On("Delete", mock.Anything, mock.Anything).Return(nil, nil) 266 }) 267 268 It("does not consider the share", func() { 269 shares, err := m.ListPublicShares(ctx, user, []*link.ListPublicSharesRequest_Filter{}, false) 270 Expect(err).ToNot(HaveOccurred()) 271 Expect(len(shares)).To(Equal(0)) 272 }) 273 }) 274 }) 275 276 Describe("GetPublicShare", func() { 277 It("gets the public share by token", func() { 278 returnedShare, err := m.GetPublicShare(ctx, user, &link.PublicShareReference{ 279 Spec: &link.PublicShareReference_Token{ 280 Token: share.Token, 281 }, 282 }, false) 283 Expect(err).ToNot(HaveOccurred()) 284 Expect(returnedShare).ToNot(BeNil()) 285 Expect(returnedShare.Id.OpaqueId).To(Equal(existingShare.Id.OpaqueId)) 286 Expect(returnedShare.Token).To(Equal(existingShare.Token)) 287 }) 288 289 It("gets the public share by id", func() { 290 returnedShare, err := m.GetPublicShare(ctx, user, &link.PublicShareReference{ 291 Spec: &link.PublicShareReference_Id{ 292 Id: &link.PublicShareId{ 293 OpaqueId: existingShare.Id.OpaqueId, 294 }, 295 }, 296 }, false) 297 Expect(err).ToNot(HaveOccurred()) 298 Expect(returnedShare).ToNot(BeNil()) 299 Expect(returnedShare.Signature).To(BeNil()) 300 }) 301 302 It("adds a signature", func() { 303 returnedShare, err := m.GetPublicShare(ctx, user, &link.PublicShareReference{ 304 Spec: &link.PublicShareReference_Id{ 305 Id: &link.PublicShareId{ 306 OpaqueId: existingShare.Id.OpaqueId, 307 }, 308 }, 309 }, true) 310 Expect(err).ToNot(HaveOccurred()) 311 Expect(returnedShare).ToNot(BeNil()) 312 Expect(returnedShare.Signature).ToNot(BeNil()) 313 }) 314 }) 315 316 Describe("RevokePublicShare", func() { 317 var ( 318 mockIndexer *indexermocks.Indexer 319 ) 320 BeforeEach(func() { 321 mockIndexer = &indexermocks.Indexer{} 322 mockIndexer.On("AddIndex", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) 323 mockIndexer.On("Add", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) 324 mockIndexer.On("Delete", mock.Anything, mock.Anything).Return(nil, nil) 325 mockIndexer.On("FindBy", mock.Anything, mock.Anything, mock.Anything).Return([]string{existingShare.Token}, nil) 326 327 indexer = mockIndexer 328 }) 329 330 It("deletes the share by token", func() { 331 storage.On("Delete", mock.Anything, mock.Anything).Return(nil) 332 ref := &link.PublicShareReference{ 333 Spec: &link.PublicShareReference_Token{ 334 Token: existingShare.Token, 335 }, 336 } 337 err := m.RevokePublicShare(ctx, user, ref) 338 Expect(err).ToNot(HaveOccurred()) 339 storage.AssertCalled(GinkgoT(), "Delete", mock.Anything, path.Join("publicshares", existingShare.Token)) 340 }) 341 342 It("deletes the share by id", func() { 343 storage.On("Delete", mock.Anything, mock.Anything).Return(nil) 344 ref := &link.PublicShareReference{ 345 Spec: &link.PublicShareReference_Id{ 346 Id: existingShare.Id, 347 }, 348 } 349 err := m.RevokePublicShare(ctx, user, ref) 350 Expect(err).ToNot(HaveOccurred()) 351 storage.AssertCalled(GinkgoT(), "Delete", mock.Anything, path.Join("publicshares", existingShare.Token)) 352 }) 353 354 It("still removes the share from the index when the share itself couldn't be found", func() { 355 storage.On("Delete", mock.Anything, mock.Anything).Return(errtypes.NotFound("")) 356 ref := &link.PublicShareReference{ 357 Spec: &link.PublicShareReference_Token{ 358 Token: existingShare.Token, 359 }, 360 } 361 err := m.RevokePublicShare(ctx, user, ref) 362 Expect(err).ToNot(HaveOccurred()) 363 364 mockIndexer.AssertCalled(GinkgoT(), "Delete", mock.Anything, mock.Anything) 365 }) 366 367 It("does not removes the share from the index when the share itself couldn't be found", func() { 368 storage.On("Delete", mock.Anything, mock.Anything).Return(errtypes.InternalError("")) 369 ref := &link.PublicShareReference{ 370 Spec: &link.PublicShareReference_Token{ 371 Token: existingShare.Token, 372 }, 373 } 374 err := m.RevokePublicShare(ctx, user, ref) 375 Expect(err).To(HaveOccurred()) 376 377 mockIndexer.AssertNotCalled(GinkgoT(), "Delete", mock.Anything, mock.Anything) 378 }) 379 }) 380 381 Describe("GetPublicShareByToken", func() { 382 It("doesn't get the share using a wrong password", func() { 383 auth := &link.PublicShareAuthentication{ 384 Spec: &link.PublicShareAuthentication_Password{ 385 Password: "wroooong", 386 }, 387 } 388 ps, err := m.GetPublicShareByToken(ctx, existingShare.Token, auth, false) 389 Expect(err).To(HaveOccurred()) 390 Expect(ps).To(BeNil()) 391 }) 392 393 It("gets the share using a password", func() { 394 auth := &link.PublicShareAuthentication{ 395 Spec: &link.PublicShareAuthentication_Password{ 396 Password: grant.Password, 397 }, 398 } 399 ps, err := m.GetPublicShareByToken(ctx, existingShare.Token, auth, false) 400 Expect(err).ToNot(HaveOccurred()) 401 Expect(ps).ToNot(BeNil()) 402 }) 403 404 It("gets the share using a signature", func() { 405 err := publicshare.AddSignature(existingShare, hashedPassword) 406 Expect(err).ToNot(HaveOccurred()) 407 auth := &link.PublicShareAuthentication{ 408 Spec: &link.PublicShareAuthentication_Signature{ 409 Signature: existingShare.Signature, 410 }, 411 } 412 ps, err := m.GetPublicShareByToken(ctx, existingShare.Token, auth, false) 413 Expect(err).ToNot(HaveOccurred()) 414 Expect(ps).ToNot(BeNil()) 415 416 }) 417 418 Context("when the share has expired", func() { 419 BeforeEach(func() { 420 t := time.Date(2022, time.January, 1, 12, 0, 0, 0, time.UTC) 421 grant.Expiration = &typespb.Timestamp{ 422 Seconds: uint64(t.Unix()), 423 } 424 }) 425 It("it doesn't consider expired shares", func() { 426 auth := &link.PublicShareAuthentication{ 427 Spec: &link.PublicShareAuthentication_Password{ 428 Password: grant.Password, 429 }, 430 } 431 ps, err := m.GetPublicShareByToken(ctx, existingShare.Token, auth, false) 432 Expect(err).To(HaveOccurred()) 433 Expect(ps).To(BeNil()) 434 }) 435 }) 436 }) 437 438 Describe("UpdatePublicShare", func() { 439 var ( 440 ref *link.PublicShareReference 441 ) 442 443 JustBeforeEach(func() { 444 ref = &link.PublicShareReference{ 445 Spec: &link.PublicShareReference_Token{ 446 Token: existingShare.Token, 447 }, 448 } 449 }) 450 451 It("fails when an invalid reference is given", func() { 452 _, err := m.UpdatePublicShare(ctx, user, &link.UpdatePublicShareRequest{ 453 Ref: &link.PublicShareReference{Spec: &link.PublicShareReference_Id{Id: &link.PublicShareId{OpaqueId: "doesnotexist"}}}, 454 }) 455 Expect(err).To(HaveOccurred()) 456 }) 457 458 It("fails when no valid update request is given", func() { 459 _, err := m.UpdatePublicShare(ctx, user, &link.UpdatePublicShareRequest{ 460 Ref: ref, 461 Update: &link.UpdatePublicShareRequest_Update{}, 462 }) 463 Expect(err).To(HaveOccurred()) 464 }) 465 466 It("updates the display name", func() { 467 ps, err := m.UpdatePublicShare(ctx, user, &link.UpdatePublicShareRequest{ 468 Ref: ref, 469 Update: &link.UpdatePublicShareRequest_Update{ 470 Type: link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME, 471 DisplayName: "new displayname", 472 }, 473 }) 474 Expect(err).ToNot(HaveOccurred()) 475 Expect(ps).ToNot(BeNil()) 476 Expect(ps.DisplayName).To(Equal("new displayname")) 477 storage.AssertCalled(GinkgoT(), "SimpleUpload", mock.Anything, path.Join("publicshares", ps.Token), mock.MatchedBy(func(data []byte) bool { 478 s := publicshare.WithPassword{} 479 err := json.Unmarshal(data, &s) 480 Expect(err).ToNot(HaveOccurred()) 481 return s.PublicShare.DisplayName == "new displayname" 482 })) 483 }) 484 485 It("updates the password", func() { 486 ps, err := m.UpdatePublicShare(ctx, user, &link.UpdatePublicShareRequest{ 487 Ref: ref, 488 Update: &link.UpdatePublicShareRequest_Update{ 489 Type: link.UpdatePublicShareRequest_Update_TYPE_PASSWORD, 490 Grant: &link.Grant{Password: "NewPass"}, 491 }, 492 }) 493 Expect(err).ToNot(HaveOccurred()) 494 Expect(ps).ToNot(BeNil()) 495 storage.AssertCalled(GinkgoT(), "SimpleUpload", mock.Anything, path.Join("publicshares", ps.Token), mock.MatchedBy(func(data []byte) bool { 496 s := publicshare.WithPassword{} 497 err := json.Unmarshal(data, &s) 498 Expect(err).ToNot(HaveOccurred()) 499 return s.Password != "" 500 })) 501 }) 502 503 It("updates the permissions", func() { 504 ps, err := m.UpdatePublicShare(ctx, user, &link.UpdatePublicShareRequest{ 505 Ref: ref, 506 Update: &link.UpdatePublicShareRequest_Update{ 507 Type: link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS, 508 Grant: &link.Grant{Permissions: &link.PublicSharePermissions{Permissions: &provider.ResourcePermissions{Delete: true}}}, 509 }, 510 }) 511 Expect(err).ToNot(HaveOccurred()) 512 Expect(ps).ToNot(BeNil()) 513 storage.AssertCalled(GinkgoT(), "SimpleUpload", mock.Anything, path.Join("publicshares", ps.Token), mock.MatchedBy(func(data []byte) bool { 514 s := publicshare.WithPassword{} 515 err := json.Unmarshal(data, &s) 516 Expect(err).ToNot(HaveOccurred()) 517 return s.PublicShare.Permissions.Permissions.Delete 518 })) 519 }) 520 521 It("updates the expiration", func() { 522 ts := utils.TSNow() 523 ps, err := m.UpdatePublicShare(ctx, user, &link.UpdatePublicShareRequest{ 524 Ref: ref, 525 Update: &link.UpdatePublicShareRequest_Update{ 526 Type: link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION, 527 Grant: &link.Grant{Expiration: utils.TSNow()}, 528 }, 529 }) 530 Expect(err).ToNot(HaveOccurred()) 531 Expect(ps).ToNot(BeNil()) 532 storage.AssertCalled(GinkgoT(), "SimpleUpload", mock.Anything, path.Join("publicshares", ps.Token), mock.MatchedBy(func(data []byte) bool { 533 s := publicshare.WithPassword{} 534 err := json.Unmarshal(data, &s) 535 Expect(err).ToNot(HaveOccurred()) 536 return s.PublicShare.Expiration != nil && s.PublicShare.Expiration.Seconds == ts.Seconds 537 })) 538 }) 539 }) 540 }) 541 })