github.com/cs3org/reva/v2@v2.27.7/pkg/ocm/storage/received/ocm.go (about) 1 // Copyright 2018-2023 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 ocm 20 21 import ( 22 "context" 23 "crypto/tls" 24 "encoding/base64" 25 "encoding/xml" 26 "io" 27 "io/fs" 28 "net/http" 29 "net/url" 30 "path/filepath" 31 "regexp" 32 "strings" 33 34 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 35 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 36 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 37 ocmpb "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" 38 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 39 typepb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 40 "github.com/rs/zerolog" 41 "github.com/studio-b12/gowebdav" 42 43 "github.com/cs3org/reva/v2/pkg/errtypes" 44 "github.com/cs3org/reva/v2/pkg/events" 45 "github.com/cs3org/reva/v2/pkg/mime" 46 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 47 "github.com/cs3org/reva/v2/pkg/rhttp/router" 48 "github.com/cs3org/reva/v2/pkg/sharedconf" 49 "github.com/cs3org/reva/v2/pkg/storage" 50 "github.com/cs3org/reva/v2/pkg/storage/fs/registry" 51 "github.com/cs3org/reva/v2/pkg/storagespace" 52 "github.com/cs3org/reva/v2/pkg/utils" 53 "github.com/cs3org/reva/v2/pkg/utils/cfg" 54 ) 55 56 func init() { 57 registry.Register("ocmreceived", New) 58 } 59 60 type driver struct { 61 c *config 62 gateway *pool.Selector[gateway.GatewayAPIClient] 63 } 64 65 type config struct { 66 GatewaySVC string `mapstructure:"gatewaysvc"` 67 Insecure bool `mapstructure:"insecure"` 68 StorageRoot string `mapstructure:"storage_root"` 69 ServiceAccountID string `mapstructure:"service_account_id"` 70 ServiceAccountSecret string `mapstructure:"service_account_secret"` 71 } 72 73 func (c *config) ApplyDefaults() { 74 c.GatewaySVC = sharedconf.GetGatewaySVC(c.GatewaySVC) 75 } 76 77 // BearerAuthenticator represents an authenticator that adds a Bearer token to the Authorization header of HTTP requests. 78 type BearerAuthenticator struct { 79 Token string 80 } 81 82 // Authorize adds the Bearer token to the Authorization header of the provided HTTP request. 83 func (b BearerAuthenticator) Authorize(_ *http.Client, r *http.Request, _ string) error { 84 r.Header.Add("Authorization", "Bearer "+b.Token) 85 return nil 86 } 87 88 // Verify is not implemented for the BearerAuthenticator. It always returns false and nil error. 89 func (BearerAuthenticator) Verify(*http.Client, *http.Response, string) (bool, error) { 90 return false, nil 91 } 92 93 // Clone creates a new instance of the BearerAuthenticator. 94 func (b BearerAuthenticator) Clone() gowebdav.Authenticator { 95 return BearerAuthenticator{Token: b.Token} 96 } 97 98 // Close is not implemented for the BearerAuthenticator. It always returns nil. 99 func (BearerAuthenticator) Close() error { 100 return nil 101 } 102 103 // New creates an OCM storage driver. 104 func New(m map[string]interface{}, _ events.Stream, _ *zerolog.Logger) (storage.FS, error) { 105 var c config 106 if err := cfg.Decode(m, &c); err != nil { 107 return nil, err 108 } 109 110 gateway, err := pool.GatewaySelector(c.GatewaySVC) 111 if err != nil { 112 return nil, err 113 } 114 115 d := &driver{ 116 c: &c, 117 gateway: gateway, 118 } 119 120 return d, nil 121 } 122 123 func shareInfoFromPath(path string) (*ocmpb.ShareId, string) { 124 // the path is of the type /share_id[/rel_path] 125 shareID, rel := router.ShiftPath(path) 126 return &ocmpb.ShareId{OpaqueId: shareID}, rel 127 } 128 129 func shareInfoFromReference(ref *provider.Reference) (*ocmpb.ShareId, string) { 130 if ref.ResourceId == nil { 131 return shareInfoFromPath(ref.Path) 132 } 133 134 if ref.ResourceId.SpaceId == ref.ResourceId.OpaqueId { 135 return &ocmpb.ShareId{OpaqueId: ref.ResourceId.SpaceId}, ref.Path 136 } 137 decodedBytes, err := base64.StdEncoding.DecodeString(ref.ResourceId.OpaqueId) 138 if err != nil { 139 // this should never happen 140 return &ocmpb.ShareId{OpaqueId: ref.ResourceId.SpaceId}, ref.Path 141 } 142 return &ocmpb.ShareId{OpaqueId: ref.ResourceId.SpaceId}, filepath.Join(string(decodedBytes), ref.Path) 143 144 } 145 146 func (d *driver) getWebDAVFromShare(ctx context.Context, forUser *userpb.UserId, shareID *ocmpb.ShareId) (*ocmpb.ReceivedShare, string, string, error) { 147 // TODO: we may want to cache the share 148 req := &ocmpb.GetReceivedOCMShareRequest{ 149 Ref: &ocmpb.ShareReference{ 150 Spec: &ocmpb.ShareReference_Id{ 151 Id: shareID, 152 }, 153 }, 154 } 155 if forUser != nil { 156 req.Opaque = utils.AppendJSONToOpaque(nil, "userid", forUser) 157 } 158 gwc, err := d.gateway.Next() 159 if err != nil { 160 return nil, "", "", err 161 } 162 res, err := gwc.GetReceivedOCMShare(ctx, req) 163 if err != nil { 164 return nil, "", "", err 165 } 166 167 if res.Status.Code != rpc.Code_CODE_OK { 168 if res.Status.Code == rpc.Code_CODE_NOT_FOUND { 169 return nil, "", "", errtypes.NotFound("share not found") 170 } 171 return nil, "", "", errtypes.InternalError(res.Status.Message) 172 } 173 174 dav, ok := getWebDAVProtocol(res.Share.Protocols) 175 if !ok { 176 return nil, "", "", errtypes.NotFound("share does not contain a WebDAV endpoint") 177 } 178 179 return res.Share, dav.Uri, dav.SharedSecret, nil 180 } 181 182 func getWebDAVProtocol(protocols []*ocmpb.Protocol) (*ocmpb.WebDAVProtocol, bool) { 183 for _, p := range protocols { 184 if dav, ok := p.Term.(*ocmpb.Protocol_WebdavOptions); ok { 185 return dav.WebdavOptions, true 186 } 187 } 188 return nil, false 189 } 190 191 func (d *driver) webdavClient(ctx context.Context, forUser *userpb.UserId, ref *provider.Reference) (*gowebdav.Client, *ocmpb.ReceivedShare, string, error) { 192 id, rel := shareInfoFromReference(ref) 193 194 share, endpoint, secret, err := d.getWebDAVFromShare(ctx, forUser, id) 195 if err != nil { 196 return nil, nil, "", err 197 } 198 199 endpoint, err = url.PathUnescape(endpoint) 200 if err != nil { 201 return nil, nil, "", err 202 } 203 204 // FIXME: it's still not clear from the OCM APIs how to use the shared secret 205 // will use as a token in the bearer authentication as this is the reva implementation 206 c := gowebdav.NewAuthClient(endpoint, gowebdav.NewPreemptiveAuth(BearerAuthenticator{Token: secret})) 207 if d.c.Insecure { 208 c.SetTransport(&http.Transport{ 209 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 210 }) 211 } 212 213 return c, share, rel, nil 214 } 215 216 func (d *driver) CreateDir(ctx context.Context, ref *provider.Reference) error { 217 client, _, rel, err := d.webdavClient(ctx, nil, ref) 218 if err != nil { 219 return err 220 } 221 return client.MkdirAll(rel, 0) 222 } 223 224 func (d *driver) Delete(ctx context.Context, ref *provider.Reference) error { 225 client, _, rel, err := d.webdavClient(ctx, nil, ref) 226 if err != nil { 227 return err 228 } 229 return client.RemoveAll(rel) 230 } 231 232 func (d *driver) TouchFile(ctx context.Context, ref *provider.Reference, markprocessing bool, mtime string) error { 233 client, _, rel, err := d.webdavClient(ctx, nil, ref) 234 if err != nil { 235 return err 236 } 237 return client.Write(rel, []byte{}, 0) 238 } 239 240 func (d *driver) Move(ctx context.Context, oldRef, newRef *provider.Reference) error { 241 client, _, relOld, err := d.webdavClient(ctx, nil, oldRef) 242 if err != nil { 243 return err 244 } 245 _, relNew := shareInfoFromReference(newRef) 246 247 return client.Rename(relOld, relNew, false) 248 } 249 250 func getPathFromShareIDAndRelPath(shareID *ocmpb.ShareId, relPath string) string { 251 return filepath.Join("/", shareID.OpaqueId, relPath) 252 } 253 254 func convertStatToResourceInfo(ref *provider.Reference, f fs.FileInfo, share *ocmpb.ReceivedShare) (*provider.ResourceInfo, error) { 255 t := provider.ResourceType_RESOURCE_TYPE_FILE 256 if f.IsDir() { 257 t = provider.ResourceType_RESOURCE_TYPE_CONTAINER 258 } 259 260 webdavFile, ok := f.(gowebdav.File) 261 if !ok { 262 return nil, errtypes.InternalError("could not get webdav props") 263 } 264 265 var name string 266 switch { 267 case share.ResourceType == provider.ResourceType_RESOURCE_TYPE_FILE: 268 name = share.Name 269 case webdavFile.Path() == "/": 270 name = share.Name 271 default: 272 name = webdavFile.Name() 273 } 274 275 opaqueid := base64.StdEncoding.EncodeToString([]byte(webdavFile.Path())) 276 277 // ids are of the format <ocmstorageproviderid>$<shareid>!<opaqueid> 278 id := &provider.ResourceId{ 279 StorageId: utils.OCMStorageProviderID, 280 SpaceId: share.Id.OpaqueId, 281 OpaqueId: opaqueid, 282 } 283 webdavProtocol, _ := getWebDAVProtocol(share.Protocols) 284 285 ri := provider.ResourceInfo{ 286 Type: t, 287 Id: id, 288 MimeType: mime.Detect(f.IsDir(), f.Name()), 289 Path: name, 290 Name: name, 291 Size: uint64(f.Size()), 292 Mtime: &typepb.Timestamp{ 293 Seconds: uint64(f.ModTime().Unix()), 294 }, 295 Etag: webdavFile.ETag(), 296 Owner: share.Creator, 297 PermissionSet: webdavProtocol.Permissions.Permissions, 298 } 299 300 if t == provider.ResourceType_RESOURCE_TYPE_FILE { 301 // get SHA1 checksum from owncloud specific properties if available 302 propstat := webdavFile.Sys().(gowebdav.Props) 303 ri.Checksum = extractChecksum(propstat) 304 } 305 306 if f.(gowebdav.File).StatusCode() == 425 { 307 ri.Opaque = utils.AppendPlainToOpaque(ri.Opaque, "status", "processing") 308 } 309 310 return &ri, nil 311 } 312 313 func extractChecksum(props gowebdav.Props) *provider.ResourceChecksum { 314 checksums := props.GetString(xml.Name{Space: "http://owncloud.org/ns", Local: "checksums"}) 315 if checksums == "" { 316 return &provider.ResourceChecksum{ 317 Type: provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_INVALID, 318 } 319 } 320 re := regexp.MustCompile("SHA1:(.*)") 321 matches := re.FindStringSubmatch(checksums) 322 if len(matches) == 2 { 323 return &provider.ResourceChecksum{ 324 Type: provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_SHA1, 325 Sum: matches[1], 326 } 327 } 328 return &provider.ResourceChecksum{ 329 Type: provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_INVALID, 330 } 331 } 332 333 func (d *driver) GetMD(ctx context.Context, ref *provider.Reference, _ []string, _ []string) (*provider.ResourceInfo, error) { 334 client, share, rel, err := d.webdavClient(ctx, nil, ref) 335 if err != nil { 336 return nil, err 337 } 338 339 info, err := client.StatWithProps(rel, []string{}) // request all properties by giving an empty list 340 if err != nil { 341 if gowebdav.IsErrNotFound(err) { 342 return nil, errtypes.NotFound(ref.GetPath()) 343 } 344 return nil, err 345 } 346 347 return convertStatToResourceInfo(ref, info, share) 348 } 349 350 func (d *driver) ListFolder(ctx context.Context, ref *provider.Reference, _ []string, _ []string) ([]*provider.ResourceInfo, error) { 351 client, share, rel, err := d.webdavClient(ctx, nil, ref) 352 if err != nil { 353 return nil, err 354 } 355 356 list, err := client.ReadDirWithProps(rel, []string{}) // request all properties by giving an empty list 357 if err != nil { 358 return nil, err 359 } 360 361 res := make([]*provider.ResourceInfo, 0, len(list)) 362 for _, r := range list { 363 info, err := convertStatToResourceInfo(ref, r, share) 364 if err != nil { 365 return nil, err 366 } 367 res = append(res, info) 368 } 369 return res, nil 370 } 371 372 func (d *driver) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) { 373 client, share, rel, err := d.webdavClient(ctx, nil, ref) 374 if err != nil { 375 return nil, nil, err 376 } 377 378 info, err := client.StatWithProps(rel, []string{}) // request all properties by giving an empty list 379 if err != nil { 380 if gowebdav.IsErrNotFound(err) { 381 return nil, nil, errtypes.NotFound(ref.GetPath()) 382 } 383 return nil, nil, err 384 } 385 md, err := convertStatToResourceInfo(ref, info, share) 386 if err != nil { 387 return nil, nil, err 388 } 389 390 if !openReaderfunc(md) { 391 return md, nil, nil 392 } 393 394 reader, err := client.ReadStream(rel) 395 return md, reader, err 396 } 397 398 func (d *driver) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { 399 shareID, rel := shareInfoFromReference(&provider.Reference{ 400 ResourceId: id, 401 }) 402 return getPathFromShareIDAndRelPath(shareID, rel), nil 403 } 404 405 func (d *driver) Shutdown(ctx context.Context) error { 406 return nil 407 } 408 409 func (d *driver) CreateHome(ctx context.Context) error { 410 return errtypes.NotSupported("operation not supported") 411 } 412 413 func (d *driver) GetHome(ctx context.Context) (string, error) { 414 return "", errtypes.NotSupported("operation not supported") 415 } 416 417 func (d *driver) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { 418 return nil, errtypes.NotSupported("operation not supported") 419 } 420 421 func (d *driver) DownloadRevision(ctx context.Context, ref *provider.Reference, key string, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) { 422 return nil, nil, errtypes.NotSupported("operation not supported") 423 } 424 425 func (d *driver) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error { 426 return errtypes.NotSupported("operation not supported") 427 } 428 429 func (d *driver) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) { 430 return nil, errtypes.NotSupported("operation not supported") 431 } 432 433 func (d *driver) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error { 434 return errtypes.NotSupported("operation not supported") 435 } 436 437 func (d *driver) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error { 438 return errtypes.NotSupported("operation not supported") 439 } 440 441 func (d *driver) EmptyRecycle(ctx context.Context, ref *provider.Reference) error { 442 return errtypes.NotSupported("operation not supported") 443 } 444 445 func (d *driver) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { 446 return errtypes.NotSupported("operation not supported") 447 } 448 449 func (d *driver) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { 450 return errtypes.NotSupported("operation not supported") 451 } 452 453 func (d *driver) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { 454 return errtypes.NotSupported("operation not supported") 455 } 456 457 func (d *driver) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { 458 return errtypes.NotSupported("operation not supported") 459 } 460 461 func (d *driver) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) { 462 return nil, errtypes.NotSupported("operation not supported") 463 } 464 465 func (d *driver) GetQuota(ctx context.Context, ref *provider.Reference) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64, uint64, error) { 466 return 0, 0, 0, errtypes.NotSupported("operation not supported") 467 } 468 469 func (d *driver) CreateReference(ctx context.Context, path string, targetURI *url.URL) error { 470 return errtypes.NotSupported("operation not supported") 471 } 472 473 func (d *driver) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error { 474 return errtypes.NotSupported("operation not supported") 475 } 476 477 func (d *driver) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error { 478 return errtypes.NotSupported("operation not supported") 479 } 480 481 // SetLock sets a lock on a file 482 func (d *driver) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { 483 client, _, rel, err := d.webdavClient(ctx, nil, ref) 484 if err != nil { 485 return err 486 } 487 488 return client.Lock(rel, lock.GetLockId()) 489 } 490 491 func (d *driver) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { 492 client, _, rel, err := d.webdavClient(ctx, nil, ref) 493 if err != nil { 494 return nil, err 495 } 496 497 token, err := client.GetLock(rel) 498 if err != nil { 499 return nil, err 500 } 501 502 return &provider.Lock{LockId: token, Type: provider.LockType_LOCK_TYPE_EXCL}, nil 503 } 504 505 func (d *driver) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error { 506 client, _, rel, err := d.webdavClient(ctx, nil, ref) 507 if err != nil { 508 return err 509 } 510 511 return client.RefreshLock(rel, lock.GetLockId()) 512 } 513 514 // Unlock removes a lock from a file 515 func (d *driver) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { 516 client, _, rel, err := d.webdavClient(ctx, nil, ref) 517 if err != nil { 518 return err 519 } 520 521 return client.Unlock(rel, lock.GetLockId()) 522 } 523 524 func (d *driver) ListStorageSpaces(ctx context.Context, filters []*provider.ListStorageSpacesRequest_Filter, _ bool) ([]*provider.StorageSpace, error) { 525 spaceTypes := map[string]struct{}{} 526 var exists = struct{}{} 527 appendTypes := []string{} 528 for _, f := range filters { 529 if f.Type == provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE { 530 spaceType := f.GetSpaceType() 531 if spaceType == "+mountpoint" { 532 appendTypes = append(appendTypes, strings.TrimPrefix(spaceType, "+")) 533 continue 534 } 535 spaceTypes[spaceType] = exists 536 } 537 } 538 gwc, err := d.gateway.Next() 539 if err != nil { 540 return nil, err 541 } 542 lrsRes, err := gwc.ListReceivedOCMShares(ctx, &ocmpb.ListReceivedOCMSharesRequest{}) 543 if err != nil { 544 return nil, err 545 } 546 // FIXME This might have to be ListOCMShares 547 // these are grants 548 549 lsRes, err := gwc.ListOCMShares(ctx, &ocmpb.ListOCMSharesRequest{}) 550 if err != nil { 551 return nil, err 552 } 553 554 if len(spaceTypes) == 0 { 555 spaceTypes["mountpoint"] = exists 556 } 557 for _, s := range appendTypes { 558 spaceTypes[s] = exists 559 } 560 561 spaces := []*provider.StorageSpace{} 562 for k := range spaceTypes { 563 if k == "mountpoint" { 564 for _, share := range lrsRes.Shares { 565 root := &provider.ResourceId{ 566 StorageId: utils.OCMStorageProviderID, 567 SpaceId: share.Id.OpaqueId, 568 OpaqueId: share.Id.OpaqueId, 569 } 570 space := &provider.StorageSpace{ 571 Id: &provider.StorageSpaceId{ 572 OpaqueId: storagespace.FormatResourceID(root), 573 }, 574 SpaceType: "mountpoint", 575 Owner: &userpb.User{ 576 Id: share.Grantee.GetUserId(), 577 }, 578 Root: root, 579 } 580 581 spaces = append(spaces, space) 582 } 583 for _, share := range lsRes.Shares { 584 root := &provider.ResourceId{ 585 StorageId: utils.OCMStorageProviderID, 586 SpaceId: share.Id.OpaqueId, 587 OpaqueId: share.Id.OpaqueId, 588 } 589 space := &provider.StorageSpace{ 590 Id: &provider.StorageSpaceId{ 591 OpaqueId: storagespace.FormatResourceID(root), 592 }, 593 SpaceType: "mountpoint", 594 Owner: &userpb.User{ 595 Id: share.Grantee.GetUserId(), 596 }, 597 Root: root, 598 } 599 600 spaces = append(spaces, space) 601 } 602 } 603 } 604 605 return spaces, nil 606 } 607 608 func (d *driver) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { 609 return nil, errtypes.NotSupported("operation not supported") 610 } 611 612 func (d *driver) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { 613 return nil, errtypes.NotSupported("operation not supported") 614 } 615 616 func (d *driver) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error { 617 return errtypes.NotSupported("operation not supported") 618 }