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 })