Backfill tests

This commit is contained in:
Joshua Casey 2023-01-04 11:42:50 -06:00
parent cd17bdb5f7
commit 02ed2d9c95
6 changed files with 192 additions and 1 deletions

View File

@ -582,6 +582,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantUnnecessaryStoredRecords int
wantPasswordGrantCall *expectedPasswordGrant
wantDownstreamCustomSessionData *psession.CustomSessionData
wantAdditionalClaims map[string]interface{}
}
tests := []testCase{
{
@ -711,6 +712,40 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
},
{
name: "OIDC upstream password grant happy path using GET with additional claim mappings",
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(passwordGrantUpstreamOIDCIdentityProviderBuilder().
WithAdditionalClaimMappings(map[string]string{
"downstreamCustomClaim": "upstreamCustomClaim",
"downstreamOtherClaim": "upstreamOtherClaim",
"downstreamMissingClaim": "upstreamMissingClaim",
}).
WithIDTokenClaim("upstreamCustomClaim", "i am a claim value").
WithIDTokenClaim("upstreamOtherClaim", "other claim value").
Build()),
method: http.MethodGet,
path: happyGetRequestPath,
customUsernameHeader: pointer.String(oidcUpstreamUsername),
customPasswordHeader: pointer.String(oidcUpstreamPassword),
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
wantStatus: http.StatusFound,
wantContentType: htmlContentType,
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
wantDownstreamRedirectURI: downstreamRedirectURI,
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
wantDownstreamNonce: downstreamNonce,
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
wantAdditionalClaims: map[string]interface{}{
"downstreamCustomClaim": "i am a claim value",
"downstreamOtherClaim": "other claim value",
},
},
{
name: "LDAP cli upstream happy path using GET",
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
@ -3126,6 +3161,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
test.wantDownstreamClientID,
test.wantDownstreamRedirectURI,
test.wantDownstreamCustomSessionData,
test.wantAdditionalClaims,
)
default:
require.Empty(t, rsp.Header().Values("Location"))
@ -3176,9 +3212,15 @@ func TestAuthorizationEndpoint(t *testing.T) {
oidcClientsClient := supervisorClient.ConfigV1alpha1().OIDCClients("some-namespace")
oauthHelperWithRealStorage, kubeOauthStore := createOauthHelperWithRealStorage(secretsClient, oidcClientsClient)
oauthHelperWithNullStorage, _ := createOauthHelperWithNullStorage(secretsClient, oidcClientsClient)
idps := test.idps.Build()
if len(test.wantAdditionalClaims) > 0 {
require.True(t, len(idps.GetOIDCIdentityProviders()) > 0, "wantAdditionalClaims requires at least one OIDC IDP")
}
subject := NewHandler(
downstreamIssuer,
test.idps.Build(),
idps,
oauthHelperWithNullStorage, oauthHelperWithRealStorage,
test.generateCSRF, test.generatePKCE, test.generateNonce,
test.stateEncoder, test.cookieEncoder,

View File

@ -189,6 +189,7 @@ func TestCallbackEndpoint(t *testing.T) {
wantDownstreamPKCEChallenge string
wantDownstreamPKCEChallengeMethod string
wantDownstreamCustomSessionData *psession.CustomSessionData
wantAdditionalClaims map[string]interface{}
wantAuthcodeExchangeCall *expectedAuthcodeExchange
}{
@ -223,6 +224,49 @@ func TestCallbackEndpoint(t *testing.T) {
args: happyExchangeAndValidateTokensArgs,
},
},
{
name: "GET with good state and cookie with additional params",
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().
WithAdditionalClaimMappings(map[string]string{
"downstreamCustomClaim": "upstreamCustomClaim",
"downstreamOtherClaim": "upstreamOtherClaim",
"downstreamMissingClaim": "upstreamMissingClaim",
}).
WithIDTokenClaim("upstreamCustomClaim", "i am a claim value").
WithIDTokenClaim("upstreamOtherClaim", "other claim value").
Build()),
method: http.MethodGet,
path: newRequestPath().WithState(
happyUpstreamStateParam().WithAuthorizeRequestParams(
shallowCopyAndModifyQuery(
happyDownstreamRequestParamsQuery,
map[string]string{"response_mode": "form_post"},
).Encode(),
).Build(t, happyStateCodec),
).String(),
csrfCookie: happyCSRFCookie,
wantStatus: http.StatusOK,
wantContentType: "text/html;charset=UTF-8",
wantBodyFormResponseRegexp: `<code id="manual-auth-code">(.+)</code>`,
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
wantDownstreamNonce: downstreamNonce,
wantDownstreamClientID: downstreamPinnipedClientID,
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
performedByUpstreamName: happyUpstreamIDPName,
args: happyExchangeAndValidateTokensArgs,
},
wantAdditionalClaims: map[string]interface{}{
"downstreamCustomClaim": "i am a claim value",
"downstreamOtherClaim": "other claim value",
},
},
{
name: "GET with good state and cookie and successful upstream token exchange returns 303 to downstream client callback with its state and code",
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
@ -1463,6 +1507,7 @@ func TestCallbackEndpoint(t *testing.T) {
test.wantDownstreamClientID,
downstreamRedirectURI,
test.wantDownstreamCustomSessionData,
test.wantAdditionalClaims,
)
// Otherwise, expect an empty response body.
@ -1490,6 +1535,7 @@ func TestCallbackEndpoint(t *testing.T) {
test.wantDownstreamClientID,
downstreamRedirectURI,
test.wantDownstreamCustomSessionData,
test.wantAdditionalClaims,
)
}
})

View File

@ -0,0 +1,66 @@
package downstreamsession
import (
"testing"
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/testutil/oidctestutil"
)
func TestMapAdditionalClaimsFromUpstreamIDToken(t *testing.T) {
tests := []struct {
name string
additionalClaimMappings map[string]string
upstreamClaims map[string]interface{}
wantClaims map[string]interface{}
}{
{
name: "happy path",
additionalClaimMappings: map[string]string{
"email": "notification_email",
},
upstreamClaims: map[string]interface{}{
"notification_email": "test@example.com",
},
wantClaims: map[string]interface{}{
"email": "test@example.com",
},
},
{
name: "missing",
additionalClaimMappings: map[string]string{
"email": "email",
},
upstreamClaims: map[string]interface{}{},
wantClaims: map[string]interface{}{},
},
{
name: "complex",
additionalClaimMappings: map[string]string{
"complex": "complex",
},
upstreamClaims: map[string]interface{}{
"complex": map[string]string{
"subClaim": "subValue",
},
},
wantClaims: map[string]interface{}{
"complex": map[string]string{
"subClaim": "subValue",
},
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
idp := oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().
WithAdditionalClaimMappings(test.additionalClaimMappings).
Build()
actual := MapAdditionalClaimsFromUpstreamIDToken(idp, test.upstreamClaims)
require.Equal(t, test.wantClaims, actual)
})
}
}

View File

@ -1027,6 +1027,7 @@ func TestPostLoginEndpoint(t *testing.T) {
tt.wantDownstreamClient,
tt.wantDownstreamRedirectURI,
tt.wantDownstreamCustomSessionData,
map[string]interface{}{},
)
case tt.wantRedirectToLoginPageError != "":
// Expecting an error redirect to the login UI page.
@ -1062,6 +1063,7 @@ func TestPostLoginEndpoint(t *testing.T) {
tt.wantDownstreamClient,
tt.wantDownstreamRedirectURI,
tt.wantDownstreamCustomSessionData,
map[string]interface{}{},
)
default:
require.Failf(t, "test should have expected a redirect or form body",

View File

@ -164,6 +164,7 @@ type TestUpstreamOIDCIdentityProvider struct {
GroupsClaim string
Scopes []string
AdditionalAuthcodeParams map[string]string
AdditionalClaimMappings map[string]string
AllowPasswordGrant bool
ExchangeAuthcodeAndValidateTokensFunc func(
@ -207,6 +208,10 @@ func (u *TestUpstreamOIDCIdentityProvider) GetAdditionalAuthcodeParams() map[str
return u.AdditionalAuthcodeParams
}
func (u *TestUpstreamOIDCIdentityProvider) GetAdditionalClaimMappings() map[string]string {
return u.AdditionalClaimMappings
}
func (u *TestUpstreamOIDCIdentityProvider) GetName() string {
return u.Name
}
@ -630,6 +635,7 @@ type TestUpstreamOIDCIdentityProviderBuilder struct {
authorizationURL url.URL
hasUserInfoURL bool
additionalAuthcodeParams map[string]string
additionalClaimMappings map[string]string
allowPasswordGrant bool
authcodeExchangeErr error
passwordGrantErr error
@ -716,6 +722,11 @@ func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAdditionalAuthcodeParams(p
return u
}
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAdditionalClaimMappings(m map[string]string) *TestUpstreamOIDCIdentityProviderBuilder {
u.additionalClaimMappings = m
return u
}
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithRefreshToken(token string) *TestUpstreamOIDCIdentityProviderBuilder {
u.refreshToken = &oidctypes.RefreshToken{Token: token}
return u
@ -792,6 +803,7 @@ func (u *TestUpstreamOIDCIdentityProviderBuilder) Build() *TestUpstreamOIDCIdent
AuthorizationURL: u.authorizationURL,
UserInfoURL: u.hasUserInfoURL,
AdditionalAuthcodeParams: u.additionalAuthcodeParams,
AdditionalClaimMappings: u.additionalClaimMappings,
ExchangeAuthcodeAndValidateTokensFunc: func(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (*oidctypes.Token, error) {
if u.authcodeExchangeErr != nil {
return nil, u.authcodeExchangeErr
@ -934,6 +946,7 @@ func RequireAuthCodeRegexpMatch(
wantDownstreamClientID string,
wantDownstreamRedirectURI string,
wantCustomSessionData *psession.CustomSessionData,
wantAdditionalClaims map[string]interface{},
) {
t.Helper()
@ -972,6 +985,7 @@ func RequireAuthCodeRegexpMatch(
wantDownstreamClientID,
wantDownstreamRedirectURI,
wantCustomSessionData,
wantAdditionalClaims,
)
// One PKCE should have been stored.
@ -1023,6 +1037,7 @@ func validateAuthcodeStorage(
wantDownstreamClientID string,
wantDownstreamRedirectURI string,
wantCustomSessionData *psession.CustomSessionData,
wantAdditionalClaims map[string]interface{},
) (*fosite.Request, *psession.PinnipedSession) {
t.Helper()
@ -1066,6 +1081,10 @@ func validateAuthcodeStorage(
require.Equal(t, wantDownstreamClientID, actualClaims.Extra["azp"])
wantDownstreamIDTokenExtraClaimsCount := 1 // should always have azp claim
if len(wantAdditionalClaims) > 0 {
wantDownstreamIDTokenExtraClaimsCount++
}
// Check the user's identity, which are put into the downstream ID token's subject, username and groups claims.
require.Equal(t, wantDownstreamIDTokenSubject, actualClaims.Subject)
if wantDownstreamIDTokenUsername == "" {
@ -1085,6 +1104,12 @@ func validateAuthcodeStorage(
actualDownstreamIDTokenGroups := actualClaims.Extra["groups"]
require.Nil(t, actualDownstreamIDTokenGroups)
}
if len(wantAdditionalClaims) > 0 {
actualAdditionalClaims, ok := actualClaims.Get("additionalClaims").(map[string]interface{})
require.True(t, ok, "expected additionalClaims to be a map[string]interface{}")
require.Equal(t, wantAdditionalClaims, actualAdditionalClaims)
}
// Make sure that we asserted on every extra claim.
require.Len(t, actualClaims.Extra, wantDownstreamIDTokenExtraClaimsCount)

View File

@ -68,6 +68,16 @@ func TestProviderConfig(t *testing.T) {
rawClaims: []byte(`{`),
}
require.False(t, p.HasUserInfoURL())
// AdditionalAuthcodeParams defaults to empty
require.Empty(t, p.AdditionalAuthcodeParams)
p.AdditionalAuthcodeParams = map[string]string{"additional": "authcodeParams"}
require.Equal(t, p.GetAdditionalAuthcodeParams(), map[string]string{"additional": "authcodeParams"})
// AdditionalClaimMappings defaults to empty
require.Empty(t, p.AdditionalClaimMappings)
p.AdditionalClaimMappings = map[string]string{"additional": "claimMappings"}
require.Equal(t, p.GetAdditionalClaimMappings(), map[string]string{"additional": "claimMappings"})
})
const (