1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-01-08 04:03:58 +02:00
oauth2-proxy/providers/oidc_test.go

278 lines
8.1 KiB
Go
Raw Normal View History

package providers
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
2021-07-17 18:55:05 +02:00
"github.com/coreos/go-oidc/v3/oidc"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
"github.com/stretchr/testify/assert"
)
type redeemTokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"`
TokenType string `json:"token_type"`
IDToken string `json:"id_token,omitempty"`
}
func newOIDCProvider(serverURL *url.URL, skipNonce bool) *OIDCProvider {
verificationOptions := internaloidc.IDTokenVerificationOptions{
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 18:12:22 +02:00
AudienceClaims: []string{"aud"},
ClientID: "https://test.myapp.com",
}
providerData := &ProviderData{
ProviderName: "oidc",
ClientID: oidcClientID,
ClientSecret: oidcSecret,
LoginURL: &url.URL{
Scheme: serverURL.Scheme,
Host: serverURL.Host,
Path: "/login/oauth/authorize"},
RedeemURL: &url.URL{
Scheme: serverURL.Scheme,
Host: serverURL.Host,
Path: "/login/oauth/access_token"},
ProfileURL: &url.URL{
Scheme: serverURL.Scheme,
Host: serverURL.Host,
Path: "/profile"},
ValidateURL: &url.URL{
Scheme: serverURL.Scheme,
Host: serverURL.Host,
Path: "/api"},
Scope: "openid profile offline_access",
EmailClaim: "email",
GroupsClaim: "groups",
UserClaim: "sub",
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 18:12:22 +02:00
Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
oidcIssuer,
mockJWKS{},
&oidc.Config{ClientID: oidcClientID},
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 18:12:22 +02:00
), verificationOptions),
}
p := NewOIDCProvider(providerData, options.OIDCOptions{
InsecureSkipNonce: skipNonce,
})
return p
}
func newOIDCServer(body []byte) (*url.URL, *httptest.Server) {
s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add("content-type", "application/json")
_, _ = rw.Write(body)
}))
u, _ := url.Parse(s.URL)
return u, s
}
2020-11-30 00:58:01 +02:00
func newTestOIDCSetup(body []byte) (*httptest.Server, *OIDCProvider) {
redeemURL, server := newOIDCServer(body)
provider := newOIDCProvider(redeemURL, false)
return server, provider
}
func TestOIDCProviderGetLoginURL(t *testing.T) {
serverURL := &url.URL{
Scheme: "https",
Host: "oauth2proxy.oidctest",
}
provider := newOIDCProvider(serverURL, true)
PKCE Support (#1541) * Add the allowed_email_domains and the allowed_groups on the auth_request endpoint + support standard wildcard char for validation with sub-domain and email-domain. Signed-off-by: Valentin Pichard <github@w3st.fr> * Fix provider data initialisation * PKCE Support Adds Code Challenge PKCE support (RFC-7636) and partial Authorization Server Metadata (RFC-8414) for detecting PKCE support. - Introduces new option `--force-code-challenge-method` to force a specific code challenge method (either `S256` or `plain`) for instances when the server has not implemented RFC-8414 in order to detect PKCE support on the discovery document. - In all other cases, if the PKCE support can be determined during discovery then the `code_challenge_methods_supported` is used and S256 is always preferred. - The force command line argument is helpful with some providers like Azure who supports PKCE but does not list it in their discovery document yet. - Initial thought was given to just always attempt PKCE since according to spec additional URL parameters should be dropped by servers which implemented OAuth 2, however other projects found cases in the wild where this causes 500 errors by buggy implementations. See: https://github.com/spring-projects/spring-security/pull/7804#issuecomment-578323810 - Due to the fact that the `code_verifier` must be saved between the redirect and callback, sessions are now created when the redirect takes place with `Authenticated: false`. The session will be recreated and marked as `Authenticated` on callback. - Individual provider implementations can choose to include or ignore code_challenge and code_verifier function parameters passed to them Note: Technically speaking `plain` is not required to be implemented since oauth2-proxy will always be able to handle S256 and servers MUST implement S256 support. > If the client is capable of using "S256", it MUST use "S256", as "S256" > is Mandatory To Implement (MTI) on the server. Clients are permitted > to use "plain" only if they cannot support "S256" for some technical > reason and know via out-of-band configuration that the server supports > "plain". Ref: RFC-7636 Sec 4.2 oauth2-proxy will always use S256 unless the user explicitly forces `plain`. Fixes #1361 * Address PR comments by moving pkce generation * Make PKCE opt-in, move to using the Nonce generater for code verifier * Make PKCE opt-in, move to using the Nonce generater for code verifier * Encrypt CodeVerifier in CSRF Token instead of Session - Update Dex for PKCE support - Expose HTTPBin for further use cases * Correct the tests * Move code challenges into extra params * Correct typo in code challenge method Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Correct the extra space in docs Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Address changelog and new line nits * Add generated docs Co-authored-by: Valentin Pichard <github@w3st.fr> Co-authored-by: Joel Speed <joel.speed@hotmail.co.uk>
2022-03-13 12:08:33 +02:00
n, err := encryption.Nonce(32)
assert.NoError(t, err)
nonce := base64.RawURLEncoding.EncodeToString(n)
// SkipNonce defaults to true
skipNonce := provider.GetLoginURL("http://redirect/", "", nonce, url.Values{})
assert.NotContains(t, skipNonce, "nonce")
provider.SkipNonce = false
withNonce := provider.GetLoginURL("http://redirect/", "", nonce, url.Values{})
assert.Contains(t, withNonce, fmt.Sprintf("nonce=%s", nonce))
PKCE Support (#1541) * Add the allowed_email_domains and the allowed_groups on the auth_request endpoint + support standard wildcard char for validation with sub-domain and email-domain. Signed-off-by: Valentin Pichard <github@w3st.fr> * Fix provider data initialisation * PKCE Support Adds Code Challenge PKCE support (RFC-7636) and partial Authorization Server Metadata (RFC-8414) for detecting PKCE support. - Introduces new option `--force-code-challenge-method` to force a specific code challenge method (either `S256` or `plain`) for instances when the server has not implemented RFC-8414 in order to detect PKCE support on the discovery document. - In all other cases, if the PKCE support can be determined during discovery then the `code_challenge_methods_supported` is used and S256 is always preferred. - The force command line argument is helpful with some providers like Azure who supports PKCE but does not list it in their discovery document yet. - Initial thought was given to just always attempt PKCE since according to spec additional URL parameters should be dropped by servers which implemented OAuth 2, however other projects found cases in the wild where this causes 500 errors by buggy implementations. See: https://github.com/spring-projects/spring-security/pull/7804#issuecomment-578323810 - Due to the fact that the `code_verifier` must be saved between the redirect and callback, sessions are now created when the redirect takes place with `Authenticated: false`. The session will be recreated and marked as `Authenticated` on callback. - Individual provider implementations can choose to include or ignore code_challenge and code_verifier function parameters passed to them Note: Technically speaking `plain` is not required to be implemented since oauth2-proxy will always be able to handle S256 and servers MUST implement S256 support. > If the client is capable of using "S256", it MUST use "S256", as "S256" > is Mandatory To Implement (MTI) on the server. Clients are permitted > to use "plain" only if they cannot support "S256" for some technical > reason and know via out-of-band configuration that the server supports > "plain". Ref: RFC-7636 Sec 4.2 oauth2-proxy will always use S256 unless the user explicitly forces `plain`. Fixes #1361 * Address PR comments by moving pkce generation * Make PKCE opt-in, move to using the Nonce generater for code verifier * Make PKCE opt-in, move to using the Nonce generater for code verifier * Encrypt CodeVerifier in CSRF Token instead of Session - Update Dex for PKCE support - Expose HTTPBin for further use cases * Correct the tests * Move code challenges into extra params * Correct typo in code challenge method Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Correct the extra space in docs Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Address changelog and new line nits * Add generated docs Co-authored-by: Valentin Pichard <github@w3st.fr> Co-authored-by: Joel Speed <joel.speed@hotmail.co.uk>
2022-03-13 12:08:33 +02:00
assert.NotContains(t, withNonce, "code_challenge")
assert.NotContains(t, withNonce, "code_challenge_method")
}
func TestOIDCProviderRedeem(t *testing.T) {
idToken, _ := newSignedTestIDToken(defaultIDToken)
body, _ := json.Marshal(redeemTokenResponse{
AccessToken: accessToken,
ExpiresIn: 10,
TokenType: "Bearer",
RefreshToken: refreshToken,
IDToken: idToken,
})
2020-11-30 00:58:01 +02:00
server, provider := newTestOIDCSetup(body)
defer server.Close()
PKCE Support (#1541) * Add the allowed_email_domains and the allowed_groups on the auth_request endpoint + support standard wildcard char for validation with sub-domain and email-domain. Signed-off-by: Valentin Pichard <github@w3st.fr> * Fix provider data initialisation * PKCE Support Adds Code Challenge PKCE support (RFC-7636) and partial Authorization Server Metadata (RFC-8414) for detecting PKCE support. - Introduces new option `--force-code-challenge-method` to force a specific code challenge method (either `S256` or `plain`) for instances when the server has not implemented RFC-8414 in order to detect PKCE support on the discovery document. - In all other cases, if the PKCE support can be determined during discovery then the `code_challenge_methods_supported` is used and S256 is always preferred. - The force command line argument is helpful with some providers like Azure who supports PKCE but does not list it in their discovery document yet. - Initial thought was given to just always attempt PKCE since according to spec additional URL parameters should be dropped by servers which implemented OAuth 2, however other projects found cases in the wild where this causes 500 errors by buggy implementations. See: https://github.com/spring-projects/spring-security/pull/7804#issuecomment-578323810 - Due to the fact that the `code_verifier` must be saved between the redirect and callback, sessions are now created when the redirect takes place with `Authenticated: false`. The session will be recreated and marked as `Authenticated` on callback. - Individual provider implementations can choose to include or ignore code_challenge and code_verifier function parameters passed to them Note: Technically speaking `plain` is not required to be implemented since oauth2-proxy will always be able to handle S256 and servers MUST implement S256 support. > If the client is capable of using "S256", it MUST use "S256", as "S256" > is Mandatory To Implement (MTI) on the server. Clients are permitted > to use "plain" only if they cannot support "S256" for some technical > reason and know via out-of-band configuration that the server supports > "plain". Ref: RFC-7636 Sec 4.2 oauth2-proxy will always use S256 unless the user explicitly forces `plain`. Fixes #1361 * Address PR comments by moving pkce generation * Make PKCE opt-in, move to using the Nonce generater for code verifier * Make PKCE opt-in, move to using the Nonce generater for code verifier * Encrypt CodeVerifier in CSRF Token instead of Session - Update Dex for PKCE support - Expose HTTPBin for further use cases * Correct the tests * Move code challenges into extra params * Correct typo in code challenge method Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Correct the extra space in docs Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Address changelog and new line nits * Add generated docs Co-authored-by: Valentin Pichard <github@w3st.fr> Co-authored-by: Joel Speed <joel.speed@hotmail.co.uk>
2022-03-13 12:08:33 +02:00
session, err := provider.Redeem(context.Background(), provider.RedeemURL.String(), "code1234", "")
assert.Equal(t, nil, err)
assert.Equal(t, defaultIDToken.Email, session.Email)
assert.Equal(t, accessToken, session.AccessToken)
assert.Equal(t, idToken, session.IDToken)
assert.Equal(t, refreshToken, session.RefreshToken)
assert.Equal(t, "123456789", session.User)
}
func TestOIDCProviderRedeem_custom_userid(t *testing.T) {
idToken, _ := newSignedTestIDToken(defaultIDToken)
body, _ := json.Marshal(redeemTokenResponse{
AccessToken: accessToken,
ExpiresIn: 10,
TokenType: "Bearer",
RefreshToken: refreshToken,
IDToken: idToken,
})
2020-11-30 00:58:01 +02:00
server, provider := newTestOIDCSetup(body)
2020-11-27 05:00:30 +02:00
provider.EmailClaim = "phone_number"
defer server.Close()
PKCE Support (#1541) * Add the allowed_email_domains and the allowed_groups on the auth_request endpoint + support standard wildcard char for validation with sub-domain and email-domain. Signed-off-by: Valentin Pichard <github@w3st.fr> * Fix provider data initialisation * PKCE Support Adds Code Challenge PKCE support (RFC-7636) and partial Authorization Server Metadata (RFC-8414) for detecting PKCE support. - Introduces new option `--force-code-challenge-method` to force a specific code challenge method (either `S256` or `plain`) for instances when the server has not implemented RFC-8414 in order to detect PKCE support on the discovery document. - In all other cases, if the PKCE support can be determined during discovery then the `code_challenge_methods_supported` is used and S256 is always preferred. - The force command line argument is helpful with some providers like Azure who supports PKCE but does not list it in their discovery document yet. - Initial thought was given to just always attempt PKCE since according to spec additional URL parameters should be dropped by servers which implemented OAuth 2, however other projects found cases in the wild where this causes 500 errors by buggy implementations. See: https://github.com/spring-projects/spring-security/pull/7804#issuecomment-578323810 - Due to the fact that the `code_verifier` must be saved between the redirect and callback, sessions are now created when the redirect takes place with `Authenticated: false`. The session will be recreated and marked as `Authenticated` on callback. - Individual provider implementations can choose to include or ignore code_challenge and code_verifier function parameters passed to them Note: Technically speaking `plain` is not required to be implemented since oauth2-proxy will always be able to handle S256 and servers MUST implement S256 support. > If the client is capable of using "S256", it MUST use "S256", as "S256" > is Mandatory To Implement (MTI) on the server. Clients are permitted > to use "plain" only if they cannot support "S256" for some technical > reason and know via out-of-band configuration that the server supports > "plain". Ref: RFC-7636 Sec 4.2 oauth2-proxy will always use S256 unless the user explicitly forces `plain`. Fixes #1361 * Address PR comments by moving pkce generation * Make PKCE opt-in, move to using the Nonce generater for code verifier * Make PKCE opt-in, move to using the Nonce generater for code verifier * Encrypt CodeVerifier in CSRF Token instead of Session - Update Dex for PKCE support - Expose HTTPBin for further use cases * Correct the tests * Move code challenges into extra params * Correct typo in code challenge method Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Correct the extra space in docs Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Address changelog and new line nits * Add generated docs Co-authored-by: Valentin Pichard <github@w3st.fr> Co-authored-by: Joel Speed <joel.speed@hotmail.co.uk>
2022-03-13 12:08:33 +02:00
session, err := provider.Redeem(context.Background(), provider.RedeemURL.String(), "code1234", "")
assert.Equal(t, nil, err)
assert.Equal(t, defaultIDToken.Phone, session.Email)
}
func TestOIDCProviderRefreshSessionIfNeededWithoutIdToken(t *testing.T) {
idToken, _ := newSignedTestIDToken(defaultIDToken)
body, _ := json.Marshal(redeemTokenResponse{
AccessToken: accessToken,
ExpiresIn: 10,
TokenType: "Bearer",
RefreshToken: refreshToken,
})
2020-11-30 00:58:01 +02:00
server, provider := newTestOIDCSetup(body)
defer server.Close()
existingSession := &sessions.SessionState{
AccessToken: "changeit",
IDToken: idToken,
CreatedAt: nil,
ExpiresOn: nil,
RefreshToken: refreshToken,
Email: "janedoe@example.com",
User: "11223344",
}
refreshed, err := provider.RefreshSession(context.Background(), existingSession)
assert.Equal(t, nil, err)
assert.Equal(t, refreshed, true)
assert.Equal(t, "janedoe@example.com", existingSession.Email)
assert.Equal(t, accessToken, existingSession.AccessToken)
assert.Equal(t, idToken, existingSession.IDToken)
assert.Equal(t, refreshToken, existingSession.RefreshToken)
assert.Equal(t, "11223344", existingSession.User)
}
func TestOIDCProviderRefreshSessionIfNeededWithIdToken(t *testing.T) {
idToken, _ := newSignedTestIDToken(defaultIDToken)
body, _ := json.Marshal(redeemTokenResponse{
AccessToken: accessToken,
ExpiresIn: 10,
TokenType: "Bearer",
RefreshToken: refreshToken,
IDToken: idToken,
})
2020-11-30 00:58:01 +02:00
server, provider := newTestOIDCSetup(body)
defer server.Close()
existingSession := &sessions.SessionState{
AccessToken: "changeit",
IDToken: "changeit",
CreatedAt: nil,
ExpiresOn: nil,
RefreshToken: refreshToken,
Email: "changeit",
User: "changeit",
}
refreshed, err := provider.RefreshSession(context.Background(), existingSession)
assert.Equal(t, nil, err)
assert.Equal(t, refreshed, true)
assert.Equal(t, defaultIDToken.Email, existingSession.Email)
assert.Equal(t, defaultIDToken.Subject, existingSession.User)
assert.Equal(t, accessToken, existingSession.AccessToken)
assert.Equal(t, idToken, existingSession.IDToken)
assert.Equal(t, refreshToken, existingSession.RefreshToken)
}
func TestOIDCProviderCreateSessionFromToken(t *testing.T) {
testCases := map[string]struct {
IDToken idTokenClaims
GroupsClaim string
ExpectedUser string
ExpectedEmail string
2020-11-30 00:58:01 +02:00
ExpectedGroups []string
}{
"Default IDToken": {
IDToken: defaultIDToken,
GroupsClaim: "groups",
ExpectedUser: "123456789",
ExpectedEmail: "janed@me.com",
ExpectedGroups: []string{"test:a", "test:b"},
},
"Minimal IDToken with no email claim": {
IDToken: minimalIDToken,
GroupsClaim: "groups",
ExpectedUser: "123456789",
ExpectedEmail: "123456789",
2020-11-30 00:58:01 +02:00
ExpectedGroups: nil,
},
"Custom Groups Claim": {
IDToken: defaultIDToken,
GroupsClaim: "roles",
ExpectedUser: "123456789",
ExpectedEmail: "janed@me.com",
ExpectedGroups: []string{"test:c", "test:d"},
},
"Complex Groups Claim": {
IDToken: complexGroupsIDToken,
GroupsClaim: "groups",
ExpectedUser: "123456789",
ExpectedEmail: "complex@claims.com",
ExpectedGroups: []string{
"{\"groupId\":\"Admin Group Id\",\"roles\":[\"Admin\"]}",
"12345",
"Just::A::String",
},
},
}
for testName, tc := range testCases {
t.Run(testName, func(t *testing.T) {
2020-11-30 00:58:01 +02:00
server, provider := newTestOIDCSetup([]byte(`{}`))
provider.GroupsClaim = tc.GroupsClaim
defer server.Close()
rawIDToken, err := newSignedTestIDToken(tc.IDToken)
assert.NoError(t, err)
ss, err := provider.CreateSessionFromToken(context.Background(), rawIDToken)
assert.NoError(t, err)
assert.Equal(t, tc.ExpectedUser, ss.User)
assert.Equal(t, tc.ExpectedEmail, ss.Email)
2020-11-30 00:58:01 +02:00
assert.Equal(t, tc.ExpectedGroups, ss.Groups)
assert.Equal(t, rawIDToken, ss.IDToken)
assert.Equal(t, rawIDToken, ss.AccessToken)
assert.Equal(t, "", ss.RefreshToken)
})
}
}