github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/gateway/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 gateway
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"net/url"
    25  	"path"
    26  	"strings"
    27  
    28  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    29  	ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
    30  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    31  	datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1"
    32  	"github.com/cs3org/reva/v2/pkg/appctx"
    33  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    34  	"github.com/cs3org/reva/v2/pkg/errtypes"
    35  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    36  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    37  	"github.com/pkg/errors"
    38  )
    39  
    40  // TODO(labkode): add multi-phase commit logic when commit share or commit ref is enabled.
    41  func (s *svc) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest) (*ocm.CreateOCMShareResponse, error) {
    42  	if len(req.AccessMethods) == 0 {
    43  		return &ocm.CreateOCMShareResponse{
    44  			Status: status.NewInvalidArg(ctx, "access methods cannot be empty"),
    45  		}, nil
    46  	}
    47  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
    48  	if err != nil {
    49  		return &ocm.CreateOCMShareResponse{
    50  			Status: status.NewInternal(ctx, "error getting user share provider client"),
    51  		}, nil
    52  	}
    53  
    54  	// persist the OCM share in the ocm share provider
    55  	res, err := c.CreateOCMShare(ctx, req)
    56  	if err != nil {
    57  		return nil, errors.Wrap(err, "gateway: error calling CreateOCMShare")
    58  	}
    59  	if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
    60  		return res, nil
    61  	}
    62  
    63  	// add a grant to the storage provider so the share can efficiently be listed
    64  	// the grant does not grant any permissions. access is granted by the OCM link token
    65  	// that is used by the public storage provider to impersonate the resource owner
    66  	status, err := s.addGrant(ctx, req.ResourceId, req.Grantee, req.AccessMethods[0].GetWebdavOptions().Permissions, req.Expiration, nil)
    67  	switch {
    68  	case err != nil:
    69  		appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg(err.Error())
    70  		return nil, errors.Wrap(err, "gateway: error adding grant to storage")
    71  	case status.Code == rpc.Code_CODE_UNIMPLEMENTED:
    72  		appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg("storing grants not supported, ignoring")
    73  	case status.Code != rpc.Code_CODE_OK:
    74  		appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg("storing grants is not successful")
    75  		return &ocm.CreateOCMShareResponse{
    76  			Status: status,
    77  		}, nil
    78  	}
    79  
    80  	return res, nil
    81  }
    82  
    83  func (s *svc) RemoveOCMShare(ctx context.Context, req *ocm.RemoveOCMShareRequest) (*ocm.RemoveOCMShareResponse, error) {
    84  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
    85  	if err != nil {
    86  		return &ocm.RemoveOCMShareResponse{
    87  			Status: status.NewInternal(ctx, "error getting user share provider client"),
    88  		}, nil
    89  	}
    90  
    91  	getShareRes, err := c.GetOCMShare(ctx, &ocm.GetOCMShareRequest{
    92  		Ref: req.Ref,
    93  	})
    94  	if err != nil {
    95  		return nil, errors.Wrap(err, "gateway: error calling GetOCMShare")
    96  	}
    97  	if getShareRes.Status.Code != rpc.Code_CODE_OK {
    98  		res := &ocm.RemoveOCMShareResponse{
    99  			Status: status.NewInternal(ctx,
   100  				"error getting ocm share when committing to the storage"),
   101  		}
   102  		return res, nil
   103  	}
   104  	share := getShareRes.Share
   105  
   106  	res, err := c.RemoveOCMShare(ctx, req)
   107  	if err != nil {
   108  		return nil, errors.Wrap(err, "gateway: error calling RemoveOCMShare")
   109  	}
   110  
   111  	// remove the grant from the storage provider
   112  	status, err := s.removeGrant(ctx, share.GetResourceId(), share.GetGrantee(), share.GetAccessMethods()[0].GetWebdavOptions().GetPermissions(), nil)
   113  	if err != nil {
   114  		return nil, errors.Wrap(err, "gateway: error removing grant from storage")
   115  	}
   116  	if status.Code != rpc.Code_CODE_OK {
   117  		return &ocm.RemoveOCMShareResponse{
   118  			Status: status,
   119  		}, err
   120  	}
   121  
   122  	return res, nil
   123  }
   124  
   125  // TODO(labkode): we need to validate share state vs storage grant and storage ref
   126  // If there are any inconsistencies, the share needs to be flag as invalid and a background process
   127  // or active fix needs to be performed.
   128  func (s *svc) GetOCMShare(ctx context.Context, req *ocm.GetOCMShareRequest) (*ocm.GetOCMShareResponse, error) {
   129  	return s.getOCMShare(ctx, req)
   130  }
   131  
   132  func (s *svc) getOCMShare(ctx context.Context, req *ocm.GetOCMShareRequest) (*ocm.GetOCMShareResponse, error) {
   133  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
   134  	if err != nil {
   135  		appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
   136  		return &ocm.GetOCMShareResponse{
   137  			Status: status.NewInternal(ctx, "error getting user share provider client"),
   138  		}, nil
   139  	}
   140  
   141  	res, err := c.GetOCMShare(ctx, req)
   142  	if err != nil {
   143  		return nil, errors.Wrap(err, "gateway: error calling GetOCMShare")
   144  	}
   145  
   146  	return res, nil
   147  }
   148  
   149  func (s *svc) GetOCMShareByToken(ctx context.Context, req *ocm.GetOCMShareByTokenRequest) (*ocm.GetOCMShareByTokenResponse, error) {
   150  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
   151  	if err != nil {
   152  		return nil, errors.Wrap(err, "gateway: error calling GetOCMShareProviderClient")
   153  	}
   154  
   155  	res, err := c.GetOCMShareByToken(ctx, req)
   156  	if err != nil {
   157  		return nil, errors.Wrap(err, "gateway: error calling GetOCMShareByToken")
   158  	}
   159  
   160  	return res, nil
   161  }
   162  
   163  // TODO(labkode): read GetShare comment.
   164  func (s *svc) ListOCMShares(ctx context.Context, req *ocm.ListOCMSharesRequest) (*ocm.ListOCMSharesResponse, error) {
   165  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
   166  	if err != nil {
   167  		appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
   168  		return &ocm.ListOCMSharesResponse{
   169  			Status: status.NewInternal(ctx, "error getting user share provider client"),
   170  		}, nil
   171  	}
   172  
   173  	res, err := c.ListOCMShares(ctx, req)
   174  	if err != nil {
   175  		return nil, errors.Wrap(err, "gateway: error calling ListOCMShares")
   176  	}
   177  
   178  	return res, nil
   179  }
   180  
   181  func (s *svc) UpdateOCMShare(ctx context.Context, req *ocm.UpdateOCMShareRequest) (*ocm.UpdateOCMShareResponse, error) {
   182  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
   183  	if err != nil {
   184  		appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
   185  		return &ocm.UpdateOCMShareResponse{
   186  			Status: status.NewInternal(ctx, "error getting share provider client"),
   187  		}, nil
   188  	}
   189  
   190  	res, err := c.UpdateOCMShare(ctx, req)
   191  	if err != nil {
   192  		return nil, errors.Wrap(err, "gateway: error calling UpdateOCMShare")
   193  	}
   194  
   195  	gRes, err := c.GetOCMShare(ctx, &ocm.GetOCMShareRequest{
   196  		Ref: req.Ref,
   197  	})
   198  	if err != nil {
   199  		return nil, errors.Wrap(err, "gateway: error calling GetOCMShare")
   200  	}
   201  	if gRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
   202  		return &ocm.UpdateOCMShareResponse{
   203  			Status: gRes.GetStatus(),
   204  		}, nil
   205  	}
   206  
   207  	creator, ok := ctxpkg.ContextGetUser(ctx)
   208  	if !ok {
   209  		return nil, errors.New("gateway: user not found in context")
   210  	}
   211  
   212  	grant := &provider.Grant{
   213  		Grantee:     gRes.GetShare().GetGrantee(),
   214  		Permissions: gRes.GetShare().GetAccessMethods()[0].GetWebdavOptions().GetPermissions(),
   215  		Expiration:  gRes.GetShare().GetExpiration(),
   216  		Creator:     creator.GetId(),
   217  	}
   218  	updateGrantStatus, err := s.updateGrant(ctx, gRes.GetShare().GetResourceId(), grant, nil)
   219  	if err != nil {
   220  		return nil, errors.Wrap(err, "gateway: error calling updateGrant")
   221  	}
   222  	if updateGrantStatus.GetCode() != rpc.Code_CODE_OK {
   223  		return &ocm.UpdateOCMShareResponse{
   224  			Status: updateGrantStatus,
   225  		}, nil
   226  	}
   227  
   228  	return res, nil
   229  }
   230  
   231  func (s *svc) ListReceivedOCMShares(ctx context.Context, req *ocm.ListReceivedOCMSharesRequest) (*ocm.ListReceivedOCMSharesResponse, error) {
   232  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
   233  	if err != nil {
   234  		appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
   235  		return &ocm.ListReceivedOCMSharesResponse{
   236  			Status: status.NewInternal(ctx, "error getting share provider client"),
   237  		}, nil
   238  	}
   239  
   240  	res, err := c.ListReceivedOCMShares(ctx, req)
   241  	if err != nil {
   242  		return nil, errors.Wrap(err, "gateway: error calling ListReceivedOCMShares")
   243  	}
   244  
   245  	return res, nil
   246  }
   247  
   248  func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceivedOCMShareRequest) (*ocm.UpdateReceivedOCMShareResponse, error) {
   249  	log := appctx.GetLogger(ctx)
   250  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
   251  	if err != nil {
   252  		appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
   253  		return &ocm.UpdateReceivedOCMShareResponse{
   254  			Status: status.NewInternal(ctx, "error getting share provider client"),
   255  		}, nil
   256  	}
   257  
   258  	// retrieve the current received share
   259  	getShareReq := &ocm.GetReceivedOCMShareRequest{
   260  		Ref: &ocm.ShareReference{
   261  			Spec: &ocm.ShareReference_Id{
   262  				Id: req.Share.Id,
   263  			},
   264  		},
   265  	}
   266  	getShareRes, err := s.GetReceivedOCMShare(ctx, getShareReq)
   267  	if err != nil {
   268  		log.Error().Err(err).Msg("gateway: error calling GetReceivedOCMShare")
   269  		return &ocm.UpdateReceivedOCMShareResponse{
   270  			Status: &rpc.Status{
   271  				Code: rpc.Code_CODE_INTERNAL,
   272  			},
   273  		}, nil
   274  	}
   275  	if getShareRes.Status.Code != rpc.Code_CODE_OK {
   276  		log.Error().Msg("gateway: error calling GetReceivedOCMShare")
   277  		return &ocm.UpdateReceivedOCMShareResponse{
   278  			Status: &rpc.Status{
   279  				Code:    rpc.Code_CODE_INTERNAL,
   280  				Message: "gateway: error calling GetReceivedOCMShare",
   281  			},
   282  		}, nil
   283  	}
   284  	share := getShareRes.Share
   285  	if share == nil {
   286  		log.Error().Err(err).Msg("gateway: got a nil share from GetReceivedOCMShare")
   287  		return &ocm.UpdateReceivedOCMShareResponse{
   288  			Status: &rpc.Status{
   289  				Code:    rpc.Code_CODE_INTERNAL,
   290  				Message: "gateway: got a nil share from GetReceivedOCMShare",
   291  			},
   292  		}, nil
   293  	}
   294  
   295  	res, err := c.UpdateReceivedOCMShare(ctx, req)
   296  	if err != nil {
   297  		log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare")
   298  		return &ocm.UpdateReceivedOCMShareResponse{
   299  			Status: &rpc.Status{
   300  				Code: rpc.Code_CODE_INTERNAL,
   301  			},
   302  		}, nil
   303  	}
   304  
   305  	for i := range req.UpdateMask.Paths {
   306  		switch req.UpdateMask.Paths[i] {
   307  		case "state":
   308  			switch req.GetShare().GetState() {
   309  			case ocm.ShareState_SHARE_STATE_ACCEPTED:
   310  				// for a transfer this is handled elsewhere
   311  			case ocm.ShareState_SHARE_STATE_PENDING:
   312  				// currently no consequences
   313  			case ocm.ShareState_SHARE_STATE_REJECTED:
   314  				// TODO
   315  				return res, nil
   316  			}
   317  		case "mount_point":
   318  			// TODO(labkode): implementing updating mount point
   319  			err = errtypes.NotSupported("gateway: update of mount point is not yet implemented")
   320  			return &ocm.UpdateReceivedOCMShareResponse{
   321  				Status: status.NewUnimplemented(ctx, err, "error updating received share"),
   322  			}, nil
   323  		default:
   324  			return nil, errtypes.NotSupported("updating " + req.UpdateMask.Paths[i] + " is not supported")
   325  		}
   326  	}
   327  	// handle transfer in case it has not already been accepted
   328  	if s.isTransferShare(share) && req.GetShare().State == ocm.ShareState_SHARE_STATE_ACCEPTED {
   329  		if share.State == ocm.ShareState_SHARE_STATE_ACCEPTED {
   330  			log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare, share already accepted.")
   331  			return &ocm.UpdateReceivedOCMShareResponse{
   332  				Status: &rpc.Status{
   333  					Code:    rpc.Code_CODE_FAILED_PRECONDITION,
   334  					Message: "Share already accepted.",
   335  				},
   336  			}, err
   337  		}
   338  		// get provided destination path
   339  		transferDestinationPath, err := s.getTransferDestinationPath(ctx, req)
   340  		if err != nil {
   341  			if err != nil {
   342  				log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare")
   343  				return &ocm.UpdateReceivedOCMShareResponse{
   344  					Status: &rpc.Status{
   345  						Code: rpc.Code_CODE_INTERNAL,
   346  					},
   347  				}, err
   348  			}
   349  		}
   350  
   351  		error := s.handleTransfer(ctx, share, transferDestinationPath)
   352  		if error != nil {
   353  			log.Err(error).Msg("gateway: error handling transfer in UpdateReceivedOCMShare")
   354  			return &ocm.UpdateReceivedOCMShareResponse{
   355  				Status: &rpc.Status{
   356  					Code: rpc.Code_CODE_INTERNAL,
   357  				},
   358  			}, error
   359  		}
   360  	}
   361  	return res, nil
   362  }
   363  
   364  func (s *svc) handleTransfer(ctx context.Context, share *ocm.ReceivedShare, transferDestinationPath string) error {
   365  	log := appctx.GetLogger(ctx)
   366  
   367  	protocol, ok := s.getTransferProtocol(share)
   368  	if !ok {
   369  		return errors.New("gateway: unable to retrieve transfer protocol")
   370  	}
   371  	sourceURI := protocol.SourceUri
   372  
   373  	// get the webdav endpoint of the grantee's idp
   374  	var granteeIdp string
   375  	if share.GetGrantee().Type == provider.GranteeType_GRANTEE_TYPE_USER {
   376  		granteeIdp = share.GetGrantee().GetUserId().Idp
   377  	}
   378  	if share.GetGrantee().Type == provider.GranteeType_GRANTEE_TYPE_GROUP {
   379  		granteeIdp = share.GetGrantee().GetGroupId().Idp
   380  	}
   381  	destWebdavEndpoint, err := s.getWebdavEndpoint(ctx, granteeIdp)
   382  	if err != nil {
   383  		log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare")
   384  		return err
   385  	}
   386  	destWebdavEndpointURL, err := url.Parse(destWebdavEndpoint)
   387  	if err != nil {
   388  		log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare: unable to parse webdav endpoint \"" + destWebdavEndpoint + "\" into URL structure")
   389  		return err
   390  	}
   391  	destWebdavHost, err := s.getWebdavHost(ctx, granteeIdp)
   392  	if err != nil {
   393  		log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare")
   394  		return err
   395  	}
   396  	var dstWebdavURLString string
   397  	if strings.Contains(destWebdavHost, "://") {
   398  		dstWebdavURLString = destWebdavHost
   399  	} else {
   400  		dstWebdavURLString = "http://" + destWebdavHost
   401  	}
   402  	dstWebdavHostURL, err := url.Parse(dstWebdavURLString)
   403  	if err != nil {
   404  		log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare: unable to parse webdav service host \"" + dstWebdavURLString + "\" into URL structure")
   405  		return err
   406  	}
   407  	destServiceHost := dstWebdavHostURL.Host + dstWebdavHostURL.Path
   408  	// optional prefix must only appear in target url path:
   409  	// http://...token...@reva.eu/prefix/?name=remote.php/webdav/home/...
   410  	destEndpointPath := strings.TrimPrefix(destWebdavEndpointURL.Path, dstWebdavHostURL.Path)
   411  	destEndpointScheme := destWebdavEndpointURL.Scheme
   412  	destToken := ctxpkg.ContextMustGetToken(ctx)
   413  	destPath := path.Join(destEndpointPath, transferDestinationPath, path.Base(share.Name))
   414  	destTargetURI := fmt.Sprintf("%s://%s@%s?name=%s", destEndpointScheme, destToken, destServiceHost, destPath)
   415  	// var destUri string
   416  	req := &datatx.CreateTransferRequest{
   417  		SrcTargetUri:  sourceURI,
   418  		DestTargetUri: destTargetURI,
   419  		ShareId:       share.Id,
   420  	}
   421  
   422  	res, err := s.CreateTransfer(ctx, req)
   423  	if err != nil {
   424  		return err
   425  	}
   426  	log.Info().Msgf("gateway: CreateTransfer: %v", res.TxInfo)
   427  	return nil
   428  }
   429  
   430  func (s *svc) isTransferShare(share *ocm.ReceivedShare) bool {
   431  	_, ok := s.getTransferProtocol(share)
   432  	return ok
   433  }
   434  
   435  func (s *svc) getTransferDestinationPath(ctx context.Context, req *ocm.UpdateReceivedOCMShareRequest) (string, error) {
   436  	log := appctx.GetLogger(ctx)
   437  	// the destination path is not part of any protocol, but an opaque field
   438  	destPathOpaque, ok := req.GetOpaque().GetMap()["transfer_destination_path"]
   439  	if ok {
   440  		switch destPathOpaque.Decoder {
   441  		case "plain":
   442  			if string(destPathOpaque.Value) != "" {
   443  				return string(destPathOpaque.Value), nil
   444  			}
   445  		default:
   446  			return "", errtypes.NotSupported("decoder of opaque entry 'transfer_destination_path' not recognized: " + destPathOpaque.Decoder)
   447  		}
   448  	}
   449  	log.Info().Msg("destination path not provided, trying default transfer destination folder")
   450  	if s.c.DataTransfersFolder == "" {
   451  		return "", errtypes.NotSupported("no destination path provided and default transfer destination folder is not set")
   452  	}
   453  	return s.c.DataTransfersFolder, nil
   454  }
   455  
   456  func (s *svc) GetReceivedOCMShare(ctx context.Context, req *ocm.GetReceivedOCMShareRequest) (*ocm.GetReceivedOCMShareResponse, error) {
   457  	c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint)
   458  	if err != nil {
   459  		appctx.GetLogger(ctx).Error().Err(err).Msg("error calling GetOCMShareProviderClient")
   460  		return &ocm.GetReceivedOCMShareResponse{
   461  			Status: status.NewInternal(ctx, "error getting share provider client"),
   462  		}, nil
   463  	}
   464  
   465  	res, err := c.GetReceivedOCMShare(ctx, req)
   466  	if err != nil {
   467  		return nil, errors.Wrap(err, "gateway: error calling GetReceivedOCMShare")
   468  	}
   469  
   470  	return res, nil
   471  }
   472  
   473  func (s *svc) getTransferProtocol(share *ocm.ReceivedShare) (*ocm.TransferProtocol, bool) {
   474  	for _, p := range share.Protocols {
   475  		if d, ok := p.Term.(*ocm.Protocol_TransferOptions); ok {
   476  			return d.TransferOptions, true
   477  		}
   478  	}
   479  	return nil, false
   480  }