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  }