func handleCallbackFunc(c *oidc.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { phttp.WriteError(w, http.StatusBadRequest, "code query param must be set") return } tok, err := c.ExchangeAuthCode(code) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify auth code with issuer: %v", err)) return } claims, err := tok.Claims() if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to construct claims: %v", err)) return } s := fmt.Sprintf(`<html><body><p>Token: %v</p><p>Claims: %v </p> <a href="/resend?jwt=%s">Resend Verification Email</a> </body></html>`, tok.Encode(), claims, tok.Encode()) w.Write([]byte(s)) } }
func handleOOBFunc(s *Server, tpl *template.Template) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { w.Header().Set("Allow", "GET") phttp.WriteError(w, http.StatusMethodNotAllowed, "GET only acceptable method") return } key := r.URL.Query().Get("code") if key == "" { phttp.WriteError(w, http.StatusBadRequest, "Invalid Session") return } sessionID, err := s.SessionManager.ExchangeKey(key) if err != nil { phttp.WriteError(w, http.StatusBadRequest, "Invalid Session") return } code, err := s.SessionManager.NewSessionKey(sessionID) if err != nil { log.Errorf("problem getting NewSessionKey: %v", err) phttp.WriteError(w, http.StatusInternalServerError, "Internal Server Error") return } execTemplate(w, tpl, map[string]string{ "code": code, }) } }
func execTemplateWithStatus(w http.ResponseWriter, tpl Template, data interface{}, status int) { w.WriteHeader(status) if err := tpl.Execute(w, data); err != nil { log.Errorf("Error loading page: %q", err) phttp.WriteError(w, http.StatusInternalServerError, "error loading page") return } }
func handleIndexFunc(oob bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { err := indexTemplate.Execute(w, map[string]interface{}{ "OOB": oob, }) if err != nil { phttp.WriteError(w, http.StatusInternalServerError, fmt.Sprintf("unable to execute template: %v", err)) } } }
func handleKeysFunc(km key.PrivateKeyManager, clock clockwork.Clock) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { w.Header().Set("Allow", "GET") phttp.WriteError(w, http.StatusMethodNotAllowed, "GET only acceptable method") return } jwks, err := km.JWKs() if err != nil { log.Errorf("Failed to get JWKs while serving HTTP request: %v", err) phttp.WriteError(w, http.StatusInternalServerError, "") return } keys := struct { Keys []jose.JWK `json:"keys"` }{ Keys: jwks, } b, err := json.Marshal(keys) if err != nil { log.Errorf("Unable to marshal signing key to JSON: %v", err) } exp := km.ExpiresAt() w.Header().Set("Expires", exp.Format(time.RFC1123)) ttl := int(exp.Sub(clock.Now()).Seconds()) w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", ttl)) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(b) } }
func handleDiscoveryFunc(cfg oidc.ProviderConfig) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { w.Header().Set("Allow", "GET") phttp.WriteError(w, http.StatusMethodNotAllowed, "GET only acceptable method") return } b, err := json.Marshal(cfg) if err != nil { log.Errorf("Unable to marshal %#v to JSON: %v", cfg, err) } w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(discoveryMaxAge.Seconds()))) w.Header().Set("Content-Type", "application/json") w.Write(b) } }
func handleLoginFunc(c *oidc.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("Could not parse request: %v", err)) } oac, err := c.OAuthClient() if err != nil { panic("unable to proceed") } u, err := url.Parse(oac.AuthCodeURL("", "", "")) if err != nil { panic("unable to proceed") } var scopes []string q := u.Query() if scope := q.Get("scope"); scope != "" { scopes = strings.Split(scope, " ") } if xClient := r.Form.Get("cross_client"); xClient != "" { xClients := strings.Split(xClient, ",") for _, x := range xClients { scopes = append(scopes, scope.ScopeGoogleCrossClient+x) } } if extraScopes := r.Form.Get("extra_scopes"); extraScopes != "" { scopes = append(scopes, strings.Split(extraScopes, ",")...) } if scopes != nil { q.Set("scope", strings.Join(scopes, " ")) u.RawQuery = q.Encode() } http.Redirect(w, r, u.String(), http.StatusFound) } }
func handleCallbackFunc(c *oidc.Client, claims *jose.Claims, refresh *string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { phttp.WriteError(w, http.StatusBadRequest, "code query param must be set") return } oac, err := c.OAuthClient() if err != nil { phttp.WriteError(w, http.StatusInternalServerError, fmt.Sprintf("unable to create oauth client: %v", err)) return } t, err := oac.RequestToken(oauth2.GrantTypeAuthCode, code) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify auth code with issuer: %v", err)) return } // Get id token and claims. tok, err := jose.ParseJWT(t.IDToken) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to parse id_token: %v", err)) return } if err := c.VerifyJWT(tok); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify the JWT: %v", err)) return } if *claims, err = tok.Claims(); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to construct claims: %v", err)) return } // Get refresh token. *refresh = t.RefreshToken w.WriteHeader(http.StatusOK) } }
func handleTokenFunc(srv OIDCServer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { w.Header().Set("Allow", "POST") phttp.WriteError(w, http.StatusMethodNotAllowed, fmt.Sprintf("POST only acceptable method")) return } err := r.ParseForm() if err != nil { log.Errorf("error parsing request: %v", err) writeTokenError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), "") return } state := r.PostForm.Get("state") user, password, ok := r.BasicAuth() if !ok { log.Errorf("error parsing basic auth") writeTokenError(w, oauth2.NewError(oauth2.ErrorInvalidClient), state) return } creds := oidc.ClientCredentials{ID: user, Secret: password} var jwt *jose.JWT var refreshToken string grantType := r.PostForm.Get("grant_type") switch grantType { case oauth2.GrantTypeAuthCode: code := r.PostForm.Get("code") if code == "" { log.Errorf("missing code param") writeTokenError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), state) return } jwt, refreshToken, err = srv.CodeToken(creds, code) if err != nil { log.Errorf("couldn't exchange code for token: %v", err) writeTokenError(w, err, state) return } case oauth2.GrantTypeClientCreds: jwt, err = srv.ClientCredsToken(creds) if err != nil { log.Errorf("couldn't creds for token: %v", err) writeTokenError(w, err, state) return } case oauth2.GrantTypeRefreshToken: token := r.PostForm.Get("refresh_token") if token == "" { writeTokenError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), state) return } jwt, err = srv.RefreshToken(creds, token) if err != nil { writeTokenError(w, err, state) return } default: log.Errorf("unsupported grant: %v", grantType) writeTokenError(w, oauth2.NewError(oauth2.ErrorUnsupportedGrantType), state) return } t := oAuth2Token{ AccessToken: jwt.Encode(), IDToken: jwt.Encode(), TokenType: "bearer", RefreshToken: refreshToken, } b, err := json.Marshal(t) if err != nil { log.Errorf("Failed marshaling %#v to JSON: %v", t, err) writeTokenError(w, oauth2.NewError(oauth2.ErrorServerError), state) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(b) } }
func handleAuthFunc(srv OIDCServer, idpcs []connector.Connector, tpl *template.Template, registrationEnabled bool) http.HandlerFunc { idx := makeConnectorMap(idpcs) return func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { w.Header().Set("Allow", "GET") phttp.WriteError(w, http.StatusMethodNotAllowed, "GET only acceptable method") return } q := r.URL.Query() register := q.Get("register") == "1" && registrationEnabled e := q.Get("error") if e != "" { sessionKey := q.Get("state") if err := srv.KillSession(sessionKey); err != nil { log.Errorf("Failed killing sessionKey %q: %v", sessionKey, err) } renderLoginPage(w, r, srv, idpcs, register, tpl) return } connectorID := q.Get("connector_id") idpc, ok := idx[connectorID] if !ok { renderLoginPage(w, r, srv, idpcs, register, tpl) return } acr, err := oauth2.ParseAuthCodeRequest(q) if err != nil { log.Errorf("Invalid auth request: %v", err) writeAuthError(w, err, acr.State) return } cm, err := srv.ClientMetadata(acr.ClientID) if err != nil { log.Errorf("Failed fetching client %q from repo: %v", acr.ClientID, err) writeAuthError(w, oauth2.NewError(oauth2.ErrorServerError), acr.State) return } if cm == nil { log.Errorf("Client %q not found", acr.ClientID) writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return } if len(cm.RedirectURLs) == 0 { log.Errorf("Client %q has no redirect URLs", acr.ClientID) writeAuthError(w, oauth2.NewError(oauth2.ErrorServerError), acr.State) return } redirectURL, err := client.ValidRedirectURL(acr.RedirectURL, cm.RedirectURLs) if err != nil { switch err { case (client.ErrorCantChooseRedirectURL): log.Errorf("Request must provide redirect URL as client %q has registered many", acr.ClientID) writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return case (client.ErrorInvalidRedirectURL): log.Errorf("Request provided unregistered redirect URL: %s", acr.RedirectURL) writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return case (client.ErrorNoValidRedirectURLs): log.Errorf("There are no registered URLs for the requested client: %s", acr.RedirectURL) writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return } } if acr.ResponseType != oauth2.ResponseTypeCode { log.Errorf("unexpected ResponseType: %v: ", acr.ResponseType) redirectAuthError(w, oauth2.NewError(oauth2.ErrorUnsupportedResponseType), acr.State, redirectURL) return } // Check scopes. var scopes []string foundOpenIDScope := false for _, scope := range acr.Scope { switch scope { case "openid": foundOpenIDScope = true scopes = append(scopes, scope) case "offline_access": // According to the spec, for offline_access scope, the client must // use a response_type value that would result in an Authorization Code. // Currently oauth2.ResponseTypeCode is the only supported response type, // and it's been checked above, so we don't need to check it again here. // // TODO(yifan): Verify that 'consent' should be in 'prompt'. scopes = append(scopes, scope) default: // Pass all other scopes. scopes = append(scopes, scope) } } if !foundOpenIDScope { log.Errorf("Invalid auth request: missing 'openid' in 'scope'") writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return } nonce := q.Get("nonce") key, err := srv.NewSession(connectorID, acr.ClientID, acr.State, redirectURL, nonce, register, acr.Scope) if err != nil { log.Errorf("Error creating new session: %v: ", err) redirectAuthError(w, err, acr.State, redirectURL) return } if register { _, ok := idpc.(*connector.LocalConnector) if ok { q := url.Values{} q.Set("code", key) ru := httpPathRegister + "?" + q.Encode() w.Header().Set("Location", ru) w.WriteHeader(http.StatusFound) return } } var p string if register { p = "select_account consent" } if shouldReprompt(r) || register { p = "select_account" } lu, err := idpc.LoginURL(key, p) if err != nil { log.Errorf("Connector.LoginURL failed: %v", err) redirectAuthError(w, err, acr.State, redirectURL) return } http.SetCookie(w, createLastSeenCookie()) w.Header().Set("Location", lu) w.WriteHeader(http.StatusFound) return } }
func renderLoginPage(w http.ResponseWriter, r *http.Request, srv OIDCServer, idpcs []connector.Connector, register bool, tpl *template.Template) { if tpl == nil { phttp.WriteError(w, http.StatusInternalServerError, "error loading login page") return } td := templateData{ Message: "Error", Instruction: "Please try again or contact the system administrator", Register: register, ShowEmailVerifiedMessage: consumeShowEmailVerifiedCookie(r, w), } // Render error if remote IdP connector errored and redirected here. q := r.URL.Query() e := q.Get("error") connectorID := q.Get("connector_id") if e != "" { td.Error = true td.Message = "Authentication Error" remoteMsg := q.Get("error_description") if remoteMsg == "" { remoteMsg = q.Get("error") } if connectorID == "" { td.Detail = remoteMsg } else { td.Detail = fmt.Sprintf("Error from %s: %s.", connectorID, remoteMsg) } execTemplate(w, tpl, td) return } if q.Get("msg_code") != "" { td.MsgCode = q.Get("msg_code") } // Render error message if client id is invalid. clientID := q.Get("client_id") cm, err := srv.ClientMetadata(clientID) if err != nil { log.Errorf("Failed fetching client %q from repo: %v", clientID, err) td.Error = true td.Message = "Server Error" execTemplate(w, tpl, td) return } if cm == nil { td.Error = true td.Message = "Authentication Error" td.Detail = "Invalid client ID" execTemplate(w, tpl, td) return } if len(idpcs) == 0 { td.Error = true td.Message = "Server Error" td.Instruction = "Unable to authenticate users at this time" td.Detail = "Authentication service may be misconfigured" execTemplate(w, tpl, td) return } link := *r.URL linkParams := link.Query() if !register { linkParams.Set("register", "1") } else { linkParams.Del("register") } linkParams.Del("msg_code") linkParams.Del("show_connectors") link.RawQuery = linkParams.Encode() td.RegisterOrLoginURL = link.String() var showConnectors map[string]struct{} // Only show the following connectors, if param is present if q.Get("show_connectors") != "" { conns := strings.Split(q.Get("show_connectors"), ",") if len(conns) != 0 { showConnectors = make(map[string]struct{}) for _, connID := range conns { showConnectors[connID] = struct{}{} } } } for _, idpc := range idpcs { id := idpc.ID() if showConnectors != nil { if _, ok := showConnectors[id]; !ok { continue } } var link Link link.ID = id displayName, ok := connectorDisplayNameMap[id] if !ok { displayName = id } link.DisplayName = displayName v := r.URL.Query() v.Set("connector_id", idpc.ID()) v.Set("response_type", "code") link.URL = httpPathAuth + "?" + v.Encode() td.Links = append(td.Links, link) } execTemplate(w, tpl, td) }
func handleLoginFunc(lf oidc.LoginFunc, tpl *template.Template, idp *LocalIdentityProvider, localErrorPath string, errorURL url.URL) http.HandlerFunc { handleGET := func(w http.ResponseWriter, r *http.Request, errMsg string) { q := r.URL.Query() sessionKey := q.Get("session_key") p := &Page{PostURL: r.URL.String(), Name: "Local", SessionKey: sessionKey} if errMsg != "" { p.Error = true p.Message = errMsg } if err := tpl.Execute(w, p); err != nil { phttp.WriteError(w, http.StatusInternalServerError, err.Error()) } } handlePOST := func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { msg := fmt.Sprintf("unable to parse form from body: %v", err) phttp.WriteError(w, http.StatusBadRequest, msg) return } userid := r.PostForm.Get("userid") if userid == "" { handleGET(w, r, "missing email address") return } password := r.PostForm.Get("password") if password == "" { handleGET(w, r, "missing password") return } ident, err := idp.Identity(userid, password) log.Errorf("IDENTITY: err: %v", err) if ident == nil || err != nil { handleGET(w, r, "invalid login") return } q := r.URL.Query() sessionKey := r.FormValue("session_key") if sessionKey == "" { q.Set("error", oauth2.ErrorInvalidRequest) q.Set("error_description", "missing session_key") redirectPostError(w, errorURL, q) return } redirectURL, err := lf(*ident, sessionKey) if err != nil { log.Errorf("Unable to log in %#v: %v", *ident, err) q.Set("error", oauth2.ErrorAccessDenied) q.Set("error_description", "login failed") redirectPostError(w, errorURL, q) return } w.Header().Set("Location", redirectURL) w.WriteHeader(http.StatusFound) } return func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "POST": handlePOST(w, r) case "GET": handleGET(w, r, "") default: w.Header().Set("Allow", "GET, POST") phttp.WriteError(w, http.StatusMethodNotAllowed, "GET and POST only acceptable methods") } } }
func handleCallbackFunc(c *oidc.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { refreshToken := r.URL.Query().Get("refresh_token") code := r.URL.Query().Get("code") oac, err := c.OAuthClient() if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to create OAuth2 client: %v", err)) return } var token oauth2.TokenResponse switch { case code != "": if token, err = oac.RequestToken(oauth2.GrantTypeAuthCode, code); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify auth code with issuer: %v", err)) return } case refreshToken != "": if token, err = oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to refresh token: %v", err)) return } if token.RefreshToken == "" { token.RefreshToken = refreshToken } default: phttp.WriteError(w, http.StatusBadRequest, "code query param must be set") return } tok, err := jose.ParseJWT(token.IDToken) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to parse JWT: %v", err)) return } claims := new(bytes.Buffer) if err := json.Indent(claims, tok.Payload, "", " "); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to construct claims: %v", err)) return } s := fmt.Sprintf(` <html> <head> <style> /* make pre wrap */ pre { white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } </style> </head> <body> <p> Token: <pre><code>%v</code></pre></p> <p> Claims: <pre><code>%v</code></pre></p> <p> Refresh Token: <pre><code>%v</code></pre></p> <p><a href="%s?refresh_token=%s">Redeem refresh token</a><p> <p><a href="/resend?jwt=%s">Resend Verification Email</a></p> </body> </html>`, tok.Encode(), claims.String(), token.RefreshToken, r.URL.Path, token.RefreshToken, tok.Encode()) w.Write([]byte(s)) } }
func handleAuthFunc(srv OIDCServer, baseURL url.URL, idpcs []connector.Connector, tpl *template.Template, registrationEnabled bool) http.HandlerFunc { idx := makeConnectorMap(idpcs) return func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { w.Header().Set("Allow", "GET") phttp.WriteError(w, http.StatusMethodNotAllowed, "GET only acceptable method") return } q := r.URL.Query() register := q.Get("register") == "1" && registrationEnabled e := q.Get("error") if e != "" { sessionKey := q.Get("state") if err := srv.KillSession(sessionKey); err != nil { log.Errorf("Failed killing sessionKey %q: %v", sessionKey, err) } renderLoginPage(w, r, srv, idpcs, register, tpl) return } connectorID := q.Get("connector_id") idpc, ok := idx[connectorID] if !ok { renderLoginPage(w, r, srv, idpcs, register, tpl) return } acr, err := oauth2.ParseAuthCodeRequest(q) if err != nil { log.Errorf("Invalid auth request: %v", err) writeAuthError(w, err, acr.State) return } cli, err := srv.Client(acr.ClientID) if err != nil { log.Errorf("Failed fetching client %q from repo: %v", acr.ClientID, err) writeAuthError(w, oauth2.NewError(oauth2.ErrorServerError), acr.State) return } if err == client.ErrorNotFound { log.Errorf("Client %q not found", acr.ClientID) writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return } redirectURL, err := cli.ValidRedirectURL(acr.RedirectURL) if err != nil { switch err { case (client.ErrorCantChooseRedirectURL): log.Errorf("Request must provide redirect URL as client %q has registered many", acr.ClientID) writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return case (client.ErrorInvalidRedirectURL): log.Errorf("Request provided unregistered redirect URL: %s", acr.RedirectURL) writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return case (client.ErrorNoValidRedirectURLs): log.Errorf("There are no registered URLs for the requested client: %s", acr.RedirectURL) writeAuthError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), acr.State) return } } if acr.ResponseType != oauth2.ResponseTypeCode { log.Errorf("unexpected ResponseType: %v: ", acr.ResponseType) redirectAuthError(w, oauth2.NewError(oauth2.ErrorUnsupportedResponseType), acr.State, redirectURL) return } // Check scopes. if scopeErr := validateScopes(srv, acr.ClientID, acr.Scope); scopeErr != nil { log.Error(scopeErr) writeAuthError(w, scopeErr, acr.State) return } nonce := q.Get("nonce") key, err := srv.NewSession(connectorID, acr.ClientID, acr.State, redirectURL, nonce, register, acr.Scope) if err != nil { log.Errorf("Error creating new session: %v: ", err) redirectAuthError(w, err, acr.State, redirectURL) return } if register { _, ok := idpc.(*connector.LocalConnector) if ok { q := url.Values{} q.Set("code", key) ru := path.Join(baseURL.Path, httpPathRegister) + "?" + q.Encode() w.Header().Set("Location", ru) w.WriteHeader(http.StatusFound) return } } var p string if register { p = "select_account consent" } if shouldReprompt(r) || register { p = "select_account" } lu, err := idpc.LoginURL(key, p) if err != nil { log.Errorf("Connector.LoginURL failed: %v", err) redirectAuthError(w, err, acr.State, redirectURL) return } http.SetCookie(w, createLastSeenCookie()) w.Header().Set("Location", lu) w.WriteHeader(http.StatusFound) return } }