diff --git a/pkg/op/config.go b/pkg/op/config.go index b3a1a2a..527e134 100644 --- a/pkg/op/config.go +++ b/pkg/op/config.go @@ -29,6 +29,8 @@ type Configuration interface { GrantTypeJWTAuthorizationSupported() bool IntrospectionAuthMethodPrivateKeyJWTSupported() bool IntrospectionEndpointSigningAlgorithmsSupported() []string + RevocationAuthMethodPrivateKeyJWTSupported() bool + RevocationEndpointSigningAlgorithmsSupported() []string RequestObjectSupported() bool RequestObjectSigningAlgorithmsSupported() []string diff --git a/pkg/op/discovery.go b/pkg/op/discovery.go index f897735..955d0fa 100644 --- a/pkg/op/discovery.go +++ b/pkg/op/discovery.go @@ -37,10 +37,12 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati TokenEndpointAuthSigningAlgValuesSupported: TokenSigAlgorithms(c), IntrospectionEndpointAuthSigningAlgValuesSupported: IntrospectionSigAlgorithms(c), IntrospectionEndpointAuthMethodsSupported: AuthMethodsIntrospectionEndpoint(c), - ClaimsSupported: SupportedClaims(c), - CodeChallengeMethodsSupported: CodeChallengeMethods(c), - UILocalesSupported: c.SupportedUILocales(), - RequestParameterSupported: c.RequestObjectSupported(), + RevocationEndpointAuthSigningAlgValuesSupported: RevocationSigAlgorithms(c), + RevocationEndpointAuthMethodsSupported: AuthMethodsRevocationEndpoint(c), + ClaimsSupported: SupportedClaims(c), + CodeChallengeMethodsSupported: CodeChallengeMethods(c), + UILocalesSupported: c.SupportedUILocales(), + RequestParameterSupported: c.RequestObjectSupported(), } } @@ -150,6 +152,20 @@ func AuthMethodsIntrospectionEndpoint(c Configuration) []oidc.AuthMethod { return authMethods } +func AuthMethodsRevocationEndpoint(c Configuration) []oidc.AuthMethod { + authMethods := []oidc.AuthMethod{ + oidc.AuthMethodNone, + oidc.AuthMethodBasic, + } + if c.AuthMethodPostSupported() { + authMethods = append(authMethods, oidc.AuthMethodPost) + } + if c.AuthMethodPrivateKeyJWTSupported() { + authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT) + } + return authMethods +} + func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod { codeMethods := make([]oidc.CodeChallengeMethod, 0, 1) if c.CodeMethodS256Supported() { @@ -165,6 +181,13 @@ func IntrospectionSigAlgorithms(c Configuration) []string { return c.IntrospectionEndpointSigningAlgorithmsSupported() } +func RevocationSigAlgorithms(c Configuration) []string { + if !c.RevocationAuthMethodPrivateKeyJWTSupported() { + return nil + } + return c.RevocationEndpointSigningAlgorithmsSupported() +} + func RequestObjectSigAlgorithms(c Configuration) []string { if !c.RequestObjectSupported() { return nil diff --git a/pkg/op/op.go b/pkg/op/op.go index c2ba032..553c67e 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -23,6 +23,7 @@ const ( defaultTokenEndpoint = "oauth/token" defaultIntrospectEndpoint = "oauth/introspect" defaultUserinfoEndpoint = "userinfo" + defaultRevocationEndpoint = "revoke" defaultEndSessionEndpoint = "end_session" defaultKeysEndpoint = "keys" ) @@ -33,6 +34,7 @@ var ( Token: NewEndpoint(defaultTokenEndpoint), Introspection: NewEndpoint(defaultIntrospectEndpoint), Userinfo: NewEndpoint(defaultUserinfoEndpoint), + Revocation: NewEndpoint(defaultRevocationEndpoint), EndSession: NewEndpoint(defaultEndSessionEndpoint), JwksURI: NewEndpoint(defaultKeysEndpoint), } @@ -222,6 +224,14 @@ func (o *openidProvider) IntrospectionEndpointSigningAlgorithmsSupported() []str return []string{"RS256"} } +func (o *openidProvider) RevocationAuthMethodPrivateKeyJWTSupported() bool { + return true +} + +func (o *openidProvider) RevocationEndpointSigningAlgorithmsSupported() []string { + return []string{"RS256"} +} + func (o *openidProvider) RequestObjectSupported() bool { return o.config.RequestObjectSupported } diff --git a/pkg/op/token_revocation.go b/pkg/op/token_revocation.go index 1eb065a..0be7812 100644 --- a/pkg/op/token_revocation.go +++ b/pkg/op/token_revocation.go @@ -2,7 +2,6 @@ package op import ( "context" - "errors" "net/http" "net/url" "strings" @@ -16,6 +15,8 @@ type Revoker interface { Crypto() Crypto Storage() Storage AccessTokenVerifier() AccessTokenVerifier + AuthMethodPrivateKeyJWTSupported() bool + AuthMethodPostSupported() bool } type RevokerJWTProfile interface { @@ -51,17 +52,23 @@ func Revoke(w http.ResponseWriter, r *http.Request, revoker Revoker) { func ParseTokenRevocationRequest(r *http.Request, revoker Revoker) (token, tokenTypeHint, clientID string, err error) { err = r.ParseForm() if err != nil { - return "", "", "", errors.New("unable to parse request") + return "", "", "", oidc.ErrInvalidRequest().WithDescription("unable to parse request").WithParent(err) } req := new(struct { oidc.RevocationRequest - oidc.ClientAssertionParams + oidc.ClientAssertionParams //for auth_method private_key_jwt + ClientID string `schema:"client_id"` //for auth_method none and post + ClientSecret string `schema:"client_secret"` //for auth_method post }) err = revoker.Decoder().Decode(req, r.Form) if err != nil { - return "", "", "", errors.New("unable to parse request") + return "", "", "", oidc.ErrInvalidRequest().WithDescription("error decoding form").WithParent(err) } - if revokerJWTProfile, ok := revoker.(RevokerJWTProfile); ok && req.ClientAssertion != "" { + if req.ClientAssertionType == oidc.ClientAssertionTypeJWTAssertion { + revokerJWTProfile, ok := revoker.(RevokerJWTProfile) + if !ok || !revoker.AuthMethodPrivateKeyJWTSupported() { + return "", "", "", oidc.ErrInvalidClient().WithDescription("auth_method private_key_jwt not supported") + } profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, revokerJWTProfile.JWTProfileVerifier()) if err == nil { return req.Token, req.TokenTypeHint, profile.Issuer, nil @@ -72,18 +79,37 @@ func ParseTokenRevocationRequest(r *http.Request, revoker Revoker) (token, token if ok { clientID, err = url.QueryUnescape(clientID) if err != nil { - return "", "", "", errors.New("invalid basic auth header") + return "", "", "", oidc.ErrInvalidClient().WithDescription("invalid basic auth header").WithParent(err) } clientSecret, err = url.QueryUnescape(clientSecret) if err != nil { - return "", "", "", errors.New("invalid basic auth header") + return "", "", "", oidc.ErrInvalidClient().WithDescription("invalid basic auth header").WithParent(err) } - if err := revoker.Storage().AuthorizeClientIDSecret(r.Context(), clientID, clientSecret); err != nil { + if err = AuthorizeClientIDSecret(r.Context(), clientID, clientSecret, revoker.Storage()); err != nil { return "", "", "", err } return req.Token, req.TokenTypeHint, clientID, nil } - return "", "", "", errors.New("invalid authorization") + if req.ClientID == "" { + return "", "", "", oidc.ErrInvalidClient().WithDescription("invalid authorization") + } + client, err := revoker.Storage().GetClientByClientID(r.Context(), req.ClientID) + if err != nil { + return "", "", "", oidc.ErrInvalidClient().WithParent(err) + } + if req.ClientSecret == "" { + if client.AuthMethod() != oidc.AuthMethodNone { + return "", "", "", oidc.ErrInvalidClient().WithDescription("invalid authorization") + } + return req.Token, req.TokenTypeHint, req.ClientID, nil + } + if client.AuthMethod() == oidc.AuthMethodPost && !revoker.AuthMethodPostSupported() { + return "", "", "", oidc.ErrInvalidClient().WithDescription("auth_method post not supported") + } + if err = AuthorizeClientIDSecret(r.Context(), req.ClientID, req.ClientSecret, revoker.Storage()); err != nil { + return "", "", "", err + } + return req.Token, req.TokenTypeHint, req.ClientID, nil } func RevocationRequestError(w http.ResponseWriter, r *http.Request, err error) {