github.com/cs3org/reva/v2@v2.27.7/internal/http/services/sciencemesh/token.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 sciencemesh 20 21 import ( 22 "encoding/json" 23 "errors" 24 "mime" 25 "net/http" 26 27 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 28 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 29 invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" 30 ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" 31 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 32 33 "github.com/cs3org/reva/v2/internal/http/services/reqres" 34 "github.com/cs3org/reva/v2/pkg/appctx" 35 "github.com/cs3org/reva/v2/pkg/events" 36 "github.com/cs3org/reva/v2/pkg/events/stream" 37 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 38 "github.com/cs3org/reva/v2/pkg/utils" 39 "github.com/cs3org/reva/v2/pkg/utils/list" 40 ) 41 42 type tokenHandler struct { 43 gatewaySelector *pool.Selector[gateway.GatewayAPIClient] 44 meshDirectoryURL string 45 providerDomain string 46 eventStream events.Stream 47 } 48 49 func (h *tokenHandler) init(c *config) error { 50 var err error 51 h.gatewaySelector, err = pool.GatewaySelector(c.GatewaySvc) 52 if err != nil { 53 return err 54 } 55 56 h.meshDirectoryURL = c.MeshDirectoryURL 57 h.providerDomain = c.ProviderDomain 58 59 if c.Events.Endpoint != "" { 60 es, err := stream.NatsFromConfig("sciencemesh-token-handler", false, stream.NatsConfig(c.Events)) 61 if err != nil { 62 return err 63 } 64 h.eventStream = es 65 } 66 67 return nil 68 } 69 70 type token struct { 71 Token string `json:"token"` 72 Description string `json:"description,omitempty"` 73 Expiration uint64 `json:"expiration,omitempty"` 74 InviteLink string `json:"invite_link,omitempty"` 75 } 76 77 // Generate generates an invitation token and if a recipient is specified, 78 // will send an email containing the link the user will use to accept the 79 // invitation. 80 func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) { 81 req, err := getGenerateRequest(r) 82 if err != nil { 83 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing parameters in request", err) 84 return 85 } 86 87 ctx := r.Context() 88 gc, err := h.gatewaySelector.Next() 89 if err != nil { 90 reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err) 91 return 92 } 93 genTokenRes, err := gc.GenerateInviteToken(ctx, &invitepb.GenerateInviteTokenRequest{ 94 Description: req.Description, 95 }) 96 switch { 97 case err != nil: 98 reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating token", err) 99 return 100 case genTokenRes.GetStatus().GetCode() == rpc.Code_CODE_NOT_FOUND: 101 reqres.WriteError(w, r, reqres.APIErrorNotFound, genTokenRes.GetStatus().GetMessage(), nil) 102 return 103 case genTokenRes.GetStatus().GetCode() != rpc.Code_CODE_OK: 104 reqres.WriteError(w, r, reqres.APIErrorServerError, genTokenRes.GetStatus().GetMessage(), errors.New(genTokenRes.GetStatus().GetMessage())) 105 return 106 } 107 108 w.Header().Set("Content-Type", "application/json") 109 w.WriteHeader(http.StatusOK) // FIXME this should be a 201 created status. Tracked in https://github.com/cs3org/reva/issues/4838 110 111 tknRes := h.prepareGenerateTokenResponse(genTokenRes.GetInviteToken()) 112 if err := json.NewEncoder(w).Encode(tknRes); err != nil { 113 reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err) 114 } 115 116 if h.eventStream != nil { 117 if err := events.Publish(ctx, h.eventStream, events.ScienceMeshInviteTokenGenerated{ 118 Sharer: genTokenRes.GetInviteToken().GetUserId(), 119 RecipientMail: req.Recipient, 120 Token: tknRes.Token, 121 Description: tknRes.Description, 122 Expiration: tknRes.Expiration, 123 InviteLink: tknRes.InviteLink, 124 Timestamp: utils.TSNow(), 125 }); err != nil { 126 log := appctx.GetLogger(ctx) 127 log.Error().Err(err). 128 Msg("failed to publish the science-mesh invite token generated event") 129 } 130 } 131 132 } 133 134 // generateRequest is the request body for the Generate endpoint. 135 type generateRequest struct { 136 Description string `json:"description"` 137 Recipient string `json:"recipient" validate:"omitempty,email"` 138 } 139 140 func getGenerateRequest(r *http.Request) (*generateRequest, error) { 141 var req generateRequest 142 contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 143 if err == nil && contentType == "application/json" { 144 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 145 return nil, err 146 } 147 } 148 149 // validate the request 150 if err := validate.Struct(req); err != nil { 151 return nil, err 152 } 153 154 return &req, nil 155 } 156 157 func (h *tokenHandler) prepareGenerateTokenResponse(tkn *invitepb.InviteToken) *token { 158 res := &token{ 159 Token: tkn.Token, 160 Description: tkn.Description, 161 } 162 if h.meshDirectoryURL != "" { 163 res.InviteLink = h.meshDirectoryURL + "?token=" + tkn.Token + "&providerDomain=" + h.providerDomain 164 } 165 if tkn.Expiration != nil { 166 res.Expiration = tkn.Expiration.Seconds 167 } 168 169 return res 170 } 171 172 type acceptInviteRequest struct { 173 Token string `json:"token"` 174 ProviderDomain string `json:"providerDomain"` 175 } 176 177 // AcceptInvite accepts an invitation from the user in the remote provider. 178 func (h *tokenHandler) AcceptInvite(w http.ResponseWriter, r *http.Request) { 179 ctx := r.Context() 180 log := appctx.GetLogger(ctx) 181 182 req, err := getAcceptInviteRequest(r) 183 if err != nil { 184 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing parameters in request", err) 185 return 186 } 187 188 if req.Token == "" || req.ProviderDomain == "" { 189 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "token and providerDomain must not be null", nil) 190 return 191 } 192 193 gc, err := h.gatewaySelector.Next() 194 if err != nil { 195 reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err) 196 return 197 } 198 providerInfo, err := gc.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ 199 Domain: req.ProviderDomain, 200 }) 201 if err != nil { 202 reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc get invite by domain info request", err) 203 return 204 } 205 if providerInfo.Status.Code != rpc.Code_CODE_OK { 206 reqres.WriteError(w, r, reqres.APIErrorServerError, "grpc forward invite request failed", errors.New(providerInfo.Status.Message)) 207 return 208 } 209 210 gc, err = h.gatewaySelector.Next() 211 if err != nil { 212 reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err) 213 return 214 } 215 forwardInviteReq := &invitepb.ForwardInviteRequest{ 216 InviteToken: &invitepb.InviteToken{ 217 Token: req.Token, 218 }, 219 OriginSystemProvider: providerInfo.ProviderInfo, 220 } 221 forwardInviteResponse, err := gc.ForwardInvite(ctx, forwardInviteReq) 222 if err != nil { 223 reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc forward invite request", err) 224 return 225 } 226 if forwardInviteResponse.Status.Code != rpc.Code_CODE_OK { 227 switch forwardInviteResponse.Status.Code { 228 case rpc.Code_CODE_NOT_FOUND: 229 reqres.WriteError(w, r, reqres.APIErrorNotFound, "token not found", nil) 230 return 231 case rpc.Code_CODE_INVALID_ARGUMENT: 232 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "token has expired", nil) 233 return 234 case rpc.Code_CODE_ALREADY_EXISTS: 235 reqres.WriteError(w, r, reqres.APIErrorAlreadyExist, "user already known", nil) 236 return 237 case rpc.Code_CODE_PERMISSION_DENIED: 238 reqres.WriteError(w, r, reqres.APIErrorUnauthenticated, "remote service not trusted", nil) 239 return 240 default: 241 reqres.WriteError(w, r, reqres.APIErrorServerError, "unexpected error: "+forwardInviteResponse.Status.Message, errors.New(forwardInviteResponse.Status.Message)) 242 return 243 } 244 } 245 246 w.WriteHeader(http.StatusOK) 247 248 log.Info().Str("token", req.Token).Str("provider", req.ProviderDomain).Msgf("invite forwarded") 249 } 250 251 func getAcceptInviteRequest(r *http.Request) (*acceptInviteRequest, error) { 252 var req acceptInviteRequest 253 contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 254 if err == nil && contentType == "application/json" { 255 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 256 return nil, err 257 } 258 } else { 259 req.Token, req.ProviderDomain = r.FormValue("token"), r.FormValue("providerDomain") 260 } 261 return &req, nil 262 } 263 264 type remoteUser struct { 265 DisplayName string `json:"display_name"` 266 Idp string `json:"idp"` 267 UserID string `json:"user_id"` 268 Mail string `json:"mail"` 269 } 270 271 // FindAccepted returns the list of all the users that accepted the invitation 272 // to the authenticated user. 273 func (h *tokenHandler) FindAccepted(w http.ResponseWriter, r *http.Request) { 274 ctx := r.Context() 275 276 gc, err := h.gatewaySelector.Next() 277 if err != nil { 278 reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err) 279 return 280 } 281 res, err := gc.FindAcceptedUsers(ctx, &invitepb.FindAcceptedUsersRequest{}) 282 if err != nil { 283 reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc find accepted users request", err) 284 return 285 } 286 287 users := list.Map(res.AcceptedUsers, func(u *userpb.User) *remoteUser { 288 return &remoteUser{ 289 DisplayName: u.DisplayName, 290 Idp: u.Id.Idp, 291 UserID: u.Id.OpaqueId, 292 Mail: u.Mail, 293 } 294 }) 295 296 if err := json.NewEncoder(w).Encode(users); err != nil { 297 reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err) 298 return 299 } 300 301 w.Header().Set("Content-Type", "application/json") 302 w.WriteHeader(http.StatusOK) 303 } 304 305 // DeleteAccepted deletes the given user from the list of the accepted users. 306 func (h *tokenHandler) DeleteAccepted(w http.ResponseWriter, r *http.Request) { 307 ctx := r.Context() 308 309 req, err := getDeleteAcceptedRequest(r) 310 if err != nil { 311 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing parameters in request", err) 312 return 313 } 314 315 gc, err := h.gatewaySelector.Next() 316 if err != nil { 317 reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err) 318 return 319 } 320 res, err := gc.DeleteAcceptedUser(ctx, &invitepb.DeleteAcceptedUserRequest{ 321 RemoteUserId: &userpb.UserId{ 322 Idp: req.Idp, 323 OpaqueId: req.UserID, 324 Type: userpb.UserType_USER_TYPE_FEDERATED, 325 }, 326 }) 327 if err != nil { 328 reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc get invite by domain info request", err) 329 return 330 } 331 if res.Status.Code != rpc.Code_CODE_OK { 332 reqres.WriteError(w, r, reqres.APIErrorServerError, "grpc forward invite request failed", errors.New(res.Status.Message)) 333 return 334 } 335 w.WriteHeader(http.StatusOK) 336 } 337 338 type deleteAcceptedRequest struct { 339 Idp string `json:"idp"` 340 UserID string `json:"user_id"` 341 } 342 343 func getDeleteAcceptedRequest(r *http.Request) (*deleteAcceptedRequest, error) { 344 var req deleteAcceptedRequest 345 contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 346 if err == nil && contentType == "application/json" { 347 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 348 return nil, err 349 } 350 } else { 351 req.Idp, req.UserID = r.FormValue("idp"), r.FormValue("user_id") 352 } 353 return &req, nil 354 } 355 356 func (h *tokenHandler) ListInvite(w http.ResponseWriter, r *http.Request) { 357 ctx := r.Context() 358 359 gc, err := h.gatewaySelector.Next() 360 if err != nil { 361 reqres.WriteError(w, r, reqres.APIErrorServerError, "error selecting gateway client", err) 362 return 363 } 364 res, err := gc.ListInviteTokens(ctx, &invitepb.ListInviteTokensRequest{}) 365 if err != nil { 366 reqres.WriteError(w, r, reqres.APIErrorServerError, "error listing tokens", err) 367 return 368 } 369 370 if res.Status.Code != rpc.Code_CODE_OK { 371 reqres.WriteError(w, r, reqres.APIErrorServerError, res.Status.Message, errors.New(res.Status.Message)) 372 return 373 } 374 375 tokens := make([]*token, 0, len(res.InviteTokens)) 376 for _, tkn := range res.InviteTokens { 377 tokens = append(tokens, h.prepareGenerateTokenResponse(tkn)) 378 } 379 380 if err := json.NewEncoder(w).Encode(tokens); err != nil { 381 reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err) 382 return 383 } 384 385 w.Header().Set("Content-Type", "application/json") 386 w.WriteHeader(http.StatusOK) 387 }