handle request object

This commit is contained in:
Livio Amstutz 2021-10-25 11:53:59 +02:00
parent 72a9829117
commit f067d723f2
7 changed files with 196 additions and 36 deletions

View file

@ -59,27 +59,28 @@ const (
//AuthRequest according to:
//https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
type AuthRequest struct {
ID string
Scopes SpaceDelimitedArray `schema:"scope"`
ResponseType ResponseType `schema:"response_type"`
ClientID string `schema:"client_id"`
RedirectURI string `schema:"redirect_uri"` //TODO: type
Scopes SpaceDelimitedArray `json:"scope" schema:"scope"`
ResponseType ResponseType `json:"response_type" schema:"response_type"`
ClientID string `json:"client_id" schema:"client_id"`
RedirectURI string `json:"redirect_uri" schema:"redirect_uri"`
State string `schema:"state"`
State string `json:"state" schema:"state"`
Nonce string `json:"nonce" schema:"nonce"`
// ResponseMode TODO: ?
ResponseMode ResponseMode `json:"response_mode" schema:"response_mode"`
Display Display `json:"display" schema:"display"`
Prompt SpaceDelimitedArray `json:"prompt" schema:"prompt"`
MaxAge *uint `json:"max_age" schema:"max_age"`
UILocales Locales `json:"ui_locales" schema:"ui_locales"`
IDTokenHint string `json:"id_token_hint" schema:"id_token_hint"`
LoginHint string `json:"login_hint" schema:"login_hint"`
ACRValues []string `json:"acr_values" schema:"acr_values"`
Nonce string `schema:"nonce"`
Display Display `schema:"display"`
Prompt SpaceDelimitedArray `schema:"prompt"`
MaxAge *uint `schema:"max_age"`
UILocales Locales `schema:"ui_locales"`
IDTokenHint string `schema:"id_token_hint"`
LoginHint string `schema:"login_hint"`
ACRValues []string `schema:"acr_values"`
CodeChallenge string `json:"code_challenge" schema:"code_challenge"`
CodeChallengeMethod CodeChallengeMethod `json:"code_challenge_method" schema:"code_challenge_method"`
CodeChallenge string `schema:"code_challenge"`
CodeChallengeMethod CodeChallengeMethod `schema:"code_challenge_method"`
//RequestParam enables OIDC requests to be passed in a single, self-contained parameter (as JWT, called Request Object)
RequestParam string `schema:"request"`
}
//GetRedirectURI returns the redirect_uri value for the ErrAuthRequest interface

View file

@ -64,6 +64,7 @@ const (
ServerError errorType = "server_error"
InteractionRequired errorType = "interaction_required"
LoginRequired errorType = "login_required"
RequestNotSupported errorType = "request_not_supported"
)
var (
@ -118,6 +119,11 @@ var (
ErrorType: LoginRequired,
}
}
ErrRequestNotSupported = func() *Error {
return &Error{
ErrorType: RequestNotSupported,
}
}
)
// DefaultToServerError checks if the error is an Error

View file

@ -6,6 +6,7 @@ import (
"time"
"golang.org/x/text/language"
"gopkg.in/square/go-jose.v2"
)
type Audience []string
@ -106,3 +107,16 @@ func (t *Time) UnmarshalJSON(data []byte) error {
func (t *Time) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(*t).UTC().Unix())
}
type RequestObject struct {
Issuer string `json:"iss"`
Audience Audience `json:"aud"`
AuthRequest
}
func (r *RequestObject) GetIssuer() string {
return r.Issuer
}
func (r *RequestObject) SetSignatureAlgorithm(algorithm jose.SignatureAlgorithm) {
}

View file

@ -2,7 +2,6 @@ package op
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
@ -41,6 +40,7 @@ type Authorizer interface {
IDTokenHintVerifier() IDTokenHintVerifier
Crypto() Crypto
Issuer() string
RequestObjectSupported() bool
}
//AuthorizeValidator is an extension of Authorizer interface
@ -70,6 +70,13 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
AuthRequestError(w, r, authReq, err, authorizer.Encoder())
return
}
if authReq.RequestParam != "" && authorizer.RequestObjectSupported() {
authReq, err = ParseRequestObject(r.Context(), authReq, authorizer.Storage(), authorizer.Issuer())
if err != nil {
AuthRequestError(w, r, authReq, err, authorizer.Encoder())
return
}
}
validation := ValidateAuthRequest
if validater, ok := authorizer.(AuthorizeValidator); ok {
validation = validater.ValidateAuthRequest
@ -79,6 +86,10 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
AuthRequestError(w, r, authReq, err, authorizer.Encoder())
return
}
if authReq.RequestParam != "" {
AuthRequestError(w, r, authReq, oidc.ErrRequestNotSupported(), authorizer.Encoder())
return
}
req, err := authorizer.Storage().CreateAuthRequest(r.Context(), authReq, userID)
if err != nil {
AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer.Encoder())
@ -92,7 +103,7 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
RedirectToLogin(req.GetID(), client, w, r)
}
//ParseAuthorizeRequest parsed the http request into a oidc.AuthRequest
//ParseAuthorizeRequest parsed the http request into an oidc.AuthRequest
func ParseAuthorizeRequest(r *http.Request, decoder httphelper.Decoder) (*oidc.AuthRequest, error) {
err := r.ParseForm()
if err != nil {
@ -106,6 +117,83 @@ func ParseAuthorizeRequest(r *http.Request, decoder httphelper.Decoder) (*oidc.A
return authReq, nil
}
//ParseRequestObject parse the `request` parameter, validates the token including the signature
//and copies the token claims into the auth request
func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, issuer string) (*oidc.AuthRequest, error) {
requestObject := new(oidc.RequestObject)
payload, err := oidc.ParseToken(authReq.RequestParam, requestObject)
if err != nil {
return nil, err
}
if requestObject.ClientID != "" && requestObject.ClientID != authReq.ClientID {
return authReq, oidc.ErrInvalidRequest()
}
if requestObject.ResponseType != "" && requestObject.ResponseType != authReq.ResponseType {
return authReq, oidc.ErrInvalidRequest()
}
if requestObject.Issuer != requestObject.ClientID {
return authReq, oidc.ErrInvalidRequest()
}
if !str.Contains(requestObject.Audience, issuer) {
return authReq, oidc.ErrInvalidRequest()
}
keySet := &jwtProfileKeySet{storage, requestObject.Issuer}
if err = oidc.CheckSignature(ctx, authReq.RequestParam, payload, requestObject, nil, keySet); err != nil {
return authReq, err
}
CopyRequestObjectToAuthRequest(authReq, requestObject)
return authReq, nil
}
//CopyRequestObjectToAuthRequest overwrites present values from the Request Object into the auth request
//and clears the `RequestParam` of the auth request
func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oidc.RequestObject) {
if str.Contains(authReq.Scopes, oidc.ScopeOpenID) && len(requestObject.Scopes) > 0 {
authReq.Scopes = requestObject.Scopes
}
if requestObject.RedirectURI != "" {
authReq.RedirectURI = requestObject.RedirectURI
}
if requestObject.State != "" {
authReq.State = requestObject.State
}
if requestObject.ResponseMode != "" {
authReq.ResponseMode = requestObject.ResponseMode
}
if requestObject.Nonce != "" {
authReq.Nonce = requestObject.Nonce
}
if requestObject.Display != "" {
authReq.Display = requestObject.Display
}
if len(requestObject.Prompt) > 0 {
authReq.Prompt = requestObject.Prompt
}
if requestObject.MaxAge != nil {
authReq.MaxAge = requestObject.MaxAge
}
if len(requestObject.UILocales) > 0 {
authReq.UILocales = requestObject.UILocales
}
if requestObject.IDTokenHint != "" {
authReq.IDTokenHint = requestObject.IDTokenHint
}
if requestObject.LoginHint != "" {
authReq.LoginHint = requestObject.LoginHint
}
if len(requestObject.ACRValues) > 0 {
authReq.ACRValues = requestObject.ACRValues
}
if requestObject.CodeChallenge != "" {
authReq.CodeChallenge = requestObject.CodeChallenge
}
if requestObject.CodeChallengeMethod != "" {
authReq.CodeChallengeMethod = requestObject.CodeChallengeMethod
}
authReq.RequestParam = ""
}
//ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier IDTokenHintVerifier) (sub string, err error) {
authReq.MaxAge, err = ValidateAuthReqPrompt(authReq.Prompt, authReq.MaxAge)

View file

@ -22,9 +22,14 @@ type Configuration interface {
AuthMethodPostSupported() bool
CodeMethodS256Supported() bool
AuthMethodPrivateKeyJWTSupported() bool
TokenEndpointSigningAlgorithmsSupported() []string
GrantTypeRefreshTokenSupported() bool
GrantTypeTokenExchangeSupported() bool
GrantTypeJWTAuthorizationSupported() bool
IntrospectionAuthMethodPrivateKeyJWTSupported() bool
IntrospectionEndpointSigningAlgorithmsSupported() []string
RequestObjectSupported() bool
RequestObjectSigningAlgorithmsSupported() []string
SupportedUILocales() []language.Tag
}

View file

@ -19,23 +19,27 @@ func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) {
func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfiguration {
return &oidc.DiscoveryConfiguration{
Issuer: c.Issuer(),
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()),
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
ScopesSupported: Scopes(c),
ResponseTypesSupported: ResponseTypes(c),
GrantTypesSupported: GrantTypes(c),
SubjectTypesSupported: SubjectTypes(c),
IDTokenSigningAlgValuesSupported: SigAlgorithms(s),
TokenEndpointAuthMethodsSupported: AuthMethodsTokenEndpoint(c),
IntrospectionEndpointAuthMethodsSupported: AuthMethodsIntrospectionEndpoint(c),
ClaimsSupported: SupportedClaims(c),
CodeChallengeMethodsSupported: CodeChallengeMethods(c),
UILocalesSupported: c.SupportedUILocales(),
Issuer: c.Issuer(),
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()),
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
ScopesSupported: Scopes(c),
ResponseTypesSupported: ResponseTypes(c),
GrantTypesSupported: GrantTypes(c),
SubjectTypesSupported: SubjectTypes(c),
IDTokenSigningAlgValuesSupported: SigAlgorithms(s),
RequestObjectSigningAlgValuesSupported: RequestObjectSigAlgorithms(c),
TokenEndpointAuthMethodsSupported: AuthMethodsTokenEndpoint(c),
TokenEndpointAuthSigningAlgValuesSupported: TokenSigAlgorithms(c),
IntrospectionEndpointAuthSigningAlgValuesSupported: IntrospectionSigAlgorithms(c),
IntrospectionEndpointAuthMethodsSupported: AuthMethodsIntrospectionEndpoint(c),
ClaimsSupported: SupportedClaims(c),
CodeChallengeMethodsSupported: CodeChallengeMethods(c),
UILocalesSupported: c.SupportedUILocales(),
RequestParameterSupported: c.RequestObjectSupported(),
}
}
@ -128,6 +132,13 @@ func AuthMethodsTokenEndpoint(c Configuration) []oidc.AuthMethod {
return authMethods
}
func TokenSigAlgorithms(c Configuration) []string {
if !c.AuthMethodPrivateKeyJWTSupported() {
return nil
}
return c.TokenEndpointSigningAlgorithmsSupported()
}
func AuthMethodsIntrospectionEndpoint(c Configuration) []oidc.AuthMethod {
authMethods := []oidc.AuthMethod{
oidc.AuthMethodBasic,
@ -145,3 +156,17 @@ func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod {
}
return codeMethods
}
func IntrospectionSigAlgorithms(c Configuration) []string {
if !c.IntrospectionAuthMethodPrivateKeyJWTSupported() {
return nil
}
return c.IntrospectionEndpointSigningAlgorithmsSupported()
}
func RequestObjectSigAlgorithms(c Configuration) []string {
if !c.RequestObjectSupported() {
return nil
}
return c.RequestObjectSigningAlgorithmsSupported()
}

View file

@ -86,6 +86,7 @@ type Config struct {
CodeMethodS256 bool
AuthMethodPrivateKeyJWT bool
GrantTypeRefreshToken bool
RequestObjectSupported bool
SupportedUILocales []language.Tag
}
@ -191,6 +192,10 @@ func (o *openidProvider) AuthMethodPrivateKeyJWTSupported() bool {
return o.config.AuthMethodPrivateKeyJWT
}
func (o *openidProvider) TokenEndpointSigningAlgorithmsSupported() []string {
return []string{"RS256"}
}
func (o *openidProvider) GrantTypeRefreshTokenSupported() bool {
return o.config.GrantTypeRefreshToken
}
@ -203,6 +208,22 @@ func (o *openidProvider) GrantTypeJWTAuthorizationSupported() bool {
return true
}
func (o *openidProvider) IntrospectionAuthMethodPrivateKeyJWTSupported() bool {
return true
}
func (o *openidProvider) IntrospectionEndpointSigningAlgorithmsSupported() []string {
return []string{"RS256"}
}
func (o *openidProvider) RequestObjectSupported() bool {
return o.config.RequestObjectSupported
}
func (o *openidProvider) RequestObjectSigningAlgorithmsSupported() []string {
return []string{"RS256"}
}
func (o *openidProvider) SupportedUILocales() []language.Tag {
return o.config.SupportedUILocales
}