diff --git a/pkg/storage/utils/decomposedfs/recycle_test.go b/pkg/storage/utils/decomposedfs/recycle_test.go index b17c1e1807..a273024b42 100644 --- a/pkg/storage/utils/decomposedfs/recycle_test.go +++ b/pkg/storage/utils/decomposedfs/recycle_test.go @@ -24,6 +24,7 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/mocks" helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -43,12 +44,21 @@ var _ = Describe("Recycle", func() { }) Context("with sufficient permissions", func() { - BeforeEach(func() { - }) - When("a user deletes files from the same space", func() { + + BeforeEach(func() { + // in this scenario user "userid" has this permissions: + registerPermissions(env.Permissions, "userid", &provider.ResourcePermissions{ + InitiateFileUpload: true, + Delete: true, + ListRecycle: true, + PurgeRecycle: true, + RestoreRecycleItem: true, + GetQuota: true, + }) + }) + JustBeforeEach(func() { - env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(2) err := env.Fs.Delete(env.Ctx, &provider.Reference{ ResourceId: env.SpaceRootRes, Path: "/dir1/file1", @@ -63,22 +73,20 @@ var _ = Describe("Recycle", func() { }) It("they are stored in the same trashbin", func() { - env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(1) items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(2)) }) It("they do not count towards the quota anymore", func() { - env.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything).Return(provider.ResourcePermissions{GetQuota: true}, nil).Times(1) _, used, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}) Expect(err).ToNot(HaveOccurred()) Expect(used).To(Equal(uint64(0))) }) It("they can be permanently deleted by this user", func() { + // mock call to blobstore env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) - env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(4) items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") Expect(err).ToNot(HaveOccurred()) @@ -94,6 +102,21 @@ var _ = Describe("Recycle", func() { Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(0)) }) + + It("they can be restored", func() { + env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) + + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + Expect(err).ToNot(HaveOccurred()) + Expect(len(items)).To(Equal(2)) + + err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/", nil) + Expect(err).ToNot(HaveOccurred()) + + items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + Expect(err).ToNot(HaveOccurred()) + Expect(len(items)).To(Equal(1)) + }) }) When("two users delete files from the same space", func() { @@ -108,10 +131,27 @@ var _ = Describe("Recycle", func() { }, Username: "anotherusername", }) + + // in this scenario user "userid" has this permissions: + registerPermissions(env.Permissions, "userid", &provider.ResourcePermissions{ + InitiateFileUpload: true, + Delete: true, + ListRecycle: true, + PurgeRecycle: true, + RestoreRecycleItem: true, + }) + + // and user "anotheruserid" has the same permissions: + registerPermissions(env.Permissions, "anotheruserid", &provider.ResourcePermissions{ + InitiateFileUpload: true, + Delete: true, + ListRecycle: true, + PurgeRecycle: true, + RestoreRecycleItem: true, + }) }) JustBeforeEach(func() { - env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(2) err := env.Fs.Delete(env.Ctx, &provider.Reference{ ResourceId: env.SpaceRootRes, Path: "/dir1/file1", @@ -127,7 +167,6 @@ var _ = Describe("Recycle", func() { }) It("they are stored in the same trashbin (for both users)", func() { - env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(2) itemsA, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") Expect(err).ToNot(HaveOccurred()) Expect(len(itemsA)).To(Equal(2)) @@ -141,7 +180,6 @@ var _ = Describe("Recycle", func() { It("they can be permanently deleted by the other user", func() { env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) - env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(4) items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") Expect(err).ToNot(HaveOccurred()) @@ -168,18 +206,56 @@ var _ = Describe("Recycle", func() { Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(0)) }) + + It("they can be restored by the other user", func() { + env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) + + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + Expect(err).ToNot(HaveOccurred()) + Expect(len(items)).To(Equal(2)) + + // pick correct ctx + var ctx1, ctx2 context.Context + switch items[0].Type { + case provider.ResourceType_RESOURCE_TYPE_FILE: + ctx1 = env.Ctx + ctx2 = ctx + case provider.ResourceType_RESOURCE_TYPE_CONTAINER: + ctx1 = ctx + ctx2 = env.Ctx + } + + err = env.Fs.RestoreRecycleItem(ctx1, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/", nil) + Expect(err).ToNot(HaveOccurred()) + + err = env.Fs.RestoreRecycleItem(ctx2, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "/", nil) + Expect(err).ToNot(HaveOccurred()) + + items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + Expect(err).ToNot(HaveOccurred()) + Expect(len(items)).To(Equal(0)) + }) }) When("a user deletes files from different spaces", func() { BeforeEach(func() { var err error - projectID, err = env.CreateTestStorageSpace("project") + projectID, err = env.CreateTestStorageSpace("project", &provider.Quota{QuotaMaxBytes: 2000}) Expect(err).ToNot(HaveOccurred()) Expect(projectID).ToNot(BeNil()) + + // in this scenario user "userid" has this permissions: + registerPermissions(env.Permissions, "userid", &provider.ResourcePermissions{ + InitiateFileUpload: true, + Delete: true, + ListRecycle: true, + PurgeRecycle: true, + RestoreRecycleItem: true, + GetQuota: true, + }) }) JustBeforeEach(func() { - env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(2) err := env.Fs.Delete(env.Ctx, &provider.Reference{ ResourceId: env.SpaceRootRes, Path: "/dir1/file1", @@ -194,7 +270,6 @@ var _ = Describe("Recycle", func() { }) It("they are stored in different trashbins", func() { - env.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(2) items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) @@ -207,6 +282,25 @@ var _ = Describe("Recycle", func() { Expect(recycled1).ToNot(Equal(recycled2)) }) + + It("they can excess the spaces quota if restored", func() { + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: projectID}, "", "/") + Expect(err).ToNot(HaveOccurred()) + Expect(len(items)).To(Equal(1)) + + // use up 2000 byte quota + _, err = env.CreateTestFile("largefile", "largefile-blobid", 2000, projectID.OpaqueId) + Expect(err).ToNot(HaveOccurred()) + + err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: projectID}, items[0].Key, "/", nil) + Expect(err).ToNot(HaveOccurred()) + + max, used, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: projectID}) + Expect(err).ToNot(HaveOccurred()) + Expect(max).To(Equal(uint64(2000))) + Expect(used).To(Equal(uint64(3234))) + }) + }) }) Context("with insufficient permissions", func() { @@ -222,38 +316,20 @@ var _ = Describe("Recycle", func() { Username: "readusername", }) - // need user with access ... - env.Permissions.On("HasPermission", - mock.MatchedBy(func(ctx context.Context) bool { - return ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == "userid" - }), - mock.Anything, - mock.Anything, - ).Return(true, nil) - - // and user with read access ... - env.Permissions.On("HasPermission", - mock.MatchedBy(func(ctx context.Context) bool { - return ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == "readuserid" - }), - mock.Anything, - mock.MatchedBy(func(f func(*provider.ResourcePermissions) bool) bool { - return f(&provider.ResourcePermissions{ListRecycle: true}) - }), - ).Return(true, nil) - env.Permissions.On("HasPermission", - mock.MatchedBy(func(ctx context.Context) bool { - return ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == "readuserid" - }), - mock.Anything, - mock.MatchedBy(func(f func(*provider.ResourcePermissions) bool) bool { - return f(&provider.ResourcePermissions{ - PurgeRecycle: true, - Delete: true, - }) - }), - ).Return(false, nil) + // in this scenario user "userid" has this permissions: + registerPermissions(env.Permissions, "userid", &provider.ResourcePermissions{ + Delete: true, + ListRecycle: true, + PurgeRecycle: true, + RestoreRecycleItem: true, + }) + + // and user "readuserid" has this permissions: + registerPermissions(env.Permissions, "readuserid", &provider.ResourcePermissions{ + ListRecycle: true, + }) }) + It("can list the trashbin", func() { err := env.Fs.Delete(env.Ctx, &provider.Reference{ ResourceId: env.SpaceRootRes, @@ -294,6 +370,22 @@ var _ = Describe("Recycle", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("permission denied")) }) + + It("cannot restore files from trashbin", func() { + err := env.Fs.Delete(env.Ctx, &provider.Reference{ + ResourceId: env.SpaceRootRes, + Path: "/dir1/file1", + }) + Expect(err).ToNot(HaveOccurred()) + + items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + Expect(err).ToNot(HaveOccurred()) + Expect(len(items)).To(Equal(1)) + + err = env.Fs.RestoreRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/", nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("permission denied")) + }) }) }) @@ -308,23 +400,20 @@ var _ = Describe("Recycle", func() { }, Username: "mrhacker", }) - env.Permissions.On("HasPermission", - mock.MatchedBy(func(ctx context.Context) bool { - return ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == "userid" - }), - mock.Anything, - mock.Anything, - ).Return(true, nil) - env.Permissions.On("HasPermission", - mock.MatchedBy(func(ctx context.Context) bool { - return ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == "hacker" - }), - mock.Anything, - mock.Anything, - ).Return(false, nil) + + // in this scenario user "userid" has this permissions: + registerPermissions(env.Permissions, "userid", &provider.ResourcePermissions{ + Delete: true, + ListRecycle: true, + PurgeRecycle: true, + RestoreRecycleItem: true, + }) + + // and user "hacker" has no permissions: + registerPermissions(env.Permissions, "hacker", &provider.ResourcePermissions{}) }) - It("cannot delete, list or purge", func() { + It("cannot delete, list, purge or restore", func() { err := env.Fs.Delete(ctx, &provider.Reference{ ResourceId: env.SpaceRootRes, Path: "/dir1/file1", @@ -349,6 +438,45 @@ var _ = Describe("Recycle", func() { err = env.Fs.PurgeRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("permission denied")) + + err = env.Fs.RestoreRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/", nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("permission denied")) }) }) }) + +func registerPermissions(m *mocks.PermissionsChecker, uid string, exp *provider.ResourcePermissions) { + // add positives + m.On("HasPermission", + mock.MatchedBy(func(ctx context.Context) bool { + return uid == "" || ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == uid + }), + mock.Anything, + mock.MatchedBy(func(r func(*provider.ResourcePermissions) bool) bool { + return exp == nil || r(exp) + }), + ).Return(true, nil) + + // add negatives + if exp != nil { + m.On("HasPermission", + mock.MatchedBy(func(ctx context.Context) bool { + return uid == "" || ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == uid + }), + mock.Anything, + mock.Anything, + ).Return(false, nil) + } + + p := provider.ResourcePermissions{} + if exp != nil { + p = *exp + } + m.On("AssemblePermissions", + mock.MatchedBy(func(ctx context.Context) bool { + return uid == "" || ctxpkg.ContextMustGetUser(ctx).Id.OpaqueId == uid + }), + mock.Anything, + ).Return(p, nil) +} diff --git a/pkg/storage/utils/decomposedfs/testhelpers/helpers.go b/pkg/storage/utils/decomposedfs/testhelpers/helpers.go index d07c7d467e..dc2194bb97 100644 --- a/pkg/storage/utils/decomposedfs/testhelpers/helpers.go +++ b/pkg/storage/utils/decomposedfs/testhelpers/helpers.go @@ -107,7 +107,7 @@ func NewTestEnv() (*TestEnv, error) { Ctx: ctx, } - env.SpaceRootRes, err = env.CreateTestStorageSpace("personal") + env.SpaceRootRes, err = env.CreateTestStorageSpace("personal", nil) return env, err } @@ -171,11 +171,12 @@ func (t *TestEnv) CreateTestFile(name, blobID string, blobSize int64, parentID s // /dir1/ // /dir1/file1 // /dir1/subdir1 -func (t *TestEnv) CreateTestStorageSpace(typ string) (*providerv1beta1.ResourceId, error) { +func (t *TestEnv) CreateTestStorageSpace(typ string, quota *providerv1beta1.Quota) (*providerv1beta1.ResourceId, error) { t.Permissions.On("HasPermission", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Times(1) // Permissions required for setup below space, err := t.Fs.CreateStorageSpace(t.Ctx, &providerv1beta1.CreateStorageSpaceRequest{ Owner: t.Owner, Type: typ, + Quota: quota, }) if err != nil { return nil, err