Merge branch 'token-refresh' into token-exchange-endpoint

Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
Margo Crawford 2020-12-09 14:56:41 -08:00 committed by Ryan Richard
commit 5f6e7de785

View File

@ -28,6 +28,7 @@ import (
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2"
josejwt "gopkg.in/square/go-jose.v2/jwt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes/fake"
@ -232,17 +233,6 @@ var (
},
}
}
happyRefreshRequest = func(refreshToken string) *http.Request {
return &http.Request{
Form: url.Values{
"grant_type": {"refresh_token"},
"scope": {"openid"},
"client_id": {goodClient},
"refresh_token": {refreshToken},
},
}
}
)
type tokenEndpointResponseExpectedValues struct {
@ -255,7 +245,7 @@ type tokenEndpointResponseExpectedValues struct {
type authcodeExchangeInputs struct {
modifyAuthRequest func(authRequest *http.Request)
modifyTokenRequest func(r *http.Request, authCode string)
modifyTokenRequest func(tokenRequest *http.Request, authCode string)
modifyStorage func(
t *testing.T,
s interface {
@ -302,9 +292,7 @@ func TestTokenEndpoint(t *testing.T) {
{
name: "openid scope was not requested from authorize endpoint",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(authRequest *http.Request) {
authRequest.Form.Set("scope", "profile email")
},
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "profile email") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"access_token", "token_type", "scope", "expires_in"}, // no id or refresh tokens
@ -316,9 +304,7 @@ func TestTokenEndpoint(t *testing.T) {
{
name: "offline_access and openid scopes were requested and granted from authorize endpoint",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(authRequest *http.Request) {
authRequest.Form.Set("scope", "openid offline_access")
},
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "access_token", "token_type", "scope", "expires_in", "refresh_token"}, // all possible tokens
@ -330,9 +316,7 @@ func TestTokenEndpoint(t *testing.T) {
{
name: "offline_access (without openid scope) was requested and granted from authorize endpoint",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(authRequest *http.Request) {
authRequest.Form.Set("scope", "offline_access")
},
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"access_token", "token_type", "scope", "expires_in", "refresh_token"}, // no id token
@ -419,7 +403,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "grant type is missing in request",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithGrantType("").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithGrantType("").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -431,7 +415,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "grant type is not authorization_code",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithGrantType("bogus").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithGrantType("bogus").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -443,7 +427,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "client id is missing in request",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithClientID("").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithClientID("").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -455,7 +439,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "client id is wrong",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithClientID("bogus").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithClientID("bogus").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusUnauthorized,
@ -467,7 +451,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "grant type is missing",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).with("grant_type", "").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithGrantType("").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -479,7 +463,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "grant type is wrong",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).with("grant_type", "bogus").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithGrantType("bogus").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -491,7 +475,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "auth code is missing in request",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithAuthCode("").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithAuthCode("").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -503,7 +487,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "auth code has never been valid",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithAuthCode("bogus").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithAuthCode("bogus").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -538,7 +522,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "redirect uri is missing in request",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithRedirectURI("").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithRedirectURI("").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -550,7 +534,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "redirect uri is wrong",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithRedirectURI("bogus").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithRedirectURI("bogus").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -562,7 +546,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "pkce is missing in request",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithPKCE("").ReadCloser()
r.Body = happyAuthcodeRequestBody(authCode).WithPKCE("").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@ -574,7 +558,7 @@ func TestTokenEndpoint(t *testing.T) {
name: "pkce is wrong",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = happyBody(authCode).WithPKCE(
r.Body = happyAuthcodeRequestBody(authCode).WithPKCE(
"bogus-verifier-that-is-at-least-43-characters-for-the-sake-of-entropy",
).ReadCloser()
},
@ -598,6 +582,8 @@ func TestTokenEndpoint(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
exchangeAuthcodeForTokens(t, test.authcodeExchange)
})
}
@ -611,9 +597,7 @@ func TestTokenEndpointWhenAuthcodeIsUsedTwice(t *testing.T) {
{
name: "authcode exchange succeeds once and then fails when the same authcode is used again",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(authRequest *http.Request) {
authRequest.Form.Set("scope", "openid offline_access profile email")
},
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access profile email") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
@ -626,6 +610,8 @@ func TestTokenEndpointWhenAuthcodeIsUsedTwice(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
// First call - should be successful.
subject, rsp, authCode, _, secrets, oauthStore := exchangeAuthcodeForTokens(t, test.authcodeExchange)
var parsedResponseBody map[string]interface{}
@ -635,7 +621,7 @@ func TestTokenEndpointWhenAuthcodeIsUsedTwice(t *testing.T) {
//
// Fosite will also revoke the access token as is recommended by the OIDC spec. Currently, we don't
// delete the OIDC storage...but we probably should.
req := httptest.NewRequest("POST", "/path/shouldn't/matter", happyBody(authCode).ReadCloser())
req := httptest.NewRequest("POST", "/path/shouldn't/matter", happyAuthcodeRequestBody(authCode).ReadCloser())
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
reusedAuthcodeResponse := httptest.NewRecorder()
subject.ServeHTTP(reusedAuthcodeResponse, req)
@ -865,7 +851,8 @@ func TestTokenExchange(t *testing.T) {
}
type refreshRequestInputs struct {
want tokenEndpointResponseExpectedValues
modifyTokenRequest func(tokenRequest *http.Request, refreshToken string, accessToken string)
want tokenEndpointResponseExpectedValues
}
func TestRefreshGrant(t *testing.T) {
@ -875,11 +862,9 @@ func TestRefreshGrant(t *testing.T) {
refreshRequest refreshRequestInputs
}{
{
name: "happy path refresh grant",
name: "happy path refresh grant with ID token",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(authRequest *http.Request) {
authRequest.Form.Set("scope", "openid offline_access")
},
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
@ -895,20 +880,177 @@ func TestRefreshGrant(t *testing.T) {
wantGrantedScopes: []string{"openid", "offline_access"},
}},
},
// TODO lots of sad path tests
{
name: "happy path refresh grant without ID token",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"offline_access"},
wantGrantedScopes: []string{"offline_access"},
},
},
refreshRequest: refreshRequestInputs{
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"offline_access"},
wantGrantedScopes: []string{"offline_access"},
}},
},
{
name: "when the refresh request adds a new scope to the list of requested scopes then it is ignored",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"openid", "offline_access"},
wantGrantedScopes: []string{"openid", "offline_access"},
},
},
refreshRequest: refreshRequestInputs{
modifyTokenRequest: func(r *http.Request, refreshToken string, accessToken string) {
r.Body = happyRefreshRequestBody(refreshToken).WithScope("openid some-other-scope-not-from-auth-request").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"openid", "offline_access"},
wantGrantedScopes: []string{"openid", "offline_access"},
}},
},
{
name: "when the refresh request removes a scope which was originally granted from the list of requested scopes then it is ignored",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"openid", "offline_access"},
wantGrantedScopes: []string{"openid", "offline_access"},
},
},
refreshRequest: refreshRequestInputs{
modifyTokenRequest: func(r *http.Request, refreshToken string, accessToken string) {
r.Body = happyRefreshRequestBody(refreshToken).WithScope("").ReadCloser() // TODO FIX ME. WE NEED ANOTHER VALID SCOPE ON THIS CLIENT TO WRITE THIS TEST.
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"openid", "offline_access"},
wantGrantedScopes: []string{"openid", "offline_access"},
}},
},
{
name: "when the refresh request does not include a scope param then it gets all the same scopes as the original authorization request",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"openid", "offline_access"},
wantGrantedScopes: []string{"openid", "offline_access"},
},
},
refreshRequest: refreshRequestInputs{
modifyTokenRequest: func(r *http.Request, refreshToken string, accessToken string) {
r.Body = happyRefreshRequestBody(refreshToken).WithScope("").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"openid", "offline_access"},
wantGrantedScopes: []string{"openid", "offline_access"},
}},
},
{
name: "when a bad refresh token is sent in the refresh request",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"offline_access"},
wantGrantedScopes: []string{"offline_access"},
},
},
refreshRequest: refreshRequestInputs{
modifyTokenRequest: func(r *http.Request, refreshToken string, accessToken string) {
r.Body = happyRefreshRequestBody(refreshToken).WithRefreshToken("bad refresh token").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
wantErrorResponseBody: fositeInvalidAuthCodeErrorBody,
}},
},
{
name: "when the access token is sent as if it were a refresh token",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"offline_access"},
wantGrantedScopes: []string{"offline_access"},
},
},
refreshRequest: refreshRequestInputs{
modifyTokenRequest: func(r *http.Request, refreshToken string, accessToken string) {
r.Body = happyRefreshRequestBody(refreshToken).WithRefreshToken(accessToken).ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
wantErrorResponseBody: fositeInvalidAuthCodeErrorBody,
}},
},
{
name: "when the wrong client ID is included in the refresh request",
authcodeExchange: authcodeExchangeInputs{
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "offline_access") },
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusOK,
wantSuccessBodyFields: []string{"refresh_token", "access_token", "token_type", "expires_in", "scope"},
wantRequestedScopes: []string{"offline_access"},
wantGrantedScopes: []string{"offline_access"},
},
},
refreshRequest: refreshRequestInputs{
modifyTokenRequest: func(r *http.Request, refreshToken string, accessToken string) {
r.Body = happyRefreshRequestBody(refreshToken).WithClientID("wrong-client-id").ReadCloser()
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusUnauthorized,
wantErrorResponseBody: fositeInvalidClientErrorBody,
}},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
// First exchange the authcode for tokens, including a refresh token.
subject, rsp, authCode, jwtSigningKey, secrets, oauthStore := exchangeAuthcodeForTokens(t, test.authcodeExchange)
var parsedAuthcodeExchangeResponseBody map[string]interface{}
require.NoError(t, json.Unmarshal(rsp.Body.Bytes(), &parsedAuthcodeExchangeResponseBody))
// Wait one second before performing the refresh so we can see that the refreshed ID token has new issued
// at and expires at dates which are newer than the old tokens.
// If this gets too annoying in terms of making our test suite slower then we can remove it and adjust
// the expectations about the ID token that are made at the end of this test accordingly.
time.Sleep(1 * time.Second)
// Send the refresh token back and preform a refresh.
firstRefreshToken := parsedAuthcodeExchangeResponseBody["refresh_token"].(string)
req := httptest.NewRequest("POST", "/path/shouldn't/matter",
body(happyRefreshRequest(parsedAuthcodeExchangeResponseBody["refresh_token"].(string)).Form).ReadCloser())
happyRefreshRequestBody(firstRefreshToken).ReadCloser())
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if test.refreshRequest.modifyTokenRequest != nil {
test.refreshRequest.modifyTokenRequest(req, firstRefreshToken, parsedAuthcodeExchangeResponseBody["access_token"].(string))
}
refreshResponse := httptest.NewRecorder()
subject.ServeHTTP(refreshResponse, req)
t.Logf("second response: %#v", refreshResponse)
@ -921,23 +1063,63 @@ func TestRefreshGrant(t *testing.T) {
requireTokenEndpointBehavior(t, test.refreshRequest.want, wantAtHashClaimInIDToken, wantNonceValueInIDToken, refreshResponse, authCode, oauthStore, jwtSigningKey, secrets)
if test.refreshRequest.want.wantStatus == http.StatusOK {
wantIDToken := contains(test.refreshRequest.want.wantSuccessBodyFields, "id_token")
var parsedRefreshResponseBody map[string]interface{}
require.NoError(t, json.Unmarshal(refreshResponse.Body.Bytes(), &parsedRefreshResponseBody))
// Check that we got back new tokens.
require.NotEqual(t, parsedAuthcodeExchangeResponseBody["access_token"].(string), parsedRefreshResponseBody["access_token"].(string))
require.NotEqual(t, parsedAuthcodeExchangeResponseBody["refresh_token"].(string), parsedRefreshResponseBody["refresh_token"].(string))
require.NotEqual(t, parsedAuthcodeExchangeResponseBody["id_token"].(string), parsedRefreshResponseBody["id_token"].(string))
if wantIDToken {
require.NotEqual(t, parsedAuthcodeExchangeResponseBody["id_token"].(string), parsedRefreshResponseBody["id_token"].(string))
}
// The other fields of the response should be the same as the original response. Note that expires_in is a number of seconds from now.
require.Equal(t, parsedAuthcodeExchangeResponseBody["token_type"].(string), parsedRefreshResponseBody["token_type"].(string))
require.Equal(t, parsedAuthcodeExchangeResponseBody["expires_in"].(float64), parsedRefreshResponseBody["expires_in"].(float64))
require.InDelta(t, parsedAuthcodeExchangeResponseBody["expires_in"].(float64), parsedRefreshResponseBody["expires_in"].(float64), 2)
require.Equal(t, parsedAuthcodeExchangeResponseBody["scope"].(string), parsedRefreshResponseBody["scope"].(string))
if wantIDToken {
var claimsOfFirstIDToken map[string]interface{}
firstIDTokenDecoded, _ := josejwt.ParseSigned(parsedAuthcodeExchangeResponseBody["id_token"].(string))
err := firstIDTokenDecoded.UnsafeClaimsWithoutVerification(&claimsOfFirstIDToken)
require.NoError(t, err)
var claimsOfSecondIDToken map[string]interface{}
secondIDTokenDecoded, _ := josejwt.ParseSigned(parsedRefreshResponseBody["id_token"].(string))
err = secondIDTokenDecoded.UnsafeClaimsWithoutVerification(&claimsOfSecondIDToken)
require.NoError(t, err)
requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID
requireClaimsAreNotEqual(t, "exp", claimsOfFirstIDToken, claimsOfSecondIDToken) // expires at
require.Greater(t, claimsOfSecondIDToken["exp"], claimsOfFirstIDToken["exp"])
requireClaimsAreNotEqual(t, "iat", claimsOfFirstIDToken, claimsOfSecondIDToken) // issued at
require.Greater(t, claimsOfSecondIDToken["iat"], claimsOfFirstIDToken["iat"])
requireClaimsAreEqual(t, "iss", claimsOfFirstIDToken, claimsOfSecondIDToken) // issuer
requireClaimsAreEqual(t, "aud", claimsOfFirstIDToken, claimsOfSecondIDToken) // audience
requireClaimsAreEqual(t, "sub", claimsOfFirstIDToken, claimsOfSecondIDToken) // subject
requireClaimsAreEqual(t, "rat", claimsOfFirstIDToken, claimsOfSecondIDToken) // requested at
requireClaimsAreEqual(t, "auth_time", claimsOfFirstIDToken, claimsOfSecondIDToken) // auth time
}
}
})
}
}
func requireClaimsAreNotEqual(t *testing.T, claimName string, claimsOfTokenA map[string]interface{}, claimsOfTokenB map[string]interface{}) {
require.NotEmpty(t, claimsOfTokenA[claimName])
require.NotEmpty(t, claimsOfTokenB[claimName])
require.NotEqual(t, claimsOfTokenA[claimName], claimsOfTokenB[claimName])
}
func requireClaimsAreEqual(t *testing.T, claimName string, claimsOfTokenA map[string]interface{}, claimsOfTokenB map[string]interface{}) {
require.NotEmpty(t, claimsOfTokenA[claimName])
require.NotEmpty(t, claimsOfTokenB[claimName])
require.Equal(t, claimsOfTokenA[claimName], claimsOfTokenB[claimName])
}
func exchangeAuthcodeForTokens(t *testing.T, test authcodeExchangeInputs) (
subject http.Handler,
rsp *httptest.ResponseRecorder,
@ -979,7 +1161,7 @@ func exchangeAuthcodeForTokens(t *testing.T, test authcodeExchangeInputs) (
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, expectedNumberOfIDSessionsStored)
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 2+expectedNumberOfIDSessionsStored)
req := httptest.NewRequest("POST", "/path/shouldn't/matter", happyBody(authCode).ReadCloser())
req := httptest.NewRequest("POST", "/path/shouldn't/matter", happyAuthcodeRequestBody(authCode).ReadCloser())
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if test.modifyTokenRequest != nil {
test.modifyTokenRequest(req, authCode)
@ -1064,7 +1246,7 @@ func hashAccessToken(accessToken string) string {
type body url.Values
func happyBody(happyAuthCode string) body {
func happyAuthcodeRequestBody(happyAuthCode string) body {
return map[string][]string{
"grant_type": {"authorization_code"},
"code": {happyAuthCode},
@ -1074,10 +1256,23 @@ func happyBody(happyAuthCode string) body {
}
}
func happyRefreshRequestBody(refreshToken string) body {
return map[string][]string{
"grant_type": {"refresh_token"},
"scope": {"openid"},
"client_id": {goodClient},
"refresh_token": {refreshToken},
}
}
func (b body) WithGrantType(grantType string) body {
return b.with("grant_type", grantType)
}
func (b body) WithRefreshToken(refreshToken string) body {
return b.with("refresh_token", refreshToken)
}
func (b body) WithClientID(clientID string) body {
return b.with("client_id", clientID)
}
@ -1086,6 +1281,10 @@ func (b body) WithAuthCode(code string) body {
return b.with("code", code)
}
func (b body) WithScope(scope string) body {
return b.with("scope", scope)
}
func (b body) WithRedirectURI(redirectURI string) body {
return b.with("redirect_uri", redirectURI)
}