forked from zitadel/oidc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
token_revocation.go
139 lines (130 loc) · 4.67 KB
/
token_revocation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package op
import (
"context"
"net/http"
"net/url"
"strings"
httphelper "github.com/zitadel/oidc/pkg/http"
"github.com/zitadel/oidc/pkg/oidc"
)
type Revoker interface {
Decoder() httphelper.Decoder
Crypto() Crypto
Storage() Storage
AccessTokenVerifier() AccessTokenVerifier
AuthMethodPrivateKeyJWTSupported() bool
AuthMethodPostSupported() bool
}
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 {
RevocationRequestError(w, r, err)
return
}
tokenID, subject, ok := getTokenIDAndSubjectForRevocation(r.Context(), revoker, token)
if ok {
token = tokenID
}
if err := revoker.Storage().RevokeToken(r.Context(), token, subject, clientID); err != nil {
RevocationRequestError(w, r, err)
return
}
httphelper.MarshalJSON(w, nil)
}
func ParseTokenRevocationRequest(r *http.Request, revoker Revoker) (token, tokenTypeHint, clientID string, err error) {
err = r.ParseForm()
if err != nil {
return "", "", "", oidc.ErrInvalidRequest().WithDescription("unable to parse request").WithParent(err)
}
req := new(struct {
oidc.RevocationRequest
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 "", "", "", oidc.ErrInvalidRequest().WithDescription("error decoding form").WithParent(err)
}
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
}
return "", "", "", err
}
clientID, clientSecret, ok := r.BasicAuth()
if ok {
clientID, err = url.QueryUnescape(clientID)
if err != nil {
return "", "", "", oidc.ErrInvalidClient().WithDescription("invalid basic auth header").WithParent(err)
}
clientSecret, err = url.QueryUnescape(clientSecret)
if err != nil {
return "", "", "", oidc.ErrInvalidClient().WithDescription("invalid basic auth header").WithParent(err)
}
if err = AuthorizeClientIDSecret(r.Context(), clientID, clientSecret, revoker.Storage()); err != nil {
return "", "", "", err
}
return req.Token, req.TokenTypeHint, clientID, nil
}
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) {
e := oidc.DefaultToServerError(err, err.Error())
status := http.StatusBadRequest
switch e.ErrorType {
case oidc.InvalidClient:
status = 401
case oidc.ServerError:
status = 500
}
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
}