github.com/cs3org/reva/v2@v2.27.7/pkg/publicshare/manager/cs3/cs3.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 cs3 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "net/url" 26 "path" 27 "strconv" 28 "strings" 29 "sync" 30 "time" 31 32 "github.com/mitchellh/mapstructure" 33 "github.com/pkg/errors" 34 "golang.org/x/crypto/bcrypt" 35 "google.golang.org/protobuf/proto" 36 37 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 38 user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 39 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 40 link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" 41 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 42 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 43 "github.com/cs3org/reva/v2/pkg/appctx" 44 "github.com/cs3org/reva/v2/pkg/errtypes" 45 "github.com/cs3org/reva/v2/pkg/publicshare" 46 "github.com/cs3org/reva/v2/pkg/publicshare/manager/registry" 47 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 48 "github.com/cs3org/reva/v2/pkg/storage/utils/indexer" 49 indexerErrors "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/errors" 50 "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/option" 51 "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" 52 "github.com/cs3org/reva/v2/pkg/storagespace" 53 "github.com/cs3org/reva/v2/pkg/utils" 54 ) 55 56 func init() { 57 registry.Register("cs3", NewDefault) 58 } 59 60 // Manager implements a publicshare manager using a cs3 storage backend 61 type Manager struct { 62 gatewayClient gateway.GatewayAPIClient 63 sync.RWMutex 64 65 storage metadata.Storage 66 indexer indexer.Indexer 67 passwordHashCost int 68 69 initialized bool 70 } 71 72 type config struct { 73 GatewayAddr string `mapstructure:"gateway_addr"` 74 ProviderAddr string `mapstructure:"provider_addr"` 75 ServiceUserID string `mapstructure:"service_user_id"` 76 ServiceUserIdp string `mapstructure:"service_user_idp"` 77 MachineAuthAPIKey string `mapstructure:"machine_auth_apikey"` 78 } 79 80 // NewDefault returns a new manager instance with default dependencies 81 func NewDefault(m map[string]interface{}) (publicshare.Manager, error) { 82 c := &config{} 83 if err := mapstructure.Decode(m, c); err != nil { 84 err = errors.Wrap(err, "error creating a new manager") 85 return nil, err 86 } 87 88 s, err := metadata.NewCS3Storage(c.GatewayAddr, c.ProviderAddr, c.ServiceUserID, c.ServiceUserIdp, c.MachineAuthAPIKey) 89 if err != nil { 90 return nil, err 91 } 92 indexer := indexer.CreateIndexer(s) 93 94 client, err := pool.GetGatewayServiceClient(c.GatewayAddr) 95 if err != nil { 96 return nil, err 97 } 98 99 return New(client, s, indexer, bcrypt.DefaultCost) 100 } 101 102 // New returns a new manager instance 103 func New(gatewayClient gateway.GatewayAPIClient, storage metadata.Storage, indexer indexer.Indexer, passwordHashCost int) (*Manager, error) { 104 return &Manager{ 105 gatewayClient: gatewayClient, 106 storage: storage, 107 indexer: indexer, 108 passwordHashCost: passwordHashCost, 109 initialized: false, 110 }, nil 111 } 112 113 func (m *Manager) initialize() error { 114 if m.initialized { 115 return nil 116 } 117 118 m.Lock() 119 defer m.Unlock() 120 121 if m.initialized { // check if initialization happened while grabbing the lock 122 return nil 123 } 124 125 err := m.storage.Init(context.Background(), "public-share-manager-metadata") 126 if err != nil { 127 return err 128 } 129 if err := m.storage.MakeDirIfNotExist(context.Background(), "publicshares"); err != nil { 130 return err 131 } 132 err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByField("Id.OpaqueId"), "Token", "publicshares", "unique", nil, true) 133 if err != nil { 134 return err 135 } 136 err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{ 137 Name: "Owner", 138 Func: indexOwnerFunc, 139 }, "Token", "publicshares", "non_unique", nil, true) 140 if err != nil { 141 return err 142 } 143 err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{ 144 Name: "Creator", 145 Func: indexCreatorFunc, 146 }, "Token", "publicshares", "non_unique", nil, true) 147 if err != nil { 148 return err 149 } 150 err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{ 151 Name: "ResourceId", 152 Func: indexResourceIDFunc, 153 }, "Token", "publicshares", "non_unique", nil, true) 154 if err != nil { 155 return err 156 } 157 m.initialized = true 158 return nil 159 } 160 161 // Dump exports public shares to channels (e.g. during migration) 162 func (m *Manager) Dump(ctx context.Context, shareChan chan<- *publicshare.WithPassword) error { 163 if err := m.initialize(); err != nil { 164 return err 165 } 166 167 pshares, err := m.storage.ListDir(ctx, "publicshares") 168 if err != nil { 169 return err 170 } 171 172 for _, v := range pshares { 173 var local publicshare.WithPassword 174 ps, err := m.getByToken(ctx, v.Name) 175 if err != nil { 176 return err 177 } 178 local.Password = ps.Password 179 proto.Merge(&local.PublicShare, &ps.PublicShare) 180 181 shareChan <- &local 182 } 183 184 return nil 185 } 186 187 // Load imports public shares and received shares from channels (e.g. during migration) 188 func (m *Manager) Load(ctx context.Context, shareChan <-chan *publicshare.WithPassword) error { 189 log := appctx.GetLogger(ctx) 190 if err := m.initialize(); err != nil { 191 return err 192 } 193 for ps := range shareChan { 194 if err := m.persist(context.Background(), ps); err != nil { 195 log.Error().Err(err).Interface("publicshare", ps).Msg("error loading public share") 196 } 197 } 198 return nil 199 } 200 201 // CreatePublicShare creates a new public share 202 func (m *Manager) CreatePublicShare(ctx context.Context, u *user.User, ri *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) { 203 if err := m.initialize(); err != nil { 204 return nil, err 205 } 206 207 id := &link.PublicShareId{ 208 OpaqueId: utils.RandString(15), 209 } 210 211 tkn := utils.RandString(15) 212 now := time.Now().UnixNano() 213 214 displayName, quicklink := tkn, false 215 if ri.ArbitraryMetadata != nil { 216 metadataName, ok := ri.ArbitraryMetadata.Metadata["name"] 217 if ok { 218 displayName = metadataName 219 } 220 221 quicklink, _ = strconv.ParseBool(ri.ArbitraryMetadata.Metadata["quicklink"]) 222 } 223 224 var passwordProtected bool 225 password := g.Password 226 if password != "" { 227 h, err := bcrypt.GenerateFromPassword([]byte(password), m.passwordHashCost) 228 if err != nil { 229 return nil, errors.Wrap(err, "could not hash share password") 230 } 231 password = string(h) 232 passwordProtected = true 233 } 234 235 createdAt := &typespb.Timestamp{ 236 Seconds: uint64(now / int64(time.Second)), 237 Nanos: uint32(now % int64(time.Second)), 238 } 239 240 s := &publicshare.WithPassword{ 241 PublicShare: link.PublicShare{ 242 Id: id, 243 Owner: ri.GetOwner(), 244 Creator: u.Id, 245 ResourceId: ri.Id, 246 Token: tkn, 247 Permissions: g.Permissions, 248 Ctime: createdAt, 249 Mtime: createdAt, 250 PasswordProtected: passwordProtected, 251 Expiration: g.Expiration, 252 DisplayName: displayName, 253 Quicklink: quicklink, 254 }, 255 Password: password, 256 } 257 258 err := m.persist(ctx, s) 259 if err != nil { 260 return nil, err 261 } 262 263 return &s.PublicShare, nil 264 } 265 266 // UpdatePublicShare updates an existing public share 267 func (m *Manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error) { 268 if err := m.initialize(); err != nil { 269 return nil, err 270 } 271 272 ps, err := m.getWithPassword(ctx, req.Ref) 273 if err != nil { 274 return nil, err 275 } 276 277 switch req.Update.Type { 278 case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME: 279 ps.PublicShare.DisplayName = req.Update.DisplayName 280 case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS: 281 ps.PublicShare.Permissions = req.Update.Grant.Permissions 282 case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION: 283 ps.PublicShare.Expiration = req.Update.Grant.Expiration 284 case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD: 285 if req.Update.Grant.Password == "" { 286 ps.Password = "" 287 ps.PublicShare.PasswordProtected = false 288 } else { 289 h, err := bcrypt.GenerateFromPassword([]byte(req.Update.Grant.Password), m.passwordHashCost) 290 if err != nil { 291 return nil, errors.Wrap(err, "could not hash share password") 292 } 293 ps.Password = string(h) 294 ps.PublicShare.PasswordProtected = true 295 } 296 default: 297 return nil, errtypes.BadRequest("no valid update type given") 298 } 299 300 err = m.persist(ctx, ps) 301 if err != nil { 302 return nil, err 303 } 304 305 return &ps.PublicShare, nil 306 } 307 308 // GetPublicShare returns an existing public share 309 func (m *Manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (*link.PublicShare, error) { 310 if err := m.initialize(); err != nil { 311 return nil, err 312 } 313 314 ps, err := m.getWithPassword(ctx, ref) 315 if err != nil { 316 return nil, err 317 } 318 319 if ps.PublicShare.PasswordProtected && sign { 320 err = publicshare.AddSignature(&ps.PublicShare, ps.Password) 321 if err != nil { 322 return nil, err 323 } 324 } 325 326 return &ps.PublicShare, nil 327 } 328 329 func (m *Manager) getWithPassword(ctx context.Context, ref *link.PublicShareReference) (*publicshare.WithPassword, error) { 330 switch { 331 case ref.GetToken() != "": 332 return m.getByToken(ctx, ref.GetToken()) 333 case ref.GetId().GetOpaqueId() != "": 334 return m.getByID(ctx, ref.GetId().GetOpaqueId()) 335 default: 336 return nil, errtypes.BadRequest("neither id nor token given") 337 } 338 } 339 340 func (m *Manager) getByID(ctx context.Context, id string) (*publicshare.WithPassword, error) { 341 tokens, err := m.indexer.FindBy(&link.PublicShare{}, 342 indexer.NewField("Id.OpaqueId", id), 343 ) 344 if err != nil { 345 return nil, err 346 } 347 if len(tokens) == 0 { 348 return nil, errtypes.NotFound("publicshare with the given id not found") 349 } 350 return m.getByToken(ctx, tokens[0]) 351 } 352 353 func (m *Manager) getByToken(ctx context.Context, token string) (*publicshare.WithPassword, error) { 354 fn := path.Join("publicshares", token) 355 data, err := m.storage.SimpleDownload(ctx, fn) 356 if err != nil { 357 return nil, err 358 } 359 360 ps := &publicshare.WithPassword{} 361 err = json.Unmarshal(data, ps) 362 if err != nil { 363 return nil, err 364 } 365 id := storagespace.UpdateLegacyResourceID(ps.PublicShare.ResourceId) 366 ps.PublicShare.ResourceId = id 367 return ps, nil 368 } 369 370 // ListPublicShares lists existing public shares matching the given filters 371 func (m *Manager) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error) { 372 if err := m.initialize(); err != nil { 373 return nil, err 374 } 375 376 log := appctx.GetLogger(ctx) 377 var rIDs []*provider.ResourceId 378 if len(filters) != 0 { 379 grouped := publicshare.GroupFiltersByType(filters) 380 for _, g := range grouped { 381 for _, f := range g { 382 if f.GetResourceId() != nil { 383 rIDs = append(rIDs, f.GetResourceId()) 384 } 385 } 386 } 387 } 388 var ( 389 createdShareTokens []string 390 err error 391 ) 392 393 // in spaces, always use the resourceId 394 if len(rIDs) != 0 { 395 for _, rID := range rIDs { 396 shareTokens, err := m.indexer.FindBy(&link.PublicShare{}, 397 indexer.NewField("ResourceId", resourceIDToIndex(rID)), 398 ) 399 if err != nil { 400 return nil, err 401 } 402 createdShareTokens = append(createdShareTokens, shareTokens...) 403 } 404 } else { 405 // fallback for legacy use 406 createdShareTokens, err = m.indexer.FindBy(&link.PublicShare{}, 407 indexer.NewField("Owner", userIDToIndex(u.Id)), 408 indexer.NewField("Creator", userIDToIndex(u.Id)), 409 ) 410 if err != nil { 411 return nil, err 412 } 413 } 414 415 // We use shareMem as a temporary lookup store to check which shares were 416 // already added. This is to prevent duplicates. 417 shareMem := make(map[string]struct{}) 418 result := []*link.PublicShare{} 419 for _, token := range createdShareTokens { 420 ps, err := m.getByToken(ctx, token) 421 if err != nil { 422 return nil, err 423 } 424 425 if !publicshare.MatchesFilters(&ps.PublicShare, filters) { 426 continue 427 } 428 429 if publicshare.IsExpired(&ps.PublicShare) { 430 ref := &link.PublicShareReference{ 431 Spec: &link.PublicShareReference_Id{ 432 Id: ps.PublicShare.Id, 433 }, 434 } 435 if err := m.RevokePublicShare(ctx, u, ref); err != nil { 436 log.Error().Err(err). 437 Str("public_share_token", ps.PublicShare.Token). 438 Str("public_share_id", ps.PublicShare.Id.OpaqueId). 439 Msg("failed to revoke expired public share") 440 } 441 continue 442 } 443 444 if ps.PublicShare.PasswordProtected && sign { 445 err = publicshare.AddSignature(&ps.PublicShare, ps.Password) 446 if err != nil { 447 return nil, err 448 } 449 } 450 result = append(result, &ps.PublicShare) 451 shareMem[ps.PublicShare.Token] = struct{}{} 452 } 453 454 // If a user requests to list shares which have not been created by them 455 // we have to explicitly fetch these shares and check if the user is 456 // allowed to list the shares. 457 // Only then can we add these shares to the result. 458 grouped := publicshare.GroupFiltersByType(filters) 459 idFilter, ok := grouped[link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID] 460 if !ok { 461 return result, nil 462 } 463 464 var tokens []string 465 if len(idFilter) > 0 { 466 idFilters := make([]indexer.Field, 0, len(idFilter)) 467 for _, filter := range idFilter { 468 resourceID := filter.GetResourceId() 469 idFilters = append(idFilters, indexer.NewField("ResourceId", resourceIDToIndex(resourceID))) 470 } 471 tokens, err = m.indexer.FindBy(&link.PublicShare{}, idFilters...) 472 if err != nil { 473 return nil, err 474 } 475 } 476 477 // statMem is used as a local cache to prevent statting resources which 478 // already have been checked. 479 statMem := make(map[string]struct{}) 480 for _, token := range tokens { 481 if _, handled := shareMem[token]; handled { 482 // We don't want to add a share multiple times when we added it 483 // already. 484 continue 485 } 486 487 s, err := m.getByToken(ctx, token) 488 if err != nil { 489 return nil, err 490 } 491 492 if _, checked := statMem[resourceIDToIndex(s.PublicShare.GetResourceId())]; !checked { 493 sReq := &provider.StatRequest{ 494 Ref: &provider.Reference{ResourceId: s.PublicShare.GetResourceId()}, 495 } 496 sRes, err := m.gatewayClient.Stat(ctx, sReq) 497 if err != nil { 498 continue 499 } 500 if sRes.Status.Code != rpc.Code_CODE_OK { 501 continue 502 } 503 if !sRes.Info.PermissionSet.ListGrants { 504 continue 505 } 506 statMem[resourceIDToIndex(s.PublicShare.GetResourceId())] = struct{}{} 507 } 508 509 if publicshare.MatchesFilters(&s.PublicShare, filters) { 510 result = append(result, &s.PublicShare) 511 shareMem[s.PublicShare.Token] = struct{}{} 512 } 513 } 514 return result, nil 515 } 516 517 // RevokePublicShare revokes an existing public share 518 func (m *Manager) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error { 519 if err := m.initialize(); err != nil { 520 return err 521 } 522 523 ps, err := m.GetPublicShare(ctx, u, ref, false) 524 if err != nil { 525 return err 526 } 527 528 err = m.storage.Delete(ctx, path.Join("publicshares", ps.Token)) 529 if err != nil { 530 if _, ok := err.(errtypes.NotFound); !ok { 531 return err 532 } 533 } 534 535 return m.indexer.Delete(ps) 536 } 537 538 // GetPublicShareByToken gets an existing public share in an unauthenticated context using either a password or a signature 539 func (m *Manager) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) { 540 if err := m.initialize(); err != nil { 541 return nil, err 542 } 543 544 ps, err := m.getByToken(ctx, token) 545 if err != nil { 546 return nil, err 547 } 548 549 if publicshare.IsExpired(&ps.PublicShare) { 550 return nil, errtypes.NotFound("public share has expired") 551 } 552 553 if ps.PublicShare.PasswordProtected { 554 if !publicshare.Authenticate(&ps.PublicShare, ps.Password, auth) { 555 return nil, errtypes.InvalidCredentials("access denied") 556 } 557 } 558 559 return &ps.PublicShare, nil 560 } 561 562 func indexOwnerFunc(v interface{}) (string, error) { 563 ps, ok := v.(*link.PublicShare) 564 if !ok { 565 return "", fmt.Errorf("given entity is not a public share") 566 } 567 return userIDToIndex(ps.Owner), nil 568 } 569 570 func indexCreatorFunc(v interface{}) (string, error) { 571 ps, ok := v.(*link.PublicShare) 572 if !ok { 573 return "", fmt.Errorf("given entity is not a public share") 574 } 575 return userIDToIndex(ps.Creator), nil 576 } 577 578 func indexResourceIDFunc(v interface{}) (string, error) { 579 ps, ok := v.(*link.PublicShare) 580 if !ok { 581 return "", fmt.Errorf("given entity is not a public share") 582 } 583 return resourceIDToIndex(ps.ResourceId), nil 584 } 585 586 func userIDToIndex(id *user.UserId) string { 587 return url.QueryEscape(id.Idp + ":" + id.OpaqueId) 588 } 589 590 func resourceIDToIndex(id *provider.ResourceId) string { 591 return strings.Join([]string{id.StorageId, id.OpaqueId}, "!") 592 } 593 594 func (m *Manager) persist(ctx context.Context, ps *publicshare.WithPassword) error { 595 data, err := json.Marshal(ps) 596 if err != nil { 597 return err 598 } 599 600 fn := path.Join("publicshares", ps.PublicShare.Token) 601 err = m.storage.SimpleUpload(ctx, fn, data) 602 if err != nil { 603 return err 604 } 605 606 _, err = m.indexer.Add(&ps.PublicShare) 607 if err != nil { 608 if _, ok := err.(*indexerErrors.AlreadyExistsErr); ok { 609 return nil 610 } 611 err = m.indexer.Delete(&ps.PublicShare) 612 if err != nil { 613 return err 614 } 615 _, err = m.indexer.Add(&ps.PublicShare) 616 return err 617 } 618 619 return nil 620 }