From b97d2b7f03c114c3812df4b4293282aabafc8894 Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Fri, 28 Feb 2025 11:51:07 +0000 Subject: [PATCH] improve OIDC Discovery and Tidy Up (#222) --- charts/identity/Chart.yaml | 4 +- pkg/handler/handler.go | 4 + pkg/oauth2/oauth2.go | 359 ++++++++------- pkg/oauth2/oidc/authorization_code.go | 12 +- pkg/oauth2/oidc/types.go | 6 + pkg/oauth2/providers/google/provider.go | 11 +- pkg/oauth2/providers/microsoft/provider.go | 4 +- pkg/openapi/schema.go | 512 +++++++++++---------- pkg/openapi/server.spec.yaml | 24 + pkg/openapi/types.go | 21 + 10 files changed, 533 insertions(+), 424 deletions(-) diff --git a/charts/identity/Chart.yaml b/charts/identity/Chart.yaml index 60031b38..42ab783e 100644 --- a/charts/identity/Chart.yaml +++ b/charts/identity/Chart.yaml @@ -4,8 +4,8 @@ description: A Helm chart for deploying Unikorn's IdP type: application -version: v0.2.59-rc1 -appVersion: v0.2.59-rc1 +version: v0.2.59-rc2 +appVersion: v0.2.59-rc2 icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index e9862249..cf678bae 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -120,6 +120,10 @@ func (h *Handler) GetWellKnownOpenidConfiguration(w http.ResponseWriter, r *http }, ResponseTypesSupported: []openapi.ResponseType{ openapi.ResponseTypeCode, + openapi.ResponseTypeIdToken, + }, + ResponseModesSupported: []openapi.ResponseMode{ + openapi.Query, }, TokenEndpointAuthMethodsSupported: []openapi.AuthMethod{ openapi.ClientSecretBasic, diff --git a/pkg/oauth2/oauth2.go b/pkg/oauth2/oauth2.go index ea3f97d5..7911eaaf 100644 --- a/pkg/oauth2/oauth2.go +++ b/pkg/oauth2/oauth2.go @@ -88,7 +88,7 @@ type Options struct { // CodeCacheSize is used to set the number of authorization code in flight. CodeCacheSize int - // Bool to indicate whether sign up is allowed + // Bool to indicate whether sign up is allowed. AuthenticateUnknownUsers bool } @@ -208,18 +208,39 @@ func htmlError(w http.ResponseWriter, r *http.Request, status int, description s } } -// authorizationError redirects to the client's callback URI with an error +// redirector wraps up error redirects. +type redirector struct { + w http.ResponseWriter + r *http.Request + redirectURI string + state string +} + +func newRedirector(w http.ResponseWriter, r *http.Request, redirectURI, state string) *redirector { + return &redirector{ + w: w, + r: r, + redirectURI: redirectURI, + state: state, + } +} + +func (e *redirector) redirect(values url.Values) { + http.Redirect(e.w, e.r, e.redirectURI+"?"+values.Encode(), http.StatusFound) +} + +// raise redirects to the client's callback URI with an error // code in the query. -func authorizationError(w http.ResponseWriter, r *http.Request, redirectURI string, kind Error, description string) { - values := &url.Values{} +func (e *redirector) raise(kind Error, description string) { + values := url.Values{} values.Set("error", string(kind)) values.Set("error_description", description) - if r.URL.Query().Has("state") { - values.Set("state", r.URL.Query().Get("state")) + if e.state != "" { + values.Set("state", e.state) } - http.Redirect(w, r, redirectURI+"?"+values.Encode(), http.StatusFound) + e.redirect(values) } // lookupClient returns the oauth2 client given its ID. @@ -370,42 +391,58 @@ func (a *Authenticator) authorizationValidateNonRedirecting(w http.ResponseWrite return client, true } +// getCodeChallengeMethod handles defaulting when a code challenge is provided. func getCodeChallengeMethod(query url.Values) openapi.CodeChallengeMethod { if query.Has("code_challenge_method") { return openapi.CodeChallengeMethod(query.Get("code_challenge_method")) } - // Default to "plain" return openapi.Plain } +var ( + //nolint:gochecknoglobals + allowedResponseTypes = []string{ + string(openapi.ResponseTypeCode), + } + + //nolint:gochecknoglobals + allowedResponseModes = []string{ + string(openapi.Query), + } + + //nolint:gochecknoglobals + allowedCodeChallengeMethods = []string{ + string(openapi.Plain), + string(openapi.S256), + } +) + // authorizationValidateRedirecting checks autohorization request parameters after // the redirect URI has been validated. If any of these fail, we redirect but with an // error query rather than a code for the client to pick up and run with. -func authorizationValidateRedirecting(w http.ResponseWriter, r *http.Request, query url.Values, client *unikornv1.OAuth2Client) bool { - codeChallengeMethod := getCodeChallengeMethod(query) - - var kind Error +func authorizationValidateRedirecting(redirector *redirector, query url.Values) bool { + if query.Has("request") { + redirector.raise(ErrorRequestNotSupported, "request object by value not supported") + return false + } - var description string + if !slices.Contains(allowedResponseTypes, query.Get("response_type")) { + redirector.raise(ErrorUnsupportedResponseType, "response_type must be one of "+strings.Join(allowedResponseTypes, ", ")) + return false + } - switch { - case query.Has("request"): - kind = ErrorRequestNotSupported - description = "request object by value not supported" - case query.Get("response_type") != "code": - kind = ErrorUnsupportedResponseType - description = "response_type must be 'code'" - case codeChallengeMethod != openapi.S256 && codeChallengeMethod != openapi.Plain: - kind = ErrorInvalidRequest - description = "code_challenge_method unsupported'" - default: - return true + if query.Has("response_mode") && !slices.Contains(allowedResponseModes, query.Get("response_mode")) { + redirector.raise(ErrorRequestNotSupported, "response_mode must be one of "+strings.Join(allowedResponseModes, ", ")) + return false } - authorizationError(w, r, client.Spec.RedirectURI, kind, description) + if !slices.Contains(allowedCodeChallengeMethods, string(getCodeChallengeMethod(query))) { + redirector.raise(ErrorInvalidRequest, "code_challenge_method must be one of "+strings.Join(allowedCodeChallengeMethods, ", ")) + return false + } - return false + return true } // encodeCodeChallengeS256 performs code verifier to code challenge translation @@ -446,7 +483,7 @@ func (a *Authenticator) getUser(ctx context.Context, id string) (*unikornv1.User } //nolint:cyclop -func (a *Authenticator) authorizationSilent(w http.ResponseWriter, r *http.Request, query url.Values, client *unikornv1.OAuth2Client) bool { +func (a *Authenticator) authorizationSilent(r *http.Request, redirector *redirector, query url.Values) bool { if !query.Has("max_age") && query.Get("prompt") != "none" { return false } @@ -518,7 +555,7 @@ func (a *Authenticator) authorizationSilent(w http.ResponseWriter, r *http.Reque return false } - q := &url.Values{} + q := url.Values{} q.Set("code", newCode) if query.Has("state") { @@ -527,11 +564,23 @@ func (a *Authenticator) authorizationSilent(w http.ResponseWriter, r *http.Reque a.codeCache.Add(newCode, nil, time.Minute) - http.Redirect(w, r, client.Spec.RedirectURI+"?"+q.Encode(), http.StatusFound) + redirector.redirect(q) return true } +func getAuthorizationQuery(r *http.Request) (url.Values, error) { + if r.Method == http.MethodGet { + return r.URL.Query(), nil + } + + if err := r.ParseForm(); err != nil { + return nil, err + } + + return r.Form, nil +} + // Authorization redirects the client to the OIDC autorization endpoint // to get an authorization code. Note that this function is responsible for // either returning an authorization grant or error via a HTTP 302 redirect, @@ -542,35 +591,38 @@ func (a *Authenticator) authorizationSilent(w http.ResponseWriter, r *http.Reque func (a *Authenticator) Authorization(w http.ResponseWriter, r *http.Request) { log := log.FromContext(r.Context()) - var query url.Values - - if r.Method == http.MethodGet { - query = r.URL.Query() - } else { - if err := r.ParseForm(); err != nil { - htmlError(w, r, http.StatusBadRequest, "failed to parse POST data") - - return - } - - query = r.Form + // Extract the client supplied parameters. + query, err := getAuthorizationQuery(r) + if err != nil { + htmlError(w, r, http.StatusBadRequest, "failed to get authorization query") } + // Get the client corresponding to the request, if this errors then we cannot + // trust the redirect URI and must render an error page. client, ok := a.authorizationValidateNonRedirecting(w, r, query) if !ok { return } - if !authorizationValidateRedirecting(w, r, query, client) { + redirector := newRedirector(w, r, client.Spec.RedirectURI, query.Get("state")) + + // Validate the other request parameters based on what we support, on error this + // returns control back to the client via the redirect. + if !authorizationValidateRedirecting(redirector, query) { return } - if a.authorizationSilent(w, r, query, client) { + // If 'max_age' is set and not zero, or 'prompt=none', then we may be able to silently + // authenticate the user with a browser cookie, instantly returning an authorization + // code to the client. + if a.authorizationSilent(r, redirector, query) { return } + // If that wasn't able to be handled and prompt=none, then we need to return an + // interaction_required error. if query.Get("prompt") == "none" { - authorizationError(w, r, client.Spec.RedirectURI, ErrorInteractionRequired, "login required but no prompt requested") + redirector.raise(ErrorInteractionRequired, "login required but no prompt requested") return } @@ -581,13 +633,13 @@ func (a *Authenticator) Authorization(w http.ResponseWriter, r *http.Request) { state, err := a.issuer.EncodeJWEToken(r.Context(), stateClaims, jose.TokenTypeLoginDialogState) if err != nil { - authorizationError(w, r, client.Spec.RedirectURI, ErrorServerError, "failed to encode request state") + redirector.raise(ErrorServerError, "failed to encode request state") return } supportedTypes, err := a.getProviderTypes(r.Context()) if err != nil { - authorizationError(w, r, client.Spec.RedirectURI, ErrorServerError, "failed to get oauth2 providers") + redirector.raise(ErrorServerError, "failed to get oauth2 providers") return } @@ -606,7 +658,7 @@ func (a *Authenticator) Authorization(w http.ResponseWriter, r *http.Request) { // Otherwise use the internal version. body, err := html.Login(loginQuery.Encode()) if err != nil { - authorizationError(w, r, client.Spec.RedirectURI, ErrorServerError, "failed to render login template") + redirector.raise(ErrorServerError, "failed to render login template") return } @@ -619,13 +671,80 @@ func (a *Authenticator) Authorization(w http.ResponseWriter, r *http.Request) { } } -// providerAuthenticationRequest takes a client provided email address and routes it -// to the correct identity provider, if we can. -func (a *Authenticator) providerAuthenticationRequest(w http.ResponseWriter, r *http.Request, client *unikornv1.OAuth2Client, provider *unikornv1.OAuth2Provider, query url.Values, email string) { +// Login handles the response from the user login prompt. +func (a *Authenticator) Login(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + htmlError(w, r, http.StatusBadRequest, "form parse failure") + return + } + + if !r.Form.Has("state") { + htmlError(w, r, http.StatusBadRequest, "state field missing") + return + } + + state := &LoginStateClaims{} + + if err := a.issuer.DecodeJWEToken(r.Context(), r.Form.Get("state"), state, jose.TokenTypeLoginDialogState); err != nil { + htmlError(w, r, http.StatusBadRequest, "login state failed to decode") + return + } + + query, err := url.ParseQuery(state.Query) + if err != nil { + htmlError(w, r, http.StatusBadRequest, "failed to parse query") + return + } + + redirector := newRedirector(w, r, query.Get("redirect_uri"), query.Get("state")) + + // Handle the case where the provider is explicitly specified. + if providerType := r.Form.Get("provider"); providerType != "" { + provider, err := a.lookupProviderByType(r.Context(), unikornv1.IdentityProviderType(providerType)) + if err != nil { + redirector.raise(ErrorServerError, err.Error()) + return + } + + a.providerAuthenticationRequest(w, r, redirector, provider, query, "") + + return + } + + // Otherwise we need to infer the provider. + email := r.Form.Get("email") + if email == "" { + redirector.raise(ErrorServerError, "email query parameter not specified") + return + } + + organization, err := a.lookupOrganization(r.Context(), email) + if err != nil { + redirector.raise(ErrorServerError, err.Error()) + return + } + + provider, err := a.lookupProviderByID(r.Context(), *organization.Spec.ProviderID, organization) + if err != nil { + redirector.raise(ErrorServerError, err.Error()) + return + } + + a.providerAuthenticationRequest(w, r, redirector, provider, query, email) +} + +// providerAuthenticationRequest kicks off the authorization flow with the backend +// provider. +func (a *Authenticator) providerAuthenticationRequest(w http.ResponseWriter, r *http.Request, redirector *redirector, provider *unikornv1.OAuth2Provider, query url.Values, email string) { + // Try infer the email address if one was not specified. + if email == "" && query.Has("login_hint") { + email = query.Get("login_hint") + } + // OIDC requires a nonce, just some random data base64 URL encoded will suffice. nonce, err := randomString(16) if err != nil { - authorizationError(w, r, client.Spec.RedirectURI, ErrorServerError, "unable to create oidc nonce: "+err.Error()) + redirector.raise(ErrorServerError, "unable to create oidc nonce: "+err.Error()) return } @@ -635,7 +754,7 @@ func (a *Authenticator) providerAuthenticationRequest(w http.ResponseWriter, r * // it's talking to the same client. codeVerifier, err := randomString(32) if err != nil { - authorizationError(w, r, client.Spec.RedirectURI, ErrorServerError, "unable to create oauth2 code verifier: "+err.Error()) + redirector.raise(ErrorServerError, "unable to create oauth2 code verifier: "+err.Error()) return } @@ -651,7 +770,7 @@ func (a *Authenticator) providerAuthenticationRequest(w http.ResponseWriter, r * state, err := a.issuer.EncodeJWEToken(r.Context(), oidcState, jose.TokenTypeLoginState) if err != nil { - authorizationError(w, r, client.Spec.RedirectURI, ErrorServerError, "failed to encode oidc state: "+err.Error()) + redirector.raise(ErrorServerError, "failed to encode oidc state: "+err.Error()) return } @@ -664,7 +783,7 @@ func (a *Authenticator) providerAuthenticationRequest(w http.ResponseWriter, r * config, err := driver.Config(r.Context(), configParameters) if err != nil { - authorizationError(w, r, client.Spec.RedirectURI, ErrorServerError, "unable to create oauth2 config: "+err.Error()) + redirector.raise(ErrorServerError, "unable to create oauth2 config: "+err.Error()) return } @@ -678,84 +797,13 @@ func (a *Authenticator) providerAuthenticationRequest(w http.ResponseWriter, r * url, err := driver.AuthorizationURL(config, parameters) if err != nil { - authorizationError(w, r, client.Spec.RedirectURI, ErrorServerError, "unable to create oauth2 redirect: "+err.Error()) + redirector.raise(ErrorServerError, "unable to create oauth2 redirect: "+err.Error()) return } http.Redirect(w, r, url, http.StatusFound) } -// Login handles the response from the user login prompt. -// -//nolint:cyclop -func (a *Authenticator) Login(w http.ResponseWriter, r *http.Request) { - log := log.FromContext(r.Context()) - - if err := r.ParseForm(); err != nil { - log.Error(err, "form parse failed") - return - } - - if !r.Form.Has("state") { - log.Info("state doesn't exist in form") - return - } - - state := &LoginStateClaims{} - - if err := a.issuer.DecodeJWEToken(r.Context(), r.Form.Get("state"), state, jose.TokenTypeLoginDialogState); err != nil { - htmlError(w, r, http.StatusBadRequest, "login state failed to decode") - return - } - - query, err := url.ParseQuery(state.Query) - if err != nil { - log.Error(err, "failed to parse query") - return - } - - client, err := a.lookupClient(r.Context(), query.Get("client_id")) - if err != nil { - htmlError(w, r, http.StatusBadRequest, "unable to lookup client") - return - } - - // Handle the case where the provider is explicitly specified. - if providerType := r.Form.Get("provider"); providerType != "" { - provider, err := a.lookupProviderByType(r.Context(), unikornv1.IdentityProviderType(providerType)) - if err != nil { - authorizationError(w, r, query.Get("redirect_uri"), ErrorServerError, err.Error()) - return - } - - a.providerAuthenticationRequest(w, r, client, provider, query, "") - - return - } - - // Otherwise we need to infer the provider. - email := r.Form.Get("email") - - if email == "" { - authorizationError(w, r, query.Get("redirect_uri"), ErrorServerError, "email query parameter not specified") - return - } - - organization, err := a.lookupOrganization(r.Context(), email) - if err != nil { - authorizationError(w, r, query.Get("redirect_uri"), ErrorServerError, err.Error()) - return - } - - provider, err := a.lookupProviderByID(r.Context(), *organization.Spec.ProviderID, organization) - if err != nil { - authorizationError(w, r, query.Get("redirect_uri"), ErrorServerError, err.Error()) - return - } - - a.providerAuthenticationRequest(w, r, client, provider, query, email) -} - // OIDCCallback is called by the authorization endpoint in order to return an // authorization back to us. We then exchange the code for an ID token, and // refresh token. Remember, as far as the client is concerned we're still doing @@ -786,21 +834,21 @@ func (a *Authenticator) Callback(w http.ResponseWriter, r *http.Request) { return } - redirectURI := clientQuery.Get("redirect_uri") + redirector := newRedirector(w, r, clientQuery.Get("redirect_uri"), clientQuery.Get("state")) if query.Has("error") { - authorizationError(w, r, redirectURI, Error(query.Get("error")), query.Get("description")) + redirector.raise(Error(query.Get("error")), query.Get("error_description")) return } if !query.Has("code") { - authorizationError(w, r, redirectURI, ErrorServerError, "oidc callback does not contain an authorization code") + redirector.raise(ErrorServerError, "oidc callback does not contain an authorization code") return } provider, err := a.lookupProviderByID(r.Context(), state.OAuth2Provider, nil) if err != nil { - authorizationError(w, r, redirectURI, ErrorServerError, "failed to get oauth2 provider") + redirector.raise(ErrorServerError, "failed to get oauth2 provider") return } @@ -815,42 +863,39 @@ func (a *Authenticator) Callback(w http.ResponseWriter, r *http.Request) { _, idToken, err := providers.New(provider.Spec.Type).CodeExchange(r.Context(), parameters) if err != nil { - authorizationError(w, r, redirectURI, ErrorServerError, "code exchange failed: "+err.Error()) + redirector.raise(ErrorServerError, "code exchange failed: "+err.Error()) return } + q := url.Values{} + + if clientQuery.Has("state") { + q.Set("state", clientQuery.Get("state")) + } + user, err := a.rbac.GetActiveUser(r.Context(), idToken.Email.Email) if err != nil && !a.options.AuthenticateUnknownUsers { - authorizationError(w, r, redirectURI, ErrorAccessDenied, "user does not exist or is inactive") + redirector.raise(ErrorAccessDenied, "user does not exist or is inactive") return } oauth2Code := &Code{ ID: uuid.New().String(), + UserID: user.Name, ClientQuery: state.ClientQuery, OAuth2Provider: state.OAuth2Provider, Interactive: true, IDToken: idToken, } - // TODO: this is an artefact of AuthenticateUnknownUsers, delete me! - if user != nil { - oauth2Code.UserID = user.Name - } - code, err := a.issuer.EncodeJWEToken(r.Context(), oauth2Code, jose.TokenTypeAuthorizationCode) if err != nil { - authorizationError(w, r, redirectURI, ErrorServerError, "failed to encode authorization code: "+err.Error()) + redirector.raise(ErrorServerError, "failed to encode authorization code: "+err.Error()) return } - q := &url.Values{} q.Set("code", code) - if clientQuery.Has("state") { - q.Set("state", clientQuery.Get("state")) - } - a.codeCache.Add(code, nil, time.Minute) // OIDC support silent re-authentication, the expectation is that the user will @@ -869,7 +914,7 @@ func (a *Authenticator) Callback(w http.ResponseWriter, r *http.Request) { w.Header().Add("Set-Cookie", cookie.String()) - http.Redirect(w, r, redirectURI+"?"+q.Encode(), http.StatusFound) + redirector.redirect(q) } // tokenValidate does any request validation when issuing a token. @@ -925,7 +970,7 @@ func oidcHash(value string) string { } // oidcIDToken builds an OIDC ID token. -func (a *Authenticator) oidcIDToken(r *http.Request, code *Code, query url.Values, expiry time.Duration, atHash string, lastAuthenticationTime time.Time) (*string, error) { +func (a *Authenticator) oidcIDToken(r *http.Request, idToken *oidc.IDToken, query url.Values, expiry time.Duration, atHash string, lastAuthenticationTime time.Time) (*string, error) { scope := strings.Split(query.Get("scope"), " ") //nolint:nilnil @@ -937,7 +982,7 @@ func (a *Authenticator) oidcIDToken(r *http.Request, code *Code, query url.Value Claims: jwt.Claims{ Issuer: "https://" + r.Host, // TODO: we should use the user ID. - Subject: code.IDToken.Email.Email, + Subject: idToken.Email.Email, Audience: []string{ query.Get("client_id"), }, @@ -945,29 +990,33 @@ func (a *Authenticator) oidcIDToken(r *http.Request, code *Code, query url.Value IssuedAt: jwt.NewNumericDate(time.Now()), }, Default: oidc.Default{ - Nonce: query.Get("nonce"), - ATHash: atHash, - AuthTime: ptr.To(lastAuthenticationTime.Unix()), + Nonce: query.Get("nonce"), + AuthTime: ptr.To(lastAuthenticationTime.Unix()), + AuthorizedParty: query.Get("client_id"), }, } + if atHash != "" { + claims.Default.ATHash = atHash + } + // NOTE: the scope here is intended to defined what happens when you call the // userinfo endpoint (and probably the "code id_token" grant type), but Google // etc. all do this, so why not... if slices.Contains(scope, "email") { - claims.Email = code.IDToken.Email + claims.Email = idToken.Email } if slices.Contains(scope, "profile") { - claims.Profile = code.IDToken.Profile + claims.Profile = idToken.Profile } - idToken, err := a.issuer.EncodeJWT(r.Context(), claims) + token, err := a.issuer.EncodeJWT(r.Context(), claims) if err != nil { return nil, err } - return &idToken, nil + return &token, nil } func (a *Authenticator) validateClientSecret(r *http.Request, query url.Values) error { @@ -1092,7 +1141,7 @@ func (a *Authenticator) TokenAuthorizationCode(w http.ResponseWriter, r *http.Re } // Handle OIDC. - idToken, err := a.oidcIDToken(r, code, clientQuery, a.options.AccessTokenDuration, oidcHash(tokens.AccessToken), tokens.LastAuthenticationTime) + idToken, err := a.oidcIDToken(r, code.IDToken, clientQuery, a.options.AccessTokenDuration, oidcHash(tokens.AccessToken), tokens.LastAuthenticationTime) if err != nil { return nil, err } diff --git a/pkg/oauth2/oidc/authorization_code.go b/pkg/oauth2/oidc/authorization_code.go index d6faaf1f..9eb0a21c 100644 --- a/pkg/oauth2/oidc/authorization_code.go +++ b/pkg/oauth2/oidc/authorization_code.go @@ -84,12 +84,16 @@ func Authorization(config *oauth2.Config, parameters *types.AuthorizationParamte requestParameters = append(requestParameters, oauth2.SetAuthURLParam("login_hint", parameters.Email)) } - if parameters.Query.Has("prompt") { - requestParameters = append(requestParameters, oauth2.SetAuthURLParam("prompt", parameters.Query.Get("prompt"))) + passThroughValues := []string{ + "prompt", + "max_age", + "acr_values", } - if parameters.Query.Has("max_age") { - requestParameters = append(requestParameters, oauth2.SetAuthURLParam("max_age", parameters.Query.Get("max_age"))) + for _, value := range passThroughValues { + if parameters.Query.Has(value) { + requestParameters = append(requestParameters, oauth2.SetAuthURLParam(value, parameters.Query.Get(value))) + } } return common.Authorization(config, parameters, requestParameters), nil diff --git a/pkg/oauth2/oidc/types.go b/pkg/oauth2/oidc/types.go index d24cca0b..41efdc9e 100644 --- a/pkg/oauth2/oidc/types.go +++ b/pkg/oauth2/oidc/types.go @@ -32,6 +32,12 @@ type Default struct { ATHash string `json:"at_hash,omitempty"` // AuthTime is when the token was issued if max_age was requested. AuthTime *int64 `json:"auth_time,omitempty"` + // AuthenticationContextClass represents the level of trust in this user. + // A value of "0" means it doesn't even meet ISO29115 level 1, so you have + // zero trust in this user. + AuthenticationContextClass string `json:"acr,omitempty"` + // AuthorizedParty is the authorized party aka the client ID. + AuthorizedParty string `json:"azp,omitempty"` } // Profile are claims that may be returned by requesting the diff --git a/pkg/oauth2/providers/google/provider.go b/pkg/oauth2/providers/google/provider.go index 56a977b1..4386bd7b 100644 --- a/pkg/oauth2/providers/google/provider.go +++ b/pkg/oauth2/providers/google/provider.go @@ -41,12 +41,11 @@ func (*Provider) AuthorizationURL(config *oauth2.Config, parameters *types.Autho // This grants us access to a refresh token. // See: https://developers.google.com/identity/openid-connect/openid-connect#access-type-param // And: https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token - requestParameters := []oauth2.AuthCodeOption{ - oauth2.SetAuthURLParam("prompt", "consent"), - oauth2.SetAuthURLParam("access_type", "offline"), - } - - return oidc.Authorization(config, parameters, requestParameters) + // requestParameters := []oauth2.AuthCodeOption{ + // oauth2.SetAuthURLParam("prompt", "consent"), + // oauth2.SetAuthURLParam("access_type", "offline"), + // } + return oidc.Authorization(config, parameters, nil) } func (*Provider) CodeExchange(ctx context.Context, parameters *types.CodeExchangeParameters) (*oauth2.Token, *oidc.IDToken, error) { diff --git a/pkg/oauth2/providers/microsoft/provider.go b/pkg/oauth2/providers/microsoft/provider.go index 5e2c22df..db958e9a 100644 --- a/pkg/oauth2/providers/microsoft/provider.go +++ b/pkg/oauth2/providers/microsoft/provider.go @@ -38,9 +38,9 @@ func (*Provider) Config(ctx context.Context, parameters *types.ConfigParameters) // Enables refresh tokens. // See https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow. - scopes := []string{"offline_access"} + // scopes := []string{"offline_access"} - _, config, err := oidc.Config(ctx, parameters, scopes) + _, config, err := oidc.Config(ctx, parameters, nil) return config, err } diff --git a/pkg/openapi/schema.go b/pkg/openapi/schema.go index e1d92b28..75c6edd7 100644 --- a/pkg/openapi/schema.go +++ b/pkg/openapi/schema.go @@ -19,261 +19,263 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "3x4lH9rNcW1yY/fblWWtJa9qk6ZeX43SL72XaX01Qs8tpoOSY9Cbswm+Kamx82A0Zy+dK13plU2llJ0o", + "KZnJeh3eFVFjcpNqtG+S9VUtU1/d0Iqhj7VSJVdr97O19lOmvnpK11pzBJ7rK7YucNeU1bta7mFZsLRe", + "VVeNrK7ddlcPRnfVT4111ahTJd2dPhj1mcLWgq/MfrqZVI0Omw/R7ppzdUVmD2ktrS2zWDXKqf5zc6wi", + "Pq9Z//llrN2Wlw+rsVE3Otn6pJKu39aW/V7VqE9u0v12Ldu41vT6qqk3ep10va1xv1hNdxGfn1EgCspO", + "lVS36O6D008VbKYHiv1FixTnU+d+eGWaWZKkplFcvq3G01bzMjdWJuVko3QPM+ihlbsqPRaWrZc+7Cam", + "VyVNttOqlusulEa23H2qPjbt/FR+y+ctNZWsFtvLbn7aUuvYSiQnZaNYdZ4buRGQU8n7dvMJ3+by1/nV", + "S73wMDdqreY4ffdYthtvmYeSajzdtFJAg9UlJbeFQt4wbKc9NzPDojUHPDVxaEE6/qfMkLZ6dyU3b+T0", + "S6rZVW+q3XqKpJrpJm5Ps8vmTXJaMwrmyx1J1nv1VQ0lLfXGbAJ50W52qlet9ktb05+yLb2Zg9fac02e", + "Ljudwo02zV4rd+Wadjtu1O+0dOtmDDrX3ZtusnwDDHlthnQK1pOcnarTbq+ZrKLuqpxtlLX75mQ876Sv", + "asCov/Un1Uy9d7Pqd8ZPjRs987x6uXpO11edVFJu3HRXfb1ZU67LbXXS7Ldk1i6z7KZMDLr9VPPW7LZu", + "tWpfTpIermY7y6RTLwXNkOqqmexngFxZ9qfNYXdVzLx0qxV1Un1uppqPtdvxomtknzsduwxumu1ur5DU", + "nvvp5k3WCpohWi9rglRhqaDkRLktJF9K2ZlqqDMVP1kAazI3URqVy/xzXpXHy5ZqDa4vL3K3I/sh01Kr", + "Vl7PkAW57MzANHH/TOq23bl+WhgvuDJVq9f5JxMMYLUxz7Umvbt0qVWY6NOXZmmU1i47yUs7och0lkgm", + "e47R0zuzy2aZXmaUGzC1Ch2YSrS62si5BsWHuxutMCrNHh7furkr4+kh3bJIuTfqOpc1iOSOjIgFczcJ", + "eJ8YKPalcduR5frzbXs2eqxN+7cv07n1nIdqNb8Ek4dE0k4k6snlqN28TcPrTgZP6zfVm3Imab9dFcal", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/openapi/server.spec.yaml b/pkg/openapi/server.spec.yaml index 32e0103b..3388d447 100644 --- a/pkg/openapi/server.spec.yaml +++ b/pkg/openapi/server.spec.yaml @@ -979,10 +979,14 @@ components: - scopes_supported - claims_supported - response_types_supported + - response_modes_supported - token_endpoint_auth_methods_supported - grant_types_supported - id_token_signing_alg_values_supported - code_challenge_methods_supported + - claims_parameter_supported + - request_parameter_supported + - request_uri_parameter_supported properties: issuer: description: The OpenID Issuer (iss field). @@ -1018,6 +1022,11 @@ components: type: array items: $ref: '#/components/schemas/responseType' + response_modes_supported: + description: A list of supported response modes. + type: array + items: + $ref: '#/components/schemas/responseMode' token_endpoint_auth_methods_supported: description: A list of supported authentication methods for the token endpoint. type: array @@ -1038,6 +1047,15 @@ components: type: array items: $ref: '#/components/schemas/codeChallengeMethod' + claims_parameter_supported: + description: Whether claims can be requested indvidually. + type: boolean + request_parameter_supported: + description: Whether requests can be passed as a JWT object. + type: boolean + request_uri_parameter_supported: + description: Whether requests can be passed via a URI. + type: boolean scope: description: Supported scopes. type: string @@ -1073,6 +1091,12 @@ components: - token id_token - code token id_token - none + responseMode: + description: Supported response modes. + type: string + enum: + - query + - fragment authMethod: description: Supported authentication methods. type: string diff --git a/pkg/openapi/types.go b/pkg/openapi/types.go index 92c970c9..1fe2abbe 100644 --- a/pkg/openapi/types.go +++ b/pkg/openapi/types.go @@ -76,6 +76,12 @@ const ( Organization ProviderScope = "organization" ) +// Defines values for ResponseMode. +const ( + Fragment ResponseMode = "fragment" + Query ResponseMode = "query" +) + // Defines values for ResponseType. const ( ResponseTypeCode ResponseType = "code" @@ -336,6 +342,9 @@ type OpenidConfiguration struct { // AuthorizationEndpoint The oauth2 endpoint that initiates authentication. AuthorizationEndpoint string `json:"authorization_endpoint"` + // ClaimsParameterSupported Whether claims can be requested indvidually. + ClaimsParameterSupported bool `json:"claims_parameter_supported"` + // ClaimsSupported A list of supported claims ClaimsSupported []Claim `json:"claims_supported"` @@ -354,6 +363,15 @@ type OpenidConfiguration struct { // JwksUri The oauth2 endpoint that exposes public signing keys for token validation. JwksUri string `json:"jwks_uri"` + // RequestParameterSupported Whether requests can be passed as a JWT object. + RequestParameterSupported bool `json:"request_parameter_supported"` + + // RequestUriParameterSupported Whether requests can be passed via a URI. + RequestUriParameterSupported bool `json:"request_uri_parameter_supported"` + + // ResponseModesSupported A list of supported response modes. + ResponseModesSupported []ResponseMode `json:"response_modes_supported"` + // ResponseTypesSupported A list of supported response types that can be requested for the authorization endpoint. ResponseTypesSupported []ResponseType `json:"response_types_supported"` @@ -529,6 +547,9 @@ type ResourceAllocation struct { // ResourceAllocationList A list of quotas. type ResourceAllocationList = []ResourceAllocation +// ResponseMode Supported response modes. +type ResponseMode string + // ResponseType Supported response types. type ResponseType string