github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/ocmshareprovider/ocmshareprovider.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 ocmshareprovider 20 21 import ( 22 "context" 23 "fmt" 24 "net/url" 25 "path/filepath" 26 "strings" 27 "text/template" 28 "time" 29 30 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 31 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 32 ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" 33 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 34 ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" 35 providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 36 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 37 "github.com/cs3org/reva/v2/internal/http/services/ocmd" 38 "github.com/cs3org/reva/v2/pkg/appctx" 39 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 40 "github.com/cs3org/reva/v2/pkg/errtypes" 41 "github.com/cs3org/reva/v2/pkg/ocm/client" 42 "github.com/cs3org/reva/v2/pkg/ocm/share" 43 "github.com/cs3org/reva/v2/pkg/ocm/share/repository/registry" 44 ocmuser "github.com/cs3org/reva/v2/pkg/ocm/user" 45 "github.com/cs3org/reva/v2/pkg/rgrpc" 46 "github.com/cs3org/reva/v2/pkg/rgrpc/status" 47 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 48 "github.com/cs3org/reva/v2/pkg/sharedconf" 49 "github.com/cs3org/reva/v2/pkg/storage/utils/walker" 50 "github.com/cs3org/reva/v2/pkg/utils" 51 "github.com/cs3org/reva/v2/pkg/utils/cfg" 52 "github.com/pkg/errors" 53 "github.com/rs/zerolog" 54 "google.golang.org/grpc" 55 ) 56 57 func init() { 58 rgrpc.Register("ocmshareprovider", New) 59 } 60 61 type config struct { 62 Driver string `mapstructure:"driver"` 63 Drivers map[string]map[string]interface{} `mapstructure:"drivers"` 64 ClientTimeout int `mapstructure:"client_timeout"` 65 ClientInsecure bool `mapstructure:"client_insecure"` 66 GatewaySVC string `mapstructure:"gatewaysvc" validate:"required"` 67 ProviderDomain string `mapstructure:"provider_domain" validate:"required" docs:"The same domain registered in the provider authorizer"` 68 WebDAVEndpoint string `mapstructure:"webdav_endpoint" validate:"required"` 69 WebappTemplate string `mapstructure:"webapp_template"` 70 } 71 72 type service struct { 73 conf *config 74 repo share.Repository 75 client *client.OCMClient 76 gatewaySelector *pool.Selector[gateway.GatewayAPIClient] 77 webappTmpl *template.Template 78 walker walker.Walker 79 } 80 81 func (c *config) ApplyDefaults() { 82 if c.Driver == "" { 83 c.Driver = "json" 84 } 85 if c.ClientTimeout == 0 { 86 c.ClientTimeout = 10 87 } 88 if c.WebappTemplate == "" { 89 c.WebappTemplate = "https://cernbox.cern.ch/external/sciencemesh/{{.Token}}{relative-path-to-shared-resource}" 90 } 91 92 c.GatewaySVC = sharedconf.GetGatewaySVC(c.GatewaySVC) 93 } 94 95 func (s *service) Register(ss *grpc.Server) { 96 ocm.RegisterOcmAPIServer(ss, s) 97 } 98 99 func getShareRepository(c *config) (share.Repository, error) { 100 if f, ok := registry.NewFuncs[c.Driver]; ok { 101 return f(c.Drivers[c.Driver]) 102 } 103 return nil, errtypes.NotFound("driver not found: " + c.Driver) 104 } 105 106 // New creates a new ocm share provider svc. 107 func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) { 108 var c config 109 if err := cfg.Decode(m, &c); err != nil { 110 return nil, err 111 } 112 113 repo, err := getShareRepository(&c) 114 if err != nil { 115 return nil, err 116 } 117 118 client := client.New(&client.Config{ 119 Timeout: time.Duration(c.ClientTimeout) * time.Second, 120 Insecure: c.ClientInsecure, 121 }) 122 123 gatewaySelector, err := pool.GatewaySelector(c.GatewaySVC) 124 if err != nil { 125 return nil, err 126 } 127 128 tpl, err := template.New("webapp_template").Parse(c.WebappTemplate) 129 if err != nil { 130 return nil, err 131 } 132 walker := walker.NewWalker(gatewaySelector) 133 134 service := &service{ 135 conf: &c, 136 repo: repo, 137 client: client, 138 gatewaySelector: gatewaySelector, 139 webappTmpl: tpl, 140 walker: walker, 141 } 142 143 return service, nil 144 } 145 146 func (s *service) Close() error { 147 return nil 148 } 149 150 func (s *service) UnprotectedEndpoints() []string { 151 return []string{"/cs3.sharing.ocm.v1beta1.OcmAPI/GetOCMShareByToken"} 152 } 153 154 func getOCMEndpoint(originProvider *ocmprovider.ProviderInfo) (string, error) { 155 for _, s := range originProvider.Services { 156 if s.Endpoint.Type.Name == "OCM" { 157 return s.Endpoint.Path, nil 158 } 159 } 160 return "", errors.New("ocm endpoint not specified for mesh provider") 161 } 162 163 func getResourceType(info *providerpb.ResourceInfo) string { 164 switch info.Type { 165 case providerpb.ResourceType_RESOURCE_TYPE_FILE: 166 return "file" 167 case providerpb.ResourceType_RESOURCE_TYPE_CONTAINER: 168 return "folder" 169 } 170 return "unknown" 171 } 172 173 func (s *service) webdavURL(_ context.Context, share *ocm.Share) string { 174 // the url is in the form of https://cernbox.cern.ch/remote.php/dav/ocm/token 175 p, _ := url.JoinPath(s.conf.WebDAVEndpoint, "/dav/ocm", share.GetId().GetOpaqueId()) 176 return p 177 } 178 179 func (s *service) getWebdavProtocol(ctx context.Context, share *ocm.Share, m *ocm.AccessMethod_WebdavOptions) *ocmd.WebDAV { 180 var perms []string 181 if m.WebdavOptions.Permissions.InitiateFileDownload { 182 perms = append(perms, "read") 183 } 184 if m.WebdavOptions.Permissions.InitiateFileUpload { 185 perms = append(perms, "write") 186 } 187 188 return &ocmd.WebDAV{ 189 Permissions: perms, 190 URL: s.webdavURL(ctx, share), 191 SharedSecret: share.Token, 192 } 193 } 194 195 func (s *service) getWebappProtocol(share *ocm.Share) *ocmd.Webapp { 196 var b strings.Builder 197 if err := s.webappTmpl.Execute(&b, share); err != nil { 198 return nil 199 } 200 return &ocmd.Webapp{ 201 URITemplate: b.String(), 202 } 203 } 204 205 func (s *service) getDataTransferProtocol(ctx context.Context, share *ocm.Share) *ocmd.Datatx { 206 var size uint64 207 208 gatewayClient, err := s.gatewaySelector.Next() 209 if err != nil { 210 return nil 211 } 212 // get the path of the share 213 statRes, err := gatewayClient.Stat(ctx, &providerpb.StatRequest{ 214 Ref: &providerpb.Reference{ 215 ResourceId: share.ResourceId, 216 }, 217 }) 218 if err != nil { 219 return nil 220 } 221 222 err = s.walker.Walk(ctx, statRes.GetInfo().GetId(), func(path string, info *providerpb.ResourceInfo, err error) error { 223 if info.Type == providerpb.ResourceType_RESOURCE_TYPE_FILE { 224 size += info.Size 225 } 226 return nil 227 }) 228 if err != nil { 229 return nil 230 } 231 return &ocmd.Datatx{ 232 SourceURI: s.webdavURL(ctx, share), 233 Size: size, 234 } 235 } 236 237 func (s *service) getProtocols(ctx context.Context, share *ocm.Share) ocmd.Protocols { 238 var p ocmd.Protocols 239 for _, m := range share.AccessMethods { 240 var newProtocol ocmd.Protocol 241 switch t := m.Term.(type) { 242 case *ocm.AccessMethod_WebdavOptions: 243 newProtocol = s.getWebdavProtocol(ctx, share, t) 244 case *ocm.AccessMethod_WebappOptions: 245 newProtocol = s.getWebappProtocol(share) 246 case *ocm.AccessMethod_TransferOptions: 247 newProtocol = s.getDataTransferProtocol(ctx, share) 248 } 249 if newProtocol != nil { 250 p = append(p, newProtocol) 251 } 252 } 253 return p 254 } 255 256 func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest) (*ocm.CreateOCMShareResponse, error) { 257 gatewayClient, err := s.gatewaySelector.Next() 258 if err != nil { 259 return nil, err 260 } 261 statRes, err := gatewayClient.Stat(ctx, &providerpb.StatRequest{ 262 Ref: &providerpb.Reference{ 263 ResourceId: req.ResourceId, 264 }, 265 }) 266 if err != nil { 267 return nil, err 268 } 269 270 if statRes.Status.Code != rpc.Code_CODE_OK { 271 if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { 272 return &ocm.CreateOCMShareResponse{ 273 Status: status.NewNotFound(ctx, statRes.Status.Message), 274 }, nil 275 } 276 return &ocm.CreateOCMShareResponse{ 277 Status: status.NewInternal(ctx, statRes.Status.Message), 278 }, nil 279 } 280 281 info := statRes.Info 282 user := ctxpkg.ContextMustGetUser(ctx) 283 tkn := utils.RandString(32) 284 now := time.Now().UnixNano() 285 ts := &typespb.Timestamp{ 286 Seconds: uint64(now / 1000000000), 287 Nanos: uint32(now % 1000000000), 288 } 289 290 // 1. persist the share in the repository 291 ocmshare := &ocm.Share{ 292 Token: tkn, 293 Name: filepath.Base(info.Path), 294 ResourceId: req.ResourceId, 295 Grantee: req.Grantee, 296 ShareType: ocm.ShareType_SHARE_TYPE_USER, 297 Owner: info.Owner, 298 Creator: user.Id, 299 Ctime: ts, 300 Mtime: ts, 301 Expiration: req.Expiration, 302 AccessMethods: req.AccessMethods, 303 } 304 305 ocmshare, err = s.repo.StoreShare(ctx, ocmshare) 306 if err != nil { 307 if errors.Is(err, share.ErrShareAlreadyExisting) { 308 return &ocm.CreateOCMShareResponse{ 309 Status: status.NewAlreadyExists(ctx, err, "share already exists"), 310 }, nil 311 } 312 return &ocm.CreateOCMShareResponse{ 313 Status: status.NewInternal(ctx, err.Error()), 314 }, nil 315 } 316 317 // 2. create the share on the remote provider 318 // 2.a get the ocm endpoint of the remote provider 319 ocmEndpoint, err := getOCMEndpoint(req.RecipientMeshProvider) 320 if err != nil { 321 return &ocm.CreateOCMShareResponse{ 322 Status: status.NewInvalidArg(ctx, "the selected provider does not have an OCM endpoint"), 323 }, nil 324 } 325 326 // 2.b replace outgoing user ids with ocm user ids 327 // unpack the federated user id 328 shareWith := ocmuser.FormatOCMUser(ocmuser.RemoteID(req.GetGrantee().GetUserId())) 329 330 // wrap the local user id in a federated user id 331 owner := ocmuser.FormatOCMUser(ocmuser.FederatedID(info.Owner, s.conf.ProviderDomain)) 332 sender := ocmuser.FormatOCMUser(ocmuser.FederatedID(user.Id, s.conf.ProviderDomain)) 333 334 newShareReq := &client.NewShareRequest{ 335 ShareWith: shareWith, 336 Name: ocmshare.Name, 337 ProviderID: ocmshare.Id.OpaqueId, 338 Owner: owner, 339 Sender: sender, 340 SenderDisplayName: user.DisplayName, 341 ShareType: "user", 342 ResourceType: getResourceType(info), 343 Protocols: s.getProtocols(ctx, ocmshare), 344 } 345 346 if req.Expiration != nil { 347 newShareReq.Expiration = req.Expiration.Seconds 348 } 349 350 // 2.c make POST /shares request 351 newShareRes, err := s.client.NewShare(ctx, ocmEndpoint, newShareReq) 352 if err != nil { 353 err2 := s.repo.DeleteShare(ctx, user, &ocm.ShareReference{Spec: &ocm.ShareReference_Id{Id: ocmshare.Id}}) 354 if err2 != nil { 355 appctx.GetLogger(ctx).Error().Err(err2).Str("shareid", ocmshare.GetId().GetOpaqueId()).Msg("could not delete local ocm share") 356 } 357 // TODO remove the share from the local storage 358 switch { 359 case errors.Is(err, client.ErrInvalidParameters): 360 return &ocm.CreateOCMShareResponse{ 361 Status: status.NewInvalidArg(ctx, err.Error()), 362 }, nil 363 case errors.Is(err, client.ErrServiceNotTrusted): 364 return &ocm.CreateOCMShareResponse{ 365 Status: status.NewInvalidArg(ctx, err.Error()), 366 }, nil 367 default: 368 return &ocm.CreateOCMShareResponse{ 369 Status: status.NewInternal(ctx, err.Error()), 370 }, nil 371 } 372 } 373 374 res := &ocm.CreateOCMShareResponse{ 375 Status: status.NewOK(ctx), 376 Share: ocmshare, 377 RecipientDisplayName: newShareRes.RecipientDisplayName, 378 } 379 return res, nil 380 } 381 382 func (s *service) RemoveOCMShare(ctx context.Context, req *ocm.RemoveOCMShareRequest) (*ocm.RemoveOCMShareResponse, error) { 383 user := ctxpkg.ContextMustGetUser(ctx) 384 getShareRes, err := s.GetOCMShare(ctx, &ocm.GetOCMShareRequest{Ref: req.Ref}) 385 if err != nil { 386 return &ocm.RemoveOCMShareResponse{ 387 Status: status.NewInternal(ctx, "error getting ocm share"), 388 }, nil 389 } 390 if getShareRes.Status.Code != rpc.Code_CODE_OK { 391 return &ocm.RemoveOCMShareResponse{ 392 Status: getShareRes.GetStatus(), 393 }, nil 394 } 395 396 if err := s.repo.DeleteShare(ctx, user, req.Ref); err != nil { 397 if errors.Is(err, share.ErrShareNotFound) { 398 return &ocm.RemoveOCMShareResponse{ 399 Status: status.NewNotFound(ctx, "share does not exist"), 400 }, nil 401 } 402 return &ocm.RemoveOCMShareResponse{ 403 Status: status.NewInternal(ctx, "error deleting share"), 404 }, nil 405 } 406 407 err = s.notify(ctx, client.SHARE_UNSHARED, getShareRes.GetShare()) 408 if err != nil { 409 // Continue even if the notification fails. The share has been removed locally. 410 appctx.GetLogger(ctx).Err(err).Msg("error notifying ocm remote provider") 411 } 412 413 return &ocm.RemoveOCMShareResponse{ 414 Status: status.NewOK(ctx), 415 }, nil 416 } 417 418 func (s *service) GetOCMShare(ctx context.Context, req *ocm.GetOCMShareRequest) (*ocm.GetOCMShareResponse, error) { 419 // if the request is by token, the user does not need to be in the ctx 420 var user *userpb.User 421 if req.Ref.GetToken() == "" { 422 user = ctxpkg.ContextMustGetUser(ctx) 423 } 424 ocmshare, err := s.repo.GetShare(ctx, user, req.Ref) 425 if err != nil { 426 if errors.Is(err, share.ErrShareNotFound) { 427 return &ocm.GetOCMShareResponse{ 428 Status: status.NewNotFound(ctx, "share does not exist"), 429 }, nil 430 } 431 return &ocm.GetOCMShareResponse{ 432 Status: status.NewInternal(ctx, "error getting share"), 433 }, nil 434 } 435 436 return &ocm.GetOCMShareResponse{ 437 Status: status.NewOK(ctx), 438 Share: ocmshare, 439 }, nil 440 } 441 442 func (s *service) GetOCMShareByToken(ctx context.Context, req *ocm.GetOCMShareByTokenRequest) (*ocm.GetOCMShareByTokenResponse, error) { 443 ocmshare, err := s.repo.GetShare(ctx, nil, &ocm.ShareReference{ 444 Spec: &ocm.ShareReference_Token{ 445 Token: req.Token, 446 }, 447 }) 448 if err != nil { 449 if errors.Is(err, share.ErrShareNotFound) { 450 return &ocm.GetOCMShareByTokenResponse{ 451 Status: status.NewNotFound(ctx, "share does not exist"), 452 }, nil 453 } 454 return &ocm.GetOCMShareByTokenResponse{ 455 Status: status.NewInternal(ctx, "error getting share"), 456 }, nil 457 } 458 459 return &ocm.GetOCMShareByTokenResponse{ 460 Status: status.NewOK(ctx), 461 Share: ocmshare, 462 }, nil 463 } 464 465 func (s *service) ListOCMShares(ctx context.Context, req *ocm.ListOCMSharesRequest) (*ocm.ListOCMSharesResponse, error) { 466 user := ctxpkg.ContextMustGetUser(ctx) 467 shares, err := s.repo.ListShares(ctx, user, req.Filters) 468 if err != nil { 469 return &ocm.ListOCMSharesResponse{ 470 Status: status.NewInternal(ctx, "error listing shares"), 471 }, nil 472 } 473 474 res := &ocm.ListOCMSharesResponse{ 475 Status: status.NewOK(ctx), 476 Shares: shares, 477 } 478 return res, nil 479 } 480 481 func (s *service) UpdateOCMShare(ctx context.Context, req *ocm.UpdateOCMShareRequest) (*ocm.UpdateOCMShareResponse, error) { 482 user := ctxpkg.ContextMustGetUser(ctx) 483 if len(req.Field) == 0 { 484 return &ocm.UpdateOCMShareResponse{ 485 Status: status.NewOK(ctx), 486 }, nil 487 } 488 489 getShareRes, err := s.GetOCMShare(ctx, &ocm.GetOCMShareRequest{Ref: req.Ref}) 490 if err != nil { 491 return &ocm.UpdateOCMShareResponse{ 492 Status: status.NewInternal(ctx, "error getting ocm share"), 493 }, nil 494 } 495 if getShareRes.Status.Code != rpc.Code_CODE_OK { 496 return &ocm.UpdateOCMShareResponse{ 497 Status: getShareRes.GetStatus(), 498 }, nil 499 } 500 501 uShare, err := s.repo.UpdateShare(ctx, user, req.Ref, req.Field...) 502 if err != nil { 503 if errors.Is(err, share.ErrShareNotFound) { 504 return &ocm.UpdateOCMShareResponse{ 505 Status: status.NewNotFound(ctx, "share does not exist"), 506 }, nil 507 } 508 return &ocm.UpdateOCMShareResponse{ 509 Status: status.NewInternal(ctx, "error updating share"), 510 }, nil 511 } 512 513 err = s.notify(ctx, client.SHARE_CHANGE_PERMISSION, uShare) 514 if err != nil { 515 // Disallow update if the remoter provider could not be notified to avoid inconsistencies 516 // between the local and remote shares. User still can delete the share. 517 err = fmt.Errorf("error notifying ocm remote provider: %w", err) 518 appctx.GetLogger(ctx).Err(err).Send() 519 520 // Revert the share changes. 521 if _, err := s.repo.StoreShare(ctx, getShareRes.GetShare()); err != nil { 522 appctx.GetLogger(ctx).Err(err).Msg("error reverting ocm share changes") 523 } 524 return &ocm.UpdateOCMShareResponse{ 525 Status: status.NewInternal(ctx, err.Error()), 526 }, nil 527 } 528 529 return &ocm.UpdateOCMShareResponse{ 530 Status: status.NewOK(ctx), 531 }, nil 532 } 533 534 func (s *service) ListReceivedOCMShares(ctx context.Context, req *ocm.ListReceivedOCMSharesRequest) (*ocm.ListReceivedOCMSharesResponse, error) { 535 user := ctxpkg.ContextMustGetUser(ctx) 536 shares, err := s.repo.ListReceivedShares(ctx, user) 537 if err != nil { 538 return &ocm.ListReceivedOCMSharesResponse{ 539 Status: status.NewInternal(ctx, "error listing received shares"), 540 }, nil 541 } 542 543 res := &ocm.ListReceivedOCMSharesResponse{ 544 Status: status.NewOK(ctx), 545 Shares: shares, 546 } 547 return res, nil 548 } 549 550 func (s *service) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceivedOCMShareRequest) (*ocm.UpdateReceivedOCMShareResponse, error) { 551 user := ctxpkg.ContextMustGetUser(ctx) 552 _, err := s.repo.UpdateReceivedShare(ctx, user, req.Share, req.UpdateMask) 553 if err != nil { 554 if errors.Is(err, share.ErrShareNotFound) { 555 return &ocm.UpdateReceivedOCMShareResponse{ 556 Status: status.NewNotFound(ctx, "share does not exist"), 557 }, nil 558 } 559 return &ocm.UpdateReceivedOCMShareResponse{ 560 Status: status.NewInternal(ctx, "error updating received share"), 561 }, nil 562 } 563 564 res := &ocm.UpdateReceivedOCMShareResponse{ 565 Status: status.NewOK(ctx), 566 } 567 return res, nil 568 } 569 570 func (s *service) GetReceivedOCMShare(ctx context.Context, req *ocm.GetReceivedOCMShareRequest) (*ocm.GetReceivedOCMShareResponse, error) { 571 user := ctxpkg.ContextMustGetUser(ctx) 572 if user.Id.GetType() == userpb.UserType_USER_TYPE_SERVICE { 573 var uid userpb.UserId 574 _ = utils.ReadJSONFromOpaque(req.Opaque, "userid", &uid) 575 user = &userpb.User{ 576 Id: &uid, 577 } 578 } 579 580 ocmshare, err := s.repo.GetReceivedShare(ctx, user, req.Ref) 581 if err != nil { 582 if errors.Is(err, share.ErrShareNotFound) { 583 return &ocm.GetReceivedOCMShareResponse{ 584 Status: status.NewNotFound(ctx, "share does not exist"), 585 }, nil 586 } 587 return &ocm.GetReceivedOCMShareResponse{ 588 Status: status.NewInternal(ctx, "error getting received share: "+err.Error()), 589 }, nil 590 } 591 592 res := &ocm.GetReceivedOCMShareResponse{ 593 Status: status.NewOK(ctx), 594 Share: ocmshare, 595 } 596 return res, nil 597 } 598 599 // https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1notifications/post 600 func (s *service) notify(ctx context.Context, notificationType string, share *ocm.Share) error { 601 gatewayClient, err := s.gatewaySelector.Next() 602 if err != nil { 603 return err 604 } 605 providerInfoResp, err := gatewayClient.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ 606 Domain: share.GetGrantee().GetUserId().GetIdp(), 607 }) 608 if err != nil { 609 return err 610 } 611 if providerInfoResp.Status.Code != rpc.Code_CODE_OK { 612 return fmt.Errorf("error getting provider info: %s", providerInfoResp.Status.Message) 613 } 614 ocmEndpoint, err := getOCMEndpoint(providerInfoResp.GetProviderInfo()) 615 if err != nil { 616 return err 617 } 618 619 notification := &client.Notification{} 620 switch notificationType { 621 case client.SHARE_UNSHARED: 622 notification.Grantee = share.GetGrantee().GetUserId().GetOpaqueId() 623 case client.SHARE_CHANGE_PERMISSION: 624 notification.Grantee = share.GetGrantee().GetUserId().GetOpaqueId() 625 notification.Protocols = s.getProtocols(ctx, share) 626 default: 627 return fmt.Errorf("unknown notification type: %s", notificationType) 628 } 629 630 newShareReq := &client.NotificationRequest{ 631 NotificationType: notificationType, 632 ResourceType: "file", // use type "file" for shared files or folders 633 ProviderId: share.GetId().GetOpaqueId(), 634 Notification: notification, 635 } 636 err = s.client.NotifyRemote(ctx, ocmEndpoint, newShareReq) 637 if err != nil { 638 appctx.GetLogger(ctx).Err(err).Msg("error notifying ocm remote provider") 639 return err 640 } 641 return nil 642 }