github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/ocminvitemanager/ocminvitemanager.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 ocminvitemanager 20 21 import ( 22 "context" 23 "time" 24 25 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 26 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 27 invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" 28 ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" 29 rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 30 ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" 31 "github.com/cs3org/reva/v2/pkg/errtypes" 32 "github.com/cs3org/reva/v2/pkg/ocm/client" 33 "github.com/cs3org/reva/v2/pkg/ocm/invite" 34 "github.com/cs3org/reva/v2/pkg/ocm/invite/repository/registry" 35 ocmuser "github.com/cs3org/reva/v2/pkg/ocm/user" 36 "github.com/cs3org/reva/v2/pkg/rgrpc" 37 "github.com/cs3org/reva/v2/pkg/rgrpc/status" 38 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 39 "github.com/cs3org/reva/v2/pkg/sharedconf" 40 "github.com/cs3org/reva/v2/pkg/utils" 41 "github.com/cs3org/reva/v2/pkg/utils/cfg" 42 "github.com/pkg/errors" 43 "github.com/rs/zerolog" 44 "google.golang.org/grpc" 45 ) 46 47 func init() { 48 rgrpc.Register("ocminvitemanager", New) 49 } 50 51 type config struct { 52 Driver string `mapstructure:"driver"` 53 Drivers map[string]map[string]interface{} `mapstructure:"drivers"` 54 TokenExpiration string `mapstructure:"token_expiration"` 55 OCMClientTimeout int `mapstructure:"ocm_timeout"` 56 OCMClientInsecure bool `mapstructure:"ocm_insecure"` 57 GatewaySVC string `mapstructure:"gatewaysvc" validate:"required"` 58 ProviderDomain string `mapstructure:"provider_domain" validate:"required" docs:"The same domain registered in the provider authorizer"` 59 60 tokenExpiration time.Duration 61 } 62 63 type service struct { 64 conf *config 65 repo invite.Repository 66 ocmClient *client.OCMClient 67 gatewaySelector *pool.Selector[gateway.GatewayAPIClient] 68 } 69 70 func (c *config) ApplyDefaults() { 71 if c.Driver == "" { 72 c.Driver = "json" 73 } 74 if c.TokenExpiration == "" { 75 c.TokenExpiration = "24h" 76 } 77 78 c.GatewaySVC = sharedconf.GetGatewaySVC(c.GatewaySVC) 79 } 80 81 func (s *service) Register(ss *grpc.Server) { 82 invitepb.RegisterInviteAPIServer(ss, s) 83 } 84 85 func getInviteRepository(c *config) (invite.Repository, error) { 86 if f, ok := registry.NewFuncs[c.Driver]; ok { 87 return f(c.Drivers[c.Driver]) 88 } 89 return nil, errtypes.NotFound("driver not found: " + c.Driver) 90 } 91 92 // New creates a new OCM invite manager svc. 93 func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) { 94 var c config 95 if err := cfg.Decode(m, &c); err != nil { 96 return nil, err 97 } 98 99 p, err := time.ParseDuration(c.TokenExpiration) 100 if err != nil { 101 return nil, err 102 } 103 c.tokenExpiration = p 104 105 repo, err := getInviteRepository(&c) 106 if err != nil { 107 return nil, err 108 } 109 110 gatewaySelector, err := pool.GatewaySelector(c.GatewaySVC) 111 if err != nil { 112 return nil, err 113 } 114 115 service := &service{ 116 conf: &c, 117 repo: repo, 118 ocmClient: client.New(&client.Config{ 119 Timeout: time.Duration(c.OCMClientTimeout) * time.Second, 120 Insecure: c.OCMClientInsecure, 121 }), 122 gatewaySelector: gatewaySelector, 123 } 124 return service, nil 125 } 126 127 func (s *service) Close() error { 128 return nil 129 } 130 131 func (s *service) UnprotectedEndpoints() []string { 132 return []string{"/cs3.ocm.invite.v1beta1.InviteAPI/AcceptInvite", "/cs3.ocm.invite.v1beta1.InviteAPI/GetAcceptedUser"} 133 } 134 135 func (s *service) GenerateInviteToken(ctx context.Context, req *invitepb.GenerateInviteTokenRequest) (*invitepb.GenerateInviteTokenResponse, error) { 136 user := ctxpkg.ContextMustGetUser(ctx) 137 token := CreateToken(s.conf.tokenExpiration, user.GetId(), req.Description) 138 139 if err := s.repo.AddToken(ctx, token); err != nil { 140 return &invitepb.GenerateInviteTokenResponse{ 141 Status: status.NewInternal(ctx, "error generating invite token"), 142 }, nil 143 } 144 145 return &invitepb.GenerateInviteTokenResponse{ 146 Status: status.NewOK(ctx), 147 InviteToken: token, 148 }, nil 149 } 150 151 func (s *service) ListInviteTokens(ctx context.Context, req *invitepb.ListInviteTokensRequest) (*invitepb.ListInviteTokensResponse, error) { 152 user := ctxpkg.ContextMustGetUser(ctx) 153 tokens, err := s.repo.ListTokens(ctx, user.Id) 154 if err != nil { 155 return &invitepb.ListInviteTokensResponse{ 156 Status: status.NewInternal(ctx, "error listing tokens"), 157 }, nil 158 } 159 return &invitepb.ListInviteTokensResponse{ 160 Status: status.NewOK(ctx), 161 InviteTokens: tokens, 162 }, nil 163 } 164 165 func (s *service) ForwardInvite(ctx context.Context, req *invitepb.ForwardInviteRequest) (*invitepb.ForwardInviteResponse, error) { 166 user := ctxpkg.ContextMustGetUser(ctx) 167 168 ocmEndpoint, err := getOCMEndpoint(req.GetOriginSystemProvider()) 169 if err != nil { 170 return nil, err 171 } 172 173 if req.GetOriginSystemProvider().Domain == s.conf.ProviderDomain { 174 return &invitepb.ForwardInviteResponse{ 175 Status: status.NewInvalid(ctx, "can not accept an invite from the same instance"), 176 }, nil 177 } 178 179 // Accept the invitation on the remote OCM provider 180 remoteUser, err := s.ocmClient.InviteAccepted(ctx, ocmEndpoint, &client.InviteAcceptedRequest{ 181 Token: req.InviteToken.GetToken(), 182 RecipientProvider: s.conf.ProviderDomain, 183 // The UserID is only a string here. To not loose the IDP information we use the FederatedID encoding 184 // i.e. base64(UserID@IDP) 185 UserID: ocmuser.FederatedID(user.GetId(), "").GetOpaqueId(), 186 Email: user.GetMail(), 187 Name: user.GetDisplayName(), 188 }) 189 if err != nil { 190 switch { 191 case errors.Is(err, client.ErrTokenInvalid): 192 return &invitepb.ForwardInviteResponse{ 193 Status: status.NewInvalid(ctx, "token not valid"), 194 }, nil 195 case errors.Is(err, client.ErrTokenNotFound): 196 return &invitepb.ForwardInviteResponse{ 197 Status: status.NewNotFound(ctx, "token not found"), 198 }, nil 199 case errors.Is(err, client.ErrUserAlreadyAccepted): 200 return &invitepb.ForwardInviteResponse{ 201 Status: status.NewAlreadyExists(ctx, err, err.Error()), 202 }, nil 203 case errors.Is(err, client.ErrServiceNotTrusted): 204 return &invitepb.ForwardInviteResponse{ 205 Status: status.NewPermissionDenied(ctx, err, err.Error()), 206 }, nil 207 default: 208 return &invitepb.ForwardInviteResponse{ 209 Status: status.NewInternal(ctx, err.Error()), 210 }, nil 211 } 212 } 213 214 // create a link between the user that accepted the share (in ctx) 215 // and the remote one (the initiator), so at the end of the invitation workflow they 216 // know each other 217 218 // remoteUser.UserID is the federated ID (just a string), to get a unique CS3 userid 219 // we're using the provider domain as the IDP part of the ID 220 remoteUserID := &userpb.UserId{ 221 Type: userpb.UserType_USER_TYPE_FEDERATED, 222 Idp: req.GetOriginSystemProvider().Domain, 223 OpaqueId: remoteUser.UserID, 224 } 225 226 if err := s.repo.AddRemoteUser(ctx, user.Id, &userpb.User{ 227 Id: remoteUserID, 228 Mail: remoteUser.Email, 229 DisplayName: remoteUser.Name, 230 }); err != nil { 231 if errors.Is(err, invite.ErrUserAlreadyAccepted) { 232 return &invitepb.ForwardInviteResponse{ 233 Status: status.NewAlreadyExists(ctx, err, err.Error()), 234 }, nil 235 } 236 return &invitepb.ForwardInviteResponse{ 237 Status: status.NewInternal(ctx, err.Error()), 238 }, nil 239 } 240 241 return &invitepb.ForwardInviteResponse{ 242 Status: status.NewOK(ctx), 243 UserId: remoteUserID, 244 Email: remoteUser.Email, 245 DisplayName: remoteUser.Name, 246 }, nil 247 } 248 249 func getOCMEndpoint(originProvider *ocmprovider.ProviderInfo) (string, error) { 250 for _, s := range originProvider.Services { 251 if s.Endpoint.Type.Name == "OCM" { 252 return s.Endpoint.Path, nil 253 } 254 } 255 return "", errors.New("ocm endpoint not specified for mesh provider") 256 } 257 258 func (s *service) AcceptInvite(ctx context.Context, req *invitepb.AcceptInviteRequest) (*invitepb.AcceptInviteResponse, error) { 259 token, err := s.repo.GetToken(ctx, req.InviteToken.Token) 260 if err != nil { 261 if errors.Is(err, invite.ErrTokenNotFound) { 262 return &invitepb.AcceptInviteResponse{ 263 Status: status.NewNotFound(ctx, "token not found"), 264 }, nil 265 } 266 return &invitepb.AcceptInviteResponse{ 267 Status: status.NewInternal(ctx, err.Error()), 268 }, nil 269 } 270 271 if !isTokenValid(token) { 272 return &invitepb.AcceptInviteResponse{ 273 Status: status.NewInvalid(ctx, "token is not valid"), 274 }, nil 275 } 276 277 initiator, err := s.getUserInfo(ctx, token.UserId) 278 if err != nil { 279 return &invitepb.AcceptInviteResponse{ 280 Status: status.NewInternal(ctx, err.Error()), 281 }, nil 282 } 283 284 remoteUser := req.GetRemoteUser() 285 286 if err := s.repo.AddRemoteUser(ctx, token.GetUserId(), remoteUser); err != nil { 287 if !errors.Is(err, invite.ErrUserAlreadyAccepted) { 288 // skip error if user was already accepted 289 return &invitepb.AcceptInviteResponse{ 290 Status: status.NewInternal(ctx, err.Error()), 291 }, nil 292 } 293 } 294 295 return &invitepb.AcceptInviteResponse{ 296 Status: status.NewOK(ctx), 297 UserId: initiator.GetId(), 298 Email: initiator.Mail, 299 DisplayName: initiator.DisplayName, 300 }, nil 301 } 302 303 func (s *service) getUserInfo(ctx context.Context, id *userpb.UserId) (*userpb.User, error) { 304 gw, err := s.gatewaySelector.Next() 305 if err != nil { 306 return nil, nil 307 } 308 res, err := gw.GetUser(ctx, &userpb.GetUserRequest{ 309 UserId: id, 310 }) 311 if err != nil { 312 return nil, err 313 } 314 if res.Status.Code != rpcv1beta1.Code_CODE_OK { 315 return nil, errors.New(res.Status.Message) 316 } 317 318 return res.User, nil 319 } 320 321 func isTokenValid(token *invitepb.InviteToken) bool { 322 return time.Now().Unix() < int64(token.Expiration.Seconds) 323 } 324 325 func (s *service) GetAcceptedUser(ctx context.Context, req *invitepb.GetAcceptedUserRequest) (*invitepb.GetAcceptedUserResponse, error) { 326 user, ok := getUserFilter(ctx, req) 327 if !ok { 328 return &invitepb.GetAcceptedUserResponse{ 329 Status: status.NewInvalidArg(ctx, "user not found"), 330 }, nil 331 } 332 remoteUser, err := s.repo.GetRemoteUser(ctx, user.GetId(), req.GetRemoteUserId()) 333 if err != nil { 334 return &invitepb.GetAcceptedUserResponse{ 335 Status: status.NewInternal(ctx, "error fetching remote user details"), 336 }, nil 337 } 338 339 return &invitepb.GetAcceptedUserResponse{ 340 Status: status.NewOK(ctx), 341 RemoteUser: remoteUser, 342 }, nil 343 } 344 345 func getUserFilter(ctx context.Context, req *invitepb.GetAcceptedUserRequest) (*userpb.User, bool) { 346 user, ok := ctxpkg.ContextGetUser(ctx) 347 if ok { 348 return user, true 349 } 350 351 if req.Opaque == nil || req.Opaque.Map == nil { 352 return nil, false 353 } 354 355 v, ok := req.Opaque.Map["user-filter"] 356 if !ok { 357 return nil, false 358 } 359 360 var u userpb.UserId 361 if err := utils.UnmarshalJSONToProtoV1(v.Value, &u); err != nil { 362 return nil, false 363 } 364 return &userpb.User{Id: &u}, true 365 } 366 367 func (s *service) FindAcceptedUsers(ctx context.Context, req *invitepb.FindAcceptedUsersRequest) (*invitepb.FindAcceptedUsersResponse, error) { 368 user := ctxpkg.ContextMustGetUser(ctx) 369 acceptedUsers, err := s.repo.FindRemoteUsers(ctx, user.GetId(), req.GetFilter()) 370 if err != nil { 371 return &invitepb.FindAcceptedUsersResponse{ 372 Status: status.NewInternal(ctx, "error finding remote users: "+err.Error()), 373 }, nil 374 } 375 376 return &invitepb.FindAcceptedUsersResponse{ 377 Status: status.NewOK(ctx), 378 AcceptedUsers: acceptedUsers, 379 }, nil 380 } 381 382 func (s *service) DeleteAcceptedUser(ctx context.Context, req *invitepb.DeleteAcceptedUserRequest) (*invitepb.DeleteAcceptedUserResponse, error) { 383 user := ctxpkg.ContextMustGetUser(ctx) 384 if err := s.repo.DeleteRemoteUser(ctx, user.Id, req.RemoteUserId); err != nil { 385 return &invitepb.DeleteAcceptedUserResponse{ 386 Status: status.NewInternal(ctx, "error deleting remote users: "+err.Error()), 387 }, nil 388 } 389 390 return &invitepb.DeleteAcceptedUserResponse{ 391 Status: status.NewOK(ctx), 392 }, nil 393 }