Add integration test to verify that additionalClaims are present in an ID Token
Co-authored-by: Ryan Richard <richardry@vmware.com> Co-authored-by: Joshua Casey <joshuatcasey@gmail.com> Co-authored-by: Benjamin A. Petersen <ben@benjaminapetersen.me>
This commit is contained in:
parent
9acc456fd7
commit
a94bbe70c7
@ -226,6 +226,9 @@ func TestSupervisorLogin_Browser(t *testing.T) {
|
|||||||
wantDownstreamIDTokenUsernameToMatch func(username string) string
|
wantDownstreamIDTokenUsernameToMatch func(username string) string
|
||||||
// The expected ID token groups claim value, for the original ID token and the refreshed ID token.
|
// The expected ID token groups claim value, for the original ID token and the refreshed ID token.
|
||||||
wantDownstreamIDTokenGroups []string
|
wantDownstreamIDTokenGroups []string
|
||||||
|
// The expected ID token additional claims, which will be nested under claim "additionalClaims",
|
||||||
|
// for the original ID token and the refreshed ID token.
|
||||||
|
wantDownstreamIDTokenAdditionalClaims map[string]interface{}
|
||||||
|
|
||||||
// Want the authorization endpoint to redirect to the callback with this error type.
|
// Want the authorization endpoint to redirect to the callback with this error type.
|
||||||
// The rest of the flow will be skipped since the initial authorization failed.
|
// The rest of the flow will be skipped since the initial authorization failed.
|
||||||
@ -1318,6 +1321,43 @@ func TestSupervisorLogin_Browser(t *testing.T) {
|
|||||||
wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Username) + "$" },
|
wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Username) + "$" },
|
||||||
wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups,
|
wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "oidc upstream with downstream dynamic client happy path, requesting all scopes, with additional claims",
|
||||||
|
maybeSkip: skipNever,
|
||||||
|
createIDP: func(t *testing.T) string {
|
||||||
|
spec := basicOIDCIdentityProviderSpec()
|
||||||
|
spec.Claims = idpv1alpha1.OIDCClaims{
|
||||||
|
Username: env.SupervisorUpstreamOIDC.UsernameClaim,
|
||||||
|
Groups: env.SupervisorUpstreamOIDC.GroupsClaim,
|
||||||
|
AdditionalClaimMappings: map[string]string{
|
||||||
|
"upstream_issuer✅": "iss",
|
||||||
|
"upstream_username": env.SupervisorUpstreamOIDC.UsernameClaim,
|
||||||
|
"not_existing": "not_existing_upstream_claim",
|
||||||
|
"upstream_groups": env.SupervisorUpstreamOIDC.GroupsClaim,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
spec.AuthorizationConfig = idpv1alpha1.OIDCAuthorizationConfig{
|
||||||
|
AdditionalScopes: env.SupervisorUpstreamOIDC.AdditionalScopes,
|
||||||
|
}
|
||||||
|
return testlib.CreateTestOIDCIdentityProvider(t, spec, idpv1alpha1.PhaseReady).Name
|
||||||
|
},
|
||||||
|
createOIDCClient: func(t *testing.T, callbackURL string) (string, string) {
|
||||||
|
return testlib.CreateOIDCClient(t, configv1alpha1.OIDCClientSpec{
|
||||||
|
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
|
||||||
|
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
|
||||||
|
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
|
||||||
|
}, configv1alpha1.PhaseReady)
|
||||||
|
},
|
||||||
|
requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC,
|
||||||
|
// the ID token Subject should include the upstream user ID after the upstream issuer name
|
||||||
|
wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+",
|
||||||
|
wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Username) + "$" },
|
||||||
|
wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups,
|
||||||
|
wantDownstreamIDTokenAdditionalClaims: wantGroupsInAdditionalClaimsIfGroupsExist(map[string]interface{}{
|
||||||
|
"upstream_issuer✅": env.SupervisorUpstreamOIDC.Issuer,
|
||||||
|
"upstream_username": env.SupervisorUpstreamOIDC.Username,
|
||||||
|
}, "upstream_groups", env.SupervisorUpstreamOIDC.ExpectedGroups),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "ldap upstream with downstream dynamic client happy path, requesting all scopes",
|
name: "ldap upstream with downstream dynamic client happy path, requesting all scopes",
|
||||||
maybeSkip: skipLDAPTests,
|
maybeSkip: skipLDAPTests,
|
||||||
@ -1663,6 +1703,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
|
|||||||
tt.wantDownstreamIDTokenSubjectToMatch,
|
tt.wantDownstreamIDTokenSubjectToMatch,
|
||||||
tt.wantDownstreamIDTokenUsernameToMatch,
|
tt.wantDownstreamIDTokenUsernameToMatch,
|
||||||
tt.wantDownstreamIDTokenGroups,
|
tt.wantDownstreamIDTokenGroups,
|
||||||
|
tt.wantDownstreamIDTokenAdditionalClaims,
|
||||||
tt.wantAuthorizationErrorType,
|
tt.wantAuthorizationErrorType,
|
||||||
tt.wantAuthorizationErrorDescription,
|
tt.wantAuthorizationErrorDescription,
|
||||||
tt.wantAuthcodeExchangeError,
|
tt.wantAuthcodeExchangeError,
|
||||||
@ -1672,6 +1713,13 @@ func TestSupervisorLogin_Browser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wantGroupsInAdditionalClaimsIfGroupsExist(additionalClaims map[string]interface{}, wantGroupsAdditionalClaimName string, wantGroups []string) map[string]interface{} {
|
||||||
|
if len(wantGroups) > 0 {
|
||||||
|
additionalClaims[wantGroupsAdditionalClaimName] = wantGroups
|
||||||
|
}
|
||||||
|
return additionalClaims
|
||||||
|
}
|
||||||
|
|
||||||
func requireSuccessfulLDAPIdentityProviderConditions(t *testing.T, ldapIDP *idpv1alpha1.LDAPIdentityProvider, expectedLDAPConnectionValidMessage string) {
|
func requireSuccessfulLDAPIdentityProviderConditions(t *testing.T, ldapIDP *idpv1alpha1.LDAPIdentityProvider, expectedLDAPConnectionValidMessage string) {
|
||||||
require.Len(t, ldapIDP.Status.Conditions, 3)
|
require.Len(t, ldapIDP.Status.Conditions, 3)
|
||||||
|
|
||||||
@ -1805,6 +1853,7 @@ func testSupervisorLogin(
|
|||||||
wantDownstreamIDTokenSubjectToMatch string,
|
wantDownstreamIDTokenSubjectToMatch string,
|
||||||
wantDownstreamIDTokenUsernameToMatch func(username string) string,
|
wantDownstreamIDTokenUsernameToMatch func(username string) string,
|
||||||
wantDownstreamIDTokenGroups []string,
|
wantDownstreamIDTokenGroups []string,
|
||||||
|
wantDownstreamIDTokenAdditionalClaims map[string]interface{},
|
||||||
wantAuthorizationErrorType string,
|
wantAuthorizationErrorType string,
|
||||||
wantAuthorizationErrorDescription string,
|
wantAuthorizationErrorDescription string,
|
||||||
wantAuthcodeExchangeError string,
|
wantAuthcodeExchangeError string,
|
||||||
@ -2015,9 +2064,21 @@ func testSupervisorLogin(
|
|||||||
// If the test wants the groups scope to have been granted, then also expect the claim in the ID token.
|
// If the test wants the groups scope to have been granted, then also expect the claim in the ID token.
|
||||||
expectedIDTokenClaims = append(expectedIDTokenClaims, "groups")
|
expectedIDTokenClaims = append(expectedIDTokenClaims, "groups")
|
||||||
}
|
}
|
||||||
verifyTokenResponse(t,
|
if len(wantDownstreamIDTokenAdditionalClaims) > 0 {
|
||||||
tokenResponse, discovery, downstreamOAuth2Config, nonceParam,
|
expectedIDTokenClaims = append(expectedIDTokenClaims, "additionalClaims")
|
||||||
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch(username), wantDownstreamIDTokenGroups)
|
}
|
||||||
|
verifyTokenResponse(
|
||||||
|
t,
|
||||||
|
tokenResponse,
|
||||||
|
discovery,
|
||||||
|
downstreamOAuth2Config,
|
||||||
|
nonceParam,
|
||||||
|
expectedIDTokenClaims,
|
||||||
|
wantDownstreamIDTokenSubjectToMatch,
|
||||||
|
wantDownstreamIDTokenUsernameToMatch(username),
|
||||||
|
wantDownstreamIDTokenGroups,
|
||||||
|
wantDownstreamIDTokenAdditionalClaims,
|
||||||
|
)
|
||||||
|
|
||||||
// token exchange on the original token
|
// token exchange on the original token
|
||||||
if requestTokenExchangeAud == "" {
|
if requestTokenExchangeAud == "" {
|
||||||
@ -2063,9 +2124,21 @@ func testSupervisorLogin(
|
|||||||
// If the test wants the groups scope to have been granted, then also expect the claim in the refreshed ID token.
|
// If the test wants the groups scope to have been granted, then also expect the claim in the refreshed ID token.
|
||||||
expectRefreshedIDTokenClaims = append(expectRefreshedIDTokenClaims, "groups")
|
expectRefreshedIDTokenClaims = append(expectRefreshedIDTokenClaims, "groups")
|
||||||
}
|
}
|
||||||
verifyTokenResponse(t,
|
if len(wantDownstreamIDTokenAdditionalClaims) > 0 {
|
||||||
refreshedTokenResponse, discovery, downstreamOAuth2Config, "",
|
expectRefreshedIDTokenClaims = append(expectRefreshedIDTokenClaims, "additionalClaims")
|
||||||
expectRefreshedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch(username), wantRefreshedGroups)
|
}
|
||||||
|
verifyTokenResponse(
|
||||||
|
t,
|
||||||
|
refreshedTokenResponse,
|
||||||
|
discovery,
|
||||||
|
downstreamOAuth2Config,
|
||||||
|
"",
|
||||||
|
expectRefreshedIDTokenClaims,
|
||||||
|
wantDownstreamIDTokenSubjectToMatch,
|
||||||
|
wantDownstreamIDTokenUsernameToMatch(username),
|
||||||
|
wantRefreshedGroups,
|
||||||
|
wantDownstreamIDTokenAdditionalClaims,
|
||||||
|
)
|
||||||
|
|
||||||
require.NotEqual(t, tokenResponse.AccessToken, refreshedTokenResponse.AccessToken)
|
require.NotEqual(t, tokenResponse.AccessToken, refreshedTokenResponse.AccessToken)
|
||||||
require.NotEqual(t, tokenResponse.RefreshToken, refreshedTokenResponse.RefreshToken)
|
require.NotEqual(t, tokenResponse.RefreshToken, refreshedTokenResponse.RefreshToken)
|
||||||
@ -2126,7 +2199,9 @@ func verifyTokenResponse(
|
|||||||
downstreamOAuth2Config oauth2.Config,
|
downstreamOAuth2Config oauth2.Config,
|
||||||
nonceParam nonce.Nonce,
|
nonceParam nonce.Nonce,
|
||||||
expectedIDTokenClaims []string,
|
expectedIDTokenClaims []string,
|
||||||
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenGroups []string,
|
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string,
|
||||||
|
wantDownstreamIDTokenGroups []string,
|
||||||
|
wantDownstreamIDTokenAdditionalClaims map[string]interface{},
|
||||||
) {
|
) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -2171,6 +2246,9 @@ func verifyTokenResponse(
|
|||||||
// Check the groups claim.
|
// Check the groups claim.
|
||||||
require.ElementsMatch(t, wantDownstreamIDTokenGroups, idTokenClaims["groups"])
|
require.ElementsMatch(t, wantDownstreamIDTokenGroups, idTokenClaims["groups"])
|
||||||
|
|
||||||
|
// Check the "additionalClaims" claim.
|
||||||
|
require.Equal(t, wantDownstreamIDTokenAdditionalClaims, idTokenClaims["additionalClaims"])
|
||||||
|
|
||||||
// Some light verification of the other tokens that were returned.
|
// Some light verification of the other tokens that were returned.
|
||||||
require.NotEmpty(t, tokenResponse.AccessToken)
|
require.NotEmpty(t, tokenResponse.AccessToken)
|
||||||
require.Equal(t, "bearer", tokenResponse.TokenType)
|
require.Equal(t, "bearer", tokenResponse.TokenType)
|
||||||
|
Loading…
Reference in New Issue
Block a user