wip
This commit is contained in:
parent
d7b5f4d4ea
commit
bfe8dc11ce
@ -74,6 +74,7 @@ func MakeDownstreamSession(
|
|||||||
extras[oidcapi.IDTokenClaimGroups] = groups
|
extras[oidcapi.IDTokenClaimGroups] = groups
|
||||||
}
|
}
|
||||||
if len(additionalClaims) > 0 {
|
if len(additionalClaims) > 0 {
|
||||||
|
// TODO: make "additionalClaims" a string constant, possibly in oidcapi?
|
||||||
extras["additionalClaims"] = additionalClaims
|
extras["additionalClaims"] = additionalClaims
|
||||||
}
|
}
|
||||||
openIDSession.IDTokenClaims().Extra = extras
|
openIDSession.IDTokenClaims().Extra = extras
|
||||||
|
@ -285,6 +285,7 @@ type tokenEndpointResponseExpectedValues struct {
|
|||||||
wantUpstreamOIDCValidateTokenCall *expectedUpstreamValidateTokens
|
wantUpstreamOIDCValidateTokenCall *expectedUpstreamValidateTokens
|
||||||
wantCustomSessionDataStored *psession.CustomSessionData
|
wantCustomSessionDataStored *psession.CustomSessionData
|
||||||
wantWarnings []RecordedWarning
|
wantWarnings []RecordedWarning
|
||||||
|
wantAdditionalClaims map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type authcodeExchangeInputs struct {
|
type authcodeExchangeInputs struct {
|
||||||
@ -297,6 +298,7 @@ type authcodeExchangeInputs struct {
|
|||||||
)
|
)
|
||||||
makeOathHelper OauthHelperFactoryFunc
|
makeOathHelper OauthHelperFactoryFunc
|
||||||
customSessionData *psession.CustomSessionData
|
customSessionData *psession.CustomSessionData
|
||||||
|
modifySession func(*psession.PinnipedSession)
|
||||||
want tokenEndpointResponseExpectedValues
|
want tokenEndpointResponseExpectedValues
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,6 +346,33 @@ func TestTokenEndpointAuthcodeExchange(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "request is valid and tokens are issued with additional claims",
|
||||||
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
|
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid profile email username groups") },
|
||||||
|
modifySession: func(session *psession.PinnipedSession) {
|
||||||
|
session.IDTokenClaims().Extra["additionalClaims"] = map[string]interface{}{
|
||||||
|
"upstream1": "value1",
|
||||||
|
"upstream2": "value2",
|
||||||
|
"upstream3": "value3",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
want: tokenEndpointResponseExpectedValues{
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantClientID: pinnipedCLIClientID,
|
||||||
|
wantSuccessBodyFields: []string{"id_token", "access_token", "token_type", "scope", "expires_in"}, // no refresh token
|
||||||
|
wantRequestedScopes: []string{"openid", "profile", "email", "username", "groups"},
|
||||||
|
wantGrantedScopes: []string{"openid", "username", "groups"},
|
||||||
|
wantUsername: goodUsername,
|
||||||
|
wantGroups: goodGroups,
|
||||||
|
wantAdditionalClaims: map[string]interface{}{
|
||||||
|
"upstream1": "value1",
|
||||||
|
"upstream2": "value2",
|
||||||
|
"upstream3": "value3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "request is valid and tokens are issued for dynamic client",
|
name: "request is valid and tokens are issued for dynamic client",
|
||||||
kubeResources: addFullyCapableDynamicClientAndSecretToKubeResources,
|
kubeResources: addFullyCapableDynamicClientAndSecretToKubeResources,
|
||||||
@ -3853,10 +3882,10 @@ func exchangeAuthcodeForTokens(
|
|||||||
// Use lower minimum required bcrypt cost than we would use in production to keep unit the tests fast.
|
// Use lower minimum required bcrypt cost than we would use in production to keep unit the tests fast.
|
||||||
oauthStore = oidc.NewKubeStorage(secrets, oidcClientsClient, oidc.DefaultOIDCTimeoutsConfiguration(), bcrypt.MinCost)
|
oauthStore = oidc.NewKubeStorage(secrets, oidcClientsClient, oidc.DefaultOIDCTimeoutsConfiguration(), bcrypt.MinCost)
|
||||||
if test.makeOathHelper != nil {
|
if test.makeOathHelper != nil {
|
||||||
oauthHelper, authCode, jwtSigningKey = test.makeOathHelper(t, authRequest, oauthStore, test.customSessionData)
|
oauthHelper, authCode, jwtSigningKey = test.makeOathHelper(t, authRequest, oauthStore, test.customSessionData, test.modifySession)
|
||||||
} else {
|
} else {
|
||||||
// Note that makeHappyOauthHelper() calls simulateAuthEndpointHavingAlreadyRun() to preload the session storage.
|
// Note that makeHappyOauthHelper() calls simulateAuthEndpointHavingAlreadyRun() to preload the session storage.
|
||||||
oauthHelper, authCode, jwtSigningKey = makeHappyOauthHelper(t, authRequest, oauthStore, test.customSessionData)
|
oauthHelper, authCode, jwtSigningKey = makeHappyOauthHelper(t, authRequest, oauthStore, test.customSessionData, test.modifySession)
|
||||||
}
|
}
|
||||||
|
|
||||||
if test.modifyStorage != nil {
|
if test.modifyStorage != nil {
|
||||||
@ -3948,7 +3977,7 @@ func requireTokenEndpointBehavior(
|
|||||||
expectedNumberOfIDSessionsStored := 0
|
expectedNumberOfIDSessionsStored := 0
|
||||||
if wantIDToken {
|
if wantIDToken {
|
||||||
expectedNumberOfIDSessionsStored = 1
|
expectedNumberOfIDSessionsStored = 1
|
||||||
requireValidIDToken(t, parsedResponseBody, jwtSigningKey, test.wantClientID, wantNonceValueInIDToken, test.wantUsername, test.wantGroups, parsedResponseBody["access_token"].(string), requestTime)
|
requireValidIDToken(t, parsedResponseBody, jwtSigningKey, test.wantClientID, wantNonceValueInIDToken, test.wantUsername, test.wantGroups, test.wantAdditionalClaims, parsedResponseBody["access_token"].(string), requestTime)
|
||||||
}
|
}
|
||||||
if wantRefreshToken {
|
if wantRefreshToken {
|
||||||
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantClientID, test.wantRequestedScopes, test.wantGrantedScopes, test.wantUsername, test.wantGroups, test.wantCustomSessionDataStored, secrets, requestTime)
|
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantClientID, test.wantRequestedScopes, test.wantGrantedScopes, test.wantUsername, test.wantGroups, test.wantCustomSessionDataStored, secrets, requestTime)
|
||||||
@ -4058,6 +4087,7 @@ type OauthHelperFactoryFunc func(
|
|||||||
authRequest *http.Request,
|
authRequest *http.Request,
|
||||||
store fositestoragei.AllFositeStorage,
|
store fositestoragei.AllFositeStorage,
|
||||||
initialCustomSessionData *psession.CustomSessionData,
|
initialCustomSessionData *psession.CustomSessionData,
|
||||||
|
sessionModifier func(session *psession.PinnipedSession),
|
||||||
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey)
|
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey)
|
||||||
|
|
||||||
func makeHappyOauthHelper(
|
func makeHappyOauthHelper(
|
||||||
@ -4065,12 +4095,13 @@ func makeHappyOauthHelper(
|
|||||||
authRequest *http.Request,
|
authRequest *http.Request,
|
||||||
store fositestoragei.AllFositeStorage,
|
store fositestoragei.AllFositeStorage,
|
||||||
initialCustomSessionData *psession.CustomSessionData,
|
initialCustomSessionData *psession.CustomSessionData,
|
||||||
|
sessionModifier func(session *psession.PinnipedSession),
|
||||||
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey) {
|
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
|
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
|
||||||
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper, initialCustomSessionData)
|
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper, initialCustomSessionData, sessionModifier)
|
||||||
return oauthHelper, authResponder.GetCode(), jwtSigningKey
|
return oauthHelper, authResponder.GetCode(), jwtSigningKey
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4092,12 +4123,13 @@ func makeOauthHelperWithJWTKeyThatWorksOnlyOnce(
|
|||||||
authRequest *http.Request,
|
authRequest *http.Request,
|
||||||
store fositestoragei.AllFositeStorage,
|
store fositestoragei.AllFositeStorage,
|
||||||
initialCustomSessionData *psession.CustomSessionData,
|
initialCustomSessionData *psession.CustomSessionData,
|
||||||
|
modifySession func(session *psession.PinnipedSession),
|
||||||
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey) {
|
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
|
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
|
||||||
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, &singleUseJWKProvider{DynamicJWKSProvider: jwkProvider}, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, &singleUseJWKProvider{DynamicJWKSProvider: jwkProvider}, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper, initialCustomSessionData)
|
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper, initialCustomSessionData, modifySession)
|
||||||
return oauthHelper, authResponder.GetCode(), jwtSigningKey
|
return oauthHelper, authResponder.GetCode(), jwtSigningKey
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4106,12 +4138,13 @@ func makeOauthHelperWithNilPrivateJWTSigningKey(
|
|||||||
authRequest *http.Request,
|
authRequest *http.Request,
|
||||||
store fositestoragei.AllFositeStorage,
|
store fositestoragei.AllFositeStorage,
|
||||||
initialCustomSessionData *psession.CustomSessionData,
|
initialCustomSessionData *psession.CustomSessionData,
|
||||||
|
modifySession func(session *psession.PinnipedSession),
|
||||||
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey) {
|
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
jwkProvider := jwks.NewDynamicJWKSProvider() // empty provider which contains no signing key for this issuer
|
jwkProvider := jwks.NewDynamicJWKSProvider() // empty provider which contains no signing key for this issuer
|
||||||
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper, initialCustomSessionData)
|
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper, initialCustomSessionData, modifySession)
|
||||||
return oauthHelper, authResponder.GetCode(), nil
|
return oauthHelper, authResponder.GetCode(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4121,6 +4154,7 @@ func simulateAuthEndpointHavingAlreadyRun(
|
|||||||
authRequest *http.Request,
|
authRequest *http.Request,
|
||||||
oauthHelper fosite.OAuth2Provider,
|
oauthHelper fosite.OAuth2Provider,
|
||||||
initialCustomSessionData *psession.CustomSessionData,
|
initialCustomSessionData *psession.CustomSessionData,
|
||||||
|
modifySession func(session *psession.PinnipedSession),
|
||||||
) fosite.AuthorizeResponder {
|
) fosite.AuthorizeResponder {
|
||||||
// We only set the fields in the session that Fosite wants us to set.
|
// We only set the fields in the session that Fosite wants us to set.
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -4137,6 +4171,10 @@ func simulateAuthEndpointHavingAlreadyRun(
|
|||||||
},
|
},
|
||||||
Custom: initialCustomSessionData,
|
Custom: initialCustomSessionData,
|
||||||
}
|
}
|
||||||
|
if modifySession != nil {
|
||||||
|
modifySession(session)
|
||||||
|
}
|
||||||
|
|
||||||
authRequester, err := oauthHelper.NewAuthorizeRequest(ctx, authRequest)
|
authRequester, err := oauthHelper.NewAuthorizeRequest(ctx, authRequest)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if strings.Contains(authRequest.Form.Get("scope"), "openid") {
|
if strings.Contains(authRequest.Form.Get("scope"), "openid") {
|
||||||
@ -4518,6 +4556,7 @@ func requireValidIDToken(
|
|||||||
wantNonceValueInIDToken bool,
|
wantNonceValueInIDToken bool,
|
||||||
wantUsernameInIDToken string,
|
wantUsernameInIDToken string,
|
||||||
wantGroupsInIDToken []string,
|
wantGroupsInIDToken []string,
|
||||||
|
wantAdditionalClaims map[string]interface{},
|
||||||
actualAccessToken string,
|
actualAccessToken string,
|
||||||
requestTime time.Time,
|
requestTime time.Time,
|
||||||
) {
|
) {
|
||||||
@ -4532,18 +4571,19 @@ func requireValidIDToken(
|
|||||||
token := oidctestutil.VerifyECDSAIDToken(t, goodIssuer, wantClientID, jwtSigningKey, idTokenString)
|
token := oidctestutil.VerifyECDSAIDToken(t, goodIssuer, wantClientID, jwtSigningKey, idTokenString)
|
||||||
|
|
||||||
var claims struct {
|
var claims struct {
|
||||||
Subject string `json:"sub"`
|
Subject string `json:"sub"`
|
||||||
Audience []string `json:"aud"`
|
Audience []string `json:"aud"`
|
||||||
Issuer string `json:"iss"`
|
Issuer string `json:"iss"`
|
||||||
JTI string `json:"jti"`
|
JTI string `json:"jti"`
|
||||||
Nonce string `json:"nonce"`
|
Nonce string `json:"nonce"`
|
||||||
AccessTokenHash string `json:"at_hash"`
|
AccessTokenHash string `json:"at_hash"`
|
||||||
ExpiresAt int64 `json:"exp"`
|
ExpiresAt int64 `json:"exp"`
|
||||||
IssuedAt int64 `json:"iat"`
|
IssuedAt int64 `json:"iat"`
|
||||||
RequestedAt int64 `json:"rat"`
|
RequestedAt int64 `json:"rat"`
|
||||||
AuthTime int64 `json:"auth_time"`
|
AuthTime int64 `json:"auth_time"`
|
||||||
Groups []string `json:"groups"`
|
Groups []string `json:"groups"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
AdditionalClaims map[string]interface{} `json:"additionalClaims"`
|
||||||
}
|
}
|
||||||
|
|
||||||
idTokenFields := []string{"sub", "aud", "iss", "jti", "auth_time", "exp", "iat", "rat", "azp", "at_hash"}
|
idTokenFields := []string{"sub", "aud", "iss", "jti", "auth_time", "exp", "iat", "rat", "azp", "at_hash"}
|
||||||
@ -4556,6 +4596,9 @@ func requireValidIDToken(
|
|||||||
if wantGroupsInIDToken != nil {
|
if wantGroupsInIDToken != nil {
|
||||||
idTokenFields = append(idTokenFields, "groups")
|
idTokenFields = append(idTokenFields, "groups")
|
||||||
}
|
}
|
||||||
|
if len(wantAdditionalClaims) > 0 {
|
||||||
|
idTokenFields = append(idTokenFields, "additionalClaims")
|
||||||
|
}
|
||||||
|
|
||||||
// make sure that these are the only fields in the token
|
// make sure that these are the only fields in the token
|
||||||
var m map[string]interface{}
|
var m map[string]interface{}
|
||||||
@ -4573,6 +4616,8 @@ func requireValidIDToken(
|
|||||||
require.Equal(t, wantClientID, m["azp"])
|
require.Equal(t, wantClientID, m["azp"])
|
||||||
require.Equal(t, goodIssuer, claims.Issuer)
|
require.Equal(t, goodIssuer, claims.Issuer)
|
||||||
require.NotEmpty(t, claims.JTI)
|
require.NotEmpty(t, claims.JTI)
|
||||||
|
require.Equal(t, wantAdditionalClaims, claims.AdditionalClaims)
|
||||||
|
require.NotEqual(t, map[string]interface{}{}, claims.AdditionalClaims, "additionalClaims may never be present and empty in the id token")
|
||||||
|
|
||||||
if wantNonceValueInIDToken {
|
if wantNonceValueInIDToken {
|
||||||
require.Equal(t, goodNonce, claims.Nonce)
|
require.Equal(t, goodNonce, claims.Nonce)
|
||||||
|
@ -928,6 +928,7 @@ func VerifyECDSAIDToken(
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequireAuthCodeRegexpMatch TODO (jtc): rename me?
|
||||||
func RequireAuthCodeRegexpMatch(
|
func RequireAuthCodeRegexpMatch(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
actualContent string,
|
actualContent string,
|
||||||
@ -1109,6 +1110,7 @@ func validateAuthcodeStorage(
|
|||||||
require.True(t, ok, "expected additionalClaims to be a map[string]interface{}")
|
require.True(t, ok, "expected additionalClaims to be a map[string]interface{}")
|
||||||
require.Equal(t, wantAdditionalClaims, actualAdditionalClaims)
|
require.Equal(t, wantAdditionalClaims, actualAdditionalClaims)
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: change assertion to verify that key `additionalClaims` DNE in actualClaims
|
||||||
require.Nil(t, actualClaims.Get("additionalClaims"), "additionalClaims must be nil when there are no wanted additional claims")
|
require.Nil(t, actualClaims.Get("additionalClaims"), "additionalClaims must be nil when there are no wanted additional claims")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user