github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/upload_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  	"bytes"
    23  	"context"
    24  	"io"
    25  	"os"
    26  
    27  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    28  	cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
    29  	v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    30  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    31  	ruser "github.com/cs3org/reva/v2/pkg/ctx"
    32  	"github.com/cs3org/reva/v2/pkg/errtypes"
    33  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    34  	"github.com/cs3org/reva/v2/pkg/storage"
    35  	"github.com/cs3org/reva/v2/pkg/storage/cache"
    36  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs"
    37  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects"
    38  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup"
    39  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata"
    40  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
    41  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
    42  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options"
    43  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions"
    44  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions/mocks"
    45  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/timemanager"
    46  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree"
    47  	treemocks "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/mocks"
    48  	"github.com/cs3org/reva/v2/pkg/storagespace"
    49  	"github.com/cs3org/reva/v2/pkg/store"
    50  	"github.com/cs3org/reva/v2/tests/helpers"
    51  	"github.com/rs/zerolog"
    52  	"github.com/stretchr/testify/mock"
    53  	"google.golang.org/grpc"
    54  
    55  	. "github.com/onsi/ginkgo/v2"
    56  	. "github.com/onsi/gomega"
    57  )
    58  
    59  var _ = Describe("File uploads", func() {
    60  	var (
    61  		ref     *provider.Reference
    62  		rootRef *provider.Reference
    63  		fs      storage.FS
    64  		user    *userpb.User
    65  		ctx     context.Context
    66  
    67  		o                    *options.Options
    68  		lu                   *lookup.Lookup
    69  		pmock                *mocks.PermissionsChecker
    70  		cs3permissionsclient *mocks.CS3PermissionsClient
    71  		permissionsSelector  pool.Selectable[cs3permissions.PermissionsAPIClient]
    72  		bs                   *treemocks.Blobstore
    73  	)
    74  
    75  	BeforeEach(func() {
    76  		ref = &provider.Reference{
    77  			ResourceId: &provider.ResourceId{
    78  				SpaceId: "u-s-e-r-id",
    79  			},
    80  			Path: "/foo",
    81  		}
    82  
    83  		user = &userpb.User{
    84  			Id: &userpb.UserId{
    85  				Idp:      "idp",
    86  				OpaqueId: "u-s-e-r-id",
    87  				Type:     userpb.UserType_USER_TYPE_PRIMARY,
    88  			},
    89  			Username: "username",
    90  		}
    91  
    92  		rootRef = &provider.Reference{
    93  			ResourceId: &provider.ResourceId{
    94  				SpaceId:  "u-s-e-r-id",
    95  				OpaqueId: "u-s-e-r-id",
    96  			},
    97  			Path: "/",
    98  		}
    99  
   100  		ctx = ruser.ContextSetUser(context.Background(), user)
   101  
   102  		tmpRoot, err := helpers.TempDir("reva-unit-tests-*-root")
   103  		Expect(err).ToNot(HaveOccurred())
   104  
   105  		o, err = options.New(map[string]interface{}{
   106  			"root": tmpRoot,
   107  		})
   108  		Expect(err).ToNot(HaveOccurred())
   109  		lu = lookup.New(metadata.NewXattrsBackend(o.Root, cache.Config{}), o, &timemanager.Manager{})
   110  		pmock = &mocks.PermissionsChecker{}
   111  		cs3permissionsclient = &mocks.CS3PermissionsClient{}
   112  		pool.RemoveSelector("PermissionsSelector" + "any")
   113  		permissionsSelector = pool.GetSelector[cs3permissions.PermissionsAPIClient](
   114  			"PermissionsSelector",
   115  			"any",
   116  			func(cc grpc.ClientConnInterface) cs3permissions.PermissionsAPIClient {
   117  				return cs3permissionsclient
   118  			},
   119  		)
   120  
   121  		bs = &treemocks.Blobstore{}
   122  	})
   123  
   124  	AfterEach(func() {
   125  		root := o.Root
   126  		if root != "" {
   127  			os.RemoveAll(root)
   128  		}
   129  	})
   130  
   131  	BeforeEach(func() {
   132  		cs3permissionsclient.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything).Return(&cs3permissions.CheckPermissionResponse{
   133  			Status: &v1beta11.Status{Code: v1beta11.Code_CODE_OK},
   134  		}, nil)
   135  		pmock.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{
   136  			Stat:     true,
   137  			AddGrant: true,
   138  		}, nil).Times(1)
   139  		var err error
   140  		tree := tree.New(lu, bs, o, store.Create(), &zerolog.Logger{})
   141  
   142  		aspects := aspects.Aspects{
   143  			Lookup:      lu,
   144  			Tree:        tree,
   145  			Permissions: permissions.NewPermissions(pmock, permissionsSelector),
   146  			Trashbin:    &decomposedfs.DecomposedfsTrashbin{},
   147  		}
   148  		fs, err = decomposedfs.New(o, aspects, &zerolog.Logger{})
   149  		Expect(err).ToNot(HaveOccurred())
   150  
   151  		resp, err := fs.CreateStorageSpace(ctx, &provider.CreateStorageSpaceRequest{Owner: user, Type: "personal"})
   152  		Expect(err).ToNot(HaveOccurred())
   153  		Expect(resp.Status.Code).To(Equal(v1beta11.Code_CODE_OK))
   154  		resID, err := storagespace.ParseID(resp.StorageSpace.Id.OpaqueId)
   155  		Expect(err).ToNot(HaveOccurred())
   156  		ref.ResourceId = &resID
   157  	})
   158  
   159  	Context("the user's quota is exceeded", func() {
   160  		BeforeEach(func() {
   161  			pmock.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{
   162  				Stat:     true,
   163  				GetQuota: true,
   164  			}, nil)
   165  		})
   166  		When("the user wants to initiate a file upload", func() {
   167  			It("fails", func() {
   168  				var originalFunc = node.CheckQuota
   169  				node.CheckQuota = func(ctx context.Context, spaceRoot *node.Node, overwrite bool, oldSize, newSize uint64) (quotaSufficient bool, err error) {
   170  					return false, errtypes.InsufficientStorage("quota exceeded")
   171  				}
   172  				_, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{})
   173  				Expect(err).To(MatchError(errtypes.InsufficientStorage("quota exceeded")))
   174  				node.CheckQuota = originalFunc
   175  			})
   176  		})
   177  	})
   178  
   179  	Context("the user has insufficient permissions", func() {
   180  		BeforeEach(func() {
   181  			pmock.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{
   182  				Stat: true,
   183  			}, nil)
   184  		})
   185  
   186  		When("the user wants to initiate a file upload", func() {
   187  			It("fails", func() {
   188  				msg := "error: permission denied: u-s-e-r-id!u-s-e-r-id/foo"
   189  				_, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{})
   190  				Expect(err).To(MatchError(msg))
   191  			})
   192  		})
   193  	})
   194  
   195  	Context("with insufficient permissions, home node", func() {
   196  		JustBeforeEach(func() {
   197  			var err error
   198  			// the space name attribute is the stop condition in the lookup
   199  			h, err := lu.NodeFromResource(ctx, rootRef)
   200  			Expect(err).ToNot(HaveOccurred())
   201  			err = h.SetXattrString(ctx, prefixes.SpaceNameAttr, "username")
   202  			Expect(err).ToNot(HaveOccurred())
   203  			pmock.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{
   204  				Stat: true,
   205  			}, nil)
   206  		})
   207  
   208  		When("the user wants to initiate a file upload", func() {
   209  			It("fails", func() {
   210  				msg := "error: permission denied: u-s-e-r-id!u-s-e-r-id/foo"
   211  				_, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{})
   212  				Expect(err).To(MatchError(msg))
   213  			})
   214  		})
   215  	})
   216  
   217  	Context("with sufficient permissions", func() {
   218  		BeforeEach(func() {
   219  			pmock.On("AssemblePermissions", mock.Anything, mock.Anything).
   220  				Return(&provider.ResourcePermissions{
   221  					Stat:               true,
   222  					GetQuota:           true,
   223  					InitiateFileUpload: true,
   224  					ListContainer:      true,
   225  				}, nil)
   226  		})
   227  
   228  		When("the user initiates a non zero byte file upload", func() {
   229  			It("succeeds", func() {
   230  				uploadIds, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{})
   231  
   232  				Expect(err).ToNot(HaveOccurred())
   233  				Expect(len(uploadIds)).To(Equal(2))
   234  				Expect(uploadIds["simple"]).ToNot(BeEmpty())
   235  				Expect(uploadIds["tus"]).ToNot(BeEmpty())
   236  
   237  				resources, err := fs.ListFolder(ctx, rootRef, []string{}, []string{})
   238  				Expect(err).ToNot(HaveOccurred())
   239  				Expect(len(resources)).To(Equal(0))
   240  			})
   241  		})
   242  
   243  		When("the user initiates a zero byte file upload", func() {
   244  			It("succeeds", func() {
   245  				bs.On("Upload", mock.AnythingOfType("*node.Node"), mock.AnythingOfType("string"), mock.Anything).
   246  					Return(nil)
   247  				uploadIds, err := fs.InitiateUpload(ctx, ref, 0, map[string]string{})
   248  
   249  				Expect(err).ToNot(HaveOccurred())
   250  				Expect(len(uploadIds)).To(Equal(2))
   251  				Expect(uploadIds["simple"]).ToNot(BeEmpty())
   252  				Expect(uploadIds["tus"]).ToNot(BeEmpty())
   253  
   254  				resources, err := fs.ListFolder(ctx, rootRef, []string{}, []string{})
   255  				Expect(err).ToNot(HaveOccurred())
   256  				Expect(len(resources)).To(Equal(1))
   257  			})
   258  
   259  			It("fails when trying to upload empty data. 0-byte uploads are finished during initialization already", func() {
   260  				bs.On("Upload", mock.AnythingOfType("*node.Node"), mock.AnythingOfType("string"), mock.Anything).
   261  					Return(nil)
   262  				uploadIds, err := fs.InitiateUpload(ctx, ref, 0, map[string]string{})
   263  
   264  				Expect(err).ToNot(HaveOccurred())
   265  				Expect(len(uploadIds)).To(Equal(2))
   266  				Expect(uploadIds["simple"]).ToNot(BeEmpty())
   267  
   268  				uploadRef := &provider.Reference{Path: "/" + uploadIds["simple"]}
   269  
   270  				_, err = fs.Upload(ctx, storage.UploadRequest{
   271  					Ref:    uploadRef,
   272  					Body:   io.NopCloser(bytes.NewReader([]byte(""))),
   273  					Length: 0,
   274  				}, nil)
   275  
   276  				Expect(err).To(HaveOccurred())
   277  			})
   278  		})
   279  
   280  		When("the user uploads a non zero byte file", func() {
   281  			It("succeeds", func() {
   282  				var (
   283  					fileContent = []byte("0123456789")
   284  				)
   285  
   286  				uploadIds, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{})
   287  
   288  				Expect(err).ToNot(HaveOccurred())
   289  				Expect(len(uploadIds)).To(Equal(2))
   290  				Expect(uploadIds["simple"]).ToNot(BeEmpty())
   291  				Expect(uploadIds["tus"]).ToNot(BeEmpty())
   292  
   293  				uploadRef := &provider.Reference{Path: "/" + uploadIds["simple"]}
   294  
   295  				bs.On("Upload", mock.AnythingOfType("*node.Node"), mock.AnythingOfType("string"), mock.Anything).
   296  					Return(nil).
   297  					Run(func(args mock.Arguments) {
   298  						data, err := os.ReadFile(args.Get(1).(string))
   299  
   300  						Expect(err).ToNot(HaveOccurred())
   301  						Expect(data).To(Equal([]byte("0123456789")))
   302  					})
   303  
   304  				_, err = fs.Upload(ctx, storage.UploadRequest{
   305  					Ref:    uploadRef,
   306  					Body:   io.NopCloser(bytes.NewReader(fileContent)),
   307  					Length: int64(len(fileContent)),
   308  				}, nil)
   309  
   310  				Expect(err).ToNot(HaveOccurred())
   311  				bs.AssertCalled(GinkgoT(), "Upload", mock.Anything, mock.Anything, mock.Anything)
   312  
   313  				resources, err := fs.ListFolder(ctx, rootRef, []string{}, []string{})
   314  
   315  				Expect(err).ToNot(HaveOccurred())
   316  				Expect(len(resources)).To(Equal(1))
   317  				Expect(resources[0].Path).To(Equal(ref.Path))
   318  			})
   319  		})
   320  
   321  		When("the user tries to upload a file without intialising the upload", func() {
   322  			It("fails", func() {
   323  				var (
   324  					fileContent = []byte("0123456789")
   325  				)
   326  
   327  				uploadRef := &provider.Reference{Path: "/some-non-existent-upload-reference"}
   328  				_, err := fs.Upload(ctx, storage.UploadRequest{
   329  					Ref:    uploadRef,
   330  					Body:   io.NopCloser(bytes.NewReader(fileContent)),
   331  					Length: int64(len(fileContent)),
   332  				}, nil)
   333  
   334  				Expect(err).To(HaveOccurred())
   335  
   336  				resources, err := fs.ListFolder(ctx, rootRef, []string{}, []string{})
   337  
   338  				Expect(err).ToNot(HaveOccurred())
   339  				Expect(len(resources)).To(Equal(0))
   340  			})
   341  		})
   342  
   343  	})
   344  })