github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/spaces_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 decomposedfs_test
    20  
    21  import (
    22  	"context"
    23  
    24  	userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    25  	cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
    26  	rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    27  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    28  	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    29  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    30  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
    31  	helpers "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/testhelpers"
    32  	. "github.com/onsi/ginkgo/v2"
    33  	. "github.com/onsi/gomega"
    34  	"github.com/stretchr/testify/mock"
    35  	"google.golang.org/grpc"
    36  )
    37  
    38  var _ = Describe("Spaces", func() {
    39  
    40  	Describe("Create Space", func() {
    41  		var (
    42  			env *helpers.TestEnv
    43  		)
    44  		BeforeEach(func() {
    45  			var err error
    46  			env, err = helpers.NewTestEnv(nil)
    47  			Expect(err).ToNot(HaveOccurred())
    48  			env.PermissionsClient.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything).Return(
    49  				func(ctx context.Context, in *cs3permissions.CheckPermissionRequest, opts ...grpc.CallOption) *cs3permissions.CheckPermissionResponse {
    50  					if in.Permission == "Drives.DeletePersonal" && ctxpkg.ContextMustGetUser(ctx).Id.GetOpaqueId() == env.DeleteHomeSpacesUser.Id.OpaqueId {
    51  						return &cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_OK}}
    52  					}
    53  					if in.Permission == "Drives.DeleteProject" && ctxpkg.ContextMustGetUser(ctx).Id.GetOpaqueId() == env.DeleteAllSpacesUser.Id.OpaqueId {
    54  						return &cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_OK}}
    55  					}
    56  					if (in.Permission == "Drives.Create" || in.Permission == "Drives.List") && ctxpkg.ContextMustGetUser(ctx).Id.GetOpaqueId() == helpers.OwnerID {
    57  						return &cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_OK}}
    58  					}
    59  					// any other user
    60  					return &cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_PERMISSION_DENIED}}
    61  				},
    62  				nil)
    63  			env.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, n *node.Node) *provider.ResourcePermissions {
    64  				if ctxpkg.ContextMustGetUser(ctx).Id.GetOpaqueId() == "25b69780-5f39-43be-a7ac-a9b9e9fe4230" {
    65  					return node.OwnerPermissions() // id of owner/admin
    66  				}
    67  				return node.NoPermissions()
    68  			}, nil)
    69  		})
    70  
    71  		AfterEach(func() {
    72  			if env != nil {
    73  				env.Cleanup()
    74  			}
    75  		})
    76  
    77  		Context("during login", func() {
    78  			It("space is created", func() {
    79  				resp, err := env.Fs.ListStorageSpaces(env.Ctx, nil, false)
    80  				Expect(err).ToNot(HaveOccurred())
    81  				Expect(len(resp)).To(Equal(1))
    82  				Expect(string(resp[0].Opaque.GetMap()["spaceAlias"].Value)).To(Equal("personal/username"))
    83  				Expect(resp[0].Name).To(Equal("username"))
    84  				Expect(resp[0].SpaceType).To(Equal("personal"))
    85  			})
    86  		})
    87  		Context("when creating a space", func() {
    88  			It("project space is created", func() {
    89  				env.Owner = nil
    90  				resp, err := env.Fs.CreateStorageSpace(env.Ctx, &provider.CreateStorageSpaceRequest{Name: "Mission to Mars", Type: "project"})
    91  				Expect(err).ToNot(HaveOccurred())
    92  				Expect(resp.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK))
    93  				Expect(resp.StorageSpace).ToNot(Equal(nil))
    94  				Expect(string(resp.StorageSpace.Opaque.Map["spaceAlias"].Value)).To(Equal("project/mission-to-mars"))
    95  				Expect(resp.StorageSpace.Name).To(Equal("Mission to Mars"))
    96  				Expect(resp.StorageSpace.SpaceType).To(Equal("project"))
    97  			})
    98  		})
    99  
   100  		Context("needs to check node permissions", func() {
   101  			It("returns false on requesting for other user with canlistallspaces und no unrestricted privilege", func() {
   102  				resp := env.Fs.MustCheckNodePermissions(env.Ctx, false)
   103  				Expect(resp).To(Equal(true))
   104  			})
   105  			It("returns true on requesting unrestricted as non-admin", func() {
   106  				ctx := ctxpkg.ContextSetUser(context.Background(), env.Users[0])
   107  				resp := env.Fs.MustCheckNodePermissions(ctx, true)
   108  				Expect(resp).To(Equal(true))
   109  			})
   110  			It("returns true on requesting for own spaces", func() {
   111  				ctx := ctxpkg.ContextSetUser(context.Background(), env.Users[0])
   112  				resp := env.Fs.MustCheckNodePermissions(ctx, false)
   113  				Expect(resp).To(Equal(true))
   114  			})
   115  			It("returns false on unrestricted", func() {
   116  				resp := env.Fs.MustCheckNodePermissions(env.Ctx, true)
   117  				Expect(resp).To(Equal(false))
   118  			})
   119  		})
   120  
   121  		Context("can delete homespace", func() {
   122  			It("fails on trying to delete a homespace as non-admin", func() {
   123  				ctx := ctxpkg.ContextSetUser(context.Background(), env.Users[1])
   124  				resp, err := env.Fs.ListStorageSpaces(env.Ctx, nil, false)
   125  				Expect(err).ToNot(HaveOccurred())
   126  				Expect(len(resp)).To(Equal(1))
   127  				Expect(string(resp[0].Opaque.GetMap()["spaceAlias"].Value)).To(Equal("personal/username"))
   128  				Expect(resp[0].Name).To(Equal("username"))
   129  				Expect(resp[0].SpaceType).To(Equal("personal"))
   130  				err = env.Fs.DeleteStorageSpace(ctx, &provider.DeleteStorageSpaceRequest{
   131  					Id: resp[0].GetId(),
   132  				})
   133  				Expect(err).To(HaveOccurred())
   134  			})
   135  			It("succeeds on trying to delete homespace as user with 'delete-all-home-spaces' permission", func() {
   136  				ctx := ctxpkg.ContextSetUser(context.Background(), env.DeleteHomeSpacesUser)
   137  				resp, err := env.Fs.ListStorageSpaces(env.Ctx, nil, false)
   138  				Expect(err).ToNot(HaveOccurred())
   139  				Expect(len(resp)).To(Equal(1))
   140  				Expect(string(resp[0].Opaque.GetMap()["spaceAlias"].Value)).To(Equal("personal/username"))
   141  				Expect(resp[0].Name).To(Equal("username"))
   142  				Expect(resp[0].SpaceType).To(Equal("personal"))
   143  				err = env.Fs.DeleteStorageSpace(ctx, &provider.DeleteStorageSpaceRequest{
   144  					Id: resp[0].GetId(),
   145  				})
   146  				Expect(err).To(Not(HaveOccurred()))
   147  			})
   148  			It("fails on trying to delete homespace as user with 'delete-all-spaces' permission", func() {
   149  				ctx := ctxpkg.ContextSetUser(context.Background(), env.DeleteAllSpacesUser)
   150  				resp, err := env.Fs.ListStorageSpaces(env.Ctx, nil, false)
   151  				Expect(err).ToNot(HaveOccurred())
   152  				Expect(len(resp)).To(Equal(1))
   153  				Expect(string(resp[0].Opaque.GetMap()["spaceAlias"].Value)).To(Equal("personal/username"))
   154  				Expect(resp[0].Name).To(Equal("username"))
   155  				Expect(resp[0].SpaceType).To(Equal("personal"))
   156  				err = env.Fs.DeleteStorageSpace(ctx, &provider.DeleteStorageSpaceRequest{
   157  					Id: resp[0].GetId(),
   158  				})
   159  				Expect(err).To(HaveOccurred())
   160  			})
   161  		})
   162  
   163  		Context("can delete (purge) project spaces", func() {
   164  			var delReq *provider.DeleteStorageSpaceRequest
   165  			BeforeEach(func() {
   166  				resp, err := env.Fs.CreateStorageSpace(env.Ctx, &provider.CreateStorageSpaceRequest{Name: "Mission to Venus", Type: "project"})
   167  				Expect(err).ToNot(HaveOccurred())
   168  				Expect(resp.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK))
   169  				Expect(resp.StorageSpace).ToNot(Equal(nil))
   170  				spaceID := resp.StorageSpace.GetId()
   171  				err = env.Fs.DeleteStorageSpace(env.Ctx, &provider.DeleteStorageSpaceRequest{
   172  					Id: spaceID,
   173  				})
   174  				Expect(err).To(Not(HaveOccurred()))
   175  				delReq = &provider.DeleteStorageSpaceRequest{
   176  					Opaque: &typesv1beta1.Opaque{
   177  						Map: map[string]*typesv1beta1.OpaqueEntry{
   178  							"purge": {
   179  								Decoder: "plain",
   180  								Value:   []byte("true"),
   181  							},
   182  						},
   183  					},
   184  					Id: spaceID,
   185  				}
   186  			})
   187  			It("fails as a unprivileged user", func() {
   188  				ctx := ctxpkg.ContextSetUser(context.Background(), env.Users[1])
   189  				err := env.Fs.DeleteStorageSpace(ctx, delReq)
   190  				Expect(err).To(HaveOccurred())
   191  			})
   192  			It("fails as a user with 'delete-all-home-spaces privilege", func() {
   193  				ctx := ctxpkg.ContextSetUser(context.Background(), env.DeleteHomeSpacesUser)
   194  				err := env.Fs.DeleteStorageSpace(ctx, delReq)
   195  				Expect(err).To(HaveOccurred())
   196  			})
   197  			It("succeeds as a user with 'delete-all-spaces privilege", func() {
   198  				ctx := ctxpkg.ContextSetUser(context.Background(), env.DeleteAllSpacesUser)
   199  				err := env.Fs.DeleteStorageSpace(ctx, delReq)
   200  				Expect(err).To(Not(HaveOccurred()))
   201  			})
   202  			It("succeeds as the space owner", func() {
   203  				err := env.Fs.DeleteStorageSpace(env.Ctx, delReq)
   204  				Expect(err).To(Not(HaveOccurred()))
   205  			})
   206  		})
   207  
   208  		Describe("Create Spaces with custom alias template", func() {
   209  			var (
   210  				env *helpers.TestEnv
   211  			)
   212  
   213  			BeforeEach(func() {
   214  				var err error
   215  				env, err = helpers.NewTestEnv(map[string]interface{}{
   216  					"personalspacealias_template": "{{.SpaceType}}/{{.Email.Local}}@{{.Email.Domain}}",
   217  					"generalspacealias_template":  "{{.SpaceType}}:{{.SpaceName | replace \" \" \"-\" | upper}}",
   218  				})
   219  				Expect(err).ToNot(HaveOccurred())
   220  				env.PermissionsClient.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything).Return(&cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_OK}}, nil)
   221  				env.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{
   222  					Stat:     true,
   223  					AddGrant: true,
   224  					GetQuota: true,
   225  				}, nil)
   226  			})
   227  
   228  			AfterEach(func() {
   229  				if env != nil {
   230  					env.Cleanup()
   231  				}
   232  			})
   233  			Context("during login", func() {
   234  				It("personal space is created with custom alias", func() {
   235  					resp, err := env.Fs.ListStorageSpaces(env.Ctx, nil, false)
   236  					Expect(err).ToNot(HaveOccurred())
   237  					Expect(len(resp)).To(Equal(1))
   238  					Expect(string(resp[0].Opaque.GetMap()["spaceAlias"].Value)).To(Equal("personal/username@_unknown"))
   239  				})
   240  			})
   241  			Context("creating a space", func() {
   242  				It("project space is created with custom alias", func() {
   243  					env.Owner = nil
   244  					resp, err := env.Fs.CreateStorageSpace(env.Ctx, &provider.CreateStorageSpaceRequest{Name: "Mission to Venus", Type: "project"})
   245  					Expect(err).ToNot(HaveOccurred())
   246  					Expect(resp.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK))
   247  					Expect(resp.StorageSpace).ToNot(Equal(nil))
   248  					Expect(string(resp.StorageSpace.Opaque.Map["spaceAlias"].Value)).To(Equal("project:MISSION-TO-VENUS"))
   249  
   250  				})
   251  			})
   252  		})
   253  	})
   254  
   255  	Describe("Update Space", func() {
   256  		var (
   257  			env     *helpers.TestEnv
   258  			spaceid *provider.StorageSpaceId
   259  
   260  			manager  = &userv1beta1.User{Id: &userv1beta1.UserId{OpaqueId: "manager"}}
   261  			editor   = &userv1beta1.User{Id: &userv1beta1.UserId{OpaqueId: "editor"}}
   262  			viewer   = &userv1beta1.User{Id: &userv1beta1.UserId{OpaqueId: "viewer"}}
   263  			nomember = &userv1beta1.User{Id: &userv1beta1.UserId{OpaqueId: "nomember"}}
   264  			admin    = &userv1beta1.User{Id: &userv1beta1.UserId{OpaqueId: "admin"}}
   265  		)
   266  		BeforeEach(func() {
   267  			var err error
   268  			env, err = helpers.NewTestEnv(nil)
   269  			Expect(err).ToNot(HaveOccurred())
   270  
   271  			// space permissions
   272  			env.PermissionsClient.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything).Return(
   273  				func(ctx context.Context, in *cs3permissions.CheckPermissionRequest, opts ...grpc.CallOption) *cs3permissions.CheckPermissionResponse {
   274  					switch ctxpkg.ContextMustGetUser(ctx).GetId().GetOpaqueId() {
   275  					case manager.GetId().GetOpaqueId():
   276  						switch in.Permission {
   277  						case "Drives.Create":
   278  							return &cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_OK}}
   279  						default:
   280  							return &cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_PERMISSION_DENIED}}
   281  						}
   282  					case admin.GetId().GetOpaqueId():
   283  						return &cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_OK}}
   284  					default:
   285  						return &cs3permissions.CheckPermissionResponse{Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_PERMISSION_DENIED}}
   286  					}
   287  				}, nil)
   288  
   289  			env.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(
   290  				func(ctx context.Context, n *node.Node) *provider.ResourcePermissions {
   291  					switch ctxpkg.ContextMustGetUser(ctx).GetId().GetOpaqueId() {
   292  					case manager.GetId().GetOpaqueId():
   293  						return node.OwnerPermissions() // id of owner/admin
   294  					case editor.GetId().GetOpaqueId():
   295  						return &provider.ResourcePermissions{InitiateFileUpload: true} // mock editor
   296  					case viewer.GetId().GetOpaqueId():
   297  						return &provider.ResourcePermissions{Stat: true} // mock viewer
   298  					default:
   299  						return node.NoPermissions()
   300  					}
   301  				}, nil)
   302  
   303  			resp, err := env.Fs.CreateStorageSpace(ctxpkg.ContextSetUser(context.Background(), manager), &provider.CreateStorageSpaceRequest{Name: "Mission to Venus", Type: "project"})
   304  			Expect(err).ToNot(HaveOccurred())
   305  
   306  			spaceid = resp.GetStorageSpace().GetId()
   307  		})
   308  
   309  		AfterEach(func() {
   310  			if env != nil {
   311  				env.Cleanup()
   312  			}
   313  		})
   314  
   315  		DescribeTable("update project spaces",
   316  			func(details func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest), expectedStatusCode rpcv1beta1.Code) {
   317  				u, req := details()
   318  				r, err := env.Fs.UpdateStorageSpace(ctxpkg.ContextSetUser(context.Background(), u), req)
   319  				Expect(err).ToNot(HaveOccurred())
   320  				Expect(r.Status.Code).To(Equal(expectedStatusCode))
   321  			},
   322  
   323  			Entry("Manager can change everything but quota",
   324  				func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest) {
   325  					return manager, &provider.UpdateStorageSpaceRequest{
   326  						StorageSpace: &provider.StorageSpace{
   327  							Name: "new space name",
   328  							Id:   spaceid,
   329  							Opaque: &typesv1beta1.Opaque{
   330  								Map: map[string]*typesv1beta1.OpaqueEntry{
   331  									"description": {
   332  										Decoder: "plain",
   333  										Value:   []byte("new description"),
   334  									},
   335  									"spacealias": {
   336  										Decoder: "plain",
   337  										Value:   []byte("new alias"),
   338  									},
   339  									"image": {
   340  										Decoder: "plain",
   341  										Value:   []byte("a$b!c"),
   342  									},
   343  									"readme": {
   344  										Decoder: "plain",
   345  										Value:   []byte("f$g!h"),
   346  									},
   347  								},
   348  							},
   349  						},
   350  					}
   351  				},
   352  				rpcv1beta1.Code_CODE_OK,
   353  			),
   354  			Entry("Manager cannot change quota, even with large request",
   355  				func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest) {
   356  					return manager, &provider.UpdateStorageSpaceRequest{
   357  						StorageSpace: &provider.StorageSpace{
   358  							Name:  "new space name",
   359  							Id:    spaceid,
   360  							Quota: &provider.Quota{QuotaMaxBytes: uint64(1000)},
   361  							Opaque: &typesv1beta1.Opaque{
   362  								Map: map[string]*typesv1beta1.OpaqueEntry{
   363  									"description": {
   364  										Decoder: "plain",
   365  										Value:   []byte("new description"),
   366  									},
   367  									"spacealias": {
   368  										Decoder: "plain",
   369  										Value:   []byte("new alias"),
   370  									},
   371  									"image": {
   372  										Decoder: "plain",
   373  										Value:   []byte("a$b!c"),
   374  									},
   375  									"readme": {
   376  										Decoder: "plain",
   377  										Value:   []byte("f$g!h"),
   378  									},
   379  								},
   380  							},
   381  						},
   382  					}
   383  				},
   384  				rpcv1beta1.Code_CODE_PERMISSION_DENIED,
   385  			),
   386  			Entry("Editor cannot change quota",
   387  				func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest) {
   388  					return editor, &provider.UpdateStorageSpaceRequest{
   389  						StorageSpace: &provider.StorageSpace{
   390  							Id:    spaceid,
   391  							Quota: &provider.Quota{QuotaMaxBytes: uint64(1000)},
   392  						},
   393  					}
   394  				},
   395  				rpcv1beta1.Code_CODE_PERMISSION_DENIED,
   396  			),
   397  			Entry("Editor cannot change name",
   398  				func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest) {
   399  					return editor, &provider.UpdateStorageSpaceRequest{
   400  						StorageSpace: &provider.StorageSpace{
   401  							Name: "new spacename",
   402  							Id:   spaceid,
   403  						},
   404  					}
   405  				},
   406  				rpcv1beta1.Code_CODE_PERMISSION_DENIED,
   407  			),
   408  			Entry("Admin can change quota",
   409  				func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest) {
   410  					return admin, &provider.UpdateStorageSpaceRequest{
   411  						StorageSpace: &provider.StorageSpace{
   412  							Id:    spaceid,
   413  							Quota: &provider.Quota{QuotaMaxBytes: uint64(1000)},
   414  						},
   415  					}
   416  				},
   417  				rpcv1beta1.Code_CODE_OK,
   418  			),
   419  			Entry("Admin can change name and description",
   420  				func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest) {
   421  					return admin, &provider.UpdateStorageSpaceRequest{
   422  						StorageSpace: &provider.StorageSpace{
   423  							Name: "new spacename",
   424  							Id:   spaceid,
   425  							Opaque: &typesv1beta1.Opaque{
   426  								Map: map[string]*typesv1beta1.OpaqueEntry{
   427  									"description": {
   428  										Decoder: "plain",
   429  										Value:   []byte("new description"),
   430  									},
   431  								},
   432  							},
   433  						},
   434  					}
   435  				},
   436  				rpcv1beta1.Code_CODE_OK,
   437  			),
   438  			Entry("Viewer gets OK when he changes nothing",
   439  				func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest) {
   440  					return viewer, &provider.UpdateStorageSpaceRequest{
   441  						StorageSpace: &provider.StorageSpace{
   442  							Id: spaceid,
   443  						},
   444  					}
   445  				},
   446  				rpcv1beta1.Code_CODE_OK,
   447  			),
   448  			Entry("NoMember will not find the space",
   449  				func() (*userv1beta1.User, *provider.UpdateStorageSpaceRequest) {
   450  					return nomember, &provider.UpdateStorageSpaceRequest{
   451  						StorageSpace: &provider.StorageSpace{
   452  							Id: spaceid,
   453  						},
   454  					}
   455  				},
   456  				rpcv1beta1.Code_CODE_NOT_FOUND,
   457  			),
   458  		)
   459  	})
   460  })