begin revocation
This commit is contained in:
parent
cef977adc2
commit
9721c25336
6 changed files with 144 additions and 2 deletions
6
pkg/oidc/revocation.go
Normal file
6
pkg/oidc/revocation.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
type RevocationRequest struct {
|
||||||
|
Token string `schema:"token"`
|
||||||
|
TokenTypeHint string `schema:"token_type_hint"`
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ type Configuration interface {
|
||||||
TokenEndpoint() Endpoint
|
TokenEndpoint() Endpoint
|
||||||
IntrospectionEndpoint() Endpoint
|
IntrospectionEndpoint() Endpoint
|
||||||
UserinfoEndpoint() Endpoint
|
UserinfoEndpoint() Endpoint
|
||||||
|
RevocationEndpoint() Endpoint
|
||||||
EndSessionEndpoint() Endpoint
|
EndSessionEndpoint() Endpoint
|
||||||
KeysEndpoint() Endpoint
|
KeysEndpoint() Endpoint
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati
|
||||||
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
|
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
|
||||||
IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()),
|
IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()),
|
||||||
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
|
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
|
||||||
|
RevocationEndpoint: c.RevocationEndpoint().Absolute(c.Issuer()),
|
||||||
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
|
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
|
||||||
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
|
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
|
||||||
ScopesSupported: Scopes(c),
|
ScopesSupported: Scopes(c),
|
||||||
|
|
19
pkg/op/op.go
19
pkg/op/op.go
|
@ -74,6 +74,7 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router
|
||||||
router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o)))
|
router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o)))
|
||||||
router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o))
|
router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o))
|
||||||
router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o))
|
router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o))
|
||||||
|
router.HandleFunc(o.RevocationEndpoint().Relative(), revocationHandler(o))
|
||||||
router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o)))
|
router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o)))
|
||||||
router.HandleFunc(o.KeysEndpoint().Relative(), keysHandler(o.Storage()))
|
router.HandleFunc(o.KeysEndpoint().Relative(), keysHandler(o.Storage()))
|
||||||
return router
|
return router
|
||||||
|
@ -95,6 +96,7 @@ type endpoints struct {
|
||||||
Token Endpoint
|
Token Endpoint
|
||||||
Introspection Endpoint
|
Introspection Endpoint
|
||||||
Userinfo Endpoint
|
Userinfo Endpoint
|
||||||
|
Revocation Endpoint
|
||||||
EndSession Endpoint
|
EndSession Endpoint
|
||||||
CheckSessionIframe Endpoint
|
CheckSessionIframe Endpoint
|
||||||
JwksURI Endpoint
|
JwksURI Endpoint
|
||||||
|
@ -172,6 +174,10 @@ func (o *openidProvider) UserinfoEndpoint() Endpoint {
|
||||||
return o.endpoints.Userinfo
|
return o.endpoints.Userinfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *openidProvider) RevocationEndpoint() Endpoint {
|
||||||
|
return o.endpoints.Revocation
|
||||||
|
}
|
||||||
|
|
||||||
func (o *openidProvider) EndSessionEndpoint() Endpoint {
|
func (o *openidProvider) EndSessionEndpoint() Endpoint {
|
||||||
return o.endpoints.EndSession
|
return o.endpoints.EndSession
|
||||||
}
|
}
|
||||||
|
@ -352,6 +358,16 @@ func WithCustomUserinfoEndpoint(endpoint Endpoint) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithCustomRevocationEndpoint(endpoint Endpoint) Option {
|
||||||
|
return func(o *openidProvider) error {
|
||||||
|
if err := endpoint.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.endpoints.Revocation = endpoint
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithCustomEndSessionEndpoint(endpoint Endpoint) Option {
|
func WithCustomEndSessionEndpoint(endpoint Endpoint) Option {
|
||||||
return func(o *openidProvider) error {
|
return func(o *openidProvider) error {
|
||||||
if err := endpoint.Validate(); err != nil {
|
if err := endpoint.Validate(); err != nil {
|
||||||
|
@ -372,11 +388,12 @@ func WithCustomKeysEndpoint(endpoint Endpoint) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithCustomEndpoints(auth, token, userInfo, endSession, keys Endpoint) Option {
|
func WithCustomEndpoints(auth, token, userInfo, revocation, endSession, keys Endpoint) Option {
|
||||||
return func(o *openidProvider) error {
|
return func(o *openidProvider) error {
|
||||||
o.endpoints.Authorization = auth
|
o.endpoints.Authorization = auth
|
||||||
o.endpoints.Token = token
|
o.endpoints.Token = token
|
||||||
o.endpoints.Userinfo = userInfo
|
o.endpoints.Userinfo = userInfo
|
||||||
|
o.endpoints.Revocation = revocation
|
||||||
o.endpoints.EndSession = endSession
|
o.endpoints.EndSession = endSession
|
||||||
o.endpoints.JwksURI = keys
|
o.endpoints.JwksURI = keys
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -20,7 +20,8 @@ type AuthStorage interface {
|
||||||
CreateAccessAndRefreshTokens(ctx context.Context, request TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshToken string, expiration time.Time, err error)
|
CreateAccessAndRefreshTokens(ctx context.Context, request TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshToken string, expiration time.Time, err error)
|
||||||
TokenRequestByRefreshToken(ctx context.Context, refreshToken string) (RefreshTokenRequest, error)
|
TokenRequestByRefreshToken(ctx context.Context, refreshToken string) (RefreshTokenRequest, error)
|
||||||
|
|
||||||
TerminateSession(context.Context, string, string) error
|
TerminateSession(ctx context.Context, userID string, clientID string) error
|
||||||
|
RevokeToken(ctx context.Context, token string, userID string, clientID string) *oidc.Error
|
||||||
|
|
||||||
GetSigningKey(context.Context, chan<- jose.SigningKey)
|
GetSigningKey(context.Context, chan<- jose.SigningKey)
|
||||||
GetKeySet(context.Context) (*jose.JSONWebKeySet, error)
|
GetKeySet(context.Context) (*jose.JSONWebKeySet, error)
|
||||||
|
|
116
pkg/op/token_revocation.go
Normal file
116
pkg/op/token_revocation.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package op
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
httphelper "github.com/caos/oidc/pkg/http"
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Revoker interface {
|
||||||
|
Decoder() httphelper.Decoder
|
||||||
|
Crypto() Crypto
|
||||||
|
Storage() Storage
|
||||||
|
AccessTokenVerifier() AccessTokenVerifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type RevokerJWTProfile interface {
|
||||||
|
Revoker
|
||||||
|
JWTProfileVerifier() JWTProfileVerifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func revocationHandler(revoker Revoker) func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
Revoke(w, r, revoker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Revoke(w http.ResponseWriter, r *http.Request, revoker Revoker) {
|
||||||
|
token, _, clientID, err := ParseTokenRevocationRequest(r, revoker)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tokenID, subject, ok := getTokenIDAndSubjectForRevocation(r.Context(), revoker, token)
|
||||||
|
if ok {
|
||||||
|
err := revoker.Storage().RevokeToken(r.Context(), tokenID, subject, clientID)
|
||||||
|
if err != nil {
|
||||||
|
RevocationRequestError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httphelper.MarshalJSON(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//if tokenID != "" {
|
||||||
|
// token = tokenID
|
||||||
|
//}
|
||||||
|
err = revoker.Storage().RevokeToken(r.Context(), token, subject, clientID)
|
||||||
|
httphelper.MarshalJSON(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
req := new(struct {
|
||||||
|
oidc.RevocationRequest
|
||||||
|
oidc.ClientAssertionParams
|
||||||
|
})
|
||||||
|
err = revoker.Decoder().Decode(req, r.Form)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", errors.New("unable to parse request")
|
||||||
|
}
|
||||||
|
if revokerJWTProfile, ok := revoker.(RevokerJWTProfile); ok && req.ClientAssertion != "" {
|
||||||
|
profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, revokerJWTProfile.JWTProfileVerifier())
|
||||||
|
if err == nil {
|
||||||
|
return req.Token, req.TokenTypeHint, profile.Issuer, nil
|
||||||
|
}
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
clientID, clientSecret, ok := r.BasicAuth()
|
||||||
|
if ok {
|
||||||
|
clientID, err = url.QueryUnescape(clientID)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", errors.New("invalid basic auth header")
|
||||||
|
}
|
||||||
|
clientSecret, err = url.QueryUnescape(clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", errors.New("invalid basic auth header")
|
||||||
|
}
|
||||||
|
if err := revoker.Storage().AuthorizeClientIDSecret(r.Context(), clientID, clientSecret); err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
return req.Token, req.TokenTypeHint, clientID, nil
|
||||||
|
}
|
||||||
|
return "", "", "", errors.New("invalid authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RevocationRequestError(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
e := oidc.DefaultToServerError(err, err.Error())
|
||||||
|
status := http.StatusBadRequest
|
||||||
|
if e.ErrorType == oidc.InvalidClient {
|
||||||
|
status = 401
|
||||||
|
}
|
||||||
|
httphelper.MarshalJSONWithStatus(w, e, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTokenIDAndSubjectForRevocation(ctx context.Context, userinfoProvider UserinfoProvider, accessToken string) (string, string, bool) {
|
||||||
|
tokenIDSubject, err := userinfoProvider.Crypto().Decrypt(accessToken)
|
||||||
|
if err == nil {
|
||||||
|
splitToken := strings.Split(tokenIDSubject, ":")
|
||||||
|
if len(splitToken) != 2 {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return splitToken[0], splitToken[1], true
|
||||||
|
}
|
||||||
|
accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier())
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return accessTokenClaims.GetTokenID(), accessTokenClaims.GetSubject(), true
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue