diff --git a/internal/federationdomain/endpoints/auth/auth_handler_test.go b/internal/federationdomain/endpoints/auth/auth_handler_test.go index ba83e7efe..48fced93b 100644 --- a/internal/federationdomain/endpoints/auth/auth_handler_test.go +++ b/internal/federationdomain/endpoints/auth/auth_handler_test.go @@ -30,17 +30,16 @@ import ( supervisorfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake" "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/typed/config/v1alpha1" "go.pinniped.dev/internal/authenticators" - "go.pinniped.dev/internal/celtransformer" "go.pinniped.dev/internal/federationdomain/csrftoken" "go.pinniped.dev/internal/federationdomain/endpoints/jwks" "go.pinniped.dev/internal/federationdomain/oidc" "go.pinniped.dev/internal/federationdomain/oidcclientvalidator" "go.pinniped.dev/internal/federationdomain/storage" "go.pinniped.dev/internal/here" - "go.pinniped.dev/internal/idtransform" "go.pinniped.dev/internal/psession" "go.pinniped.dev/internal/testutil" "go.pinniped.dev/internal/testutil/oidctestutil" + "go.pinniped.dev/internal/testutil/transformtestutil" "go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/pkce" ) @@ -612,23 +611,8 @@ func TestAuthorizationEndpoint(t *testing.T) { //nolint:gocyclo encodedIncomingCookieCSRFValue, err := happyCookieEncoder.Encode("csrf", incomingCookieCSRFValue) require.NoError(t, err) - transformer, err := celtransformer.NewCELTransformer(5 * time.Second) // CI workers can be slow, so allow slow transforms - require.NoError(t, err) - - prefixUsernameAndGroupsPipeline := idtransform.NewTransformationPipeline() - rejectAuthPipeline := idtransform.NewTransformationPipeline() - - var compiledTransform idtransform.IdentityTransformation - compiledTransform, err = transformer.CompileTransformation(&celtransformer.UsernameTransformation{Expression: fmt.Sprintf(`"%s" + username`, transformationUsernamePrefix)}, nil) - require.NoError(t, err) - prefixUsernameAndGroupsPipeline.AppendTransformation(compiledTransform) - compiledTransform, err = transformer.CompileTransformation(&celtransformer.GroupsTransformation{Expression: fmt.Sprintf(`groups.map(g, "%s" + g)`, transformationGroupsPrefix)}, nil) - require.NoError(t, err) - prefixUsernameAndGroupsPipeline.AppendTransformation(compiledTransform) - - compiledTransform, err = transformer.CompileTransformation(&celtransformer.AllowAuthenticationPolicy{Expression: `username == "someone-special"`}, nil) - require.NoError(t, err) - rejectAuthPipeline.AppendTransformation(compiledTransform) + prefixUsernameAndGroupsPipeline := transformtestutil.NewPrefixingPipeline(t, transformationUsernamePrefix, transformationGroupsPrefix) + rejectAuthPipeline := transformtestutil.NewRejectAllAuthPipeline(t) type testCase struct { name string diff --git a/internal/federationdomain/endpoints/callback/callback_handler_test.go b/internal/federationdomain/endpoints/callback/callback_handler_test.go index 08ce3d3a8..f903d99f7 100644 --- a/internal/federationdomain/endpoints/callback/callback_handler_test.go +++ b/internal/federationdomain/endpoints/callback/callback_handler_test.go @@ -28,6 +28,7 @@ import ( "go.pinniped.dev/internal/psession" "go.pinniped.dev/internal/testutil" "go.pinniped.dev/internal/testutil/oidctestutil" + "go.pinniped.dev/internal/testutil/transformtestutil" "go.pinniped.dev/pkg/oidcclient/nonce" oidcpkce "go.pinniped.dev/pkg/oidcclient/pkce" ) @@ -65,6 +66,9 @@ const ( downstreamPKCEChallengeMethod = "S256" htmlContentType = "text/html; charset=utf-8" + + transformationUsernamePrefix = "username_prefix:" + transformationGroupsPrefix = "groups_prefix:" ) var ( @@ -102,13 +106,13 @@ var ( UpstreamSubject: oidcUpstreamSubject, }, } - happyDownstreamCustomSessionDataWithUsernameAndGroups = func(wantUsername string, wantGroups []string) *psession.CustomSessionData { + happyDownstreamCustomSessionDataWithUsernameAndGroups = func(wantDownstreamUsername, wantUpstreamUsername string, wantUpstreamGroups []string) *psession.CustomSessionData { copyOfCustomSession := *happyDownstreamCustomSessionData copyOfOIDC := *(happyDownstreamCustomSessionData.OIDC) copyOfCustomSession.OIDC = ©OfOIDC - copyOfCustomSession.Username = wantUsername - copyOfCustomSession.UpstreamUsername = wantUsername - copyOfCustomSession.UpstreamGroups = wantGroups + copyOfCustomSession.Username = wantDownstreamUsername + copyOfCustomSession.UpstreamUsername = wantUpstreamUsername + copyOfCustomSession.UpstreamGroups = wantUpstreamGroups return ©OfCustomSession } happyDownstreamAccessTokenCustomSessionData = &psession.CustomSessionData{ @@ -172,6 +176,9 @@ func TestCallbackEndpoint(t *testing.T) { require.NoError(t, kubeClient.Tracker().Add(secret)) } + prefixUsernameAndGroupsPipeline := transformtestutil.NewPrefixingPipeline(t, transformationUsernamePrefix, transformationGroupsPrefix) + rejectAuthPipeline := transformtestutil.NewRejectAllAuthPipeline(t) + tests := []struct { name string @@ -439,7 +446,11 @@ func TestCallbackEndpoint(t *testing.T) { wantDownstreamClientID: downstreamPinnipedClientID, wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, - wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups(oidcUpstreamIssuer+"?sub="+oidcUpstreamSubjectQueryEscaped, nil), + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + oidcUpstreamIssuer+"?sub="+oidcUpstreamSubjectQueryEscaped, + oidcUpstreamIssuer+"?sub="+oidcUpstreamSubjectQueryEscaped, + nil, + ), wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ performedByUpstreamName: happyUpstreamIDPName, args: happyExchangeAndValidateTokensArgs, @@ -465,7 +476,11 @@ func TestCallbackEndpoint(t *testing.T) { wantDownstreamClientID: downstreamPinnipedClientID, wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, - wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups("joe@whitehouse.gov", oidcUpstreamGroupMembership), + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + "joe@whitehouse.gov", + "joe@whitehouse.gov", + oidcUpstreamGroupMembership, + ), wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ performedByUpstreamName: happyUpstreamIDPName, args: happyExchangeAndValidateTokensArgs, @@ -493,7 +508,11 @@ func TestCallbackEndpoint(t *testing.T) { wantDownstreamClientID: downstreamPinnipedClientID, wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, - wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups("joe@whitehouse.gov", oidcUpstreamGroupMembership), + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + "joe@whitehouse.gov", + "joe@whitehouse.gov", + oidcUpstreamGroupMembership, + ), wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ performedByUpstreamName: happyUpstreamIDPName, args: happyExchangeAndValidateTokensArgs, @@ -522,7 +541,11 @@ func TestCallbackEndpoint(t *testing.T) { wantDownstreamClientID: downstreamPinnipedClientID, wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, - wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups("joe", oidcUpstreamGroupMembership), + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + "joe", + "joe", + oidcUpstreamGroupMembership, + ), wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ performedByUpstreamName: happyUpstreamIDPName, args: happyExchangeAndValidateTokensArgs, @@ -653,7 +676,11 @@ func TestCallbackEndpoint(t *testing.T) { wantDownstreamClientID: downstreamPinnipedClientID, wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, - wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups(oidcUpstreamSubject, oidcUpstreamGroupMembership), + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + oidcUpstreamSubject, + oidcUpstreamSubject, + oidcUpstreamGroupMembership, + ), wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ performedByUpstreamName: happyUpstreamIDPName, args: happyExchangeAndValidateTokensArgs, @@ -679,7 +706,11 @@ func TestCallbackEndpoint(t *testing.T) { wantDownstreamClientID: downstreamPinnipedClientID, wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, - wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups(oidcUpstreamUsername, []string{"notAnArrayGroup1 notAnArrayGroup2"}), + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + oidcUpstreamUsername, + oidcUpstreamUsername, + []string{"notAnArrayGroup1 notAnArrayGroup2"}, + ), wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ performedByUpstreamName: happyUpstreamIDPName, args: happyExchangeAndValidateTokensArgs, @@ -705,7 +736,11 @@ func TestCallbackEndpoint(t *testing.T) { wantDownstreamClientID: downstreamPinnipedClientID, wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, - wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups(oidcUpstreamUsername, []string{"group1", "group2"}), + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + oidcUpstreamUsername, + oidcUpstreamUsername, + []string{"group1", "group2"}, + ), wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ performedByUpstreamName: happyUpstreamIDPName, args: happyExchangeAndValidateTokensArgs, @@ -857,6 +892,35 @@ func TestCallbackEndpoint(t *testing.T) { args: happyExchangeAndValidateTokensArgs, }, }, + { + name: "using identity transformations which modify the username and group names", + idps: oidctestutil.NewUpstreamIDPListerBuilder(). + WithOIDC(happyUpstream().WithTransformsForFederationDomain(prefixUsernameAndGroupsPipeline).Build()), + method: http.MethodGet, + path: newRequestPath().WithState(happyState).String(), + csrfCookie: happyCSRFCookie, + wantStatus: http.StatusSeeOther, + wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, + wantBody: "", + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenUsername: transformationUsernamePrefix + oidcUpstreamUsername, + wantDownstreamIDTokenGroups: testutil.AddPrefixToEach(transformationGroupsPrefix, oidcUpstreamGroupMembership), + wantDownstreamRequestedScopes: happyDownstreamScopesRequested, + wantDownstreamGrantedScopes: happyDownstreamScopesGranted, + wantDownstreamNonce: downstreamNonce, + wantDownstreamClientID: downstreamPinnipedClientID, + wantDownstreamPKCEChallenge: downstreamPKCEChallenge, + wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + transformationUsernamePrefix+oidcUpstreamUsername, + oidcUpstreamUsername, + oidcUpstreamGroupMembership, + ), + wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ + performedByUpstreamName: happyUpstreamIDPName, + args: happyExchangeAndValidateTokensArgs, + }, + }, // Pre-upstream-exchange verification { @@ -1168,7 +1232,7 @@ func TestCallbackEndpoint(t *testing.T) { }, }, { - name: "the OIDCIdentityProvider CRD has been deleted", + name: "the OIDCIdentityProvider resource has been deleted", idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(otherUpstreamOIDCIdentityProvider), method: http.MethodGet, path: newRequestPath().WithState(happyState).String(), @@ -1260,7 +1324,11 @@ func TestCallbackEndpoint(t *testing.T) { wantDownstreamClientID: downstreamPinnipedClientID, wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, - wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups(oidcUpstreamUsername, nil), + wantDownstreamCustomSessionData: happyDownstreamCustomSessionDataWithUsernameAndGroups( + oidcUpstreamUsername, + oidcUpstreamUsername, + nil, + ), wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ performedByUpstreamName: happyUpstreamIDPName, args: happyExchangeAndValidateTokensArgs, @@ -1442,7 +1510,23 @@ func TestCallbackEndpoint(t *testing.T) { args: happyExchangeAndValidateTokensArgs, }, }, + { + name: "using identity transformations which reject the authentication", + idps: oidctestutil.NewUpstreamIDPListerBuilder(). + WithOIDC(happyUpstream().WithTransformsForFederationDomain(rejectAuthPipeline).Build()), + method: http.MethodGet, + path: newRequestPath().WithState(happyState).String(), + csrfCookie: happyCSRFCookie, + wantStatus: http.StatusUnprocessableEntity, + wantContentType: htmlContentType, + wantBody: "Unprocessable Entity: configured identity policy rejected this authentication: authentication was rejected by a configured policy\n", + wantAuthcodeExchangeCall: &expectedAuthcodeExchange{ + performedByUpstreamName: happyUpstreamIDPName, + args: happyExchangeAndValidateTokensArgs, + }, + }, } + for _, test := range tests { test := test diff --git a/internal/testutil/transformtestutil/transformtestutil.go b/internal/testutil/transformtestutil/transformtestutil.go new file mode 100644 index 000000000..63576a184 --- /dev/null +++ b/internal/testutil/transformtestutil/transformtestutil.go @@ -0,0 +1,58 @@ +// Copyright 2023 the Pinniped contributors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package transformtestutil + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "go.pinniped.dev/internal/celtransformer" + "go.pinniped.dev/internal/idtransform" +) + +func NewPrefixingPipeline(t *testing.T, usernamePrefix, groupsPrefix string) *idtransform.TransformationPipeline { + t.Helper() + + transformer, err := celtransformer.NewCELTransformer(5 * time.Second) + require.NoError(t, err) + + p := idtransform.NewTransformationPipeline() + + userTransform, err := transformer.CompileTransformation( + &celtransformer.UsernameTransformation{Expression: fmt.Sprintf(`"%s" + username`, usernamePrefix)}, + nil, + ) + require.NoError(t, err) + p.AppendTransformation(userTransform) + + groupsTransform, err := transformer.CompileTransformation( + &celtransformer.GroupsTransformation{Expression: fmt.Sprintf(`groups.map(g, "%s" + g)`, groupsPrefix)}, + nil, + ) + require.NoError(t, err) + p.AppendTransformation(groupsTransform) + + return p +} + +func NewRejectAllAuthPipeline(t *testing.T) *idtransform.TransformationPipeline { + t.Helper() + + transformer, err := celtransformer.NewCELTransformer(5 * time.Second) + require.NoError(t, err) + + p := idtransform.NewTransformationPipeline() + + compiledTransform, err := transformer.CompileTransformation( + &celtransformer.AllowAuthenticationPolicy{Expression: `false`}, + nil, + ) + require.NoError(t, err) + p.AppendTransformation(compiledTransform) + + return p +}