Merge remote-tracking branch 'origin/token-refresh' into token-exchange-endpoint
This commit is contained in:
commit
ef3f837800
@ -28,6 +28,7 @@ import (
|
|||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/crud"
|
"go.pinniped.dev/internal/crud"
|
||||||
"go.pinniped.dev/internal/fositestorage/accesstoken"
|
"go.pinniped.dev/internal/fositestorage/accesstoken"
|
||||||
@ -192,10 +193,8 @@ var (
|
|||||||
"status_code": 503
|
"status_code": 503
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
)
|
|
||||||
|
|
||||||
func TestTokenEndpoint(t *testing.T) {
|
happyAuthRequest = &http.Request{
|
||||||
happyAuthRequest := &http.Request{
|
|
||||||
Form: url.Values{
|
Form: url.Values{
|
||||||
"response_type": {"code"},
|
"response_type": {"code"},
|
||||||
"scope": {"openid profile email"},
|
"scope": {"openid profile email"},
|
||||||
@ -207,399 +206,442 @@ func TestTokenEndpoint(t *testing.T) {
|
|||||||
"redirect_uri": {goodRedirectURI},
|
"redirect_uri": {goodRedirectURI},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type authcodeExchangeInputs struct {
|
||||||
|
modifyAuthRequest func(authRequest *http.Request)
|
||||||
|
modifyTokenRequest func(r *http.Request, authCode string)
|
||||||
|
modifyStorage func(
|
||||||
|
t *testing.T,
|
||||||
|
s interface {
|
||||||
|
oauth2.TokenRevocationStorage
|
||||||
|
oauth2.CoreStorage
|
||||||
|
openid.OpenIDConnectRequestStorage
|
||||||
|
pkce.PKCERequestStorage
|
||||||
|
fosite.ClientManager
|
||||||
|
},
|
||||||
|
authCode string,
|
||||||
|
)
|
||||||
|
makeOathHelper func(
|
||||||
|
t *testing.T,
|
||||||
|
authRequest *http.Request,
|
||||||
|
store interface {
|
||||||
|
oauth2.TokenRevocationStorage
|
||||||
|
oauth2.CoreStorage
|
||||||
|
openid.OpenIDConnectRequestStorage
|
||||||
|
pkce.PKCERequestStorage
|
||||||
|
fosite.ClientManager
|
||||||
|
},
|
||||||
|
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey)
|
||||||
|
|
||||||
|
wantStatus int
|
||||||
|
wantBodyFields []string
|
||||||
|
wantRequestedScopes []string
|
||||||
|
wantExactBody string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenEndpoint(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
authcodeExchange authcodeExchangeInputs
|
||||||
modifyAuthRequest func(authRequest *http.Request)
|
|
||||||
modifyTokenRequest func(r *http.Request, authCode string)
|
|
||||||
modifyStorage func(
|
|
||||||
t *testing.T,
|
|
||||||
s interface {
|
|
||||||
oauth2.TokenRevocationStorage
|
|
||||||
oauth2.CoreStorage
|
|
||||||
openid.OpenIDConnectRequestStorage
|
|
||||||
pkce.PKCERequestStorage
|
|
||||||
fosite.ClientManager
|
|
||||||
},
|
|
||||||
authCode string,
|
|
||||||
)
|
|
||||||
makeOathHelper func(
|
|
||||||
t *testing.T,
|
|
||||||
authRequest *http.Request,
|
|
||||||
store interface {
|
|
||||||
oauth2.TokenRevocationStorage
|
|
||||||
oauth2.CoreStorage
|
|
||||||
openid.OpenIDConnectRequestStorage
|
|
||||||
pkce.PKCERequestStorage
|
|
||||||
fosite.ClientManager
|
|
||||||
},
|
|
||||||
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey)
|
|
||||||
|
|
||||||
wantStatus int
|
|
||||||
wantBodyFields []string
|
|
||||||
wantExactBody string
|
|
||||||
wantRequestedScopes []string
|
|
||||||
}{
|
}{
|
||||||
// happy path
|
// happy path
|
||||||
{
|
{
|
||||||
name: "request is valid and tokens are issued",
|
name: "request is valid and tokens are issued",
|
||||||
wantStatus: http.StatusOK,
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
wantBodyFields: []string{"id_token", "access_token", "token_type", "scope", "expires_in"}, // no refresh token
|
wantStatus: http.StatusOK,
|
||||||
wantRequestedScopes: []string{"openid", "profile", "email"},
|
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",
|
name: "openid scope was not requested from authorize endpoint",
|
||||||
modifyAuthRequest: func(authRequest *http.Request) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
authRequest.Form.Set("scope", "profile email")
|
modifyAuthRequest: func(authRequest *http.Request) {
|
||||||
|
authRequest.Form.Set("scope", "profile email")
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantBodyFields: []string{"access_token", "token_type", "scope", "expires_in"}, // no id or refresh tokens
|
||||||
|
wantRequestedScopes: []string{"profile", "email"},
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusOK,
|
|
||||||
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",
|
name: "offline_access and openid scopes were requested and granted from authorize endpoint",
|
||||||
modifyAuthRequest: func(authRequest *http.Request) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
authRequest.Form.Set("scope", "openid offline_access")
|
modifyAuthRequest: func(authRequest *http.Request) {
|
||||||
|
authRequest.Form.Set("scope", "openid offline_access")
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantBodyFields: []string{"id_token", "access_token", "token_type", "scope", "expires_in", "refresh_token"}, // all possible tokens
|
||||||
|
wantRequestedScopes: []string{"openid", "offline_access"},
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusOK,
|
|
||||||
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",
|
name: "offline_access (without openid scope) was requested and granted from authorize endpoint",
|
||||||
modifyAuthRequest: func(authRequest *http.Request) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
authRequest.Form.Set("scope", "offline_access")
|
modifyAuthRequest: func(authRequest *http.Request) {
|
||||||
|
authRequest.Form.Set("scope", "offline_access")
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantBodyFields: []string{"access_token", "token_type", "scope", "expires_in", "refresh_token"}, // no id token
|
||||||
|
wantRequestedScopes: []string{"offline_access"},
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusOK,
|
|
||||||
wantBodyFields: []string{"access_token", "token_type", "scope", "expires_in", "refresh_token"}, // no id token
|
|
||||||
wantRequestedScopes: []string{"offline_access"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// sad path
|
// sad path
|
||||||
{
|
{
|
||||||
name: "GET method is wrong",
|
name: "GET method is wrong",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodGet },
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
wantStatus: http.StatusBadRequest,
|
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodGet },
|
||||||
wantExactBody: fositeInvalidMethodErrorBody("GET"),
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidMethodErrorBody("GET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PUT method is wrong",
|
name: "PUT method is wrong",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodPut },
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
wantStatus: http.StatusBadRequest,
|
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodPut },
|
||||||
wantExactBody: fositeInvalidMethodErrorBody("PUT"),
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidMethodErrorBody("PUT"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PATCH method is wrong",
|
name: "PATCH method is wrong",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodPatch },
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
wantStatus: http.StatusBadRequest,
|
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodPatch },
|
||||||
wantExactBody: fositeInvalidMethodErrorBody("PATCH"),
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidMethodErrorBody("PATCH"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "DELETE method is wrong",
|
name: "DELETE method is wrong",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodDelete },
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
wantStatus: http.StatusBadRequest,
|
modifyTokenRequest: func(r *http.Request, authCode string) { r.Method = http.MethodDelete },
|
||||||
wantExactBody: fositeInvalidMethodErrorBody("DELETE"),
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidMethodErrorBody("DELETE"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "content type is invalid",
|
name: "content type is invalid",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Header.Set("Content-Type", "text/plain") },
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
wantStatus: http.StatusBadRequest,
|
modifyTokenRequest: func(r *http.Request, authCode string) { r.Header.Set("Content-Type", "text/plain") },
|
||||||
wantExactBody: fositeEmptyPayloadErrorBody,
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeEmptyPayloadErrorBody,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "payload is not valid form serialization",
|
name: "payload is not valid form serialization",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = ioutil.NopCloser(strings.NewReader("this newline character is not allowed in a form serialization: \n"))
|
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,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeMissingGrantTypeErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "payload is empty",
|
name: "payload is empty",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) { r.Body = nil },
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
wantStatus: http.StatusBadRequest,
|
modifyTokenRequest: func(r *http.Request, authCode string) { r.Body = nil },
|
||||||
wantExactBody: fositeInvalidPayloadErrorBody,
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidPayloadErrorBody,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "grant type is missing in request",
|
name: "grant type is missing in request",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithGrantType("").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithGrantType("").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeMissingGrantTypeErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeMissingGrantTypeErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "grant type is not authorization_code",
|
name: "grant type is not authorization_code",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithGrantType("bogus").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithGrantType("bogus").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidRequestErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeInvalidRequestErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "client id is missing in request",
|
name: "client id is missing in request",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithClientID("").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithClientID("").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeMissingClientErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeMissingClientErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "client id is wrong",
|
name: "client id is wrong",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithClientID("bogus").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithClientID("bogus").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusUnauthorized,
|
||||||
|
wantExactBody: fositeInvalidClientErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
wantExactBody: fositeInvalidClientErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "auth code is missing in request",
|
name: "auth code is missing in request",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithAuthCode("").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithAuthCode("").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidAuthCodeErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeInvalidAuthCodeErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "auth code has never been valid",
|
name: "auth code has never been valid",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithAuthCode("bogus").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithAuthCode("bogus").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidAuthCodeErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeInvalidAuthCodeErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "auth code is invalidated",
|
name: "auth code is invalidated",
|
||||||
modifyStorage: func(
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
t *testing.T,
|
modifyStorage: func(
|
||||||
s interface {
|
t *testing.T,
|
||||||
oauth2.TokenRevocationStorage
|
s interface {
|
||||||
oauth2.CoreStorage
|
oauth2.TokenRevocationStorage
|
||||||
openid.OpenIDConnectRequestStorage
|
oauth2.CoreStorage
|
||||||
pkce.PKCERequestStorage
|
openid.OpenIDConnectRequestStorage
|
||||||
fosite.ClientManager
|
pkce.PKCERequestStorage
|
||||||
|
fosite.ClientManager
|
||||||
|
},
|
||||||
|
authCode string,
|
||||||
|
) {
|
||||||
|
err := s.InvalidateAuthorizeCodeSession(context.Background(), getFositeDataSignature(t, authCode))
|
||||||
|
require.NoError(t, err)
|
||||||
},
|
},
|
||||||
authCode string,
|
wantStatus: http.StatusBadRequest,
|
||||||
) {
|
wantExactBody: fositeReusedAuthCodeErrorBody,
|
||||||
err := s.InvalidateAuthorizeCodeSession(context.Background(), getFositeDataSignature(t, authCode))
|
|
||||||
require.NoError(t, err)
|
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeReusedAuthCodeErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "redirect uri is missing in request",
|
name: "redirect uri is missing in request",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithRedirectURI("").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithRedirectURI("").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidRedirectURIErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeInvalidRedirectURIErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "redirect uri is wrong",
|
name: "redirect uri is wrong",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithRedirectURI("bogus").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithRedirectURI("bogus").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeInvalidRedirectURIErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeInvalidRedirectURIErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pkce is missing in request",
|
name: "pkce is missing in request",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithPKCE("").ReadCloser()
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
|
r.Body = happyBody(authCode).WithPKCE("").ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeMissingPKCEVerifierErrorBody,
|
||||||
},
|
},
|
||||||
wantStatus: http.StatusBadRequest,
|
|
||||||
wantExactBody: fositeMissingPKCEVerifierErrorBody,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pkce is wrong",
|
name: "pkce is wrong",
|
||||||
modifyTokenRequest: func(r *http.Request, authCode string) {
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
r.Body = happyBody(authCode).WithPKCE(
|
modifyTokenRequest: func(r *http.Request, authCode string) {
|
||||||
"bogus-verifier-that-is-at-least-43-characters-for-the-sake-of-entropy",
|
r.Body = happyBody(authCode).WithPKCE(
|
||||||
).ReadCloser()
|
"bogus-verifier-that-is-at-least-43-characters-for-the-sake-of-entropy",
|
||||||
|
).ReadCloser()
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusBadRequest,
|
||||||
|
wantExactBody: fositeWrongPKCEVerifierErrorBody,
|
||||||
},
|
},
|
||||||
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",
|
name: "private signing key for JWTs has not yet been provided by the controller who is responsible for dynamically providing it",
|
||||||
makeOathHelper: makeOauthHelperWithNilPrivateJWTSigningKey,
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
wantStatus: http.StatusServiceUnavailable,
|
makeOathHelper: makeOauthHelperWithNilPrivateJWTSigningKey,
|
||||||
wantExactBody: fositeTemporarilyUnavailableErrorBody,
|
wantStatus: http.StatusServiceUnavailable,
|
||||||
|
wantExactBody: fositeTemporarilyUnavailableErrorBody,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
authRequest := deepCopyRequestForm(happyAuthRequest)
|
exchangeAuthcodeForTokens(t, test.authcodeExchange)
|
||||||
if test.modifyAuthRequest != nil {
|
|
||||||
test.modifyAuthRequest(authRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
client := fake.NewSimpleClientset()
|
|
||||||
secrets := client.CoreV1().Secrets("some-namespace")
|
|
||||||
|
|
||||||
var oauthHelper fosite.OAuth2Provider
|
|
||||||
var authCode string
|
|
||||||
var jwtSigningKey *ecdsa.PrivateKey
|
|
||||||
|
|
||||||
oauthStore := oidc.NewKubeStorage(secrets)
|
|
||||||
if test.makeOathHelper != nil {
|
|
||||||
oauthHelper, authCode, jwtSigningKey = test.makeOathHelper(t, authRequest, oauthStore)
|
|
||||||
} else {
|
|
||||||
oauthHelper, authCode, jwtSigningKey = makeHappyOauthHelper(t, authRequest, oauthStore)
|
|
||||||
}
|
|
||||||
|
|
||||||
if test.modifyStorage != nil {
|
|
||||||
test.modifyStorage(t, oauthStore, authCode)
|
|
||||||
}
|
|
||||||
subject := NewHandler(oauthHelper)
|
|
||||||
|
|
||||||
authorizeEndpointGrantedOpenIDScope := strings.Contains(authRequest.Form.Get("scope"), "openid")
|
|
||||||
expectedNumberOfIDSessionsStored := 0
|
|
||||||
if authorizeEndpointGrantedOpenIDScope {
|
|
||||||
expectedNumberOfIDSessionsStored = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
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}, expectedNumberOfIDSessionsStored)
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 2+expectedNumberOfIDSessionsStored)
|
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "/path/shouldn't/matter", happyBody(authCode).ReadCloser())
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
if test.modifyTokenRequest != nil {
|
|
||||||
test.modifyTokenRequest(req, authCode)
|
|
||||||
}
|
|
||||||
rsp := httptest.NewRecorder()
|
|
||||||
|
|
||||||
subject.ServeHTTP(rsp, req)
|
|
||||||
t.Logf("response: %#v", rsp)
|
|
||||||
t.Logf("response body: %q", rsp.Body.String())
|
|
||||||
|
|
||||||
require.Equal(t, test.wantStatus, rsp.Code)
|
|
||||||
testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), "application/json")
|
|
||||||
if test.wantBodyFields != nil {
|
|
||||||
var parsedResponseBody map[string]interface{}
|
|
||||||
require.NoError(t, json.Unmarshal(rsp.Body.Bytes(), &parsedResponseBody))
|
|
||||||
require.ElementsMatch(t, test.wantBodyFields, getMapKeys(parsedResponseBody))
|
|
||||||
|
|
||||||
wantIDToken := contains(test.wantBodyFields, "id_token")
|
|
||||||
wantRefreshToken := contains(test.wantBodyFields, "refresh_token")
|
|
||||||
|
|
||||||
code := req.PostForm.Get("code")
|
|
||||||
requireInvalidAuthCodeStorage(t, code, oauthStore)
|
|
||||||
requireValidAccessTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, wantIDToken, wantRefreshToken)
|
|
||||||
requireInvalidPKCEStorage(t, code, oauthStore)
|
|
||||||
requireValidOIDCStorage(t, parsedResponseBody, code, oauthStore, test.wantRequestedScopes, wantIDToken, wantRefreshToken)
|
|
||||||
|
|
||||||
expectedNumberOfRefreshTokenSessionsStored := 0
|
|
||||||
if wantRefreshToken {
|
|
||||||
expectedNumberOfRefreshTokenSessionsStored = 1
|
|
||||||
}
|
|
||||||
expectedNumberOfIDSessionsStored = 0
|
|
||||||
if wantIDToken {
|
|
||||||
expectedNumberOfIDSessionsStored = 1
|
|
||||||
requireValidIDToken(t, parsedResponseBody, jwtSigningKey)
|
|
||||||
}
|
|
||||||
if wantRefreshToken {
|
|
||||||
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, wantIDToken, wantRefreshToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: accesstoken.TypeLabelValue}, 1)
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: storagepkce.TypeLabelValue}, 0)
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: refreshtoken.TypeLabelValue}, expectedNumberOfRefreshTokenSessionsStored)
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, expectedNumberOfIDSessionsStored)
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 2+expectedNumberOfRefreshTokenSessionsStored+expectedNumberOfIDSessionsStored)
|
|
||||||
} else {
|
|
||||||
require.JSONEq(t, test.wantExactBody, rsp.Body.String())
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("auth code is used twice", func(t *testing.T) {
|
func TestTokenEndpointWhenAuthcodeIsUsedTwice(t *testing.T) {
|
||||||
authRequest := deepCopyRequestForm(happyAuthRequest)
|
tests := []struct {
|
||||||
authRequest.Form.Set("scope", "openid offline_access profile email")
|
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))
|
||||||
|
|
||||||
wantRequestedScopes := []string{"openid", "offline_access", "profile", "email"}
|
// Second call - should be unsuccessful since auth code was already used.
|
||||||
wantBodyFields := []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"}
|
//
|
||||||
wantGrantedOpenidScope := true
|
// Fosite will also revoke the access token as is recommended by the OIDC spec. Currently, we don't
|
||||||
wantGrantedOfflineAccessScope := true
|
// 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())
|
||||||
|
|
||||||
client := fake.NewSimpleClientset()
|
// This was previously invalidated by the first request, so it remains invalidated
|
||||||
secrets := client.CoreV1().Secrets("some-namespace")
|
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)
|
||||||
|
|
||||||
oauthStore := oidc.NewKubeStorage(secrets)
|
// Check that the access token and refresh token storage were both deleted, and the number of other storage objects did not change.
|
||||||
oauthHelper, authCode, jwtSigningKey := makeHappyOauthHelper(t, authRequest, oauthStore)
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
||||||
subject := NewHandler(oauthHelper)
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
func exchangeAuthcodeForTokens(t *testing.T, test authcodeExchangeInputs) (
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: storagepkce.TypeLabelValue}, 1)
|
subject http.Handler,
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, 1)
|
rsp *httptest.ResponseRecorder,
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 3)
|
authCode string,
|
||||||
|
secrets v1.SecretInterface,
|
||||||
|
oauthStore *oidc.KubeStorage,
|
||||||
|
) {
|
||||||
|
authRequest := deepCopyRequestForm(happyAuthRequest)
|
||||||
|
if test.modifyAuthRequest != nil {
|
||||||
|
test.modifyAuthRequest(authRequest)
|
||||||
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "/path/shouldn't/matter", happyBody(authCode).ReadCloser())
|
client := fake.NewSimpleClientset()
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
secrets = client.CoreV1().Secrets("some-namespace")
|
||||||
|
|
||||||
// First call - should be successful.
|
var oauthHelper fosite.OAuth2Provider
|
||||||
rsp0 := httptest.NewRecorder()
|
var jwtSigningKey *ecdsa.PrivateKey
|
||||||
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)
|
|
||||||
|
|
||||||
|
oauthStore = oidc.NewKubeStorage(secrets)
|
||||||
|
if test.makeOathHelper != nil {
|
||||||
|
oauthHelper, authCode, jwtSigningKey = test.makeOathHelper(t, authRequest, oauthStore)
|
||||||
|
} else {
|
||||||
|
oauthHelper, authCode, jwtSigningKey = makeHappyOauthHelper(t, authRequest, oauthStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.modifyStorage != nil {
|
||||||
|
test.modifyStorage(t, oauthStore, authCode)
|
||||||
|
}
|
||||||
|
subject = NewHandler(oauthHelper)
|
||||||
|
|
||||||
|
authorizeEndpointGrantedOpenIDScope := strings.Contains(authRequest.Form.Get("scope"), "openid")
|
||||||
|
expectedNumberOfIDSessionsStored := 0
|
||||||
|
if authorizeEndpointGrantedOpenIDScope {
|
||||||
|
expectedNumberOfIDSessionsStored = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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}, expectedNumberOfIDSessionsStored)
|
||||||
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 2+expectedNumberOfIDSessionsStored)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("POST", "/path/shouldn't/matter", happyBody(authCode).ReadCloser())
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if test.modifyTokenRequest != nil {
|
||||||
|
test.modifyTokenRequest(req, authCode)
|
||||||
|
}
|
||||||
|
rsp = httptest.NewRecorder()
|
||||||
|
|
||||||
|
subject.ServeHTTP(rsp, req)
|
||||||
|
t.Logf("response: %#v", rsp)
|
||||||
|
t.Logf("response body: %q", rsp.Body.String())
|
||||||
|
|
||||||
|
require.Equal(t, test.wantStatus, rsp.Code)
|
||||||
|
testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), "application/json")
|
||||||
|
if test.wantBodyFields != nil {
|
||||||
var parsedResponseBody map[string]interface{}
|
var parsedResponseBody map[string]interface{}
|
||||||
require.NoError(t, json.Unmarshal(rsp0.Body.Bytes(), &parsedResponseBody))
|
require.NoError(t, json.Unmarshal(rsp.Body.Bytes(), &parsedResponseBody))
|
||||||
|
require.ElementsMatch(t, test.wantBodyFields, getMapKeys(parsedResponseBody))
|
||||||
|
|
||||||
require.ElementsMatch(t, wantBodyFields, getMapKeys(parsedResponseBody))
|
wantIDToken := contains(test.wantBodyFields, "id_token")
|
||||||
|
wantRefreshToken := contains(test.wantBodyFields, "refresh_token")
|
||||||
requireValidIDToken(t, parsedResponseBody, jwtSigningKey)
|
|
||||||
|
|
||||||
code := req.PostForm.Get("code")
|
code := req.PostForm.Get("code")
|
||||||
requireInvalidAuthCodeStorage(t, code, oauthStore)
|
requireInvalidAuthCodeStorage(t, code, oauthStore)
|
||||||
requireValidAccessTokenStorage(t, parsedResponseBody, oauthStore, wantRequestedScopes, wantGrantedOpenidScope, wantGrantedOfflineAccessScope)
|
requireValidAccessTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, wantIDToken, wantRefreshToken)
|
||||||
requireInvalidPKCEStorage(t, code, oauthStore)
|
requireInvalidPKCEStorage(t, code, oauthStore)
|
||||||
requireValidOIDCStorage(t, parsedResponseBody, code, oauthStore, wantRequestedScopes, wantGrantedOpenidScope, wantGrantedOfflineAccessScope)
|
requireValidOIDCStorage(t, parsedResponseBody, code, oauthStore, test.wantRequestedScopes, wantIDToken, wantRefreshToken)
|
||||||
|
|
||||||
|
expectedNumberOfRefreshTokenSessionsStored := 0
|
||||||
|
if wantRefreshToken {
|
||||||
|
expectedNumberOfRefreshTokenSessionsStored = 1
|
||||||
|
}
|
||||||
|
expectedNumberOfIDSessionsStored = 0
|
||||||
|
if wantIDToken {
|
||||||
|
expectedNumberOfIDSessionsStored = 1
|
||||||
|
requireValidIDToken(t, parsedResponseBody, jwtSigningKey)
|
||||||
|
}
|
||||||
|
if wantRefreshToken {
|
||||||
|
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, wantIDToken, wantRefreshToken)
|
||||||
|
}
|
||||||
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
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: 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{crud.SecretLabelKey: storagepkce.TypeLabelValue}, 0)
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 4)
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: refreshtoken.TypeLabelValue}, expectedNumberOfRefreshTokenSessionsStored)
|
||||||
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, expectedNumberOfIDSessionsStored)
|
||||||
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{}, 2+expectedNumberOfRefreshTokenSessionsStored+expectedNumberOfIDSessionsStored)
|
||||||
|
} else {
|
||||||
|
require.JSONEq(t, test.wantExactBody, rsp.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
// Second call - should be unsuccessful since auth code was already used.
|
return subject, rsp, authCode, secrets, oauthStore
|
||||||
//
|
|
||||||
// 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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type body url.Values
|
type body url.Values
|
||||||
|
Loading…
Reference in New Issue
Block a user