-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
handler_oidc_userinfo.go
153 lines (110 loc) · 5.31 KB
/
handler_oidc_userinfo.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package handlers
import (
"encoding/json"
"fmt"
"net/http"
"time"
oauthelia2 "authelia.com/provider/oauth2"
"authelia.com/provider/oauth2/handler/oauth2"
"authelia.com/provider/oauth2/token/jwt"
"authelia.com/provider/oauth2/x/errorsx"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/oidc"
)
// OpenIDConnectUserinfo handles GET/POST requests to the OpenID Connect 1.0 UserInfo endpoint.
//
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) {
var (
requestID uuid.UUID
tokenType oauthelia2.TokenType
requester oauthelia2.AccessRequester
client oidc.Client
err error
)
if requestID, err = uuid.NewRandom(); err != nil {
errorsx.WriteJSONError(rw, req, oauthelia2.ErrServerError)
return
}
oidcSession := oidc.NewSession()
ctx.Logger.Debugf("UserInfo Request with id '%s' is being processed", requestID)
if tokenType, requester, err = ctx.Providers.OpenIDConnect.IntrospectToken(req.Context(), oauthelia2.AccessTokenFromRequest(req), oauthelia2.AccessToken, oidcSession); err != nil {
ctx.Logger.Errorf("UserInfo Request with id '%s' failed with error: %s", requestID, oauthelia2.ErrorToDebugRFC6749Error(err))
if rfc := oauthelia2.ErrorToRFC6749Error(err); rfc.StatusCode() == http.StatusUnauthorized {
rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer %s`, oidc.RFC6750Header("", "", rfc)))
}
errorsx.WriteJSONError(rw, req, err)
return
}
clientID := requester.GetClient().GetID()
if tokenType != oauthelia2.AccessToken {
ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed with error: bearer authorization failed as the token is not an access token", requestID, client.GetID())
errStr := "Only access tokens are allowed in the authorization header."
rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errStr))
errorsx.WriteJSONErrorCode(rw, req, http.StatusUnauthorized, errors.New(errStr))
return
}
if client, err = ctx.Providers.OpenIDConnect.GetRegisteredClient(ctx, clientID); err != nil {
ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed to retrieve client configuration with error: %s", requestID, client.GetID(), oauthelia2.ErrorToDebugRFC6749Error(err))
errorsx.WriteJSONError(rw, req, err)
return
}
var (
original map[string]any
)
switch session := requester.GetSession().(type) {
case *oidc.Session:
original = session.IDTokenClaims().ToMap()
case *oauth2.JWTSession:
original = session.JWTClaims.ToMap()
default:
ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed to handle session with type '%T'", requestID, client.GetID(), session)
errorsx.WriteJSONError(rw, req, oauthelia2.ErrServerError.WithDebugf("Failed to handle session with type '%T'.", session))
return
}
claims := map[string]any{}
oidcApplyUserInfoClaims(clientID, requester.GetGrantedScopes(), original, claims, oidcCtxDetailResolver(ctx))
var token string
ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requestID, clientID, claims)
switch alg := client.GetUserinfoSignedResponseAlg(); alg {
case oidc.SigningAlgNone:
ctx.Logger.Debugf("UserInfo Request with id '%s' on client with id '%s' is being returned unsigned as per the registered client configuration", requestID, client.GetID())
rw.Header().Set(fasthttp.HeaderContentType, "application/json; charset=utf-8")
rw.Header().Set(fasthttp.HeaderCacheControl, "no-store")
rw.Header().Set(fasthttp.HeaderPragma, "no-cache")
rw.WriteHeader(http.StatusOK)
_ = json.NewEncoder(rw).Encode(claims)
default:
var jwk *oidc.JWK
if jwk = ctx.Providers.OpenIDConnect.KeyManager.Get(ctx, client.GetUserinfoSignedResponseKeyID(), alg); jwk == nil {
errorsx.WriteJSONError(rw, req, errors.WithStack(oauthelia2.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", alg)))
return
}
ctx.Logger.Debugf("UserInfo Request with id '%s' on client with id '%s' is being returned signed as per the registered client configuration with key id '%s' using the '%s' algorithm", requestID, client.GetID(), jwk.KeyID(), jwk.JWK().Algorithm)
var jti uuid.UUID
if jti, err = uuid.NewRandom(); err != nil {
errorsx.WriteJSONError(rw, req, oauthelia2.ErrServerError.WithHint("Could not generate JTI."))
return
}
claims[oidc.ClaimJWTID] = jti.String()
claims[oidc.ClaimIssuedAt] = time.Now().UTC().Unix()
headers := &jwt.Headers{
Extra: map[string]any{
oidc.JWTHeaderKeyIdentifier: jwk.KeyID(),
},
}
if token, _, err = jwk.Strategy().Generate(req.Context(), claims, headers); err != nil {
errorsx.WriteJSONError(rw, req, err)
return
}
rw.Header().Set(fasthttp.HeaderContentType, "application/jwt; charset=utf-8")
rw.Header().Set(fasthttp.HeaderCacheControl, "no-store")
rw.Header().Set(fasthttp.HeaderPragma, "no-cache")
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte(token))
}
ctx.Logger.Debugf("UserInfo Request with id '%s' on client with id '%s' was successfully processed", requestID, client.GetID())
}