github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/spaces.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 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "math" 26 "os" 27 "path/filepath" 28 "strconv" 29 "strings" 30 "sync/atomic" 31 "time" 32 33 userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 34 v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 35 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 36 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 37 "github.com/cs3org/reva/v2/internal/grpc/services/storageprovider" 38 "github.com/cs3org/reva/v2/pkg/appctx" 39 ocsconv "github.com/cs3org/reva/v2/pkg/conversions" 40 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 41 "github.com/cs3org/reva/v2/pkg/errtypes" 42 "github.com/cs3org/reva/v2/pkg/events" 43 "github.com/cs3org/reva/v2/pkg/rgrpc/status" 44 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 45 sdk "github.com/cs3org/reva/v2/pkg/sdk/common" 46 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" 47 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" 48 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" 49 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions" 50 "github.com/cs3org/reva/v2/pkg/storage/utils/templates" 51 "github.com/cs3org/reva/v2/pkg/storagespace" 52 "github.com/cs3org/reva/v2/pkg/utils" 53 "github.com/pkg/errors" 54 "github.com/shamaton/msgpack/v2" 55 "golang.org/x/sync/errgroup" 56 ) 57 58 const ( 59 _spaceTypePersonal = "personal" 60 _spaceTypeProject = "project" 61 spaceTypeShare = "share" 62 spaceTypeAny = "*" 63 spaceIDAny = "*" 64 65 quotaUnrestricted = 0 66 ) 67 68 // CreateStorageSpace creates a storage space 69 func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { 70 ctx = storageprovider.WithSpaceType(ctx, "") 71 u := ctxpkg.ContextMustGetUser(ctx) 72 73 // "everything is a resource" this is the unique ID for the Space resource. 74 spaceID, err := fs.lu.GenerateSpaceID(req.Type, req.GetOwner()) 75 if err != nil { 76 return nil, err 77 } 78 if reqSpaceID := utils.ReadPlainFromOpaque(req.Opaque, "spaceid"); reqSpaceID != "" { 79 spaceID = reqSpaceID 80 } 81 82 // Check if space already exists 83 rootPath := "" 84 switch req.Type { 85 case _spaceTypePersonal: 86 if fs.o.PersonalSpacePathTemplate != "" { 87 rootPath = filepath.Join(fs.o.Root, templates.WithUser(u, fs.o.PersonalSpacePathTemplate)) 88 } 89 default: 90 if fs.o.GeneralSpacePathTemplate != "" { 91 rootPath = filepath.Join(fs.o.Root, templates.WithSpacePropertiesAndUser(u, req.Type, req.Name, spaceID, fs.o.GeneralSpacePathTemplate)) 92 } 93 } 94 if rootPath != "" { 95 if _, err := os.Stat(rootPath); err == nil { 96 return nil, errtypes.AlreadyExists("decomposedfs: spaces: space already exists") 97 } 98 } 99 100 description := utils.ReadPlainFromOpaque(req.Opaque, "description") 101 alias := utils.ReadPlainFromOpaque(req.Opaque, "spaceAlias") 102 if alias == "" { 103 alias = templates.WithSpacePropertiesAndUser(u, req.Type, req.Name, spaceID, fs.o.GeneralSpaceAliasTemplate) 104 } 105 if req.Type == _spaceTypePersonal { 106 alias = templates.WithSpacePropertiesAndUser(u, req.Type, req.Name, spaceID, fs.o.PersonalSpaceAliasTemplate) 107 } 108 109 root, err := node.ReadNode(ctx, fs.lu, spaceID, spaceID, true, nil, false) // will fall into `Exists` case below 110 switch { 111 case err != nil: 112 return nil, err 113 case !fs.p.CreateSpace(ctx, spaceID): 114 return nil, errtypes.PermissionDenied(spaceID) 115 case root.Exists: 116 return nil, errtypes.AlreadyExists("decomposedfs: spaces: space already exists") 117 } 118 119 // create a directory node 120 root.SetType(provider.ResourceType_RESOURCE_TYPE_CONTAINER) 121 if rootPath == "" { 122 rootPath = root.InternalPath() 123 } 124 125 // set 755 permissions for the base dir 126 if err := os.MkdirAll(filepath.Dir(rootPath), 0755); err != nil { 127 return nil, errors.Wrap(err, fmt.Sprintf("Decomposedfs: error creating spaces base dir %s", filepath.Dir(rootPath))) 128 } 129 130 // 770 permissions for the space 131 if err := os.MkdirAll(rootPath, 0770); err != nil { 132 return nil, errors.Wrap(err, fmt.Sprintf("Decomposedfs: error creating space %s", rootPath)) 133 } 134 135 // Store id in cache 136 if c, ok := fs.lu.(node.IDCacher); ok { 137 if err := c.CacheID(ctx, spaceID, spaceID, rootPath); err != nil { 138 return nil, err 139 } 140 } 141 142 if req.GetOwner() != nil && req.GetOwner().GetId() != nil { 143 root.SetOwner(req.GetOwner().GetId()) 144 } else { 145 root.SetOwner(&userv1beta1.UserId{OpaqueId: spaceID, Type: userv1beta1.UserType_USER_TYPE_SPACE_OWNER}) 146 } 147 148 metadata := node.Attributes{} 149 metadata.SetString(prefixes.IDAttr, spaceID) 150 metadata.SetString(prefixes.SpaceIDAttr, spaceID) 151 metadata.SetString(prefixes.OwnerIDAttr, root.Owner().GetOpaqueId()) 152 metadata.SetString(prefixes.OwnerIDPAttr, root.Owner().GetIdp()) 153 metadata.SetString(prefixes.OwnerTypeAttr, utils.UserTypeToString(root.Owner().GetType())) 154 155 // always mark the space root node as the end of propagation 156 metadata.SetString(prefixes.PropagationAttr, "1") 157 metadata.SetString(prefixes.NameAttr, req.Name) 158 metadata.SetString(prefixes.SpaceNameAttr, req.Name) 159 160 // This space is empty so set initial treesize to 0 161 metadata.SetUInt64(prefixes.TreesizeAttr, 0) 162 163 if req.Type != "" { 164 metadata.SetString(prefixes.SpaceTypeAttr, req.Type) 165 } 166 167 if q := req.GetQuota(); q != nil { 168 // set default space quota 169 if fs.o.MaxQuota != quotaUnrestricted && q.GetQuotaMaxBytes() > fs.o.MaxQuota { 170 return nil, errtypes.BadRequest("decompsedFS: requested quota is higher than allowed") 171 } 172 metadata.SetInt64(prefixes.QuotaAttr, int64(q.QuotaMaxBytes)) 173 } else if fs.o.MaxQuota != quotaUnrestricted { 174 // If no quota was requested but a max quota was set then the the storage space has a quota 175 // of max quota. 176 metadata.SetInt64(prefixes.QuotaAttr, int64(fs.o.MaxQuota)) 177 } 178 179 if description != "" { 180 metadata.SetString(prefixes.SpaceDescriptionAttr, description) 181 } 182 183 if alias != "" { 184 metadata.SetString(prefixes.SpaceAliasAttr, alias) 185 } 186 187 if err := root.SetXattrsWithContext(ctx, metadata, true); err != nil { 188 return nil, err 189 } 190 191 // Write index 192 err = fs.updateIndexes(ctx, &provider.Grantee{ 193 Type: provider.GranteeType_GRANTEE_TYPE_USER, 194 Id: &provider.Grantee_UserId{UserId: req.GetOwner().GetId()}, 195 }, req.Type, root.ID, root.ID) 196 if err != nil { 197 return nil, err 198 } 199 200 ctx = storageprovider.WithSpaceType(ctx, req.Type) 201 202 if req.Type != _spaceTypePersonal { 203 if err := fs.AddGrant(ctx, &provider.Reference{ 204 ResourceId: &provider.ResourceId{ 205 SpaceId: spaceID, 206 OpaqueId: spaceID, 207 }, 208 }, &provider.Grant{ 209 Grantee: &provider.Grantee{ 210 Type: provider.GranteeType_GRANTEE_TYPE_USER, 211 Id: &provider.Grantee_UserId{ 212 UserId: u.Id, 213 }, 214 }, 215 Permissions: ocsconv.NewManagerRole().CS3ResourcePermissions(), 216 }); err != nil { 217 return nil, err 218 } 219 } 220 221 space, err := fs.StorageSpaceFromNode(ctx, root, true) 222 if err != nil { 223 return nil, err 224 } 225 226 resp := &provider.CreateStorageSpaceResponse{ 227 Status: &v1beta11.Status{ 228 Code: v1beta11.Code_CODE_OK, 229 }, 230 StorageSpace: space, 231 } 232 return resp, nil 233 } 234 235 // ListStorageSpaces returns a list of StorageSpaces. 236 // The list can be filtered by space type or space id. 237 // Spaces are persisted with symlinks in /spaces/<type>/<spaceid> pointing to ../../nodes/<nodeid>, the root node of the space 238 // The spaceid is a concatenation of storageid + "!" + nodeid 239 func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) { 240 // TODO check filters 241 242 // TODO when a space symlink is broken delete the space for cleanup 243 // read permissions are deduced from the node? 244 245 // TODO for absolute references this actually requires us to move all user homes into a subfolder of /nodes/root, 246 // e.g. /nodes/root/<space type> otherwise storage space names might collide even though they are of different types 247 // /nodes/root/personal/foo and /nodes/root/shares/foo might be two very different spaces, a /nodes/root/foo is not expressive enough 248 // we would not need /nodes/root if access always happened via spaceid+relative path 249 250 var ( 251 spaceID = spaceIDAny 252 nodeID = spaceIDAny 253 requestedUserID *userv1beta1.UserId 254 ) 255 256 spaceTypes := map[string]struct{}{} 257 258 for i := range filter { 259 switch filter[i].Type { 260 case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: 261 switch filter[i].GetSpaceType() { 262 case "+mountpoint": 263 // TODO include mount poits 264 case "+grant": 265 // TODO include grants 266 default: 267 spaceTypes[filter[i].GetSpaceType()] = struct{}{} 268 } 269 case provider.ListStorageSpacesRequest_Filter_TYPE_ID: 270 _, spaceID, nodeID, _ = storagespace.SplitID(filter[i].GetId().OpaqueId) 271 if strings.Contains(nodeID, "/") { 272 return []*provider.StorageSpace{}, nil 273 } 274 case provider.ListStorageSpacesRequest_Filter_TYPE_USER: 275 // TODO: refactor this to GetUserId() in cs3 276 requestedUserID = filter[i].GetUser() 277 case provider.ListStorageSpacesRequest_Filter_TYPE_OWNER: 278 // TODO: improve further by not evaluating shares 279 requestedUserID = filter[i].GetOwner() 280 } 281 } 282 if len(spaceTypes) == 0 { 283 spaceTypes[spaceTypeAny] = struct{}{} 284 } 285 286 authenticatedUserID := ctxpkg.ContextMustGetUser(ctx).GetId().GetOpaqueId() 287 288 if !fs.p.ListSpacesOfUser(ctx, requestedUserID) { 289 return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list spaces of other users", authenticatedUserID)) 290 } 291 292 checkNodePermissions := fs.MustCheckNodePermissions(ctx, unrestricted) 293 294 spaces := []*provider.StorageSpace{} 295 // build the glob path, eg. 296 // /path/to/root/spaces/{spaceType}/{spaceId} 297 // /path/to/root/spaces/personal/nodeid 298 // /path/to/root/spaces/shared/nodeid 299 300 if spaceID != spaceIDAny && nodeID != spaceIDAny { 301 // try directly reading the node 302 n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, false) // permission to read disabled space is checked later 303 if err != nil { 304 appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not read node") 305 return nil, err 306 } 307 if !n.Exists { 308 // return empty list 309 return spaces, nil 310 } 311 space, err := fs.StorageSpaceFromNode(ctx, n, checkNodePermissions) 312 if err != nil { 313 return nil, err 314 } 315 // filter space types 316 _, ok1 := spaceTypes[spaceTypeAny] 317 _, ok2 := spaceTypes[space.SpaceType] 318 if ok1 || ok2 { 319 spaces = append(spaces, space) 320 } 321 // TODO: filter user id 322 return spaces, nil 323 } 324 325 matches := map[string]string{} 326 var allMatches map[string]string 327 var err error 328 329 if requestedUserID != nil { 330 allMatches, err = fs.userSpaceIndex.Load(requestedUserID.GetOpaqueId()) 331 // do not return an error if the user has no spaces 332 if err != nil && !os.IsNotExist(err) { 333 return nil, errors.Wrap(err, "error reading user index") 334 } 335 336 if nodeID == spaceIDAny { 337 for spaceID, nodeID := range allMatches { 338 matches[spaceID] = nodeID 339 } 340 } else { 341 matches[allMatches[nodeID]] = allMatches[nodeID] 342 } 343 344 // get Groups for userid 345 user := ctxpkg.ContextMustGetUser(ctx) 346 // TODO the user from context may not have groups populated 347 if !utils.UserIDEqual(user.GetId(), requestedUserID) { 348 user, err = fs.UserIDToUserAndGroups(ctx, requestedUserID) 349 if err != nil { 350 return nil, err // TODO log and continue? 351 } 352 } 353 354 for _, group := range user.Groups { 355 allMatches, err = fs.groupSpaceIndex.Load(group) 356 if err != nil { 357 if os.IsNotExist(err) { 358 continue // no spaces for this group 359 } 360 return nil, errors.Wrap(err, "error reading group index") 361 } 362 363 if nodeID == spaceIDAny { 364 for spaceID, nodeID := range allMatches { 365 matches[spaceID] = nodeID 366 } 367 } else { 368 matches[allMatches[nodeID]] = allMatches[nodeID] 369 } 370 } 371 372 } 373 374 if requestedUserID == nil { 375 if _, ok := spaceTypes[spaceTypeAny]; ok { 376 // TODO do not hardcode dirs 377 spaceTypes = map[string]struct{}{ 378 "personal": {}, 379 "project": {}, 380 "share": {}, 381 } 382 } 383 384 for spaceType := range spaceTypes { 385 allMatches, err = fs.spaceTypeIndex.Load(spaceType) 386 if err != nil { 387 if os.IsNotExist(err) { 388 continue // no spaces for this space type 389 } 390 return nil, errors.Wrap(err, "error reading type index") 391 } 392 393 if nodeID == spaceIDAny { 394 for spaceID, nodeID := range allMatches { 395 matches[spaceID] = nodeID 396 } 397 } else { 398 matches[allMatches[nodeID]] = allMatches[nodeID] 399 } 400 } 401 } 402 403 // FIXME if the space does not exist try a node as the space root. 404 405 // But then the whole /spaces/{spaceType}/{spaceid} becomes obsolete 406 // we can alway just look up by nodeid 407 // -> no. The /spaces folder is used for efficient lookup by type, otherwise we would have 408 // to iterate over all nodes and read the type from extended attributes 409 // -> but for lookup by id we can use the node directly. 410 // But what about sharding nodes by space? 411 // an efficient lookup would be possible if we received a spaceid&opaqueid in the request 412 // the personal spaces must also use the nodeid and not the name 413 numShares := atomic.Int64{} 414 errg, ctx := errgroup.WithContext(ctx) 415 work := make(chan []string, len(matches)) 416 results := make(chan *provider.StorageSpace, len(matches)) 417 418 // Distribute work 419 errg.Go(func() error { 420 defer close(work) 421 for spaceID, nodeID := range matches { 422 select { 423 case work <- []string{spaceID, nodeID}: 424 case <-ctx.Done(): 425 return ctx.Err() 426 } 427 } 428 return nil 429 }) 430 431 // Spawn workers that'll concurrently work the queue 432 numWorkers := 20 433 if len(matches) < numWorkers { 434 numWorkers = len(matches) 435 } 436 for i := 0; i < numWorkers; i++ { 437 errg.Go(func() error { 438 for match := range work { 439 spaceID, nodeID, err := fs.tp.ResolveSpaceIDIndexEntry(match[0], match[1]) 440 if err != nil { 441 appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("resolve space id index entry, skipping") 442 continue 443 } 444 445 n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, true) 446 if err != nil { 447 appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not read node, skipping") 448 continue 449 } 450 451 if !n.Exists { 452 continue 453 } 454 455 space, err := fs.StorageSpaceFromNode(ctx, n, checkNodePermissions) 456 if err != nil { 457 switch err.(type) { 458 case errtypes.IsPermissionDenied: 459 // ok 460 case errtypes.NotFound: 461 // ok 462 default: 463 appctx.GetLogger(ctx).Error().Err(err).Str("id", nodeID).Msg("could not convert to storage space") 464 } 465 continue 466 } 467 468 // FIXME type share evolved to grant on the edge branch ... make it configurable if the driver should support them or not for now ... ignore type share 469 if space.SpaceType == spaceTypeShare { 470 numShares.Add(1) 471 // do not list shares as spaces for the owner 472 continue 473 } 474 475 // TODO apply more filters 476 _, ok1 := spaceTypes[spaceTypeAny] 477 _, ok2 := spaceTypes[space.SpaceType] 478 if ok1 || ok2 { 479 select { 480 case results <- space: 481 case <-ctx.Done(): 482 return ctx.Err() 483 } 484 } 485 } 486 return nil 487 }) 488 } 489 490 // Wait for things to settle down, then close results chan 491 go func() { 492 _ = errg.Wait() // error is checked later 493 close(results) 494 }() 495 496 for r := range results { 497 spaces = append(spaces, r) 498 } 499 500 // if there are no matches (or they happened to be spaces for the owner) and the node is a child return a space 501 if int64(len(matches)) <= numShares.Load() && nodeID != spaceID { 502 // try node id 503 n, err := node.ReadNode(ctx, fs.lu, spaceID, nodeID, true, nil, false) // permission to read disabled space is checked in storageSpaceFromNode 504 if err != nil { 505 return nil, err 506 } 507 if n.Exists { 508 space, err := fs.StorageSpaceFromNode(ctx, n, checkNodePermissions) 509 if err != nil { 510 return nil, err 511 } 512 spaces = append(spaces, space) 513 } 514 } 515 516 return spaces, nil 517 } 518 519 // UserIDToUserAndGroups converts a user ID to a user with groups 520 func (fs *Decomposedfs) UserIDToUserAndGroups(ctx context.Context, userid *userv1beta1.UserId) (*userv1beta1.User, error) { 521 user, err := fs.UserCache.Get(userid.GetOpaqueId()) 522 if err == nil { 523 return user.(*userv1beta1.User), nil 524 } 525 526 gwConn, err := pool.GetGatewayServiceClient(fs.o.GatewayAddr) 527 if err != nil { 528 return nil, err 529 } 530 getUserResponse, err := gwConn.GetUser(ctx, &userv1beta1.GetUserRequest{ 531 UserId: userid, 532 SkipFetchingUserGroups: false, 533 }) 534 if err != nil { 535 return nil, err 536 } 537 if getUserResponse.Status.Code != v1beta11.Code_CODE_OK { 538 return nil, status.NewErrorFromCode(getUserResponse.Status.Code, "gateway") 539 } 540 _ = fs.UserCache.Set(userid.GetOpaqueId(), getUserResponse.GetUser()) 541 return getUserResponse.GetUser(), nil 542 } 543 544 // MustCheckNodePermissions checks if permission checks are needed to be performed when user requests spaces 545 func (fs *Decomposedfs) MustCheckNodePermissions(ctx context.Context, unrestricted bool) bool { 546 // canListAllSpaces indicates if the user has the permission from the global user role 547 canListAllSpaces := fs.p.ListAllSpaces(ctx) 548 // unrestricted is the param which indicates if the user wants to list all spaces or only the spaces he is part of 549 // if a user lists all spaces unrestricted and doesn't have the permissions from the role, we need to check 550 // the nodePermissions and this will return a spaces list where the user has access to 551 // we can only skip the NodePermissions check if both values are true 552 if canListAllSpaces && unrestricted { 553 return false 554 } 555 return true 556 } 557 558 // UpdateStorageSpace updates a storage space 559 func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { 560 var restore bool 561 if req.Opaque != nil { 562 _, restore = req.Opaque.Map["restore"] 563 } 564 565 space := req.StorageSpace 566 _, spaceID, _, _ := storagespace.SplitID(space.Id.OpaqueId) 567 568 metadata := make(node.Attributes, 5) 569 if space.Name != "" { 570 metadata.SetString(prefixes.NameAttr, space.Name) 571 metadata.SetString(prefixes.SpaceNameAttr, space.Name) 572 } 573 574 if space.Quota != nil { 575 if fs.o.MaxQuota != quotaUnrestricted && fs.o.MaxQuota < space.Quota.QuotaMaxBytes { 576 return &provider.UpdateStorageSpaceResponse{ 577 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_INVALID_ARGUMENT, Message: "decompsedFS: requested quota is higher than allowed"}, 578 }, nil 579 } else if fs.o.MaxQuota != quotaUnrestricted && space.Quota.QuotaMaxBytes == quotaUnrestricted { 580 // If the caller wants to unrestrict the space we give it the maximum allowed quota. 581 space.Quota.QuotaMaxBytes = fs.o.MaxQuota 582 } 583 metadata.SetInt64(prefixes.QuotaAttr, int64(space.Quota.QuotaMaxBytes)) 584 } 585 586 // TODO also return values which are not in the request 587 if space.Opaque != nil { 588 if description, ok := space.Opaque.Map["description"]; ok { 589 metadata[prefixes.SpaceDescriptionAttr] = description.Value 590 } 591 if alias := utils.ReadPlainFromOpaque(space.Opaque, "spaceAlias"); alias != "" { 592 metadata.SetString(prefixes.SpaceAliasAttr, alias) 593 } 594 if image := utils.ReadPlainFromOpaque(space.Opaque, "image"); image != "" { 595 imageID, err := storagespace.ParseID(image) 596 if err != nil { 597 return &provider.UpdateStorageSpaceResponse{ 598 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND, Message: "decomposedFS: space image resource not found"}, 599 }, nil 600 } 601 metadata.SetString(prefixes.SpaceImageAttr, imageID.OpaqueId) 602 } 603 if readme := utils.ReadPlainFromOpaque(space.Opaque, "readme"); readme != "" { 604 readmeID, err := storagespace.ParseID(readme) 605 if err != nil { 606 return &provider.UpdateStorageSpaceResponse{ 607 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND, Message: "decomposedFS: space readme resource not found"}, 608 }, nil 609 } 610 metadata.SetString(prefixes.SpaceReadmeAttr, readmeID.OpaqueId) 611 } 612 } 613 614 // check which permissions are needed 615 spaceNode, err := node.ReadNode(ctx, fs.lu, spaceID, spaceID, true, nil, false) 616 if err != nil { 617 return nil, err 618 } 619 620 if !spaceNode.Exists { 621 return &provider.UpdateStorageSpaceResponse{ 622 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND}, 623 }, nil 624 } 625 626 sp, err := fs.p.AssemblePermissions(ctx, spaceNode) 627 if err != nil { 628 return &provider.UpdateStorageSpaceResponse{ 629 Status: status.NewStatusFromErrType(ctx, "assembling permissions failed", err), 630 }, nil 631 632 } 633 634 if !restore && len(metadata) == 0 && !permissions.IsViewer(sp) { 635 // you may land here when making an update request without changes 636 // check if user has access to the drive before continuing 637 return &provider.UpdateStorageSpaceResponse{ 638 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND}, 639 }, nil 640 } 641 642 if !permissions.IsManager(sp) { 643 // We are not a space manager. We need to check for additional permissions. 644 k := []string{prefixes.NameAttr, prefixes.SpaceDescriptionAttr} 645 if !permissions.IsEditor(sp) { 646 k = append(k, prefixes.SpaceReadmeAttr, prefixes.SpaceAliasAttr, prefixes.SpaceImageAttr) 647 } 648 649 if mapHasKey(metadata, k...) && !fs.p.ManageSpaceProperties(ctx, spaceID) { 650 return &provider.UpdateStorageSpaceResponse{ 651 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_PERMISSION_DENIED}, 652 }, nil 653 } 654 655 if restore && !fs.p.SpaceAbility(ctx, spaceID) { 656 return &provider.UpdateStorageSpaceResponse{ 657 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_NOT_FOUND}, 658 }, nil 659 } 660 } 661 662 if mapHasKey(metadata, prefixes.QuotaAttr) { 663 typ, err := spaceNode.SpaceRoot.Xattr(ctx, prefixes.SpaceTypeAttr) 664 if err != nil { 665 return &provider.UpdateStorageSpaceResponse{ 666 Status: &v1beta11.Status{ 667 Code: v1beta11.Code_CODE_INTERNAL, 668 Message: "space has no type", 669 }, 670 }, nil 671 } 672 673 if !fs.p.SetSpaceQuota(ctx, spaceID, string(typ)) { 674 return &provider.UpdateStorageSpaceResponse{ 675 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_PERMISSION_DENIED}, 676 }, nil 677 } 678 } 679 metadata[prefixes.TreeMTimeAttr] = []byte(time.Now().UTC().Format(time.RFC3339Nano)) 680 681 err = spaceNode.SetXattrsWithContext(ctx, metadata, true) 682 if err != nil { 683 return nil, err 684 } 685 686 if restore { 687 if err := spaceNode.SetDTime(ctx, nil); err != nil { 688 return nil, err 689 } 690 } 691 692 // send back the updated data from the storage 693 updatedSpace, err := fs.StorageSpaceFromNode(ctx, spaceNode, false) 694 if err != nil { 695 return nil, err 696 } 697 698 return &provider.UpdateStorageSpaceResponse{ 699 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_OK}, 700 StorageSpace: updatedSpace, 701 }, nil 702 } 703 704 // DeleteStorageSpace deletes a storage space 705 func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error { 706 opaque := req.Opaque 707 var purge bool 708 if opaque != nil { 709 _, purge = opaque.Map["purge"] 710 } 711 712 _, spaceID, _, err := storagespace.SplitID(req.Id.GetOpaqueId()) 713 if err != nil { 714 return err 715 } 716 717 n, err := node.ReadNode(ctx, fs.lu, spaceID, spaceID, true, nil, false) // permission to read disabled space is checked later 718 if err != nil { 719 return err 720 } 721 722 st, err := n.SpaceRoot.XattrString(ctx, prefixes.SpaceTypeAttr) 723 if err != nil { 724 return errtypes.InternalError(fmt.Sprintf("space %s does not have a spacetype, possible corrupt decompsedfs", n.ID)) 725 } 726 727 if err := canDeleteSpace(ctx, spaceID, st, purge, n, fs.p); err != nil { 728 return err 729 } 730 if purge { 731 if !n.IsDisabled(ctx) { 732 return errtypes.NewErrtypeFromStatus(status.NewInvalid(ctx, "can't purge enabled space")) 733 } 734 735 // TODO invalidate ALL indexes in msgpack, not only by type 736 spaceType, err := n.XattrString(ctx, prefixes.SpaceTypeAttr) 737 if err != nil { 738 return err 739 } 740 if err := fs.spaceTypeIndex.Remove(spaceType, spaceID); err != nil { 741 return err 742 } 743 744 // invalidate cache 745 if err := fs.lu.MetadataBackend().Purge(ctx, n.InternalPath()); err != nil { 746 return err 747 } 748 749 root := fs.getSpaceRoot(spaceID) 750 751 // walkfn will delete the blob if the node has one 752 walkfn := func(path string, info os.FileInfo, err error) error { 753 if err != nil { 754 return err 755 } 756 757 if filepath.Ext(path) != ".mpk" { 758 return nil 759 } 760 761 b, err := os.ReadFile(path) 762 if err != nil { 763 return err 764 } 765 766 m := map[string][]byte{} 767 if err := msgpack.Unmarshal(b, &m); err != nil { 768 return err 769 } 770 771 bid := m["user.ocis.blobid"] 772 if string(bid) == "" { 773 return nil 774 } 775 776 if err := fs.tp.DeleteBlob(&node.Node{ 777 BlobID: string(bid), 778 SpaceID: spaceID, 779 }); err != nil { 780 return err 781 } 782 783 // remove .mpk file so subsequent attempts will not try to delete the blob again 784 return os.Remove(path) 785 } 786 787 // This is deletes all blobs of the space 788 // NOTE: This isn't needed when no s3 is used, but we can't differentiate that here... 789 if err := filepath.Walk(root, walkfn); err != nil { 790 return err 791 } 792 793 // remove space metadata 794 if err := os.RemoveAll(root); err != nil { 795 return err 796 } 797 798 // try removing the space root node 799 // Note that this will fail when there are other spaceids starting with the same two digits. 800 _ = os.Remove(filepath.Dir(root)) 801 802 return nil 803 } 804 805 // mark as disabled by writing a dtime attribute 806 dtime := time.Now() 807 return n.SetDTime(ctx, &dtime) 808 } 809 810 // the value of `target` depends on the implementation: 811 // - for ocis/s3ng it is the relative link to the space root 812 // - for the posixfs it is the node id 813 func (fs *Decomposedfs) updateIndexes(ctx context.Context, grantee *provider.Grantee, spaceType, spaceID, nodeID string) error { 814 target := fs.tp.BuildSpaceIDIndexEntry(spaceID, nodeID) 815 err := fs.linkStorageSpaceType(ctx, spaceType, spaceID, target) 816 if err != nil { 817 return err 818 } 819 if isShareGrant(ctx) { 820 // FIXME we should count the references for the by-type index currently removing the second share from the same 821 // space cannot determine if the by-type should be deletet, which is why we never delete them ... 822 return nil 823 } 824 825 // create space grant index 826 switch { 827 case grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER: 828 return fs.linkSpaceByUser(ctx, grantee.GetUserId().GetOpaqueId(), spaceID, target) 829 case grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP: 830 return fs.linkSpaceByGroup(ctx, grantee.GetGroupId().GetOpaqueId(), spaceID, target) 831 default: 832 return errtypes.BadRequest("invalid grantee type: " + grantee.GetType().String()) 833 } 834 } 835 836 func (fs *Decomposedfs) linkSpaceByUser(ctx context.Context, userID, spaceID, target string) error { 837 return fs.userSpaceIndex.Add(userID, spaceID, target) 838 } 839 840 func (fs *Decomposedfs) linkSpaceByGroup(ctx context.Context, groupID, spaceID, target string) error { 841 return fs.groupSpaceIndex.Add(groupID, spaceID, target) 842 } 843 844 func (fs *Decomposedfs) linkStorageSpaceType(ctx context.Context, spaceType, spaceID, target string) error { 845 return fs.spaceTypeIndex.Add(spaceType, spaceID, target) 846 } 847 848 func (fs *Decomposedfs) StorageSpaceFromNode(ctx context.Context, n *node.Node, checkPermissions bool) (*provider.StorageSpace, error) { 849 user := ctxpkg.ContextMustGetUser(ctx) 850 if checkPermissions { 851 rp, err := fs.p.AssemblePermissions(ctx, n) 852 switch { 853 case err != nil: 854 return nil, err 855 case !rp.Stat: 856 return nil, errtypes.NotFound(fmt.Sprintf("space %s not found", n.ID)) 857 } 858 859 if n.SpaceRoot.IsDisabled(ctx) { 860 rp, err := fs.p.AssemblePermissions(ctx, n) 861 if err != nil || !permissions.IsManager(rp) { 862 return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.ID)) 863 } 864 } 865 } 866 867 sublog := appctx.GetLogger(ctx).With().Str("spaceid", n.SpaceID).Logger() 868 869 var err error 870 // TODO apply more filters 871 var sname string 872 if sname, err = n.SpaceRoot.XattrString(ctx, prefixes.SpaceNameAttr); err != nil { 873 // FIXME: Is that a severe problem? 874 sublog.Debug().Err(err).Msg("space does not have a name attribute") 875 } 876 877 /* 878 if err := n.FindStorageSpaceRoot(); err != nil { 879 return nil, err 880 } 881 */ 882 883 // read the grants from the current node, not the root 884 grants, err := n.ListGrants(ctx) 885 if err != nil { 886 return nil, err 887 } 888 889 grantMap := make(map[string]*provider.ResourcePermissions, len(grants)) 890 grantExpiration := make(map[string]*types.Timestamp) 891 groupMap := make(map[string]struct{}) 892 for _, g := range grants { 893 var id string 894 switch g.Grantee.Type { 895 case provider.GranteeType_GRANTEE_TYPE_GROUP: 896 id = g.Grantee.GetGroupId().OpaqueId 897 groupMap[id] = struct{}{} 898 case provider.GranteeType_GRANTEE_TYPE_USER: 899 id = g.Grantee.GetUserId().OpaqueId 900 default: 901 continue 902 } 903 904 if g.Expiration != nil { 905 // We are doing this check here because we want to remove expired grants "on access". 906 // This way we don't have to have a cron job checking the grants in regular intervals. 907 // The tradeof obviously is that this code is here. 908 if isGrantExpired(g) { 909 var errDeleteGrant, errIndexRemove error 910 911 errDeleteGrant = n.DeleteGrant(ctx, g, true) 912 if errDeleteGrant != nil { 913 sublog.Error().Err(err).Str("grantee", id). 914 Msg("failed to delete expired space grant") 915 } 916 if n.IsSpaceRoot(ctx) { 917 // invalidate space grant 918 switch { 919 case g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER: 920 // remove from user index 921 errIndexRemove = fs.userSpaceIndex.Remove(g.Grantee.GetUserId().GetOpaqueId(), n.SpaceID) 922 if errIndexRemove != nil { 923 sublog.Error().Err(err).Str("grantee", id). 924 Msg("failed to delete expired user space index") 925 } 926 case g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP: 927 // remove from group index 928 errIndexRemove = fs.groupSpaceIndex.Remove(g.Grantee.GetGroupId().GetOpaqueId(), n.SpaceID) 929 if errIndexRemove != nil { 930 sublog.Error().Err(err).Str("grantee", id). 931 Msg("failed to delete expired group space index") 932 } 933 } 934 } 935 936 // publish SpaceMembershipExpired event 937 if errDeleteGrant == nil && errIndexRemove == nil { 938 ev := events.SpaceMembershipExpired{ 939 SpaceOwner: n.SpaceOwnerOrManager(ctx), 940 SpaceID: &provider.StorageSpaceId{OpaqueId: n.SpaceID}, 941 SpaceName: sname, 942 ExpiredAt: time.Unix(int64(g.Expiration.Seconds), int64(g.Expiration.Nanos)), 943 Timestamp: utils.TSNow(), 944 } 945 switch { 946 case g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER: 947 ev.GranteeUserID = g.Grantee.GetUserId() 948 case g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP: 949 ev.GranteeGroupID = g.Grantee.GetGroupId() 950 } 951 err = events.Publish(ctx, fs.stream, ev) 952 if err != nil { 953 sublog.Error().Err(err).Msg("error publishing SpaceMembershipExpired event") 954 } 955 } 956 957 continue 958 } 959 grantExpiration[id] = g.Expiration 960 } 961 grantMap[id] = g.Permissions 962 } 963 964 grantMapJSON, err := json.Marshal(grantMap) 965 if err != nil { 966 return nil, err 967 } 968 969 grantExpirationMapJSON, err := json.Marshal(grantExpiration) 970 if err != nil { 971 return nil, err 972 } 973 974 groupMapJSON, err := json.Marshal(groupMap) 975 if err != nil { 976 return nil, err 977 } 978 979 ssID, err := storagespace.FormatReference( 980 &provider.Reference{ 981 ResourceId: &provider.ResourceId{ 982 SpaceId: n.SpaceRoot.SpaceID, 983 OpaqueId: n.SpaceRoot.ID}, 984 }, 985 ) 986 if err != nil { 987 return nil, err 988 } 989 space := &provider.StorageSpace{ 990 Opaque: &types.Opaque{ 991 Map: map[string]*types.OpaqueEntry{ 992 "grants": { 993 Decoder: "json", 994 Value: grantMapJSON, 995 }, 996 "grants_expirations": { 997 Decoder: "json", 998 Value: grantExpirationMapJSON, 999 }, 1000 "groups": { 1001 Decoder: "json", 1002 Value: groupMapJSON, 1003 }, 1004 }, 1005 }, 1006 Id: &provider.StorageSpaceId{OpaqueId: ssID}, 1007 Root: &provider.ResourceId{ 1008 SpaceId: n.SpaceRoot.SpaceID, 1009 OpaqueId: n.SpaceRoot.ID, 1010 }, 1011 Name: sname, 1012 // SpaceType is read from xattr below 1013 // Mtime is set either as node.tmtime or as fi.mtime below 1014 } 1015 1016 space.SpaceType, err = n.SpaceRoot.XattrString(ctx, prefixes.SpaceTypeAttr) 1017 if err != nil { 1018 appctx.GetLogger(ctx).Debug().Err(err).Msg("space does not have a type attribute") 1019 } 1020 1021 if n.SpaceRoot.IsDisabled(ctx) { 1022 space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "trashed", "trashed") 1023 } 1024 1025 if n.Owner() != nil && n.Owner().OpaqueId != "" { 1026 space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object 1027 Id: n.Owner(), 1028 } 1029 } 1030 1031 // we set the space mtime to the root item mtime 1032 // override the stat mtime with a tmtime if it is present 1033 var tmtime time.Time 1034 if tmt, err := n.GetTMTime(ctx); err == nil { 1035 tmtime = tmt 1036 un := tmt.UnixNano() 1037 space.Mtime = &types.Timestamp{ 1038 Seconds: uint64(un / 1000000000), 1039 Nanos: uint32(un % 1000000000), 1040 } 1041 } else if fi, err := os.Stat(n.InternalPath()); err == nil { 1042 // fall back to stat mtime 1043 tmtime = fi.ModTime() 1044 un := fi.ModTime().UnixNano() 1045 space.Mtime = &types.Timestamp{ 1046 Seconds: uint64(un / 1000000000), 1047 Nanos: uint32(un % 1000000000), 1048 } 1049 } 1050 1051 etag, err := node.CalculateEtag(n.ID, tmtime) 1052 if err != nil { 1053 return nil, err 1054 } 1055 space.Opaque.Map["etag"] = &types.OpaqueEntry{ 1056 Decoder: "plain", 1057 Value: []byte(etag), 1058 } 1059 1060 spaceAttributes, err := n.SpaceRoot.Xattrs(ctx) 1061 if err != nil { 1062 return nil, err 1063 } 1064 1065 // if quota is set try parsing it as int64, otherwise don't bother 1066 if q, err := spaceAttributes.Int64(prefixes.QuotaAttr); err == nil && q >= 0 { 1067 // make sure we have a proper signed int 1068 // we use the same magic numbers to indicate: 1069 // -1 = uncalculated 1070 // -2 = unknown 1071 // -3 = unlimited 1072 space.Quota = &provider.Quota{ 1073 QuotaMaxBytes: uint64(q), 1074 QuotaMaxFiles: math.MaxUint64, // TODO MaxUInt64? = unlimited? why even max files? 0 = unlimited? 1075 } 1076 1077 } 1078 if si := spaceAttributes.String(prefixes.SpaceImageAttr); si != "" { 1079 space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "image", storagespace.FormatResourceID( 1080 &provider.ResourceId{StorageId: space.Root.StorageId, SpaceId: space.Root.SpaceId, OpaqueId: si}, 1081 )) 1082 } 1083 if sd := spaceAttributes.String(prefixes.SpaceDescriptionAttr); sd != "" { 1084 space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "description", sd) 1085 } 1086 if sr := spaceAttributes.String(prefixes.SpaceReadmeAttr); sr != "" { 1087 space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "readme", storagespace.FormatResourceID( 1088 &provider.ResourceId{StorageId: space.Root.StorageId, SpaceId: space.Root.SpaceId, OpaqueId: sr}, 1089 )) 1090 } 1091 if sa := spaceAttributes.String(prefixes.SpaceAliasAttr); sa != "" { 1092 space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "spaceAlias", sa) 1093 } 1094 1095 // add rootinfo 1096 ps, _ := n.SpaceRoot.PermissionSet(ctx) 1097 space.RootInfo, _ = n.SpaceRoot.AsResourceInfo(ctx, ps, []string{"quota"}, nil, false) 1098 1099 // we cannot put free, used and remaining into the quota, as quota, when set would always imply a quota limit 1100 // for now we use opaque properties with a 'quota.' prefix 1101 quotaStr := node.QuotaUnknown 1102 if quotaInOpaque := sdk.DecodeOpaqueMap(space.RootInfo.Opaque)["quota"]; quotaInOpaque != "" { 1103 quotaStr = quotaInOpaque 1104 } 1105 1106 total, used, remaining, err := fs.calculateTotalUsedRemaining(quotaStr, space.GetRootInfo().GetSize()) 1107 if err != nil { 1108 return nil, err 1109 } 1110 space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "quota.total", strconv.FormatUint(total, 10)) 1111 space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "quota.used", strconv.FormatUint(used, 10)) 1112 space.Opaque = utils.AppendPlainToOpaque(space.Opaque, "quota.remaining", strconv.FormatUint(remaining, 10)) 1113 1114 return space, nil 1115 } 1116 1117 func mapHasKey(checkMap map[string][]byte, keys ...string) bool { 1118 for _, key := range keys { 1119 if _, hasKey := checkMap[key]; hasKey { 1120 return true 1121 } 1122 } 1123 return false 1124 } 1125 1126 func isGrantExpired(g *provider.Grant) bool { 1127 if g.Expiration == nil { 1128 return false 1129 } 1130 return time.Now().After(time.Unix(int64(g.Expiration.Seconds), int64(g.Expiration.Nanos))) 1131 } 1132 1133 func (fs *Decomposedfs) getSpaceRoot(spaceID string) string { 1134 return filepath.Join(fs.o.Root, "spaces", lookup.Pathify(spaceID, 1, 2)) 1135 } 1136 1137 // Space deletion can be tricky as there are lots of different cases: 1138 // - spaces of type personal can only be disabled and deleted by users with the "delete-all-home-spaces" permission 1139 // - a user with the "delete-all-spaces" permission may delete but not enable/disable any project space 1140 // - a user with the "Drive.ReadWriteEnabled" permission may enable/disable but not delete any project space 1141 // - a project space can always be enabled/disabled/deleted by its manager (i.e. users have the "remove" grant) 1142 func canDeleteSpace(ctx context.Context, spaceID string, typ string, purge bool, n *node.Node, p permissions.Permissions) error { 1143 // delete-all-home spaces allows to disable and delete a personal space 1144 if typ == "personal" { 1145 if p.DeleteAllHomeSpaces(ctx) { 1146 return nil 1147 } 1148 return errtypes.PermissionDenied("user is not allowed to delete a personal space") 1149 } 1150 1151 // space managers are allowed to disable and delete their project spaces 1152 if rp, err := p.AssemblePermissions(ctx, n); err == nil && permissions.IsManager(rp) { 1153 return nil 1154 } 1155 1156 // delete-all-spaces permissions allows to delete (purge, NOT disable) project spaces 1157 if purge && p.DeleteAllSpaces(ctx) { 1158 return nil 1159 } 1160 1161 // Drive.ReadWriteEnabled allows to disable a space 1162 if !purge && p.SpaceAbility(ctx, spaceID) { 1163 return nil 1164 } 1165 1166 return errtypes.PermissionDenied(fmt.Sprintf("user is not allowed to delete space %s", n.ID)) 1167 }