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:
Joshua Casey 2023-01-13 16:43:39 -06:00 committed by Ryan Richard
parent 9acc456fd7
commit a94bbe70c7
1 changed files with 85 additions and 7 deletions

View File

@ -226,6 +226,9 @@ func TestSupervisorLogin_Browser(t *testing.T) {
wantDownstreamIDTokenUsernameToMatch func(username string) string
// The expected ID token groups claim value, for the original ID token and the refreshed ID token.
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.
// 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) + "$" },
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",
maybeSkip: skipLDAPTests,
@ -1663,6 +1703,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
tt.wantDownstreamIDTokenSubjectToMatch,
tt.wantDownstreamIDTokenUsernameToMatch,
tt.wantDownstreamIDTokenGroups,
tt.wantDownstreamIDTokenAdditionalClaims,
tt.wantAuthorizationErrorType,
tt.wantAuthorizationErrorDescription,
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) {
require.Len(t, ldapIDP.Status.Conditions, 3)
@ -1805,6 +1853,7 @@ func testSupervisorLogin(
wantDownstreamIDTokenSubjectToMatch string,
wantDownstreamIDTokenUsernameToMatch func(username string) string,
wantDownstreamIDTokenGroups []string,
wantDownstreamIDTokenAdditionalClaims map[string]interface{},
wantAuthorizationErrorType string,
wantAuthorizationErrorDescription 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.
expectedIDTokenClaims = append(expectedIDTokenClaims, "groups")
}
verifyTokenResponse(t,
tokenResponse, discovery, downstreamOAuth2Config, nonceParam,
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch(username), wantDownstreamIDTokenGroups)
if len(wantDownstreamIDTokenAdditionalClaims) > 0 {
expectedIDTokenClaims = append(expectedIDTokenClaims, "additionalClaims")
}
verifyTokenResponse(
t,
tokenResponse,
discovery,
downstreamOAuth2Config,
nonceParam,
expectedIDTokenClaims,
wantDownstreamIDTokenSubjectToMatch,
wantDownstreamIDTokenUsernameToMatch(username),
wantDownstreamIDTokenGroups,
wantDownstreamIDTokenAdditionalClaims,
)
// token exchange on the original token
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.
expectRefreshedIDTokenClaims = append(expectRefreshedIDTokenClaims, "groups")
}
verifyTokenResponse(t,
refreshedTokenResponse, discovery, downstreamOAuth2Config, "",
expectRefreshedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch(username), wantRefreshedGroups)
if len(wantDownstreamIDTokenAdditionalClaims) > 0 {
expectRefreshedIDTokenClaims = append(expectRefreshedIDTokenClaims, "additionalClaims")
}
verifyTokenResponse(
t,
refreshedTokenResponse,
discovery,
downstreamOAuth2Config,
"",
expectRefreshedIDTokenClaims,
wantDownstreamIDTokenSubjectToMatch,
wantDownstreamIDTokenUsernameToMatch(username),
wantRefreshedGroups,
wantDownstreamIDTokenAdditionalClaims,
)
require.NotEqual(t, tokenResponse.AccessToken, refreshedTokenResponse.AccessToken)
require.NotEqual(t, tokenResponse.RefreshToken, refreshedTokenResponse.RefreshToken)
@ -2126,7 +2199,9 @@ func verifyTokenResponse(
downstreamOAuth2Config oauth2.Config,
nonceParam nonce.Nonce,
expectedIDTokenClaims []string,
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenGroups []string,
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string,
wantDownstreamIDTokenGroups []string,
wantDownstreamIDTokenAdditionalClaims map[string]interface{},
) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
@ -2171,6 +2246,9 @@ func verifyTokenResponse(
// Check the groups claim.
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.
require.NotEmpty(t, tokenResponse.AccessToken)
require.Equal(t, "bearer", tokenResponse.TokenType)