// validRedirectURL finds the first client for which the redirect URL is valid. If found it returns the client_id of the client. func validRedirectURL(clientManager *clientmanager.ClientManager, redirectURL url.URL, clientIDs []string) (string, error) { // Find the first client with a valid redirectURL. for _, clientID := range clientIDs { metadata, err := clientManager.Metadata(clientID) if err != nil { return "", mapError(err) } if _, err := client.ValidRedirectURL(&redirectURL, metadata.RedirectURIs); err == nil { return clientID, nil } } return "", ErrorInvalidRedirectURL }
func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (schema.UserCreateResponse, error) { log.Infof("userAPI: CreateUser") if !u.Authorize(creds) { return schema.UserCreateResponse{}, ErrorUnauthorized } hash, err := generateTempHash() if err != nil { return schema.UserCreateResponse{}, mapError(err) } metadata, err := u.clientIdentityRepo.Metadata(creds.ClientID) if err != nil { return schema.UserCreateResponse{}, mapError(err) } validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURLs) if err != nil { return schema.UserCreateResponse{}, ErrorInvalidRedirectURL } id, err := u.manager.CreateUser(schemaUserToUser(usr), user.Password(hash), u.localConnectorID) if err != nil { return schema.UserCreateResponse{}, mapError(err) } userUser, err := u.manager.Get(id) if err != nil { return schema.UserCreateResponse{}, mapError(err) } usr = userToSchemaUser(userUser) url, err := u.emailer.SendInviteEmail(usr.Email, validRedirURL, creds.ClientID) // An email is sent only if we don't get a link and there's no error. emailSent := err == nil && url == nil var resetLink string if url != nil { resetLink = url.String() } return schema.UserCreateResponse{ User: &usr, EmailSent: emailSent, ResetPasswordLink: resetLink, }, nil }
func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL url.URL) (schema.ResendEmailInvitationResponse, error) { log.Infof("userAPI: ResendEmailInvitation") if !u.Authorize(creds) { return schema.ResendEmailInvitationResponse{}, ErrorUnauthorized } metadata, err := u.clientIdentityRepo.Metadata(creds.ClientID) if err != nil { return schema.ResendEmailInvitationResponse{}, mapError(err) } validRedirURL, err := client.ValidRedirectURL(&redirURL, metadata.RedirectURIs) if err != nil { return schema.ResendEmailInvitationResponse{}, ErrorInvalidRedirectURL } // Retrieve user to check if it's already created userUser, err := u.manager.Get(userID) if err != nil { return schema.ResendEmailInvitationResponse{}, mapError(err) } // Check if email is verified if userUser.EmailVerified { return schema.ResendEmailInvitationResponse{}, ErrorVerifiedEmail } url, err := u.emailer.SendInviteEmail(userUser.Email, validRedirURL, creds.ClientID) // An email is sent only if we don't get a link and there's no error. emailSent := err == nil && url == nil // If email is not sent a reset link will be generated var resetLink string if url != nil { resetLink = url.String() } return schema.ResendEmailInvitationResponse{ EmailSent: emailSent, ResetPasswordLink: resetLink, }, nil }
func (h *SendResetPasswordEmailHandler) validateRedirectURL(clientID string, redirectURL string) (url.URL, bool) { parsed, err := url.Parse(redirectURL) if err != nil { log.Errorf("Error parsing redirectURL: %v", err) return url.URL{}, false } cm, err := h.cr.Metadata(clientID) if err != nil || cm == nil { log.Errorf("Error getting ClientMetadata: %v", err) return url.URL{}, false } validURL, err := client.ValidRedirectURL(parsed, cm.RedirectURIs) if err != nil { log.Errorf("Invalid redirectURL for clientID: redirectURL:%q, clientID:%q", redirectURL, clientID) return url.URL{}, false } return validURL, true }
// handleVerifyEmailResendFunc will resend an email-verification email given a valid JWT for the user and a redirect URL. // This handler is meant to be wrapped in clientTokenMiddleware, so a valid // bearer token for the client is expected to be present. // The user's JWT should be in the "token" parameter and the redirect URL should // be in the "redirect_uri" param. Note that this re func handleVerifyEmailResendFunc( issuerURL url.URL, srvKeysFunc func() ([]key.PublicKey, error), emailer *useremail.UserEmailer, userRepo user.UserRepo, clientIdentityRepo client.ClientIdentityRepo) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var params struct { Token string `json:"token"` RedirectURI string `json:"redirectURI"` } err := decoder.Decode(¶ms) if err != nil { writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "unable to parse body as JSON")) return } token := params.Token if token == "" { writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "missing valid JWT")) return } clientID, err := getClientIDFromAuthorizedRequest(r) if err != nil { log.Errorf("Failed to extract clientID: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorInvalidRequest, "cilent could not be extracted from bearer token.")) return } cm, err := clientIdentityRepo.Metadata(clientID) if err == client.ErrorNotFound { log.Errorf("No such client: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid client_id")) return } if err != nil { log.Errorf("Error getting ClientMetadata: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } noop := func() error { return nil } keysFunc := func() []key.PublicKey { keys, err := srvKeysFunc() if err != nil { log.Errorf("Error getting keys: %v", err) } return keys } jwt, err := jose.ParseJWT(token) if err != nil { log.Errorf("Failed to Parse JWT: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "token could not be parsed")) return } verifier := oidc.NewJWTVerifier(issuerURL.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { log.Errorf("Failed to Verify JWT: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorAccessDenied, "invalid token could not be verified")) return } claims, err := jwt.Claims() if err != nil { log.Errorf("Failed to extract claims from JWT: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid token could not be parsed")) return } sub, ok, err := claims.StringClaim("sub") if err != nil || !ok || sub == "" { log.Errorf("Failed to extract sub claim from JWT: err:%q ok:%v", err, ok) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "could not extract sub claim from token")) return } usr, err := userRepo.Get(nil, sub) if err != nil { if err == user.ErrorNotFound { log.Errorf("Failed to find user specified by token: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "could not find user")) return } log.Errorf("Failed to fetch user: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } if usr.EmailVerified { log.Errorf("User's email already verified") writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "email already verified")) return } aud, _, _ := claims.StringClaim("aud") if aud != clientID { log.Errorf("aud of token and sub of bearer token must match: %v", err) writeAPIError(w, http.StatusForbidden, newAPIError(errorAccessDenied, "JWT is from another client.")) return } redirectURLStr := params.RedirectURI if redirectURLStr == "" { log.Errorf("No redirect URL: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "must provide a redirect_uri")) return } redirectURL, err := url.Parse(redirectURLStr) if err != nil { log.Errorf("Unparsable URL: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return } *redirectURL, err = client.ValidRedirectURL(redirectURL, cm.RedirectURLs) if err != nil { switch err { case (client.ErrorInvalidRedirectURL): log.Errorf("Request provided unregistered redirect URL: %s", redirectURLStr) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return case (client.ErrorNoValidRedirectURLs): log.Errorf("There are no registered URLs for the requested client: %s", redirectURL) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return } } _, err = emailer.SendEmailVerification(usr.ID, clientID, *redirectURL) if err != nil { log.Errorf("Failed to send email verification email: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } writeResponseWithBody(w, http.StatusOK, struct{}{}) } }
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 } }