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 }