refactor token_handler_test.go: easier to make more requests after initial authcode exchange
- This refactor will allow us to add new test tables for the refresh and token exchange requests, which both must come after an initial successful authcode exchange has already happened Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
parent
a9111f39af
commit
170982a688
@ -28,6 +28,7 @@ import (
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
||||
"go.pinniped.dev/internal/crud"
|
||||
"go.pinniped.dev/internal/fositestorage/accesstoken"
|
||||
@ -192,10 +193,8 @@ var (
|
||||
"status_code": 503
|
||||
}
|
||||
`)
|
||||
)
|
||||
|
||||
func TestTokenEndpoint(t *testing.T) {
|
||||
happyAuthRequest := &http.Request{
|
||||
happyAuthRequest = &http.Request{
|
||||
Form: url.Values{
|
||||
"response_type": {"code"},
|
||||
"scope": {"openid profile email"},
|
||||
@ -207,10 +206,9 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
"redirect_uri": {goodRedirectURI},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
type authcodeExchangeInputs struct {
|
||||
modifyAuthRequest func(authRequest *http.Request)
|
||||
modifyTokenRequest func(r *http.Request, authCode string)
|
||||
modifyStorage func(
|
||||
@ -238,18 +236,27 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
|
||||
wantStatus int
|
||||
wantBodyFields []string
|
||||
wantExactBody string
|
||||
wantRequestedScopes []string
|
||||
wantExactBody string
|
||||
}
|
||||
|
||||
func TestTokenEndpoint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
authcodeExchange authcodeExchangeInputs
|
||||
}{
|
||||
// happy path
|
||||
{
|
||||
name: "request is valid and tokens are issued",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyFields: []string{"id_token", "access_token", "token_type", "scope", "expires_in"}, // no refresh token
|
||||
wantRequestedScopes: []string{"openid", "profile", "email"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "openid scope was not requested from authorize endpoint",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyAuthRequest: func(authRequest *http.Request) {
|
||||
authRequest.Form.Set("scope", "profile email")
|
||||
},
|
||||
@ -257,8 +264,10 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
wantBodyFields: []string{"access_token", "token_type", "scope", "expires_in"}, // no id or refresh tokens
|
||||
wantRequestedScopes: []string{"profile", "email"},
|
||||
},
|
||||
},
|
||||
{
|
||||
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")
|
||||
},
|
||||
@ -266,8 +275,10 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
wantBodyFields: []string{"id_token", "access_token", "token_type", "scope", "expires_in", "refresh_token"}, // all possible tokens
|
||||
wantRequestedScopes: []string{"openid", "offline_access"},
|
||||
},
|
||||
},
|
||||
{
|
||||
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")
|
||||
},
|
||||
@ -275,102 +286,130 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
wantBodyFields: []string{"access_token", "token_type", "scope", "expires_in", "refresh_token"}, // no id token
|
||||
wantRequestedScopes: []string{"offline_access"},
|
||||
},
|
||||
},
|
||||
|
||||
// sad path
|
||||
{
|
||||
name: "GET method is wrong",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodGet },
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidMethodErrorBody("GET"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PUT method is wrong",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodPut },
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidMethodErrorBody("PUT"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PATCH method is wrong",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodPatch },
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidMethodErrorBody("PATCH"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DELETE method is wrong",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodDelete },
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidMethodErrorBody("DELETE"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "content type is invalid",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Header.Set("Content-Type", "text/plain") },
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeEmptyPayloadErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "payload is not valid form serialization",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = ioutil.NopCloser(strings.NewReader("this newline character is not allowed in a form serialization: \n"))
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeMissingGrantTypeErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "payload is empty",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Body = nil },
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidPayloadErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "grant type is missing in request",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithGrantType("").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeMissingGrantTypeErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "grant type is not authorization_code",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithGrantType("bogus").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidRequestErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "client id is missing in request",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithClientID("").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeMissingClientErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "client id is wrong",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithClientID("bogus").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusUnauthorized,
|
||||
wantExactBody: fositeInvalidClientErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auth code is missing in request",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithAuthCode("").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidAuthCodeErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auth code has never been valid",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithAuthCode("bogus").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidAuthCodeErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auth code is invalidated",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyStorage: func(
|
||||
t *testing.T,
|
||||
s interface {
|
||||
@ -388,32 +427,40 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeReusedAuthCodeErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redirect uri is missing in request",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithRedirectURI("").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidRedirectURIErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redirect uri is wrong",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithRedirectURI("bogus").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeInvalidRedirectURIErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pkce is missing in request",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithPKCE("").ReadCloser()
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeMissingPKCEVerifierErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pkce is wrong",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||
r.Body = happyBody(authCode).WithPKCE(
|
||||
"bogus-verifier-that-is-at-least-43-characters-for-the-sake-of-entropy",
|
||||
@ -422,29 +469,106 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantExactBody: fositeWrongPKCEVerifierErrorBody,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "private signing key for JWTs has not yet been provided by the controller who is responsible for dynamically providing it",
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
makeOathHelper: makeOauthHelperWithNilPrivateJWTSigningKey,
|
||||
wantStatus: http.StatusServiceUnavailable,
|
||||
wantExactBody: fositeTemporarilyUnavailableErrorBody,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
exchangeAuthcodeForTokens(t, test.authcodeExchange)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenEndpointWhenAuthcodeIsUsedTwice(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
authcodeExchange authcodeExchangeInputs
|
||||
wantGrantedOpenidScope bool
|
||||
wantGrantedOfflineAccessScope bool
|
||||
}{
|
||||
{
|
||||
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")
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
|
||||
wantRequestedScopes: []string{"openid", "offline_access", "profile", "email"},
|
||||
},
|
||||
wantGrantedOpenidScope: true,
|
||||
wantGrantedOfflineAccessScope: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// First call - should be successful.
|
||||
subject, rsp, authCode, secrets, oauthStore := exchangeAuthcodeForTokens(t, test.authcodeExchange)
|
||||
var parsedResponseBody map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(rsp.Body.Bytes(), &parsedResponseBody))
|
||||
|
||||
// Second call - should be unsuccessful since auth code was already used.
|
||||
//
|
||||
// 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.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rsp1 := httptest.NewRecorder()
|
||||
subject.ServeHTTP(rsp1, req)
|
||||
t.Logf("second response: %#v", rsp1)
|
||||
t.Logf("second response body: %q", rsp1.Body.String())
|
||||
require.Equal(t, http.StatusBadRequest, rsp1.Code)
|
||||
testutil.RequireEqualContentType(t, rsp1.Header().Get("Content-Type"), "application/json")
|
||||
require.JSONEq(t, fositeReusedAuthCodeErrorBody, rsp1.Body.String())
|
||||
|
||||
// This was previously invalidated by the first request, so it remains invalidated
|
||||
requireInvalidAuthCodeStorage(t, authCode, oauthStore)
|
||||
// Has now invalidated the access token that was previously handed out by the first request
|
||||
requireInvalidAccessTokenStorage(t, parsedResponseBody, oauthStore)
|
||||
// This was previously invalidated by the first request, so it remains invalidated
|
||||
requireInvalidPKCEStorage(t, authCode, oauthStore)
|
||||
// Fosite never cleans up OpenID Connect session storage, so it is still there
|
||||
requireValidOIDCStorage(t, parsedResponseBody, authCode, oauthStore, test.authcodeExchange.wantRequestedScopes, test.wantGrantedOpenidScope, test.wantGrantedOfflineAccessScope)
|
||||
|
||||
// Check that the access token and refresh token storage were both deleted, and the number of other storage objects did not change.
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: accesstoken.TypeLabelValue}, 0)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: refreshtoken.TypeLabelValue}, 0)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: storagepkce.TypeLabelValue}, 0)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func exchangeAuthcodeForTokens(t *testing.T, test authcodeExchangeInputs) (
|
||||
subject http.Handler,
|
||||
rsp *httptest.ResponseRecorder,
|
||||
authCode string,
|
||||
secrets v1.SecretInterface,
|
||||
oauthStore *oidc.KubeStorage,
|
||||
) {
|
||||
authRequest := deepCopyRequestForm(happyAuthRequest)
|
||||
if test.modifyAuthRequest != nil {
|
||||
test.modifyAuthRequest(authRequest)
|
||||
}
|
||||
|
||||
client := fake.NewSimpleClientset()
|
||||
secrets := client.CoreV1().Secrets("some-namespace")
|
||||
secrets = client.CoreV1().Secrets("some-namespace")
|
||||
|
||||
var oauthHelper fosite.OAuth2Provider
|
||||
var authCode string
|
||||
var jwtSigningKey *ecdsa.PrivateKey
|
||||
|
||||
oauthStore := oidc.NewKubeStorage(secrets)
|
||||
oauthStore = oidc.NewKubeStorage(secrets)
|
||||
if test.makeOathHelper != nil {
|
||||
oauthHelper, authCode, jwtSigningKey = test.makeOathHelper(t, authRequest, oauthStore)
|
||||
} else {
|
||||
@ -454,7 +578,7 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
if test.modifyStorage != nil {
|
||||
test.modifyStorage(t, oauthStore, authCode)
|
||||
}
|
||||
subject := NewHandler(oauthHelper)
|
||||
subject = NewHandler(oauthHelper)
|
||||
|
||||
authorizeEndpointGrantedOpenIDScope := strings.Contains(authRequest.Form.Get("scope"), "openid")
|
||||
expectedNumberOfIDSessionsStored := 0
|
||||
@ -472,7 +596,7 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
if test.modifyTokenRequest != nil {
|
||||
test.modifyTokenRequest(req, authCode)
|
||||
}
|
||||
rsp := httptest.NewRecorder()
|
||||
rsp = httptest.NewRecorder()
|
||||
|
||||
subject.ServeHTTP(rsp, req)
|
||||
t.Logf("response: %#v", rsp)
|
||||
@ -516,90 +640,8 @@ func TestTokenEndpoint(t *testing.T) {
|
||||
} else {
|
||||
require.JSONEq(t, test.wantExactBody, rsp.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("auth code is used twice", func(t *testing.T) {
|
||||
authRequest := deepCopyRequestForm(happyAuthRequest)
|
||||
authRequest.Form.Set("scope", "openid offline_access profile email")
|
||||
|
||||
wantRequestedScopes := []string{"openid", "offline_access", "profile", "email"}
|
||||
wantBodyFields := []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"}
|
||||
wantGrantedOpenidScope := true
|
||||
wantGrantedOfflineAccessScope := true
|
||||
|
||||
client := fake.NewSimpleClientset()
|
||||
secrets := client.CoreV1().Secrets("some-namespace")
|
||||
|
||||
oauthStore := oidc.NewKubeStorage(secrets)
|
||||
oauthHelper, authCode, jwtSigningKey := makeHappyOauthHelper(t, authRequest, oauthStore)
|
||||
subject := NewHandler(oauthHelper)
|
||||
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: storagepkce.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 3)
|
||||
|
||||
req := httptest.NewRequest("POST", "/path/shouldn't/matter", happyBody(authCode).ReadCloser())
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// First call - should be successful.
|
||||
rsp0 := httptest.NewRecorder()
|
||||
subject.ServeHTTP(rsp0, req)
|
||||
t.Logf("response 0: %#v", rsp0)
|
||||
t.Logf("response 0 body: %q", rsp0.Body.String())
|
||||
testutil.RequireEqualContentType(t, rsp0.Header().Get("Content-Type"), "application/json")
|
||||
require.Equal(t, http.StatusOK, rsp0.Code)
|
||||
|
||||
var parsedResponseBody map[string]interface{}
|
||||
require.NoError(t, json.Unmarshal(rsp0.Body.Bytes(), &parsedResponseBody))
|
||||
|
||||
require.ElementsMatch(t, wantBodyFields, getMapKeys(parsedResponseBody))
|
||||
|
||||
requireValidIDToken(t, parsedResponseBody, jwtSigningKey)
|
||||
|
||||
code := req.PostForm.Get("code")
|
||||
requireInvalidAuthCodeStorage(t, code, oauthStore)
|
||||
requireValidAccessTokenStorage(t, parsedResponseBody, oauthStore, wantRequestedScopes, wantGrantedOpenidScope, wantGrantedOfflineAccessScope)
|
||||
requireInvalidPKCEStorage(t, code, oauthStore)
|
||||
requireValidOIDCStorage(t, parsedResponseBody, code, oauthStore, wantRequestedScopes, wantGrantedOpenidScope, wantGrantedOfflineAccessScope)
|
||||
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: accesstoken.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: refreshtoken.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: storagepkce.TypeLabelValue}, 0)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 4)
|
||||
|
||||
// Second call - should be unsuccessful since auth code was already used.
|
||||
//
|
||||
// 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.
|
||||
rsp1 := httptest.NewRecorder()
|
||||
subject.ServeHTTP(rsp1, req)
|
||||
t.Logf("response 1: %#v", rsp1)
|
||||
t.Logf("response 1 body: %q", rsp1.Body.String())
|
||||
require.Equal(t, http.StatusBadRequest, rsp1.Code)
|
||||
testutil.RequireEqualContentType(t, rsp1.Header().Get("Content-Type"), "application/json")
|
||||
require.JSONEq(t, fositeReusedAuthCodeErrorBody, rsp1.Body.String())
|
||||
|
||||
// This was previously invalidated by the first request, so it remains invalidated
|
||||
requireInvalidAuthCodeStorage(t, code, oauthStore)
|
||||
// Has now invalidated the access token that was previously handed out by the first request
|
||||
requireInvalidAccessTokenStorage(t, parsedResponseBody, oauthStore)
|
||||
// This was previously invalidated by the first request, so it remains invalidated
|
||||
requireInvalidPKCEStorage(t, code, oauthStore)
|
||||
// Fosite never cleans up OpenID Connect session storage, so it is still there
|
||||
requireValidOIDCStorage(t, parsedResponseBody, code, oauthStore, wantRequestedScopes, wantGrantedOpenidScope, wantGrantedOfflineAccessScope)
|
||||
|
||||
// Check that the access token and refresh token storage were both deleted, and the number of other storage objects did not change.
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, 1)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: accesstoken.TypeLabelValue}, 0)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: refreshtoken.TypeLabelValue}, 0)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: storagepkce.TypeLabelValue}, 0)
|
||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 2)
|
||||
})
|
||||
return subject, rsp, authCode, secrets, oauthStore
|
||||
}
|
||||
|
||||
type body url.Values
|
||||
|
Loading…
Reference in New Issue
Block a user