github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/oauth_manager.go (about) 1 package gateway 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "math" 10 "net/http" 11 "net/url" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/lonelycode/osin" 17 uuid "github.com/satori/go.uuid" 18 "golang.org/x/crypto/bcrypt" 19 20 "strconv" 21 22 "github.com/TykTechnologies/tyk/config" 23 "github.com/TykTechnologies/tyk/headers" 24 "github.com/TykTechnologies/tyk/storage" 25 "github.com/TykTechnologies/tyk/user" 26 ) 27 28 /* 29 Sample Oaut Flow: 30 ----------------- 31 32 1. Request to /authorize 33 2. Tyk extracts all relevant data and pre-screens client_id, client_secret and redirect_uri 34 3. Instead of proxying the request it redirects the user to the login page on the resource with the client_id & secret as a POST (basically passed through) 35 4. Resource presents approve / deny window to user 36 5. If approve is clicked, resource pings oauth/authorise which is the actual authorize endpoint (requires admin key), 37 this returns oauth details to resource as well as redirect URI which it can then redirec to 38 6. User is redirected to redirect URI with auth_code 39 7. Client makes auth request for bearer token 40 8. Client API makes all calls with bearer token 41 42 Effort required by Resource Owner: 43 1. Create a login & approve/deny page 44 2. Send an API request to Tyk to generate an auth_code 45 3. Create endpoint to accept key change notifications 46 */ 47 48 // OAuthClient is a representation within an APISpec of a client 49 type OAuthClient struct { 50 ClientID string `json:"id"` 51 ClientSecret string `json:"secret"` 52 ClientRedirectURI string `json:"redirecturi"` 53 MetaData interface{} `json:"meta_data,omitempty"` 54 PolicyID string `json:"policyid"` 55 Description string `json:"description"` 56 } 57 58 func (oc *OAuthClient) GetId() string { 59 return oc.ClientID 60 } 61 62 func (oc *OAuthClient) GetSecret() string { 63 return oc.ClientSecret 64 } 65 66 func (oc *OAuthClient) GetRedirectUri() string { 67 return oc.ClientRedirectURI 68 } 69 70 func (oc *OAuthClient) GetUserData() interface{} { 71 return oc.MetaData 72 } 73 74 func (oc *OAuthClient) GetPolicyID() string { 75 return oc.PolicyID 76 } 77 78 func (oc *OAuthClient) GetDescription() string { 79 return oc.Description 80 } 81 82 // OAuthNotificationType const to reduce risk of collisions 83 type OAuthNotificationType string 84 85 // Notification codes for new and refresh codes 86 const ( 87 newAccessToken OAuthNotificationType = "new" 88 refreshAccessToken OAuthNotificationType = "refresh" 89 ) 90 91 // NewOAuthNotification is a notification sent to a 92 // web-hook when an access request or a refresh request comes in. 93 type NewOAuthNotification struct { 94 AuthCode string `json:"auth_code"` 95 NewOAuthToken string `json:"new_oauth_token"` 96 RefreshToken string `json:"refresh_token"` 97 OldRefreshToken string `json:"old_refresh_token"` 98 NotificationType OAuthNotificationType `json:"notification_type"` 99 } 100 101 // OAuthHandlers are the HTTP Handlers that manage the Tyk OAuth flow 102 type OAuthHandlers struct { 103 Manager OAuthManager 104 } 105 106 func (o *OAuthHandlers) generateOAuthOutputFromOsinResponse(osinResponse *osin.Response) []byte { 107 108 // TODO: Might need to clear this out 109 if osinResponse.Output["state"] == "" { 110 log.Debug("Removing state") 111 delete(osinResponse.Output, "state") 112 } 113 114 redirect, err := osinResponse.GetRedirectUrl() 115 if err == nil { 116 // Hack to inject redirect into response 117 osinResponse.Output["redirect_to"] = redirect 118 } 119 120 buffer := &bytes.Buffer{} 121 encoder := json.NewEncoder(buffer) 122 encoder.SetEscapeHTML(false) 123 err = encoder.Encode(&osinResponse.Output) 124 if err != nil { 125 return nil 126 } 127 return buffer.Bytes() 128 } 129 130 func (o *OAuthHandlers) notifyClientOfNewOauth(notification NewOAuthNotification) { 131 log.Info("[OAuth] Notifying client host") 132 go o.Manager.API.NotificationsDetails.SendRequest(false, 0, notification) 133 } 134 135 // HandleGenerateAuthCodeData handles a resource provider approving an OAuth request from a client 136 func (o *OAuthHandlers) HandleGenerateAuthCodeData(w http.ResponseWriter, r *http.Request) { 137 // On AUTH grab session state data and add to UserData (not validated, not good!) 138 sessionJSONData := r.FormValue("key_rules") 139 if sessionJSONData == "" { 140 log.Warning("Authorise request is missing key_rules in params, policy will be required!") 141 } 142 143 // Handle the authorisation and write the JSON output to the resource provider 144 resp := o.Manager.HandleAuthorisation(r, true, sessionJSONData) 145 code := http.StatusOK 146 msg := o.generateOAuthOutputFromOsinResponse(resp) 147 if resp.IsError { 148 code = resp.ErrorStatusCode 149 log.Error("[OAuth] OAuth response marked as error: ", resp) 150 } 151 w.WriteHeader(code) 152 w.Write(msg) 153 } 154 155 // HandleAuthorizePassthrough handles a Client Auth request, first it checks if the client 156 // is OK (otherwise it blocks the request), then it forwards on to the resource providers approval URI 157 func (o *OAuthHandlers) HandleAuthorizePassthrough(w http.ResponseWriter, r *http.Request) { 158 // Extract client data and check 159 resp := o.Manager.HandleAuthorisation(r, false, "") 160 if resp.IsError { 161 log.Error("There was an error with the request: ", resp) 162 // Something went wrong, write out the error details and kill the response 163 doJSONWrite(w, resp.ErrorStatusCode, apiError(resp.StatusText)) 164 return 165 } 166 if r.Method == "GET" { 167 loginURL := fmt.Sprintf("%s?%s", o.Manager.API.Oauth2Meta.AuthorizeLoginRedirect, r.URL.RawQuery) 168 w.Header().Add("Location", loginURL) 169 } else { 170 w.Header().Add("Location", o.Manager.API.Oauth2Meta.AuthorizeLoginRedirect) 171 } 172 w.WriteHeader(307) 173 174 } 175 176 // HandleAccessRequest handles the OAuth 2.0 token or refresh access request, and wraps Tyk's own and Osin's OAuth handlers, 177 // returns a response to the client and notifies the provider of the access request (in order to track identity against 178 // OAuth tokens without revealing tokens before they are requested). 179 func (o *OAuthHandlers) HandleAccessRequest(w http.ResponseWriter, r *http.Request) { 180 w.Header().Set(headers.ContentType, headers.ApplicationJSON) 181 // Handle response 182 resp := o.Manager.HandleAccess(r) 183 msg := o.generateOAuthOutputFromOsinResponse(resp) 184 185 if resp.IsError { 186 // Something went wrong, write out the error details and kill the response 187 w.WriteHeader(resp.ErrorStatusCode) 188 w.Write(msg) 189 return 190 } 191 192 // Ping endpoint with o_auth key and auth_key 193 authCode := r.FormValue("code") 194 oldRefreshToken := r.FormValue("refresh_token") 195 log.Debug("AUTH CODE: ", authCode) 196 newOauthToken := "" 197 if resp.Output["access_token"] != nil { 198 newOauthToken = resp.Output["access_token"].(string) 199 } 200 log.Debug("TOKEN: ", newOauthToken) 201 refreshToken := "" 202 if resp.Output["refresh_token"] != nil { 203 refreshToken = resp.Output["refresh_token"].(string) 204 } 205 log.Debug("REFRESH: ", refreshToken) 206 log.Debug("Old REFRESH: ", oldRefreshToken) 207 208 notificationType := newAccessToken 209 if oldRefreshToken != "" { 210 notificationType = refreshAccessToken 211 } 212 213 newNotification := NewOAuthNotification{ 214 AuthCode: authCode, 215 NewOAuthToken: newOauthToken, 216 RefreshToken: refreshToken, 217 OldRefreshToken: oldRefreshToken, 218 NotificationType: notificationType, 219 } 220 221 o.notifyClientOfNewOauth(newNotification) 222 223 w.WriteHeader(http.StatusOK) 224 w.Write(msg) 225 } 226 227 const ( 228 accessToken = "access_token" 229 refreshToken = "refresh_token" 230 ) 231 232 //in compliance with https://tools.ietf.org/html/rfc7009#section-2.1 233 //ToDo: set an authentication mechanism 234 func (o *OAuthHandlers) HandleRevokeToken(w http.ResponseWriter, r *http.Request) { 235 err := r.ParseForm() 236 if err != nil { 237 doJSONWrite(w, http.StatusBadRequest, apiError("error parsing form. Form malformed")) 238 return 239 } 240 241 token := r.PostFormValue("token") 242 tokenTypeHint := r.PostFormValue("token_type_hint") 243 244 if token == "" { 245 doJSONWrite(w, http.StatusBadRequest, apiError(oauthTokenEmpty)) 246 return 247 } 248 249 RevokeToken(o.Manager.OsinServer.Storage, token, tokenTypeHint) 250 doJSONWrite(w, http.StatusOK, apiOk("token revoked successfully")) 251 } 252 253 func RevokeToken(storage ExtendedOsinStorageInterface, token, tokenTypeHint string) { 254 switch tokenTypeHint { 255 case accessToken: 256 storage.RemoveAccess(token) 257 case refreshToken: 258 storage.RemoveRefresh(token) 259 default: 260 storage.RemoveAccess(token) 261 storage.RemoveRefresh(token) 262 } 263 } 264 265 func (o *OAuthHandlers) HandleRevokeAllTokens(w http.ResponseWriter, r *http.Request) { 266 err := r.ParseForm() 267 if err != nil { 268 doJSONWrite(w, http.StatusBadRequest, apiError("error parsing form. Form malformed")) 269 return 270 } 271 272 clientId := r.PostFormValue("client_id") 273 secret := r.PostFormValue("client_secret") 274 275 if clientId == "" { 276 doJSONWrite(w, http.StatusUnauthorized, apiError(oauthClientIdEmpty)) 277 return 278 } 279 280 if secret == "" { 281 doJSONWrite(w, http.StatusUnauthorized, apiError(oauthClientSecretEmpty)) 282 return 283 } 284 285 status, tokens, err := RevokeAllTokens(o.Manager.OsinServer.Storage, clientId, secret) 286 if err != nil { 287 doJSONWrite(w, status, apiError(err.Error())) 288 return 289 } 290 291 n := Notification{ 292 Command: KeySpaceUpdateNotification, 293 Payload: strings.Join(tokens, ","), 294 } 295 MainNotifier.Notify(n) 296 297 doJSONWrite(w, http.StatusOK, apiOk("tokens revoked successfully")) 298 } 299 300 func RevokeAllTokens(storage ExtendedOsinStorageInterface, clientId, clientSecret string) (int, []string, error) { 301 resp := []string{} 302 client, err := storage.GetClient(clientId) 303 log.Debug("Revoke all tokens") 304 if err != nil { 305 return http.StatusNotFound, resp, errors.New("error getting oauth client") 306 } 307 308 if client.GetSecret() != clientSecret { 309 return http.StatusUnauthorized, resp, errors.New(oauthClientSecretWrong) 310 } 311 312 clientTokens, err := storage.GetClientTokens(clientId) 313 if err != nil { 314 return http.StatusBadRequest, resp, errors.New("cannot retrieve client tokens") 315 } 316 317 log.Debug("Tokens found to be revoked:", len(clientTokens)) 318 for _, token := range clientTokens { 319 access, err := storage.LoadAccess(token.Token) 320 if err == nil { 321 resp = append(resp, access.AccessToken) 322 storage.RemoveAccess(access.AccessToken) 323 storage.RemoveRefresh(access.RefreshToken) 324 } else { 325 log.Debug("error loading access:", err.Error()) 326 } 327 } 328 329 return http.StatusOK, resp, nil 330 } 331 332 // OAuthManager handles and wraps osin OAuth2 functions to handle authorise and access requests 333 type OAuthManager struct { 334 API *APISpec 335 OsinServer *TykOsinServer 336 } 337 338 // HandleAuthorisation creates the authorisation data for the request 339 func (o *OAuthManager) HandleAuthorisation(r *http.Request, complete bool, session string) *osin.Response { 340 resp := o.OsinServer.NewResponse() 341 342 if ar := o.OsinServer.HandleAuthorizeRequest(resp, r); ar != nil { 343 // Since this is called by the Reource provider (proxied API), we assume it has been approved 344 ar.Authorized = true 345 346 if complete { 347 ar.UserData = session 348 o.OsinServer.FinishAuthorizeRequest(resp, r, ar) 349 } 350 } 351 if resp.IsError && resp.InternalError != nil { 352 log.Error(resp.InternalError) 353 } 354 355 return resp 356 } 357 358 // JSONToFormValues if r has header Content-Type set to application/json this 359 // will decode request body as json to map[string]string and adds the key/value 360 // pairs in r.Form. 361 func JSONToFormValues(r *http.Request) error { 362 if r.Header.Get("Content-Type") == "application/json" { 363 var o map[string]string 364 err := json.NewDecoder(r.Body).Decode(&o) 365 if err != nil { 366 return err 367 } 368 if len(o) > 0 { 369 if r.Form == nil { 370 r.Form = make(url.Values) 371 } 372 for k, v := range o { 373 r.Form.Set(k, v) 374 } 375 } 376 377 } 378 return nil 379 } 380 381 // HandleAccess wraps an access request with osin's primitives 382 func (o *OAuthManager) HandleAccess(r *http.Request) *osin.Response { 383 resp := o.OsinServer.NewResponse() 384 // we are intentionally ignoring errors, because this is called again by 385 // osin.We are only doing this to ensure r.From is properly initialized incase 386 // r.ParseForm was success 387 r.ParseForm() 388 if err := JSONToFormValues(r); err != nil { 389 log.Errorf("trying to set url values decoded from json body :%v", err) 390 } 391 var username string 392 393 if ar := o.OsinServer.HandleAccessRequest(resp, r); ar != nil { 394 395 session := &user.SessionState{Mutex: &sync.RWMutex{}} 396 if ar.Type == osin.PASSWORD { 397 username = r.Form.Get("username") 398 password := r.Form.Get("password") 399 searchKey := "apikey-" + storage.HashKey(o.API.OrgID+username) 400 log.Debug("Getting: ", searchKey) 401 402 var err error 403 session, err = o.OsinServer.Storage.GetUser(searchKey) 404 if err != nil { 405 log.Warning("Attempted access with non-existent user (OAuth password flow).") 406 } else { 407 var passMatch bool 408 if session.BasicAuthData.Hash == user.HashBCrypt { 409 err := bcrypt.CompareHashAndPassword([]byte(session.BasicAuthData.Password), []byte(password)) 410 if err == nil { 411 passMatch = true 412 } 413 } 414 415 if session.BasicAuthData.Hash == user.HashPlainText && 416 session.BasicAuthData.Password == password { 417 passMatch = true 418 } 419 420 if passMatch { 421 ar.Authorized = true 422 // not ideal, but we need to copy the session state across 423 pw := session.BasicAuthData.Password 424 hs := session.BasicAuthData.Hash 425 426 session.BasicAuthData.Password = "" 427 session.BasicAuthData.Hash = "" 428 asString, _ := json.Marshal(session) 429 ar.UserData = string(asString) 430 431 session.BasicAuthData.Password = pw 432 session.BasicAuthData.Hash = hs 433 434 //log.Warning("Old Keys: ", session.OauthKeys) 435 } 436 } 437 } else { 438 // Using a manual flow 439 ar.Authorized = true 440 } 441 442 // Does the user have an old OAuth token for this client? 443 if session != nil && session.OauthKeys != nil { 444 log.Debug("There's keys here bill...") 445 oldToken, foundKey := session.OauthKeys[ar.Client.GetId()] 446 if foundKey { 447 log.Info("Found old token, revoking: ", oldToken) 448 o.API.SessionManager.RemoveSession(oldToken, false) 449 } 450 } 451 452 log.Debug("[OAuth] Finishing access request ") 453 o.OsinServer.FinishAccessRequest(resp, r, ar) 454 new_token, foundNewToken := resp.Output["access_token"] 455 if username != "" && foundNewToken { 456 log.Debug("Updating token data in key") 457 if session.OauthKeys == nil { 458 session.OauthKeys = make(map[string]string) 459 } 460 session.OauthKeys[ar.Client.GetId()] = new_token.(string) 461 log.Debug("New token: ", new_token.(string)) 462 log.Debug("Keys: ", session.OauthKeys) 463 464 // add oauth-client user_fields to session's meta 465 if userData := ar.Client.GetUserData(); userData != nil { 466 var ok bool 467 session.MetaData, ok = userData.(map[string]interface{}) 468 if !ok { 469 log.WithField("oauthClientID", ar.Client.GetId()). 470 Error("Could not set session meta_data from oauth-client fields, type mismatch") 471 } else { 472 // set session alias to developer email as we do it for regular API keys created for developer 473 if devEmail, found := session.GetMetaData()[keyDataDeveloperEmail].(string); found { 474 session.Alias = devEmail 475 // we don't need it in meta-data as we set it to alias 476 session.RemoveMetaData(keyDataDeveloperEmail) 477 } 478 } 479 } 480 481 keyName := generateToken(o.API.OrgID, username) 482 483 log.Debug("Updating user:", keyName) 484 err := o.API.SessionManager.UpdateSession(keyName, session, session.Lifetime(o.API.SessionLifetime), false) 485 if err != nil { 486 log.Error(err) 487 } 488 } 489 } 490 491 if resp.IsError && resp.InternalError != nil { 492 log.Error("ERROR: ", resp.InternalError) 493 } 494 495 return resp 496 } 497 498 // These enums fix the prefix to use when storing various OAuth keys and data, since we 499 // delegate everything to the osin framework 500 const ( 501 prefixAuth = "oauth-authorize." 502 prefixClient = "oauth-clientid." 503 prefixAccess = "oauth-access." 504 prefixRefresh = "oauth-refresh." 505 prefixClientset = "oauth-clientset." 506 prefixClientIndexList = "oauth-client-index." 507 prefixClientTokens = "oauth-client-tokens." 508 ) 509 510 // swagger:model 511 type OAuthClientToken struct { 512 Token string `json:"code"` 513 Expires int64 `json:"expires"` 514 } 515 516 type ExtendedOsinClientInterface interface { 517 osin.Client 518 GetDescription() string 519 } 520 521 type ExtendedOsinStorageInterface interface { 522 osin.Storage 523 524 // Create OAuth clients 525 SetClient(id string, orgID string, client osin.Client, ignorePrefix bool) error 526 527 // Custom getter to handle prefixing issues in Redis 528 GetClientNoPrefix(id string) (osin.Client, error) 529 530 GetClientTokens(id string) ([]OAuthClientToken, error) 531 GetPaginatedClientTokens(id string, page int) ([]OAuthClientToken, int, error) 532 533 GetExtendedClient(id string) (ExtendedOsinClientInterface, error) 534 535 // Custom getter to handle prefixing issues in Redis 536 GetExtendedClientNoPrefix(id string) (ExtendedOsinClientInterface, error) 537 538 GetClients(filter string, orgID string, ignorePrefix bool) ([]ExtendedOsinClientInterface, error) 539 540 DeleteClient(id string, orgID string, ignorePrefix bool) error 541 542 // GetUser retrieves a Basic Access user token type from the key store 543 GetUser(string) (*user.SessionState, error) 544 545 // SetUser updates a Basic Access user token type in the key store 546 SetUser(string, *user.SessionState, int64) error 547 } 548 549 // TykOsinServer subclasses osin.Server so we can add the SetClient method without wrecking the lbrary 550 type TykOsinServer struct { 551 osin.Server 552 Config *osin.ServerConfig 553 Storage ExtendedOsinStorageInterface 554 AuthorizeTokenGen osin.AuthorizeTokenGen 555 AccessTokenGen osin.AccessTokenGen 556 } 557 558 // TykOsinNewServer creates a new server instance, but uses an extended interface so we can SetClient() too. 559 func TykOsinNewServer(config *osin.ServerConfig, storage ExtendedOsinStorageInterface) *TykOsinServer { 560 561 overrideServer := TykOsinServer{ 562 Config: config, 563 Storage: storage, 564 AuthorizeTokenGen: &osin.AuthorizeTokenGenDefault{}, 565 AccessTokenGen: accessTokenGen{}, 566 } 567 568 overrideServer.Server.Config = config 569 overrideServer.Server.Storage = storage 570 overrideServer.Server.AuthorizeTokenGen = overrideServer.AuthorizeTokenGen 571 overrideServer.Server.AccessTokenGen = accessTokenGen{} 572 573 return &overrideServer 574 } 575 576 // TODO: Refactor this to move prefix handling into a checker method, then it can be an unexported setting in the struct. 577 // RedisOsinStorageInterface implements osin.Storage interface to use Tyk's own storage mechanism 578 type RedisOsinStorageInterface struct { 579 store storage.Handler 580 sessionManager SessionHandler 581 redisStore storage.Handler 582 orgID string 583 } 584 585 func (r *RedisOsinStorageInterface) Clone() osin.Storage { 586 return r 587 } 588 589 func (r *RedisOsinStorageInterface) Close() {} 590 591 // GetClient will retrieve client data 592 func (r *RedisOsinStorageInterface) GetClient(id string) (osin.Client, error) { 593 key := prefixClient + id 594 595 log.Info("Getting client ID:", id) 596 597 clientJSON, err := r.store.GetKey(key) 598 if err != nil { 599 log.Errorf("Failure retrieving client ID key %q: %v", key, err) 600 return nil, err 601 } 602 603 client := new(OAuthClient) 604 if err := json.Unmarshal([]byte(clientJSON), &client); err != nil { 605 log.Error("Couldn't unmarshal OAuth client object: ", err) 606 } 607 608 return client, nil 609 } 610 611 // GetClientNoPrefix will retrieve client data, but not assign a prefix - this is an unfortunate hack, 612 // but we don't want to change the signature in Osin for GetClient to support the odd Redis prefixing 613 func (r *RedisOsinStorageInterface) GetClientNoPrefix(id string) (osin.Client, error) { 614 615 key := id 616 617 clientJSON, err := r.store.GetKey(key) 618 619 if err != nil { 620 log.Error("Failure retrieving client ID key: ", err) 621 return nil, err 622 } 623 624 client := new(OAuthClient) 625 if err := json.Unmarshal([]byte(clientJSON), client); err != nil { 626 log.Error("Couldn't unmarshal OAuth client object: ", err) 627 } 628 629 return client, nil 630 } 631 632 func (r *RedisOsinStorageInterface) GetExtendedClient(id string) (ExtendedOsinClientInterface, error) { 633 osinClient, err := r.GetClient(id) 634 if err != nil { 635 log.WithError(err).Error("Failure retrieving client ID key") 636 return nil, err 637 } 638 639 return osinClient.(*OAuthClient), err 640 } 641 642 // GetExtendedClientNoPrefix custom getter to handle prefixing issues in Redis, 643 func (r *RedisOsinStorageInterface) GetExtendedClientNoPrefix(id string) (ExtendedOsinClientInterface, error) { 644 osinClient, err := r.GetClientNoPrefix(id) 645 if err != nil { 646 log.WithError(err).Error("Failure retrieving client ID key") 647 return nil, err 648 } 649 return osinClient.(*OAuthClient), err 650 } 651 652 // GetClients will retrieve a list of clients for a prefix 653 func (r *RedisOsinStorageInterface) GetClients(filter string, orgID string, ignorePrefix bool) ([]ExtendedOsinClientInterface, error) { 654 key := prefixClient + filter 655 if ignorePrefix { 656 key = filter 657 } 658 659 indexKey := prefixClientIndexList + orgID 660 661 var clientJSON map[string]string 662 if !config.Global().Storage.EnableCluster { 663 exists, _ := r.store.Exists(indexKey) 664 if exists { 665 keys, err := r.store.GetListRange(indexKey, 0, -1) 666 if err != nil { 667 log.Error("Couldn't get OAuth client index list: ", err) 668 return nil, err 669 } 670 keyVals, err := r.store.GetMultiKey(keys) 671 if err != nil { 672 log.Error("Couldn't get OAuth client index list values: ", err) 673 return nil, err 674 } 675 676 clientJSON = make(map[string]string) 677 for i, key := range keys { 678 clientJSON[key] = keyVals[i] 679 } 680 } else { 681 clientJSON = r.store.GetKeysAndValuesWithFilter(key) 682 for key := range clientJSON { 683 r.store.AppendToSet(indexKey, key) 684 } 685 } 686 } else { 687 keyForSet := prefixClientset + prefixClient // Org ID 688 var err error 689 if clientJSON, err = r.store.GetSet(keyForSet); err != nil { 690 return nil, err 691 } 692 } 693 694 theseClients := []ExtendedOsinClientInterface{} 695 for _, clientJSON := range clientJSON { 696 client := new(OAuthClient) 697 if err := json.Unmarshal([]byte(clientJSON), &client); err != nil { 698 log.Error("Couldn't unmarshal OAuth client object: ", err) 699 return nil, err 700 } 701 theseClients = append(theseClients, client) 702 } 703 704 return theseClients, nil 705 } 706 707 // GetPaginatedClientTokens returns all tokens associated with the given id. 708 // It returns the tokens, the total number of pages of the tokens after 709 // pagination and an error if any 710 func (r *RedisOsinStorageInterface) GetPaginatedClientTokens(id string, page int) ([]OAuthClientToken, int, error) { 711 key := prefixClientTokens + id 712 713 // use current timestamp as a start score so all expired tokens won't be picked 714 nowTs := time.Now().Unix() 715 startScore := strconv.FormatInt(nowTs, 10) 716 717 log.Info("Getting client tokens sorted list:", key) 718 719 tokens, scores, err := r.store.GetSortedSetRange(key, startScore, "+inf") 720 if err != nil { 721 return nil, 0, err 722 } 723 724 // clean up expired tokens in sorted set (remove all tokens with score up to current timestamp minus retention) 725 if config.Global().OauthTokenExpiredRetainPeriod > 0 { 726 cleanupStartScore := strconv.FormatInt(nowTs-int64(config.Global().OauthTokenExpiredRetainPeriod), 10) 727 go r.store.RemoveSortedSetRange(key, "-inf", cleanupStartScore) 728 } 729 730 itemsPerPage := 100 731 732 tokenNumber := len(tokens) 733 734 if tokenNumber == 0 { 735 return []OAuthClientToken{}, 0, nil 736 } 737 738 startIdx := (page - 1) * itemsPerPage 739 endIdx := startIdx + itemsPerPage 740 741 if tokenNumber < startIdx { 742 startIdx = tokenNumber 743 } 744 745 if tokenNumber < endIdx { 746 endIdx = tokenNumber 747 } 748 749 totalPages := int(math.Ceil(float64(len(tokens)) / float64(itemsPerPage))) 750 751 tokens = tokens[startIdx:endIdx] 752 753 // convert sorted set data and scores into reply struct 754 tokensData := make([]OAuthClientToken, len(tokens)) 755 for i := range tokens { 756 tokensData[i] = OAuthClientToken{ 757 Token: tokens[i], 758 Expires: int64(scores[i]), // we store expire timestamp as a score 759 } 760 } 761 762 return tokensData, totalPages, nil 763 } 764 765 func (r *RedisOsinStorageInterface) GetClientTokens(id string) ([]OAuthClientToken, error) { 766 key := prefixClientTokens + id 767 768 // use current timestamp as a start score so all expired tokens won't be picked 769 nowTs := time.Now().Unix() 770 startScore := strconv.FormatInt(nowTs, 10) 771 772 log.Info("Getting client tokens sorted list:", key) 773 774 tokens, scores, err := r.redisStore.GetSortedSetRange(key, startScore, "+inf") 775 if err != nil { 776 return nil, err 777 } 778 779 // clean up expired tokens in sorted set (remove all tokens with score up to current timestamp minus retention) 780 if config.Global().OauthTokenExpiredRetainPeriod > 0 { 781 cleanupStartScore := strconv.FormatInt(nowTs-int64(config.Global().OauthTokenExpiredRetainPeriod), 10) 782 go r.redisStore.RemoveSortedSetRange(key, "-inf", cleanupStartScore) 783 } 784 785 if len(tokens) == 0 { 786 return []OAuthClientToken{}, nil 787 } 788 789 // convert sorted set data and scores into reply struct 790 tokensData := make([]OAuthClientToken, len(tokens)) 791 for i := range tokens { 792 tokensData[i] = OAuthClientToken{ 793 Token: tokens[i], 794 Expires: int64(scores[i]), // we store expire timestamp as a score 795 } 796 } 797 798 return tokensData, nil 799 } 800 801 // SetClient creates client data 802 func (r *RedisOsinStorageInterface) SetClient(id string, orgID string, client osin.Client, ignorePrefix bool) error { 803 clientDataJSON, err := json.Marshal(client) 804 805 if err != nil { 806 log.Error("Couldn't marshal client data: ", err) 807 return err 808 } 809 810 key := prefixClient + id 811 812 if ignorePrefix { 813 key = id 814 } 815 816 log.Debug("CREATING: ", key) 817 818 r.store.SetKey(key, string(clientDataJSON), 0) 819 820 log.Debug("Storing copy in set") 821 822 keyForSet := prefixClientset + prefixClient // Org ID 823 824 indexKey := prefixClientIndexList + orgID 825 //check if the indexKey exists 826 exists, err := r.store.Exists(indexKey) 827 if err != nil { 828 return err 829 } 830 // if it exists, delete it to avoid duplicity in the client index list 831 if exists { 832 r.store.RemoveFromList(indexKey, key) 833 } 834 // append to oauth client index list 835 r.store.AppendToSet(indexKey, key) 836 837 // In set, there is no option for update so the existing client should be removed before adding new one. 838 set, _ := r.store.GetSet(keyForSet) 839 for _, v := range set { 840 if strings.Contains(v, client.GetId()) { 841 r.store.RemoveFromSet(keyForSet, v) 842 } 843 } 844 845 r.store.AddToSet(keyForSet, string(clientDataJSON)) 846 return nil 847 } 848 849 // DeleteClient Removes a client from the system 850 func (r *RedisOsinStorageInterface) DeleteClient(id string, orgID string, ignorePrefix bool) error { 851 key := prefixClient + id 852 if ignorePrefix { 853 key = id 854 } 855 856 // Get the raw vals: 857 clientJSON, err := r.store.GetKey(key) 858 keyForSet := prefixClientset + prefixClient // Org ID 859 if err == nil { 860 log.Debug("Removing from set") 861 r.store.RemoveFromSet(keyForSet, clientJSON) 862 } 863 864 r.store.DeleteKey(key) 865 866 indexKey := prefixClientIndexList + orgID 867 // delete from oauth client 868 r.store.RemoveFromList(indexKey, key) 869 870 // delete list of tokens for this client 871 r.store.DeleteKey(prefixClientTokens + id) 872 if config.Global().SlaveOptions.UseRPC { 873 r.redisStore.RemoveFromList(indexKey, key) 874 r.redisStore.DeleteKey(prefixClientTokens + id) 875 r.redisStore.RemoveFromSet(keyForSet, clientJSON) 876 } 877 878 return nil 879 } 880 881 // SaveAuthorize saves authorisation data to Redis 882 func (r *RedisOsinStorageInterface) SaveAuthorize(authData *osin.AuthorizeData) error { 883 authDataJSON, err := json.Marshal(&authData) 884 if err != nil { 885 return err 886 } 887 key := prefixAuth + authData.Code 888 log.Debug("Saving auth code: ", key) 889 890 r.store.SetKey(key, string(authDataJSON), int64(authData.ExpiresIn)) 891 892 return nil 893 } 894 895 // LoadAuthorize loads auth data from redis 896 func (r *RedisOsinStorageInterface) LoadAuthorize(code string) (*osin.AuthorizeData, error) { 897 key := prefixAuth + code 898 log.Debug("Loading auth code: ", key) 899 authJSON, err := r.store.GetKey(key) 900 901 if err != nil { 902 log.Error("Failure retreiving auth code key: ", err) 903 return nil, err 904 } 905 906 authData := osin.AuthorizeData{Client: new(OAuthClient)} 907 if err := json.Unmarshal([]byte(authJSON), &authData); err != nil { 908 log.Error("Couldn't unmarshal OAuth auth data object (LoadAuthorize): ", err) 909 return nil, err 910 } 911 912 return &authData, nil 913 } 914 915 // RemoveAuthorize removes authorisation keys from redis 916 func (r *RedisOsinStorageInterface) RemoveAuthorize(code string) error { 917 key := prefixAuth + code 918 r.store.DeleteKey(key) 919 return nil 920 } 921 922 // SaveAccess will save a token and it's access data to redis 923 func (r *RedisOsinStorageInterface) SaveAccess(accessData *osin.AccessData) error { 924 authDataJSON, err := json.Marshal(accessData) 925 if err != nil { 926 return err 927 } 928 key := prefixAccess + storage.HashKey(accessData.AccessToken) 929 log.Debug("Saving ACCESS key: ", key) 930 931 // Overide default ExpiresIn: 932 if oauthTokenExpire := config.Global().OauthTokenExpire; oauthTokenExpire != 0 { 933 accessData.ExpiresIn = oauthTokenExpire 934 } 935 936 r.store.SetKey(key, string(authDataJSON), int64(accessData.ExpiresIn)) 937 938 // add code to list of tokens for this client 939 sortedListKey := prefixClientTokens + accessData.Client.GetId() 940 log.Debug("Adding ACCESS key to sorted list: ", sortedListKey) 941 r.redisStore.AddToSortedSet( 942 sortedListKey, 943 storage.HashKey(accessData.AccessToken), 944 float64(accessData.CreatedAt.Unix()+int64(accessData.ExpiresIn)), // set score as token expire timestamp 945 ) 946 947 // Create a user.SessionState object and register it with the authmanager 948 var newSession user.SessionState 949 950 // ------ 951 checkPolicy := true 952 if accessData.UserData != nil { 953 checkPolicy = false 954 err := json.Unmarshal([]byte(accessData.UserData.(string)), &newSession) 955 if err != nil { 956 log.Info("Couldn't decode user.SessionState from UserData, checking policy: ", err) 957 checkPolicy = true 958 } 959 } 960 961 if checkPolicy { 962 // defined in JWT middleware 963 sessionFromPolicy, err := generateSessionFromPolicy(accessData.Client.GetPolicyID(), "", false) 964 if err != nil { 965 return errors.New("Couldn't use policy or key rules to create token, failing") 966 } 967 968 newSession = sessionFromPolicy 969 } 970 971 // ------ 972 973 // Set the client ID for analytics 974 newSession.OauthClientID = accessData.Client.GetId() 975 976 // Override timeouts so that we can be in sync with Osin 977 newSession.Expires = time.Now().Unix() + int64(accessData.ExpiresIn) 978 979 c, ok := accessData.Client.(*OAuthClient) 980 if ok && c.MetaData != nil { 981 if newSession.GetMetaData() == nil { 982 newSession.SetMetaData(make(map[string]interface{})) 983 } 984 985 // Allow session inherit and *override* client values 986 for k, v := range c.MetaData.(map[string]interface{}) { 987 if _, found := newSession.GetMetaDataByKey(k); !found { 988 newSession.MetaData[k] = v 989 } 990 } 991 } 992 993 // Use the default session expiry here as this is OAuth 994 r.sessionManager.UpdateSession(accessData.AccessToken, &newSession, int64(accessData.ExpiresIn), false) 995 996 // Store the refresh token too 997 if accessData.RefreshToken != "" { 998 accessDataJSON, err := json.Marshal(accessData) 999 if err != nil { 1000 return err 1001 } 1002 key := prefixRefresh + accessData.RefreshToken 1003 refreshExpire := int64(1209600) // 14 days 1004 if oauthRefreshExpire := config.Global().OauthRefreshExpire; oauthRefreshExpire != 0 { 1005 refreshExpire = oauthRefreshExpire 1006 } 1007 r.store.SetKey(key, string(accessDataJSON), refreshExpire) 1008 log.Debug("STORING ACCESS DATA: ", string(accessDataJSON)) 1009 return nil 1010 } 1011 1012 return nil 1013 } 1014 1015 // LoadAccess will load access data from redis 1016 func (r *RedisOsinStorageInterface) LoadAccess(token string) (*osin.AccessData, error) { 1017 key := prefixAccess + storage.HashKey(token) 1018 log.Debug("Loading ACCESS key: ", key) 1019 accessJSON, err := r.store.GetKey(key) 1020 1021 if err != nil { 1022 // Fallback to unhashed value for backward compatibility 1023 key = prefixAccess + token 1024 accessJSON, err = r.store.GetKey(key) 1025 1026 if err != nil { 1027 log.Error("Failure retreiving access token by key: ", err) 1028 return nil, err 1029 } 1030 } 1031 1032 accessData := osin.AccessData{Client: new(OAuthClient)} 1033 if err := json.Unmarshal([]byte(accessJSON), &accessData); err != nil { 1034 log.Error("Couldn't unmarshal OAuth auth data object (LoadAccess): ", err) 1035 return nil, err 1036 } 1037 1038 return &accessData, nil 1039 } 1040 1041 // RemoveAccess will remove access data from Redis 1042 func (r *RedisOsinStorageInterface) RemoveAccess(token string) error { 1043 1044 access, err := r.LoadAccess(token) 1045 if err == nil { 1046 key := prefixClientTokens + access.Client.GetId() 1047 //remove from set oauth.client-tokens 1048 log.Debug("removing token from oauth client tokens list") 1049 limit := strconv.FormatFloat(float64(access.ExpireAt().Unix()), 'f', 0, 64) 1050 r.redisStore.RemoveSortedSetRange(key, limit, limit) 1051 } else { 1052 log.Warning("Cannot load access token:", token) 1053 } 1054 1055 key := prefixAccess + storage.HashKey(token) 1056 r.store.DeleteKey(key) 1057 1058 // remove the access token from central storage too 1059 r.sessionManager.RemoveSession(token, false) 1060 1061 return nil 1062 } 1063 1064 // LoadRefresh will load access data from Redis 1065 func (r *RedisOsinStorageInterface) LoadRefresh(token string) (*osin.AccessData, error) { 1066 key := prefixRefresh + token 1067 log.Debug("Loading REFRESH key: ", key) 1068 accessJSON, err := r.store.GetKey(key) 1069 1070 if err != nil { 1071 log.Error("Failure retreiving access token by key: ", err) 1072 return nil, err 1073 } 1074 1075 // new interface means having to make this nested... ick. 1076 accessData := osin.AccessData{Client: new(OAuthClient)} 1077 if err := json.Unmarshal([]byte(accessJSON), &accessData); err != nil { 1078 log.Error("Couldn't unmarshal OAuth auth data object (LoadRefresh): ", err, 1079 "; Decoding: ", accessJSON) 1080 return nil, err 1081 } 1082 1083 return &accessData, nil 1084 } 1085 1086 // RemoveRefresh will remove a refresh token from redis 1087 func (r *RedisOsinStorageInterface) RemoveRefresh(token string) error { 1088 log.Debug("is going to revoke refresh token: ", token) 1089 key := prefixRefresh + token 1090 r.store.DeleteKey(key) 1091 return nil 1092 } 1093 1094 // accessTokenGen is a modified authorization token generator that uses the same method used to generate tokens for Tyk authHandler 1095 type accessTokenGen struct{} 1096 1097 // GenerateAccessToken generates base64-encoded UUID access and refresh tokens 1098 func (accessTokenGen) GenerateAccessToken(data *osin.AccessData, generaterefresh bool) (accesstoken, refreshtoken string, err error) { 1099 log.Info("[OAuth] Generating new token") 1100 1101 var newSession user.SessionState 1102 checkPolicy := true 1103 if data.UserData != nil { 1104 checkPolicy = false 1105 err := json.Unmarshal([]byte(data.UserData.(string)), &newSession) 1106 if err != nil { 1107 log.Info("[GenerateAccessToken] Couldn't decode user.SessionState from UserData, checking policy: ", err) 1108 checkPolicy = true 1109 } 1110 } 1111 1112 if checkPolicy { 1113 // defined in JWT middleware 1114 sessionFromPolicy, err := generateSessionFromPolicy(data.Client.GetPolicyID(), "", false) 1115 if err != nil { 1116 return "", "", errors.New("Couldn't use policy or key rules to create token, failing") 1117 } 1118 1119 newSession = sessionFromPolicy 1120 } 1121 1122 accesstoken = keyGen.GenerateAuthKey(newSession.OrgID) 1123 if generaterefresh { 1124 u6 := uuid.NewV4() 1125 refreshtoken = base64.StdEncoding.EncodeToString([]byte(u6.String())) 1126 } 1127 return 1128 } 1129 1130 // LoadRefresh will load access data from Redis 1131 func (r *RedisOsinStorageInterface) GetUser(username string) (*user.SessionState, error) { 1132 key := username 1133 log.Debug("Loading User key: ", key) 1134 accessJSON, err := r.store.GetRawKey(key) 1135 1136 if err != nil { 1137 log.Error("Failure retreiving access token by key: ", err) 1138 return nil, err 1139 } 1140 1141 // new interface means having to make this nested... ick. 1142 session := user.SessionState{Mutex: &sync.RWMutex{}} 1143 if err := json.Unmarshal([]byte(accessJSON), &session); err != nil { 1144 log.Error("Couldn't unmarshal OAuth auth data object (LoadRefresh): ", err, 1145 "; Decoding: ", accessJSON) 1146 return nil, err 1147 } 1148 1149 return &session, nil 1150 } 1151 1152 func (r *RedisOsinStorageInterface) SetUser(username string, session *user.SessionState, timeout int64) error { 1153 key := username 1154 authDataJSON, err := json.Marshal(session) 1155 if err != nil { 1156 return err 1157 } 1158 1159 if err := r.store.SetRawKey(key, string(authDataJSON), timeout); err != nil { 1160 log.Error("Failure setting user token by key: ", err) 1161 return err 1162 } 1163 1164 return nil 1165 1166 }