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  }