github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/recycle_test.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package decomposedfs_test 20 21 import ( 22 "context" 23 24 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 25 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 26 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 27 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions/mocks" 28 helpers "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/testhelpers" 29 . "github.com/onsi/ginkgo/v2" 30 . "github.com/onsi/gomega" 31 "github.com/stretchr/testify/mock" 32 ) 33 34 var _ = Describe("Recycle", func() { 35 var ( 36 env *helpers.TestEnv 37 projectID *provider.ResourceId 38 ) 39 40 BeforeEach(func() { 41 var err error 42 env, err = helpers.NewTestEnv(nil) 43 Expect(err).ToNot(HaveOccurred()) 44 }) 45 46 Context("with sufficient permissions", func() { 47 When("a user deletes files from the same space", func() { 48 49 BeforeEach(func() { 50 // in this scenario user "25b69780-5f39-43be-a7ac-a9b9e9fe4230" has this permissions: 51 registerPermissions(env.Permissions, "25b69780-5f39-43be-a7ac-a9b9e9fe4230", &provider.ResourcePermissions{ 52 InitiateFileUpload: true, 53 Delete: true, 54 ListRecycle: true, 55 PurgeRecycle: true, 56 RestoreRecycleItem: true, 57 GetQuota: true, 58 }) 59 }) 60 61 JustBeforeEach(func() { 62 err := env.Fs.Delete(env.Ctx, &provider.Reference{ 63 ResourceId: env.SpaceRootRes, 64 Path: "/dir1/file1", 65 }) 66 Expect(err).ToNot(HaveOccurred()) 67 68 err = env.Fs.Delete(env.Ctx, &provider.Reference{ 69 ResourceId: env.SpaceRootRes, 70 Path: "/dir1/subdir1", 71 }) 72 Expect(err).ToNot(HaveOccurred()) 73 }) 74 75 It("they are stored in the same trashbin", func() { 76 items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 77 Expect(err).ToNot(HaveOccurred()) 78 Expect(len(items)).To(Equal(2)) 79 }) 80 81 It("they do not count towards the quota anymore", func() { 82 _, used, _, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}) 83 Expect(err).ToNot(HaveOccurred()) 84 Expect(used).To(Equal(uint64(0))) 85 }) 86 87 It("they can be permanently deleted by this user", func() { 88 // mock call to blobstore 89 env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) 90 91 items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 92 Expect(err).ToNot(HaveOccurred()) 93 Expect(len(items)).To(Equal(2)) 94 95 err = env.Fs.PurgeRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "") 96 Expect(err).ToNot(HaveOccurred()) 97 98 err = env.Fs.PurgeRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "") 99 Expect(err).ToNot(HaveOccurred()) 100 101 items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 102 Expect(err).ToNot(HaveOccurred()) 103 Expect(len(items)).To(Equal(0)) 104 }) 105 106 It("they can be restored", func() { 107 env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) 108 109 items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 110 Expect(err).ToNot(HaveOccurred()) 111 Expect(len(items)).To(Equal(2)) 112 113 err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "", nil) 114 Expect(err).ToNot(HaveOccurred()) 115 116 items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 117 Expect(err).ToNot(HaveOccurred()) 118 Expect(len(items)).To(Equal(1)) 119 }) 120 }) 121 122 When("two users delete files from the same space", func() { 123 var ctx context.Context 124 125 BeforeEach(func() { 126 ctx = ctxpkg.ContextSetUser(context.Background(), &userpb.User{ 127 Id: &userpb.UserId{ 128 Idp: "anotheridp", 129 OpaqueId: "anotheruserid", 130 Type: userpb.UserType_USER_TYPE_PRIMARY, 131 }, 132 Username: "anotherusername", 133 }) 134 135 // in this scenario user "25b69780-5f39-43be-a7ac-a9b9e9fe4230" has this permissions: 136 registerPermissions(env.Permissions, "25b69780-5f39-43be-a7ac-a9b9e9fe4230", &provider.ResourcePermissions{ 137 InitiateFileUpload: true, 138 Delete: true, 139 ListRecycle: true, 140 PurgeRecycle: true, 141 RestoreRecycleItem: true, 142 }) 143 144 // and user "anotheruserid" has the same permissions: 145 registerPermissions(env.Permissions, "anotheruserid", &provider.ResourcePermissions{ 146 InitiateFileUpload: true, 147 Delete: true, 148 ListRecycle: true, 149 PurgeRecycle: true, 150 RestoreRecycleItem: true, 151 }) 152 }) 153 154 JustBeforeEach(func() { 155 err := env.Fs.Delete(env.Ctx, &provider.Reference{ 156 ResourceId: env.SpaceRootRes, 157 Path: "/dir1/file1", 158 }) 159 Expect(err).ToNot(HaveOccurred()) 160 161 err = env.Fs.Delete(ctx, &provider.Reference{ 162 ResourceId: env.SpaceRootRes, 163 Path: "/dir1/subdir1", 164 }) 165 Expect(err).ToNot(HaveOccurred()) 166 167 }) 168 169 It("they are stored in the same trashbin (for both users)", func() { 170 itemsA, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 171 Expect(err).ToNot(HaveOccurred()) 172 Expect(len(itemsA)).To(Equal(2)) 173 itemsB, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 174 Expect(err).ToNot(HaveOccurred()) 175 Expect(len(itemsB)).To(Equal(2)) 176 Expect(itemsA).To(ConsistOf(itemsB)) 177 }) 178 179 It("they can be permanently deleted by the other user", func() { 180 env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) 181 182 items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 183 Expect(err).ToNot(HaveOccurred()) 184 Expect(len(items)).To(Equal(2)) 185 186 // pick correct ctx 187 var ctx1, ctx2 context.Context 188 switch items[0].Type { 189 case provider.ResourceType_RESOURCE_TYPE_FILE: 190 ctx1 = env.Ctx 191 ctx2 = ctx 192 case provider.ResourceType_RESOURCE_TYPE_CONTAINER: 193 ctx1 = ctx 194 ctx2 = env.Ctx 195 } 196 197 err = env.Fs.PurgeRecycleItem(ctx1, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "") 198 Expect(err).ToNot(HaveOccurred()) 199 200 err = env.Fs.PurgeRecycleItem(ctx2, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "") 201 Expect(err).ToNot(HaveOccurred()) 202 203 items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 204 Expect(err).ToNot(HaveOccurred()) 205 Expect(len(items)).To(Equal(0)) 206 }) 207 208 It("they can be restored by the other user", func() { 209 env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) 210 211 items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 212 Expect(err).ToNot(HaveOccurred()) 213 Expect(len(items)).To(Equal(2)) 214 215 // pick correct ctx 216 var ctx1, ctx2 context.Context 217 switch items[0].Type { 218 case provider.ResourceType_RESOURCE_TYPE_FILE: 219 ctx1 = env.Ctx 220 ctx2 = ctx 221 case provider.ResourceType_RESOURCE_TYPE_CONTAINER: 222 ctx1 = ctx 223 ctx2 = env.Ctx 224 } 225 226 err = env.Fs.RestoreRecycleItem(ctx1, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "", nil) 227 Expect(err).ToNot(HaveOccurred()) 228 229 err = env.Fs.RestoreRecycleItem(ctx2, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "", nil) 230 Expect(err).ToNot(HaveOccurred()) 231 232 items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 233 Expect(err).ToNot(HaveOccurred()) 234 Expect(len(items)).To(Equal(0)) 235 }) 236 }) 237 238 When("a user deletes files from different spaces", func() { 239 BeforeEach(func() { 240 var err error 241 projectID, err = env.CreateTestStorageSpace("project", &provider.Quota{QuotaMaxBytes: 2000}) 242 Expect(err).ToNot(HaveOccurred()) 243 Expect(projectID).ToNot(BeNil()) 244 245 // in this scenario user "25b69780-5f39-43be-a7ac-a9b9e9fe4230" has this permissions: 246 registerPermissions(env.Permissions, "25b69780-5f39-43be-a7ac-a9b9e9fe4230", &provider.ResourcePermissions{ 247 Stat: true, 248 InitiateFileUpload: true, 249 Delete: true, 250 ListRecycle: true, 251 PurgeRecycle: true, 252 RestoreRecycleItem: true, 253 GetQuota: true, 254 }) 255 }) 256 257 JustBeforeEach(func() { 258 err := env.Fs.Delete(env.Ctx, &provider.Reference{ 259 ResourceId: env.SpaceRootRes, 260 Path: "/dir1/file1", 261 }) 262 Expect(err).ToNot(HaveOccurred()) 263 264 err = env.Fs.Delete(env.Ctx, &provider.Reference{ 265 ResourceId: projectID, 266 Path: "/dir1/file1", 267 }) 268 Expect(err).ToNot(HaveOccurred()) 269 }) 270 271 It("they are stored in different trashbins", func() { 272 items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 273 Expect(err).ToNot(HaveOccurred()) 274 Expect(len(items)).To(Equal(1)) 275 recycled1 := items[0] 276 277 items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: projectID}, "", "") 278 Expect(err).ToNot(HaveOccurred()) 279 Expect(len(items)).To(Equal(1)) 280 recycled2 := items[0] 281 282 Expect(recycled1).ToNot(Equal(recycled2)) 283 }) 284 285 It("they can excess the spaces quota if restored", func() { 286 items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: projectID}, "", "") 287 Expect(err).ToNot(HaveOccurred()) 288 Expect(len(items)).To(Equal(1)) 289 290 // use up 2000 byte quota 291 _, err = env.CreateTestFile("largefile", "largefile-blobid", projectID.OpaqueId, projectID.SpaceId, 2000) 292 Expect(err).ToNot(HaveOccurred()) 293 294 err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: projectID}, items[0].Key, "", nil) 295 Expect(err).ToNot(HaveOccurred()) 296 297 max, used, remaining, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: projectID}) 298 Expect(err).ToNot(HaveOccurred()) 299 Expect(max).To(Equal(uint64(2000))) 300 Expect(used).To(Equal(uint64(3234))) 301 Expect(remaining).To(Equal(uint64(0))) 302 }) 303 304 }) 305 }) 306 Context("with insufficient permissions", func() { 307 When("a user who can only read from a drive", func() { 308 var ctx context.Context 309 BeforeEach(func() { 310 ctx = ctxpkg.ContextSetUser(context.Background(), &userpb.User{ 311 Id: &userpb.UserId{ 312 Idp: "readidp", 313 OpaqueId: "readuserid", 314 Type: userpb.UserType_USER_TYPE_PRIMARY, 315 }, 316 Username: "readusername", 317 }) 318 319 // in this scenario user "25b69780-5f39-43be-a7ac-a9b9e9fe4230" has this permissions: 320 registerPermissions(env.Permissions, "25b69780-5f39-43be-a7ac-a9b9e9fe4230", &provider.ResourcePermissions{ 321 Stat: true, 322 Delete: true, 323 ListRecycle: true, 324 PurgeRecycle: true, 325 RestoreRecycleItem: true, 326 InitiateFileUpload: true, 327 }) 328 329 // and user "readuserid" has this permissions: 330 registerPermissions(env.Permissions, "readuserid", &provider.ResourcePermissions{ 331 Stat: true, 332 ListRecycle: true, 333 }) 334 }) 335 336 It("can list the trashbin", func() { 337 err := env.Fs.Delete(env.Ctx, &provider.Reference{ 338 ResourceId: env.SpaceRootRes, 339 Path: "/dir1/file1", 340 }) 341 Expect(err).ToNot(HaveOccurred()) 342 343 items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 344 Expect(err).ToNot(HaveOccurred()) 345 Expect(len(items)).To(Equal(1)) 346 }) 347 348 It("cannot delete files", func() { 349 err := env.Fs.Delete(ctx, &provider.Reference{ 350 ResourceId: env.SpaceRootRes, 351 Path: "/dir1/file1", 352 }) 353 Expect(err).To(HaveOccurred()) 354 Expect(err.Error()).To(ContainSubstring("permission denied")) 355 356 items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 357 Expect(err).ToNot(HaveOccurred()) 358 Expect(len(items)).To(Equal(0)) 359 }) 360 361 It("cannot purge files from trashbin", func() { 362 err := env.Fs.Delete(env.Ctx, &provider.Reference{ 363 ResourceId: env.SpaceRootRes, 364 Path: "/dir1/file1", 365 }) 366 Expect(err).ToNot(HaveOccurred()) 367 368 items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 369 Expect(err).ToNot(HaveOccurred()) 370 Expect(len(items)).To(Equal(1)) 371 372 err = env.Fs.PurgeRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "") 373 Expect(err).To(HaveOccurred()) 374 Expect(err.Error()).To(ContainSubstring("permission denied")) 375 }) 376 377 It("cannot restore files from trashbin", func() { 378 err := env.Fs.Delete(env.Ctx, &provider.Reference{ 379 ResourceId: env.SpaceRootRes, 380 Path: "/dir1/file1", 381 }) 382 Expect(err).ToNot(HaveOccurred()) 383 384 items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 385 Expect(err).ToNot(HaveOccurred()) 386 Expect(len(items)).To(Equal(1)) 387 388 err = env.Fs.RestoreRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "", nil) 389 Expect(err).To(HaveOccurred()) 390 Expect(err.Error()).To(ContainSubstring("permission denied")) 391 }) 392 }) 393 }) 394 395 When("a user who cannot read from a drive", func() { 396 var ctx context.Context 397 BeforeEach(func() { 398 ctx = ctxpkg.ContextSetUser(context.Background(), &userpb.User{ 399 Id: &userpb.UserId{ 400 Idp: "maliciousidp", 401 OpaqueId: "h-a-c-k-er", 402 Type: userpb.UserType_USER_TYPE_PRIMARY, 403 }, 404 Username: "mrhacker", 405 }) 406 407 // in this scenario user "userid" has this permissions: 408 registerPermissions(env.Permissions, "25b69780-5f39-43be-a7ac-a9b9e9fe4230", &provider.ResourcePermissions{ 409 Stat: true, 410 Delete: true, 411 ListRecycle: true, 412 PurgeRecycle: true, 413 RestoreRecycleItem: true, 414 InitiateFileUpload: true, 415 }) 416 417 // and user "hacker" has no permissions: 418 registerPermissions(env.Permissions, "h-a-c-k-er", &provider.ResourcePermissions{}) 419 }) 420 421 It("cannot delete, list, purge or restore", func() { 422 err := env.Fs.Delete(ctx, &provider.Reference{ 423 ResourceId: env.SpaceRootRes, 424 Path: "/dir1/file1", 425 }) 426 Expect(err).To(HaveOccurred()) 427 Expect(err.Error()).To(ContainSubstring("not found")) 428 429 err = env.Fs.Delete(env.Ctx, &provider.Reference{ 430 ResourceId: env.SpaceRootRes, 431 Path: "/dir1/file1", 432 }) 433 Expect(err).ToNot(HaveOccurred()) 434 435 _, err = env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 436 Expect(err).To(HaveOccurred()) 437 Expect(err.Error()).To(ContainSubstring("not found")) 438 439 items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") 440 Expect(err).ToNot(HaveOccurred()) 441 Expect(len(items)).To(Equal(1)) 442 443 err = env.Fs.PurgeRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "") 444 Expect(err).To(HaveOccurred()) 445 Expect(err.Error()).To(ContainSubstring("not found")) 446 447 err = env.Fs.RestoreRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "", nil) 448 Expect(err).To(HaveOccurred()) 449 Expect(err.Error()).To(ContainSubstring("not found")) 450 }) 451 }) 452 }) 453 454 func registerPermissions(m *mocks.PermissionsChecker, uid string, exp *provider.ResourcePermissions) { 455 p := &provider.ResourcePermissions{} 456 if exp != nil { 457 p = exp 458 } 459 m.On("AssemblePermissions", 460 mock.MatchedBy(func(ctx context.Context) bool { 461 return uid == "" || ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == uid 462 }), 463 mock.Anything, 464 ).Return(p, nil) 465 m.On("AssembleTrashPermissions", 466 mock.MatchedBy(func(ctx context.Context) bool { 467 return uid == "" || ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == uid 468 }), 469 mock.Anything, 470 ).Return(p, nil) 471 }