1package server 2 3import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 "net/url" 10 "path" 11 "sort" 12 "strconv" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/gorilla/mux" 18 "golang.org/x/crypto/bcrypt" 19 jose "gopkg.in/square/go-jose.v2" 20 21 "github.com/concourse/dex/connector" 22 "github.com/concourse/dex/server/internal" 23 "github.com/concourse/dex/storage" 24) 25 26// newHealthChecker returns the healthz handler. The handler runs until the 27// provided context is canceled. 28func (s *Server) newHealthChecker(ctx context.Context) http.Handler { 29 h := &healthChecker{s: s} 30 31 // Perform one health check synchronously so the returned handler returns 32 // valid data immediately. 33 h.runHealthCheck() 34 35 go func() { 36 for { 37 select { 38 case <-ctx.Done(): 39 return 40 case <-time.After(time.Second * 15): 41 } 42 h.runHealthCheck() 43 } 44 }() 45 return h 46} 47 48// healthChecker periodically performs health checks on server dependenices. 49// Currently, it only checks that the storage layer is avialable. 50type healthChecker struct { 51 s *Server 52 53 // Result of the last health check: any error and the amount of time it took 54 // to query the storage. 55 mu sync.RWMutex 56 // Guarded by the mutex 57 err error 58 passed time.Duration 59} 60 61// runHealthCheck performs a single health check and makes the result available 62// for any clients performing and HTTP request against the healthChecker. 63func (h *healthChecker) runHealthCheck() { 64 t := h.s.now() 65 err := checkStorageHealth(h.s.storage, h.s.now) 66 passed := h.s.now().Sub(t) 67 if err != nil { 68 h.s.logger.Errorf("Storage health check failed: %v", err) 69 } 70 71 // Make sure to only hold the mutex to access the fields, and not while 72 // we're querying the storage object. 73 h.mu.Lock() 74 h.err = err 75 h.passed = passed 76 h.mu.Unlock() 77} 78 79func checkStorageHealth(s storage.Storage, now func() time.Time) error { 80 a := storage.AuthRequest{ 81 ID: storage.NewID(), 82 ClientID: storage.NewID(), 83 84 // Set a short expiry so if the delete fails this will be cleaned up quickly by garbage collection. 85 Expiry: now().Add(time.Minute), 86 } 87 88 if err := s.CreateAuthRequest(a); err != nil { 89 return fmt.Errorf("create auth request: %v", err) 90 } 91 if err := s.DeleteAuthRequest(a.ID); err != nil { 92 return fmt.Errorf("delete auth request: %v", err) 93 } 94 return nil 95} 96 97func (h *healthChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) { 98 h.mu.RLock() 99 err := h.err 100 t := h.passed 101 h.mu.RUnlock() 102 103 if err != nil { 104 h.s.renderError(w, http.StatusInternalServerError, "Health check failed.") 105 return 106 } 107 fmt.Fprintf(w, "Health check passed in %s", t) 108} 109 110func (s *Server) handlePublicKeys(w http.ResponseWriter, r *http.Request) { 111 // TODO(ericchiang): Cache this. 112 keys, err := s.storage.GetKeys() 113 if err != nil { 114 s.logger.Errorf("failed to get keys: %v", err) 115 s.renderError(w, http.StatusInternalServerError, "Internal server error.") 116 return 117 } 118 119 if keys.SigningKeyPub == nil { 120 s.logger.Errorf("No public keys found.") 121 s.renderError(w, http.StatusInternalServerError, "Internal server error.") 122 return 123 } 124 125 jwks := jose.JSONWebKeySet{ 126 Keys: make([]jose.JSONWebKey, len(keys.VerificationKeys)+1), 127 } 128 jwks.Keys[0] = *keys.SigningKeyPub 129 for i, verificationKey := range keys.VerificationKeys { 130 jwks.Keys[i+1] = *verificationKey.PublicKey 131 } 132 133 data, err := json.MarshalIndent(jwks, "", " ") 134 if err != nil { 135 s.logger.Errorf("failed to marshal discovery data: %v", err) 136 s.renderError(w, http.StatusInternalServerError, "Internal server error.") 137 return 138 } 139 maxAge := keys.NextRotation.Sub(s.now()) 140 if maxAge < (time.Minute * 2) { 141 maxAge = time.Minute * 2 142 } 143 144 w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, must-revalidate", int(maxAge.Seconds()))) 145 w.Header().Set("Content-Type", "application/json") 146 w.Header().Set("Content-Length", strconv.Itoa(len(data))) 147 w.Write(data) 148} 149 150type discovery struct { 151 Issuer string `json:"issuer"` 152 Auth string `json:"authorization_endpoint"` 153 Token string `json:"token_endpoint"` 154 Keys string `json:"jwks_uri"` 155 ResponseTypes []string `json:"response_types_supported"` 156 Subjects []string `json:"subject_types_supported"` 157 IDTokenAlgs []string `json:"id_token_signing_alg_values_supported"` 158 Scopes []string `json:"scopes_supported"` 159 AuthMethods []string `json:"token_endpoint_auth_methods_supported"` 160 Claims []string `json:"claims_supported"` 161} 162 163func (s *Server) discoveryHandler() (http.HandlerFunc, error) { 164 d := discovery{ 165 Issuer: s.issuerURL.String(), 166 Auth: s.absURL("/auth"), 167 Token: s.absURL("/token"), 168 Keys: s.absURL("/keys"), 169 Subjects: []string{"public"}, 170 IDTokenAlgs: []string{string(jose.RS256)}, 171 Scopes: []string{"openid", "email", "groups", "profile", "offline_access"}, 172 AuthMethods: []string{"client_secret_basic"}, 173 Claims: []string{ 174 "aud", "email", "email_verified", "exp", 175 "iat", "iss", "locale", "name", "sub", 176 }, 177 } 178 179 for responseType := range s.supportedResponseTypes { 180 d.ResponseTypes = append(d.ResponseTypes, responseType) 181 } 182 sort.Strings(d.ResponseTypes) 183 184 data, err := json.MarshalIndent(d, "", " ") 185 if err != nil { 186 return nil, fmt.Errorf("failed to marshal discovery data: %v", err) 187 } 188 189 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 190 w.Header().Set("Content-Type", "application/json") 191 w.Header().Set("Content-Length", strconv.Itoa(len(data))) 192 w.Write(data) 193 }), nil 194} 195 196// handleAuthorization handles the OAuth2 auth endpoint. 197func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) { 198 authReq, err := s.parseAuthorizationRequest(r) 199 if err != nil { 200 s.logger.Errorf("Failed to parse authorization request: %v", err) 201 if handler, ok := err.Handle(); ok { 202 // client_id and redirect_uri checked out and we can redirect back to 203 // the client with the error. 204 handler.ServeHTTP(w, r) 205 return 206 } 207 208 // Otherwise render the error to the user. 209 // 210 // TODO(ericchiang): Should we just always render the error? 211 s.renderError(w, err.Status(), err.Error()) 212 return 213 } 214 215 // TODO(ericchiang): Create this authorization request later in the login flow 216 // so users don't hit "not found" database errors if they wait at the login 217 // screen too long. 218 // 219 // See: https://github.com/dexidp/dex/issues/646 220 authReq.Expiry = s.now().Add(s.authRequestsValidFor) 221 if err := s.storage.CreateAuthRequest(authReq); err != nil { 222 s.logger.Errorf("Failed to create authorization request: %v", err) 223 s.renderError(w, http.StatusInternalServerError, "Failed to connect to the database.") 224 return 225 } 226 227 connectors, e := s.storage.ListConnectors() 228 if e != nil { 229 s.logger.Errorf("Failed to get list of connectors: %v", err) 230 s.renderError(w, http.StatusInternalServerError, "Failed to retrieve connector list.") 231 return 232 } 233 234 if len(connectors) == 1 { 235 for _, c := range connectors { 236 // TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter 237 // on create the auth request. 238 http.Redirect(w, r, s.absPath("/auth", c.ID)+"?req="+authReq.ID, http.StatusFound) 239 return 240 } 241 } 242 243 connectorInfos := make([]connectorInfo, len(connectors)) 244 i := 0 245 for _, conn := range connectors { 246 connectorInfos[i] = connectorInfo{ 247 ID: conn.ID, 248 Name: conn.Name, 249 // TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter 250 // on create the auth request. 251 URL: s.absPath("/auth", conn.ID) + "?req=" + authReq.ID, 252 } 253 i++ 254 } 255 256 if err := s.templates.login(w, connectorInfos); err != nil { 257 s.logger.Errorf("Server template error: %v", err) 258 } 259} 260 261func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) { 262 connID := mux.Vars(r)["connector"] 263 conn, err := s.getConnector(connID) 264 if err != nil { 265 s.logger.Errorf("Failed to create authorization request: %v", err) 266 s.renderError(w, http.StatusBadRequest, "Requested resource does not exist") 267 return 268 } 269 270 authReqID := r.FormValue("req") 271 272 authReq, err := s.storage.GetAuthRequest(authReqID) 273 if err != nil { 274 s.logger.Errorf("Failed to get auth request: %v", err) 275 if err == storage.ErrNotFound { 276 s.renderError(w, http.StatusBadRequest, "Login session expired.") 277 } else { 278 s.renderError(w, http.StatusInternalServerError, "Database error.") 279 } 280 return 281 } 282 283 // Set the connector being used for the login. 284 if authReq.ConnectorID != connID { 285 updater := func(a storage.AuthRequest) (storage.AuthRequest, error) { 286 a.ConnectorID = connID 287 return a, nil 288 } 289 if err := s.storage.UpdateAuthRequest(authReqID, updater); err != nil { 290 s.logger.Errorf("Failed to set connector ID on auth request: %v", err) 291 s.renderError(w, http.StatusInternalServerError, "Database error.") 292 return 293 } 294 } 295 296 scopes := parseScopes(authReq.Scopes) 297 showBacklink := len(s.connectors) > 1 298 299 switch r.Method { 300 case http.MethodGet: 301 switch conn := conn.Connector.(type) { 302 case connector.CallbackConnector: 303 // Use the auth request ID as the "state" token. 304 // 305 // TODO(ericchiang): Is this appropriate or should we also be using a nonce? 306 callbackURL, err := conn.LoginURL(scopes, s.absURL("/callback"), authReqID) 307 if err != nil { 308 s.logger.Errorf("Connector %q returned error when creating callback: %v", connID, err) 309 s.renderError(w, http.StatusInternalServerError, "Login error.") 310 return 311 } 312 http.Redirect(w, r, callbackURL, http.StatusFound) 313 case connector.PasswordConnector: 314 if err := s.templates.password(w, r.URL.String(), "", usernamePrompt(conn), false, showBacklink); err != nil { 315 s.logger.Errorf("Server template error: %v", err) 316 } 317 case connector.SAMLConnector: 318 action, value, err := conn.POSTData(scopes, authReqID) 319 if err != nil { 320 s.logger.Errorf("Creating SAML data: %v", err) 321 s.renderError(w, http.StatusInternalServerError, "Connector Login Error") 322 return 323 } 324 325 // TODO(ericchiang): Don't inline this. 326 fmt.Fprintf(w, `<!DOCTYPE html> 327 <html lang="en"> 328 <head> 329 <meta http-equiv="content-type" content="text/html; charset=utf-8"> 330 <title>SAML login</title> 331 </head> 332 <body> 333 <form method="post" action="%s" > 334 <input type="hidden" name="SAMLRequest" value="%s" /> 335 <input type="hidden" name="RelayState" value="%s" /> 336 </form> 337 <script> 338 document.forms[0].submit(); 339 </script> 340 </body> 341 </html>`, action, value, authReqID) 342 default: 343 s.renderError(w, http.StatusBadRequest, "Requested resource does not exist.") 344 } 345 case http.MethodPost: 346 passwordConnector, ok := conn.Connector.(connector.PasswordConnector) 347 if !ok { 348 s.renderError(w, http.StatusBadRequest, "Requested resource does not exist.") 349 return 350 } 351 352 username := r.FormValue("login") 353 password := r.FormValue("password") 354 355 identity, ok, err := passwordConnector.Login(r.Context(), scopes, username, password) 356 if err != nil { 357 s.logger.Errorf("Failed to login user: %v", err) 358 s.renderError(w, http.StatusInternalServerError, "Login error.") 359 return 360 } 361 if !ok { 362 if err := s.templates.password(w, r.URL.String(), username, usernamePrompt(passwordConnector), true, showBacklink); err != nil { 363 s.logger.Errorf("Server template error: %v", err) 364 } 365 return 366 } 367 redirectURL, err := s.finalizeLogin(identity, authReq, conn.Connector) 368 if err != nil { 369 s.logger.Errorf("Failed to finalize login: %v", err) 370 s.renderError(w, http.StatusInternalServerError, "Login error.") 371 return 372 } 373 374 http.Redirect(w, r, redirectURL, http.StatusSeeOther) 375 default: 376 s.renderError(w, http.StatusBadRequest, "Unsupported request method.") 377 } 378} 379 380func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) { 381 var authID string 382 switch r.Method { 383 case http.MethodGet: // OAuth2 callback 384 if authID = r.URL.Query().Get("state"); authID == "" { 385 s.renderError(w, http.StatusBadRequest, "User session error.") 386 return 387 } 388 case http.MethodPost: // SAML POST binding 389 if authID = r.PostFormValue("RelayState"); authID == "" { 390 s.renderError(w, http.StatusBadRequest, "User session error.") 391 return 392 } 393 default: 394 s.renderError(w, http.StatusBadRequest, "Method not supported") 395 return 396 } 397 398 authReq, err := s.storage.GetAuthRequest(authID) 399 if err != nil { 400 if err == storage.ErrNotFound { 401 s.logger.Errorf("Invalid 'state' parameter provided: %v", err) 402 s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.") 403 return 404 } 405 s.logger.Errorf("Failed to get auth request: %v", err) 406 s.renderError(w, http.StatusInternalServerError, "Database error.") 407 return 408 } 409 410 if connID := mux.Vars(r)["connector"]; connID != "" && connID != authReq.ConnectorID { 411 s.logger.Errorf("Connector mismatch: authentication started with id %q, but callback for id %q was triggered", authReq.ConnectorID, connID) 412 s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.") 413 return 414 } 415 416 conn, err := s.getConnector(authReq.ConnectorID) 417 if err != nil { 418 s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err) 419 s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.") 420 return 421 } 422 423 var identity connector.Identity 424 switch conn := conn.Connector.(type) { 425 case connector.CallbackConnector: 426 if r.Method != http.MethodGet { 427 s.logger.Errorf("SAML request mapped to OAuth2 connector") 428 s.renderError(w, http.StatusBadRequest, "Invalid request") 429 return 430 } 431 identity, err = conn.HandleCallback(parseScopes(authReq.Scopes), r) 432 case connector.SAMLConnector: 433 if r.Method != http.MethodPost { 434 s.logger.Errorf("OAuth2 request mapped to SAML connector") 435 s.renderError(w, http.StatusBadRequest, "Invalid request") 436 return 437 } 438 identity, err = conn.HandlePOST(parseScopes(authReq.Scopes), r.PostFormValue("SAMLResponse"), authReq.ID) 439 default: 440 s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.") 441 return 442 } 443 444 if err != nil { 445 s.logger.Errorf("Failed to authenticate: %v", err) 446 s.renderError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to authenticate: %v", err)) 447 return 448 } 449 450 redirectURL, err := s.finalizeLogin(identity, authReq, conn.Connector) 451 if err != nil { 452 s.logger.Errorf("Failed to finalize login: %v", err) 453 s.renderError(w, http.StatusInternalServerError, "Login error.") 454 return 455 } 456 457 http.Redirect(w, r, redirectURL, http.StatusSeeOther) 458} 459 460// finalizeLogin associates the user's identity with the current AuthRequest, then returns 461// the approval page's path. 462func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.AuthRequest, conn connector.Connector) (string, error) { 463 claims := storage.Claims{ 464 UserID: identity.UserID, 465 Username: identity.Username, 466 Name: identity.Name, 467 Email: identity.Email, 468 EmailVerified: identity.EmailVerified, 469 Groups: identity.Groups, 470 } 471 472 updater := func(a storage.AuthRequest) (storage.AuthRequest, error) { 473 a.LoggedIn = true 474 a.Claims = claims 475 a.ConnectorData = identity.ConnectorData 476 return a, nil 477 } 478 if err := s.storage.UpdateAuthRequest(authReq.ID, updater); err != nil { 479 return "", fmt.Errorf("failed to update auth request: %v", err) 480 } 481 482 email := claims.Email 483 if !claims.EmailVerified { 484 email = email + " (unverified)" 485 } 486 487 s.logger.Infof("login successful: connector %q, username=%q, email=%q, groups=%q", 488 authReq.ConnectorID, claims.Username, email, claims.Groups) 489 490 return path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID, nil 491} 492 493func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { 494 authReq, err := s.storage.GetAuthRequest(r.FormValue("req")) 495 if err != nil { 496 s.logger.Errorf("Failed to get auth request: %v", err) 497 s.renderError(w, http.StatusInternalServerError, "Database error.") 498 return 499 } 500 if !authReq.LoggedIn { 501 s.logger.Errorf("Auth request does not have an identity for approval") 502 s.renderError(w, http.StatusInternalServerError, "Login process not yet finalized.") 503 return 504 } 505 506 switch r.Method { 507 case http.MethodGet: 508 if s.skipApproval { 509 s.sendCodeResponse(w, r, authReq) 510 return 511 } 512 client, err := s.storage.GetClient(authReq.ClientID) 513 if err != nil { 514 s.logger.Errorf("Failed to get client %q: %v", authReq.ClientID, err) 515 s.renderError(w, http.StatusInternalServerError, "Failed to retrieve client.") 516 return 517 } 518 if err := s.templates.approval(w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes); err != nil { 519 s.logger.Errorf("Server template error: %v", err) 520 } 521 case http.MethodPost: 522 if r.FormValue("approval") != "approve" { 523 s.renderError(w, http.StatusInternalServerError, "Approval rejected.") 524 return 525 } 526 s.sendCodeResponse(w, r, authReq) 527 } 528} 529 530func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authReq storage.AuthRequest) { 531 if s.now().After(authReq.Expiry) { 532 s.renderError(w, http.StatusBadRequest, "User session has expired.") 533 return 534 } 535 536 if err := s.storage.DeleteAuthRequest(authReq.ID); err != nil { 537 if err != storage.ErrNotFound { 538 s.logger.Errorf("Failed to delete authorization request: %v", err) 539 s.renderError(w, http.StatusInternalServerError, "Internal server error.") 540 } else { 541 s.renderError(w, http.StatusBadRequest, "User session error.") 542 } 543 return 544 } 545 u, err := url.Parse(authReq.RedirectURI) 546 if err != nil { 547 s.renderError(w, http.StatusInternalServerError, "Invalid redirect URI.") 548 return 549 } 550 551 var ( 552 // Was the initial request using the implicit or hybrid flow instead of 553 // the "normal" code flow? 554 implicitOrHybrid = false 555 556 // Only present in hybrid or code flow. code.ID == "" if this is not set. 557 code storage.AuthCode 558 559 // ID token returned immediately if the response_type includes "id_token". 560 // Only valid for implicit and hybrid flows. 561 idToken string 562 idTokenExpiry time.Time 563 564 accessToken = storage.NewID() 565 ) 566 567 for _, responseType := range authReq.ResponseTypes { 568 switch responseType { 569 case responseTypeCode: 570 code = storage.AuthCode{ 571 ID: storage.NewID(), 572 ClientID: authReq.ClientID, 573 ConnectorID: authReq.ConnectorID, 574 Nonce: authReq.Nonce, 575 Scopes: authReq.Scopes, 576 Claims: authReq.Claims, 577 Expiry: s.now().Add(time.Minute * 30), 578 RedirectURI: authReq.RedirectURI, 579 ConnectorData: authReq.ConnectorData, 580 } 581 if err := s.storage.CreateAuthCode(code); err != nil { 582 s.logger.Errorf("Failed to create auth code: %v", err) 583 s.renderError(w, http.StatusInternalServerError, "Internal server error.") 584 return 585 } 586 587 // Implicit and hybrid flows that try to use the OOB redirect URI are 588 // rejected earlier. If we got here we're using the code flow. 589 if authReq.RedirectURI == redirectURIOOB { 590 if err := s.templates.oob(w, code.ID); err != nil { 591 s.logger.Errorf("Server template error: %v", err) 592 } 593 return 594 } 595 case responseTypeToken: 596 implicitOrHybrid = true 597 case responseTypeIDToken: 598 implicitOrHybrid = true 599 var err error 600 idToken, idTokenExpiry, err = s.newIDToken(authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, accessToken, authReq.ConnectorID) 601 if err != nil { 602 s.logger.Errorf("failed to create ID token: %v", err) 603 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 604 return 605 } 606 } 607 } 608 609 if implicitOrHybrid { 610 v := url.Values{} 611 v.Set("access_token", accessToken) 612 v.Set("token_type", "bearer") 613 v.Set("state", authReq.State) 614 if idToken != "" { 615 v.Set("id_token", idToken) 616 // The hybrid flow with only "code token" or "code id_token" doesn't return an 617 // "expires_in" value. If "code" wasn't provided, indicating the implicit flow, 618 // don't add it. 619 // 620 // https://openid.net/specs/openid-connect-core-1_0.html#HybridAuthResponse 621 if code.ID == "" { 622 v.Set("expires_in", strconv.Itoa(int(idTokenExpiry.Sub(s.now()).Seconds()))) 623 } 624 } 625 if code.ID != "" { 626 v.Set("code", code.ID) 627 } 628 629 // Implicit and hybrid flows return their values as part of the fragment. 630 // 631 // HTTP/1.1 303 See Other 632 // Location: https://client.example.org/cb# 633 // access_token=SlAV32hkKG 634 // &token_type=bearer 635 // &id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso 636 // &expires_in=3600 637 // &state=af0ifjsldkj 638 // 639 u.Fragment = v.Encode() 640 } else { 641 // The code flow add values to the URL query. 642 // 643 // HTTP/1.1 303 See Other 644 // Location: https://client.example.org/cb? 645 // code=SplxlOBeZQQYbYS6WxSbIA 646 // &state=af0ifjsldkj 647 // 648 q := u.Query() 649 q.Set("code", code.ID) 650 q.Set("state", authReq.State) 651 u.RawQuery = q.Encode() 652 } 653 654 http.Redirect(w, r, u.String(), http.StatusSeeOther) 655} 656 657func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) { 658 clientID, clientSecret, ok := r.BasicAuth() 659 if ok { 660 var err error 661 if clientID, err = url.QueryUnescape(clientID); err != nil { 662 s.tokenErrHelper(w, errInvalidRequest, "client_id improperly encoded", http.StatusBadRequest) 663 return 664 } 665 if clientSecret, err = url.QueryUnescape(clientSecret); err != nil { 666 s.tokenErrHelper(w, errInvalidRequest, "client_secret improperly encoded", http.StatusBadRequest) 667 return 668 } 669 } else { 670 clientID = r.PostFormValue("client_id") 671 clientSecret = r.PostFormValue("client_secret") 672 } 673 674 client, err := s.storage.GetClient(clientID) 675 if err != nil { 676 if err != storage.ErrNotFound { 677 s.logger.Errorf("failed to get client: %v", err) 678 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 679 } else { 680 s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) 681 } 682 return 683 } 684 685 if err := checkCost([]byte(client.Secret)); err != nil { 686 s.logger.Errorf("failed to check cost of client secret: %v", err) 687 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 688 return 689 } 690 if err := bcrypt.CompareHashAndPassword([]byte(client.Secret), []byte(clientSecret)); err != nil { 691 s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) 692 return 693 } 694 695 grantType := r.PostFormValue("grant_type") 696 switch grantType { 697 case grantTypeAuthorizationCode: 698 s.handleAuthCode(w, r, client) 699 case grantTypeRefreshToken: 700 s.handleRefreshToken(w, r, client) 701 case grantTypeClientCredentials: 702 s.handleClientCredentialsGrant(w, r, client) 703 case grantTypePassword: 704 s.handlePasswordGrant(w, r, client) 705 default: 706 s.tokenErrHelper(w, errInvalidGrant, "", http.StatusBadRequest) 707 } 708} 709 710// handle an access token request https://tools.ietf.org/html/rfc6749#section-4.1.3 711func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client storage.Client) { 712 code := r.PostFormValue("code") 713 redirectURI := r.PostFormValue("redirect_uri") 714 715 authCode, err := s.storage.GetAuthCode(code) 716 if err != nil || s.now().After(authCode.Expiry) || authCode.ClientID != client.ID { 717 if err != storage.ErrNotFound { 718 s.logger.Errorf("failed to get auth code: %v", err) 719 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 720 } else { 721 s.tokenErrHelper(w, errInvalidRequest, "Invalid or expired code parameter.", http.StatusBadRequest) 722 } 723 return 724 } 725 726 if authCode.RedirectURI != redirectURI { 727 s.tokenErrHelper(w, errInvalidRequest, "redirect_uri did not match URI from initial request.", http.StatusBadRequest) 728 return 729 } 730 731 accessToken := storage.NewID() 732 idToken, expiry, err := s.newIDToken(client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, accessToken, authCode.ConnectorID) 733 if err != nil { 734 s.logger.Errorf("failed to create ID token: %v", err) 735 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 736 return 737 } 738 739 if err := s.storage.DeleteAuthCode(code); err != nil { 740 s.logger.Errorf("failed to delete auth code: %v", err) 741 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 742 return 743 } 744 745 reqRefresh := func() bool { 746 // Ensure the connector supports refresh tokens. 747 // 748 // Connectors like `saml` do not implement RefreshConnector. 749 conn, err := s.getConnector(authCode.ConnectorID) 750 if err != nil { 751 s.logger.Errorf("connector with ID %q not found: %v", authCode.ConnectorID, err) 752 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 753 return false 754 } 755 756 _, ok := conn.Connector.(connector.RefreshConnector) 757 if !ok { 758 return false 759 } 760 761 for _, scope := range authCode.Scopes { 762 if scope == scopeOfflineAccess { 763 return true 764 } 765 } 766 return false 767 }() 768 var refreshToken string 769 if reqRefresh { 770 refresh := storage.RefreshToken{ 771 ID: storage.NewID(), 772 Token: storage.NewID(), 773 ClientID: authCode.ClientID, 774 ConnectorID: authCode.ConnectorID, 775 Scopes: authCode.Scopes, 776 Claims: authCode.Claims, 777 Nonce: authCode.Nonce, 778 ConnectorData: authCode.ConnectorData, 779 CreatedAt: s.now(), 780 LastUsed: s.now(), 781 } 782 token := &internal.RefreshToken{ 783 RefreshId: refresh.ID, 784 Token: refresh.Token, 785 } 786 if refreshToken, err = internal.Marshal(token); err != nil { 787 s.logger.Errorf("failed to marshal refresh token: %v", err) 788 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 789 return 790 } 791 792 if err := s.storage.CreateRefresh(refresh); err != nil { 793 s.logger.Errorf("failed to create refresh token: %v", err) 794 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 795 return 796 } 797 798 // deleteToken determines if we need to delete the newly created refresh token 799 // due to a failure in updating/creating the OfflineSession object for the 800 // corresponding user. 801 var deleteToken bool 802 defer func() { 803 if deleteToken { 804 // Delete newly created refresh token from storage. 805 if err := s.storage.DeleteRefresh(refresh.ID); err != nil { 806 s.logger.Errorf("failed to delete refresh token: %v", err) 807 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 808 return 809 } 810 } 811 }() 812 813 tokenRef := storage.RefreshTokenRef{ 814 ID: refresh.ID, 815 ClientID: refresh.ClientID, 816 CreatedAt: refresh.CreatedAt, 817 LastUsed: refresh.LastUsed, 818 } 819 820 // Try to retrieve an existing OfflineSession object for the corresponding user. 821 if session, err := s.storage.GetOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID); err != nil { 822 if err != storage.ErrNotFound { 823 s.logger.Errorf("failed to get offline session: %v", err) 824 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 825 deleteToken = true 826 return 827 } 828 offlineSessions := storage.OfflineSessions{ 829 UserID: refresh.Claims.UserID, 830 ConnID: refresh.ConnectorID, 831 Refresh: make(map[string]*storage.RefreshTokenRef), 832 } 833 offlineSessions.Refresh[tokenRef.ClientID] = &tokenRef 834 835 // Create a new OfflineSession object for the user and add a reference object for 836 // the newly received refreshtoken. 837 if err := s.storage.CreateOfflineSessions(offlineSessions); err != nil { 838 s.logger.Errorf("failed to create offline session: %v", err) 839 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 840 deleteToken = true 841 return 842 } 843 } else { 844 if oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok { 845 // Delete old refresh token from storage. 846 if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil { 847 s.logger.Errorf("failed to delete refresh token: %v", err) 848 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 849 deleteToken = true 850 return 851 } 852 } 853 854 // Update existing OfflineSession obj with new RefreshTokenRef. 855 if err := s.storage.UpdateOfflineSessions(session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { 856 old.Refresh[tokenRef.ClientID] = &tokenRef 857 return old, nil 858 }); err != nil { 859 s.logger.Errorf("failed to update offline session: %v", err) 860 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 861 deleteToken = true 862 return 863 } 864 865 } 866 } 867 s.writeAccessToken(w, idToken, accessToken, refreshToken, expiry) 868} 869 870// handle a refresh token request https://tools.ietf.org/html/rfc6749#section-6 871func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, client storage.Client) { 872 code := r.PostFormValue("refresh_token") 873 scope := r.PostFormValue("scope") 874 if code == "" { 875 s.tokenErrHelper(w, errInvalidRequest, "No refresh token in request.", http.StatusBadRequest) 876 return 877 } 878 879 token := new(internal.RefreshToken) 880 if err := internal.Unmarshal(code, token); err != nil { 881 // For backward compatibility, assume the refresh_token is a raw refresh token ID 882 // if it fails to decode. 883 // 884 // Because refresh_token values that aren't unmarshable were generated by servers 885 // that don't have a Token value, we'll still reject any attempts to claim a 886 // refresh_token twice. 887 token = &internal.RefreshToken{RefreshId: code, Token: ""} 888 } 889 890 refresh, err := s.storage.GetRefresh(token.RefreshId) 891 if err != nil { 892 s.logger.Errorf("failed to get refresh token: %v", err) 893 if err == storage.ErrNotFound { 894 s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest) 895 } else { 896 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 897 } 898 return 899 } 900 if refresh.ClientID != client.ID { 901 s.logger.Errorf("client %s trying to claim token for client %s", client.ID, refresh.ClientID) 902 s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest) 903 return 904 } 905 if refresh.Token != token.Token { 906 s.logger.Errorf("refresh token with id %s claimed twice", refresh.ID) 907 s.tokenErrHelper(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest) 908 return 909 } 910 911 // Per the OAuth2 spec, if the client has omitted the scopes, default to the original 912 // authorized scopes. 913 // 914 // https://tools.ietf.org/html/rfc6749#section-6 915 scopes := refresh.Scopes 916 if scope != "" { 917 requestedScopes := strings.Fields(scope) 918 var unauthorizedScopes []string 919 920 for _, s := range requestedScopes { 921 contains := func() bool { 922 for _, scope := range refresh.Scopes { 923 if s == scope { 924 return true 925 } 926 } 927 return false 928 }() 929 if !contains { 930 unauthorizedScopes = append(unauthorizedScopes, s) 931 } 932 } 933 934 if len(unauthorizedScopes) > 0 { 935 msg := fmt.Sprintf("Requested scopes contain unauthorized scope(s): %q.", unauthorizedScopes) 936 s.tokenErrHelper(w, errInvalidRequest, msg, http.StatusBadRequest) 937 return 938 } 939 scopes = requestedScopes 940 } 941 942 conn, err := s.getConnector(refresh.ConnectorID) 943 if err != nil { 944 s.logger.Errorf("connector with ID %q not found: %v", refresh.ConnectorID, err) 945 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 946 return 947 } 948 ident := connector.Identity{ 949 UserID: refresh.Claims.UserID, 950 Name: refresh.Claims.Name, 951 Username: refresh.Claims.Username, 952 Email: refresh.Claims.Email, 953 EmailVerified: refresh.Claims.EmailVerified, 954 Groups: refresh.Claims.Groups, 955 ConnectorData: refresh.ConnectorData, 956 } 957 958 // Can the connector refresh the identity? If so, attempt to refresh the data 959 // in the connector. 960 // 961 // TODO(ericchiang): We may want a strict mode where connectors that don't implement 962 // this interface can't perform refreshing. 963 if refreshConn, ok := conn.Connector.(connector.RefreshConnector); ok { 964 newIdent, err := refreshConn.Refresh(r.Context(), parseScopes(scopes), ident) 965 if err != nil { 966 s.logger.Errorf("failed to refresh identity: %v", err) 967 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 968 return 969 } 970 ident = newIdent 971 } 972 973 claims := storage.Claims{ 974 UserID: ident.UserID, 975 Name: ident.Name, 976 Username: ident.Username, 977 Email: ident.Email, 978 EmailVerified: ident.EmailVerified, 979 Groups: ident.Groups, 980 } 981 982 accessToken := storage.NewID() 983 idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, refresh.Nonce, accessToken, refresh.ConnectorID) 984 if err != nil { 985 s.logger.Errorf("failed to create ID token: %v", err) 986 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 987 return 988 } 989 990 newToken := &internal.RefreshToken{ 991 RefreshId: refresh.ID, 992 Token: storage.NewID(), 993 } 994 rawNewToken, err := internal.Marshal(newToken) 995 if err != nil { 996 s.logger.Errorf("failed to marshal refresh token: %v", err) 997 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 998 return 999 } 1000 1001 lastUsed := s.now() 1002 updater := func(old storage.RefreshToken) (storage.RefreshToken, error) { 1003 if old.Token != refresh.Token { 1004 return old, errors.New("refresh token claimed twice") 1005 } 1006 old.Token = newToken.Token 1007 // Update the claims of the refresh token. 1008 // 1009 // UserID intentionally ignored for now. 1010 old.Claims.Name = ident.Name 1011 old.Claims.Username = ident.Username 1012 old.Claims.Email = ident.Email 1013 old.Claims.EmailVerified = ident.EmailVerified 1014 old.Claims.Groups = ident.Groups 1015 old.ConnectorData = ident.ConnectorData 1016 old.LastUsed = lastUsed 1017 return old, nil 1018 } 1019 1020 // Update LastUsed time stamp in refresh token reference object 1021 // in offline session for the user. 1022 if err := s.storage.UpdateOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { 1023 if old.Refresh[refresh.ClientID].ID != refresh.ID { 1024 return old, errors.New("refresh token invalid") 1025 } 1026 old.Refresh[refresh.ClientID].LastUsed = lastUsed 1027 return old, nil 1028 }); err != nil { 1029 s.logger.Errorf("failed to update offline session: %v", err) 1030 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1031 return 1032 } 1033 1034 // Update refresh token in the storage. 1035 if err := s.storage.UpdateRefreshToken(refresh.ID, updater); err != nil { 1036 s.logger.Errorf("failed to update refresh token: %v", err) 1037 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1038 return 1039 } 1040 1041 s.writeAccessToken(w, idToken, accessToken, rawNewToken, expiry) 1042} 1043 1044func (s *Server) handleClientCredentialsGrant(w http.ResponseWriter, r *http.Request, client storage.Client) { 1045 1046 if err := r.ParseForm(); err != nil { 1047 s.tokenErrHelper(w, errInvalidRequest, "Couldn't parse data", http.StatusBadRequest) 1048 return 1049 } 1050 q := r.Form 1051 1052 nonce := q.Get("nonce") 1053 scopes := strings.Fields(q.Get("scope")) 1054 1055 claims := storage.Claims{UserID: client.ID} 1056 1057 accessToken := storage.NewID() 1058 idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, nonce, accessToken, "client") 1059 if err != nil { 1060 s.tokenErrHelper(w, errServerError, fmt.Sprintf("failed to create ID token: %v", err), http.StatusInternalServerError) 1061 return 1062 } 1063 1064 s.writeAccessToken(w, idToken, accessToken, "", expiry) 1065} 1066 1067func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, client storage.Client) { 1068 1069 // Parse the fields 1070 if err := r.ParseForm(); err != nil { 1071 s.tokenErrHelper(w, errInvalidRequest, "Couldn't parse data", http.StatusBadRequest) 1072 return 1073 } 1074 q := r.Form 1075 1076 nonce := q.Get("nonce") 1077 // Some clients, like the old go-oidc, provide extra whitespace. Tolerate this. 1078 scopes := strings.Fields(q.Get("scope")) 1079 1080 // Parse the scopes if they are passed 1081 var ( 1082 unrecognized []string 1083 invalidScopes []string 1084 ) 1085 hasOpenIDScope := false 1086 for _, scope := range scopes { 1087 switch scope { 1088 case scopeOpenID: 1089 hasOpenIDScope = true 1090 case scopeOfflineAccess, scopeEmail, scopeProfile, scopeGroups, scopeFederatedID: 1091 default: 1092 peerID, ok := parseCrossClientScope(scope) 1093 if !ok { 1094 unrecognized = append(unrecognized, scope) 1095 continue 1096 } 1097 1098 isTrusted, err := s.validateCrossClientTrust(client.ID, peerID) 1099 if err != nil { 1100 s.tokenErrHelper(w, errInvalidClient, fmt.Sprintf("Error validating cross client trust %v.", err), http.StatusBadRequest) 1101 return 1102 } 1103 if !isTrusted { 1104 invalidScopes = append(invalidScopes, scope) 1105 } 1106 } 1107 } 1108 if !hasOpenIDScope { 1109 s.tokenErrHelper(w, errInvalidRequest, `Missing required scope(s) ["openid"].`, http.StatusBadRequest) 1110 return 1111 } 1112 if len(unrecognized) > 0 { 1113 s.tokenErrHelper(w, errInvalidRequest, fmt.Sprintf("Unrecognized scope(s) %q", unrecognized), http.StatusBadRequest) 1114 return 1115 } 1116 if len(invalidScopes) > 0 { 1117 s.tokenErrHelper(w, errInvalidRequest, fmt.Sprintf("Client can't request scope(s) %q", invalidScopes), http.StatusBadRequest) 1118 return 1119 } 1120 1121 // Which connector 1122 connID := s.passwordConnector 1123 conn, err := s.getConnector(connID) 1124 if err != nil { 1125 s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest) 1126 return 1127 } 1128 1129 passwordConnector, ok := conn.Connector.(connector.PasswordConnector) 1130 if !ok { 1131 s.tokenErrHelper(w, errInvalidRequest, "Requested password connector does not correct type.", http.StatusBadRequest) 1132 return 1133 } 1134 1135 // Login 1136 username := q.Get("username") 1137 password := q.Get("password") 1138 identity, ok, err := passwordConnector.Login(r.Context(), parseScopes(scopes), username, password) 1139 if err != nil { 1140 s.tokenErrHelper(w, errInvalidRequest, "Could not login user", http.StatusBadRequest) 1141 return 1142 } 1143 if !ok { 1144 s.tokenErrHelper(w, errAccessDenied, "Invalid username or password", http.StatusUnauthorized) 1145 return 1146 } 1147 1148 // Build the claims to send the id token 1149 claims := storage.Claims{ 1150 UserID: identity.UserID, 1151 Username: identity.Username, 1152 Name: identity.Name, 1153 Email: identity.Email, 1154 EmailVerified: identity.EmailVerified, 1155 Groups: identity.Groups, 1156 } 1157 1158 accessToken := storage.NewID() 1159 idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, nonce, accessToken, connID) 1160 if err != nil { 1161 s.tokenErrHelper(w, errServerError, fmt.Sprintf("failed to create ID token: %v", err), http.StatusInternalServerError) 1162 return 1163 } 1164 1165 reqRefresh := func() bool { 1166 // Ensure the connector supports refresh tokens. 1167 // 1168 // Connectors like `saml` do not implement RefreshConnector. 1169 _, ok := conn.Connector.(connector.RefreshConnector) 1170 if !ok { 1171 return false 1172 } 1173 1174 for _, scope := range scopes { 1175 if scope == scopeOfflineAccess { 1176 return true 1177 } 1178 } 1179 return false 1180 }() 1181 var refreshToken string 1182 if reqRefresh { 1183 refresh := storage.RefreshToken{ 1184 ID: storage.NewID(), 1185 Token: storage.NewID(), 1186 ClientID: client.ID, 1187 ConnectorID: connID, 1188 Scopes: scopes, 1189 Claims: claims, 1190 Nonce: nonce, 1191 // ConnectorData: authCode.ConnectorData, 1192 CreatedAt: s.now(), 1193 LastUsed: s.now(), 1194 } 1195 token := &internal.RefreshToken{ 1196 RefreshId: refresh.ID, 1197 Token: refresh.Token, 1198 } 1199 if refreshToken, err = internal.Marshal(token); err != nil { 1200 s.logger.Errorf("failed to marshal refresh token: %v", err) 1201 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1202 return 1203 } 1204 1205 if err := s.storage.CreateRefresh(refresh); err != nil { 1206 s.logger.Errorf("failed to create refresh token: %v", err) 1207 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1208 return 1209 } 1210 1211 // deleteToken determines if we need to delete the newly created refresh token 1212 // due to a failure in updating/creating the OfflineSession object for the 1213 // corresponding user. 1214 var deleteToken bool 1215 defer func() { 1216 if deleteToken { 1217 // Delete newly created refresh token from storage. 1218 if err := s.storage.DeleteRefresh(refresh.ID); err != nil { 1219 s.logger.Errorf("failed to delete refresh token: %v", err) 1220 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1221 return 1222 } 1223 } 1224 }() 1225 1226 tokenRef := storage.RefreshTokenRef{ 1227 ID: refresh.ID, 1228 ClientID: refresh.ClientID, 1229 CreatedAt: refresh.CreatedAt, 1230 LastUsed: refresh.LastUsed, 1231 } 1232 1233 // Try to retrieve an existing OfflineSession object for the corresponding user. 1234 if session, err := s.storage.GetOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID); err != nil { 1235 if err != storage.ErrNotFound { 1236 s.logger.Errorf("failed to get offline session: %v", err) 1237 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1238 deleteToken = true 1239 return 1240 } 1241 offlineSessions := storage.OfflineSessions{ 1242 UserID: refresh.Claims.UserID, 1243 ConnID: refresh.ConnectorID, 1244 Refresh: make(map[string]*storage.RefreshTokenRef), 1245 } 1246 offlineSessions.Refresh[tokenRef.ClientID] = &tokenRef 1247 1248 // Create a new OfflineSession object for the user and add a reference object for 1249 // the newly received refreshtoken. 1250 if err := s.storage.CreateOfflineSessions(offlineSessions); err != nil { 1251 s.logger.Errorf("failed to create offline session: %v", err) 1252 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1253 deleteToken = true 1254 return 1255 } 1256 } else { 1257 if oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok { 1258 // Delete old refresh token from storage. 1259 if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil { 1260 s.logger.Errorf("failed to delete refresh token: %v", err) 1261 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1262 deleteToken = true 1263 return 1264 } 1265 } 1266 1267 // Update existing OfflineSession obj with new RefreshTokenRef. 1268 if err := s.storage.UpdateOfflineSessions(session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { 1269 old.Refresh[tokenRef.ClientID] = &tokenRef 1270 return old, nil 1271 }); err != nil { 1272 s.logger.Errorf("failed to update offline session: %v", err) 1273 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1274 deleteToken = true 1275 return 1276 } 1277 1278 } 1279 } 1280 1281 s.writeAccessToken(w, idToken, accessToken, refreshToken, expiry) 1282} 1283 1284func (s *Server) writeAccessToken(w http.ResponseWriter, idToken, accessToken, refreshToken string, expiry time.Time) { 1285 // TODO(ericchiang): figure out an access token story and support the user info 1286 // endpoint. For now use a random value so no one depends on the access_token 1287 // holding a specific structure. 1288 resp := struct { 1289 AccessToken string `json:"access_token"` 1290 TokenType string `json:"token_type"` 1291 ExpiresIn int `json:"expires_in"` 1292 RefreshToken string `json:"refresh_token,omitempty"` 1293 IDToken string `json:"id_token"` 1294 }{ 1295 accessToken, 1296 "bearer", 1297 int(expiry.Sub(s.now()).Seconds()), 1298 refreshToken, 1299 idToken, 1300 } 1301 data, err := json.Marshal(resp) 1302 if err != nil { 1303 s.logger.Errorf("failed to marshal access token response: %v", err) 1304 s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) 1305 return 1306 } 1307 w.Header().Set("Content-Type", "application/json") 1308 w.Header().Set("Content-Length", strconv.Itoa(len(data))) 1309 w.Write(data) 1310} 1311 1312func (s *Server) renderError(w http.ResponseWriter, status int, description string) { 1313 if err := s.templates.err(w, status, description); err != nil { 1314 s.logger.Errorf("Server template error: %v", err) 1315 } 1316} 1317 1318func (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) { 1319 if err := tokenErr(w, typ, description, statusCode); err != nil { 1320 s.logger.Errorf("token error response: %v", err) 1321 } 1322} 1323 1324// Check for username prompt override from connector. Defaults to "Username". 1325func usernamePrompt(conn connector.PasswordConnector) string { 1326 if attr := conn.Prompt(); attr != "" { 1327 return attr 1328 } 1329 return "Username" 1330} 1331