mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2024-11-24 08:52:25 +02:00
Merge pull request #700 from grnhse/oidc-no-email-tokens
Allow OIDC Bearer Tokens without emails
This commit is contained in:
commit
aceb9e2762
@ -16,6 +16,7 @@
|
||||
- [#718](https://github.com/oauth2-proxy/oauth2-proxy/pull/718) Allow Logging to stdout with separate Error Log Channel
|
||||
- [#690](https://github.com/oauth2-proxy/oauth2-proxy/pull/690) Address GoSec security findings & remediate (@NickMeves)
|
||||
- [#689](https://github.com/oauth2-proxy/oauth2-proxy/pull/689) Fix finicky logging_handler_test from time drift (@NickMeves)
|
||||
- [#700](https://github.com/oauth2-proxy/oauth2-proxy/pull/700) Allow OIDC Bearer auth IDTokens to have empty email claim & profile URL (@NickMeves)
|
||||
- [#699](https://github.com/oauth2-proxy/oauth2-proxy/pull/699) Align persistence ginkgo tests with conventions (@NickMeves)
|
||||
- [#696](https://github.com/oauth2-proxy/oauth2-proxy/pull/696) Preserve query when building redirect
|
||||
- [#561](https://github.com/oauth2-proxy/oauth2-proxy/pull/561) Refactor provider URLs to package level vars (@JoelSpeed)
|
||||
|
@ -157,7 +157,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
|
||||
newSession = &sessions.SessionState{}
|
||||
} else {
|
||||
var err error
|
||||
newSession, err = p.createSessionStateInternal(ctx, token.Extra("id_token").(string), idToken, token)
|
||||
newSession, err = p.createSessionStateInternal(ctx, idToken, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -172,7 +172,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) CreateSessionStateFromBearerToken(ctx context.Context, rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) {
|
||||
newSession, err := p.createSessionStateInternal(ctx, rawIDToken, idToken, nil)
|
||||
newSession, err := p.createSessionStateInternal(ctx, idToken, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -185,24 +185,22 @@ func (p *OIDCProvider) CreateSessionStateFromBearerToken(ctx context.Context, ra
|
||||
return newSession, nil
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) createSessionStateInternal(ctx context.Context, rawIDToken string, idToken *oidc.IDToken, token *oauth2.Token) (*sessions.SessionState, error) {
|
||||
func (p *OIDCProvider) createSessionStateInternal(ctx context.Context, idToken *oidc.IDToken, token *oauth2.Token) (*sessions.SessionState, error) {
|
||||
|
||||
newSession := &sessions.SessionState{}
|
||||
|
||||
if idToken == nil {
|
||||
return newSession, nil
|
||||
}
|
||||
accessToken := ""
|
||||
if token != nil {
|
||||
accessToken = token.AccessToken
|
||||
}
|
||||
|
||||
claims, err := p.findClaimsFromIDToken(ctx, idToken, accessToken, p.ProfileURL.String())
|
||||
claims, err := p.findClaimsFromIDToken(ctx, idToken, token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't extract claims from id_token (%v)", err)
|
||||
}
|
||||
|
||||
newSession.IDToken = rawIDToken
|
||||
if token != nil {
|
||||
newSession.IDToken = token.Extra("id_token").(string)
|
||||
}
|
||||
|
||||
newSession.Email = claims.UserID // TODO Rename SessionState.Email to .UserID in the near future
|
||||
|
||||
@ -230,8 +228,7 @@ func getOIDCHeader(accessToken string) http.Header {
|
||||
return header
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) findClaimsFromIDToken(ctx context.Context, idToken *oidc.IDToken, accessToken string, profileURL string) (*OIDCClaims, error) {
|
||||
|
||||
func (p *OIDCProvider) findClaimsFromIDToken(ctx context.Context, idToken *oidc.IDToken, token *oauth2.Token) (*OIDCClaims, error) {
|
||||
claims := &OIDCClaims{}
|
||||
// Extract default claims.
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
@ -249,7 +246,15 @@ func (p *OIDCProvider) findClaimsFromIDToken(ctx context.Context, idToken *oidc.
|
||||
|
||||
// userID claim was not present or was empty in the ID Token
|
||||
if claims.UserID == "" {
|
||||
if profileURL == "" {
|
||||
// BearerToken case, allow empty UserID
|
||||
// ProfileURL checks below won't work since we don't have an access token
|
||||
if token == nil {
|
||||
claims.UserID = claims.Subject
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
profileURL := p.ProfileURL.String()
|
||||
if profileURL == "" || token.AccessToken == "" {
|
||||
return nil, fmt.Errorf("id_token did not contain user ID claim (%q)", p.UserIDClaim)
|
||||
}
|
||||
|
||||
@ -258,7 +263,7 @@ func (p *OIDCProvider) findClaimsFromIDToken(ctx context.Context, idToken *oidc.
|
||||
// Make a query to the userinfo endpoint, and attempt to locate the email from there.
|
||||
respJSON, err := requests.New(profileURL).
|
||||
WithContext(ctx).
|
||||
WithHeaders(getOIDCHeader(accessToken)).
|
||||
WithHeaders(getOIDCHeader(token.AccessToken)).
|
||||
Do().
|
||||
UnmarshalJSON()
|
||||
if err != nil {
|
||||
|
@ -60,6 +60,22 @@ var defaultIDToken idTokenClaims = idTokenClaims{
|
||||
},
|
||||
}
|
||||
|
||||
var minimalIDToken idTokenClaims = idTokenClaims{
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
jwt.StandardClaims{
|
||||
Audience: "https://test.myapp.com",
|
||||
ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
|
||||
Id: "id-some-id",
|
||||
IssuedAt: time.Now().Unix(),
|
||||
Issuer: "https://issuer.example.com",
|
||||
NotBefore: 0,
|
||||
Subject: "minimal",
|
||||
},
|
||||
}
|
||||
|
||||
type fakeKeySetStub struct{}
|
||||
|
||||
func (fakeKeySetStub) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
|
||||
@ -253,6 +269,53 @@ func TestOIDCProviderRefreshSessionIfNeededWithIdToken(t *testing.T) {
|
||||
assert.Equal(t, refreshToken, existingSession.RefreshToken)
|
||||
}
|
||||
|
||||
func TestCreateSessionStateFromBearerToken(t *testing.T) {
|
||||
const profileURLEmail = "janed@me.com"
|
||||
|
||||
testCases := map[string]struct {
|
||||
IDToken idTokenClaims
|
||||
ExpectedUser string
|
||||
ExpectedEmail string
|
||||
}{
|
||||
"Default IDToken": {
|
||||
IDToken: defaultIDToken,
|
||||
ExpectedUser: defaultIDToken.Subject,
|
||||
ExpectedEmail: defaultIDToken.Email,
|
||||
},
|
||||
"Minimal IDToken with no email claim": {
|
||||
IDToken: minimalIDToken,
|
||||
ExpectedUser: minimalIDToken.Subject,
|
||||
ExpectedEmail: minimalIDToken.Subject,
|
||||
},
|
||||
}
|
||||
for testName, tc := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
jsonResp := []byte(fmt.Sprintf(`{"email":"%s"}`, profileURLEmail))
|
||||
server, provider := newTestSetup(jsonResp)
|
||||
defer server.Close()
|
||||
|
||||
rawIDToken, err := newSignedTestIDToken(tc.IDToken)
|
||||
assert.NoError(t, err)
|
||||
|
||||
keyset := fakeKeySetStub{}
|
||||
verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
|
||||
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
|
||||
|
||||
idToken, err := verifier.Verify(context.Background(), rawIDToken)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ss, err := provider.CreateSessionStateFromBearerToken(context.Background(), rawIDToken, idToken)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.ExpectedUser, ss.User)
|
||||
assert.Equal(t, tc.ExpectedEmail, ss.Email)
|
||||
assert.Equal(t, rawIDToken, ss.IDToken)
|
||||
assert.Equal(t, rawIDToken, ss.AccessToken)
|
||||
assert.Equal(t, "", ss.RefreshToken)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCProvider_findVerifiedIdToken(t *testing.T) {
|
||||
|
||||
server, provider := newTestSetup([]byte(""))
|
||||
|
Loading…
Reference in New Issue
Block a user