diff --git a/internal/celtransformer/celformer.go b/internal/celtransformer/celformer.go index bdf351b4..085af450 100644 --- a/internal/celtransformer/celformer.go +++ b/internal/celtransformer/celformer.go @@ -160,6 +160,7 @@ func (t *UsernameTransformation) compile(transformer *CELTransformer, consts *Tr baseCompiledTransformation: &baseCompiledTransformation{ program: program, consts: consts, + sourceExpr: t, maxExpressionRuntime: transformer.maxExpressionRuntime, }, }, nil @@ -174,6 +175,7 @@ func (t *GroupsTransformation) compile(transformer *CELTransformer, consts *Tran baseCompiledTransformation: &baseCompiledTransformation{ program: program, consts: consts, + sourceExpr: t, maxExpressionRuntime: transformer.maxExpressionRuntime, }, }, nil @@ -188,6 +190,7 @@ func (t *AllowAuthenticationPolicy) compile(transformer *CELTransformer, consts baseCompiledTransformation: &baseCompiledTransformation{ program: program, consts: consts, + sourceExpr: t, maxExpressionRuntime: transformer.maxExpressionRuntime, }, rejectedAuthenticationMessage: t.RejectedAuthenticationMessage, @@ -198,6 +201,7 @@ func (t *AllowAuthenticationPolicy) compile(transformer *CELTransformer, consts type baseCompiledTransformation struct { program cel.Program consts *TransformationConstants + sourceExpr CELTransformation maxExpressionRuntime time.Duration } @@ -302,6 +306,23 @@ func (c *compiledAllowAuthenticationPolicy) Evaluate(ctx context.Context, userna return result, nil } +type CELTransformationSource struct { + Expr CELTransformation + Consts *TransformationConstants +} + +func (c *compiledUsernameTransformation) Source() interface{} { + return &CELTransformationSource{Expr: c.sourceExpr, Consts: c.consts} +} + +func (c *compiledGroupsTransformation) Source() interface{} { + return &CELTransformationSource{Expr: c.sourceExpr, Consts: c.consts} +} + +func (c *compiledAllowAuthenticationPolicy) Source() interface{} { + return &CELTransformationSource{Expr: c.sourceExpr, Consts: c.consts} +} + func newEnv() (*cel.Env, error) { // Note that Kubernetes uses CEL in several places, which are helpful to see as an example of // how to configure the CEL compiler for production usage. Examples: diff --git a/internal/celtransformer/celformer_test.go b/internal/celtransformer/celformer_test.go index 227ed392..a38a1b23 100644 --- a/internal/celtransformer/celformer_test.go +++ b/internal/celtransformer/celformer_test.go @@ -765,6 +765,7 @@ func TestTransformer(t *testing.T) { require.NoError(t, err) pipeline := idtransform.NewTransformationPipeline() + expectedPipelineSource := []interface{}{} for _, transform := range tt.transforms { compiledTransform, err := transformer.CompileTransformation(transform, tt.consts) @@ -774,6 +775,15 @@ func TestTransformer(t *testing.T) { } require.NoError(t, err, "got an unexpected compile error") pipeline.AppendTransformation(compiledTransform) + + expectedTransformSource := &CELTransformationSource{ + Expr: transform, + Consts: tt.consts, + } + if expectedTransformSource.Consts == nil { + expectedTransformSource.Consts = &TransformationConstants{} + } + expectedPipelineSource = append(expectedPipelineSource, expectedTransformSource) } ctx := context.Background() @@ -792,6 +802,8 @@ func TestTransformer(t *testing.T) { require.Equal(t, tt.wantGroups, result.Groups) require.Equal(t, !tt.wantAuthRejected, result.AuthenticationAllowed, "AuthenticationAllowed had unexpected value") require.Equal(t, tt.wantAuthRejectedMessage, result.RejectedAuthenticationMessage) + + require.Equal(t, expectedPipelineSource, pipeline.Source()) }) } } diff --git a/internal/controller/supervisorconfig/federation_domain_watcher_test.go b/internal/controller/supervisorconfig/federation_domain_watcher_test.go index 2ff07cd3..93d9f8ac 100644 --- a/internal/controller/supervisorconfig/federation_domain_watcher_test.go +++ b/internal/controller/supervisorconfig/federation_domain_watcher_test.go @@ -17,6 +17,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" clocktesting "k8s.io/utils/clock/testing" "k8s.io/utils/pointer" @@ -25,6 +26,7 @@ import ( idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1" pinnipedfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake" pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions" + "go.pinniped.dev/internal/celtransformer" "go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/federationdomain/federationdomainproviders" "go.pinniped.dev/internal/here" @@ -1282,7 +1284,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) { Transforms: configv1alpha1.FederationDomainTransforms{ Constants: []configv1alpha1.FederationDomainTransformsConstant{ {Name: "duplicate1", Type: "string", StringValue: "abc"}, - {Name: "duplicate1", Type: "string", StringValue: "def"}, + {Name: "duplicate1", Type: "stringList", StringListValue: []string{"def"}}, {Name: "duplicate1", Type: "string", StringValue: "efg"}, {Name: "duplicate2", Type: "string", StringValue: "123"}, {Name: "duplicate2", Type: "string", StringValue: "456"}, @@ -1612,6 +1614,133 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) { ), }, }, + { + name: "the federation domain has valid IDPs and transformations and examples", + inputObjects: []runtime.Object{ + oidcIdentityProvider, + ldapIdentityProvider, + &configv1alpha1.FederationDomain{ + ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace, Generation: 123}, + Spec: configv1alpha1.FederationDomainSpec{ + Issuer: "https://issuer1.com", + IdentityProviders: []configv1alpha1.FederationDomainIdentityProvider{ + { + DisplayName: "name1", + ObjectRef: corev1.TypedLocalObjectReference{ + APIGroup: pointer.String(apiGroupSupervisor), + Kind: "OIDCIdentityProvider", + Name: oidcIdentityProvider.Name, + }, + Transforms: configv1alpha1.FederationDomainTransforms{ + Expressions: []configv1alpha1.FederationDomainTransformsExpression{ + {Type: "policy/v1", Expression: `username == "ryan" || username == "rejectMeWithDefaultMessage"`, Message: "only ryan allowed"}, + {Type: "policy/v1", Expression: `username != "rejectMeWithDefaultMessage"`}, // no message specified + {Type: "username/v1", Expression: `"pre:" + username`}, + {Type: "groups/v1", Expression: `groups.map(g, "pre:" + g)`}, + }, + Constants: []configv1alpha1.FederationDomainTransformsConstant{ + {Name: "str", Type: "string", StringValue: "abc"}, + {Name: "strL", Type: "stringList", StringListValue: []string{"def"}}, + }, + Examples: []configv1alpha1.FederationDomainTransformsExample{ + { + Username: "ryan", + Groups: []string{"a", "b"}, + Expects: configv1alpha1.FederationDomainTransformsExampleExpects{ + Username: "pre:ryan", + Groups: []string{"pre:b", "pre:a"}, + Rejected: false, + }, + }, + { + Username: "other", + Expects: configv1alpha1.FederationDomainTransformsExampleExpects{ + Rejected: true, + Message: "only ryan allowed", + }, + }, + { + Username: "rejectMeWithDefaultMessage", + Expects: configv1alpha1.FederationDomainTransformsExampleExpects{ + Rejected: true, + // Not specifying message is the same as expecting the default message. + }, + }, + { + Username: "rejectMeWithDefaultMessage", + Expects: configv1alpha1.FederationDomainTransformsExampleExpects{ + Rejected: true, + Message: "Authentication was rejected by a configured policy", // this is the default message + }, + }, + }, + }, + }, + { + DisplayName: "name2", + ObjectRef: corev1.TypedLocalObjectReference{ + APIGroup: pointer.String(apiGroupSupervisor), + Kind: "LDAPIdentityProvider", + Name: ldapIdentityProvider.Name, + }, + Transforms: configv1alpha1.FederationDomainTransforms{ + Expressions: []configv1alpha1.FederationDomainTransformsExpression{ + {Type: "username/v1", Expression: `"pre:" + username`}, + }, + Examples: []configv1alpha1.FederationDomainTransformsExample{ + { + Username: "ryan", + Groups: []string{"a", "b"}, + Expects: configv1alpha1.FederationDomainTransformsExampleExpects{ + Username: "pre:ryan", + Groups: []string{"b", "a"}, + Rejected: false, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantFDIssuers: []*federationdomainproviders.FederationDomainIssuer{ + federationDomainIssuerWithIDPs(t, "https://issuer1.com", []*federationdomainproviders.FederationDomainIdentityProvider{ + { + DisplayName: "name1", + UID: oidcIdentityProvider.UID, + Transforms: newTransformationPipeline(t, &celtransformer.TransformationConstants{ + StringConstants: map[string]string{"str": "abc"}, + StringListConstants: map[string][]string{"strL": {"def"}}, + }, + &celtransformer.AllowAuthenticationPolicy{ + Expression: `username == "ryan" || username == "rejectMeWithDefaultMessage"`, + RejectedAuthenticationMessage: "only ryan allowed", + }, + &celtransformer.AllowAuthenticationPolicy{Expression: `username != "rejectMeWithDefaultMessage"`}, + &celtransformer.UsernameTransformation{Expression: `"pre:" + username`}, + &celtransformer.GroupsTransformation{Expression: `groups.map(g, "pre:" + g)`}, + ), + }, + { + DisplayName: "name2", + UID: ldapIdentityProvider.UID, + Transforms: newTransformationPipeline(t, &celtransformer.TransformationConstants{}, + &celtransformer.UsernameTransformation{Expression: `"pre:" + username`}, + ), + }, + }), + }, + wantStatusUpdates: []*configv1alpha1.FederationDomain{ + expectedFederationDomainStatusUpdate( + &configv1alpha1.FederationDomain{ + ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace, Generation: 123}, + }, + configv1alpha1.FederationDomainPhaseReady, + allHappyConditionsSuccess("https://issuer1.com", frozenMetav1Now, 123), + ), + }, + }, { name: "the federation domain specifies illegal const type, which shouldn't really happen since the CRD validates it", inputObjects: []runtime.Object{ @@ -1719,7 +1848,12 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) { if tt.wantFDIssuers != nil { require.True(t, federationDomainsSetter.SetFederationDomainsWasCalled) - require.ElementsMatch(t, tt.wantFDIssuers, federationDomainsSetter.FederationDomainsReceived) + // This is ugly, but we cannot test equality on compiled identity transformations because cel.Program + // cannot be compared for equality. This converts them to a type which can be tested for equality, + // which should be good enough for the purposes of this test. + require.ElementsMatch(t, + convertToComparableType(tt.wantFDIssuers), + convertToComparableType(federationDomainsSetter.FederationDomainsReceived)) } else { require.False(t, federationDomainsSetter.SetFederationDomainsWasCalled) } @@ -1743,6 +1877,46 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) { } } +type comparableFederationDomainIssuer struct { + issuer string + identityProviders []*comparableFederationDomainIdentityProvider + defaultIdentityProvider *comparableFederationDomainIdentityProvider +} + +type comparableFederationDomainIdentityProvider struct { + DisplayName string + UID types.UID + TransformsSource []interface{} +} + +func makeFederationDomainIdentityProviderComparable(fdi *federationdomainproviders.FederationDomainIdentityProvider) *comparableFederationDomainIdentityProvider { + if fdi == nil { + return nil + } + return &comparableFederationDomainIdentityProvider{ + DisplayName: fdi.DisplayName, + UID: fdi.UID, + TransformsSource: fdi.Transforms.Source(), + } +} + +func convertToComparableType(fdis []*federationdomainproviders.FederationDomainIssuer) []*comparableFederationDomainIssuer { + result := []*comparableFederationDomainIssuer{} + for _, fdi := range fdis { + comparableFDIs := make([]*comparableFederationDomainIdentityProvider, len(fdi.IdentityProviders())) + for _, idp := range fdi.IdentityProviders() { + comparableFDIs = append(comparableFDIs, makeFederationDomainIdentityProviderComparable(idp)) + } + converted := &comparableFederationDomainIssuer{ + issuer: fdi.Issuer(), + identityProviders: comparableFDIs, + defaultIdentityProvider: makeFederationDomainIdentityProviderComparable(fdi.DefaultIdentityProvider()), + } + result = append(result, converted) + } + return result +} + func expectedFederationDomainStatusUpdate( fd *configv1alpha1.FederationDomain, phase configv1alpha1.FederationDomainPhase, @@ -1789,3 +1963,116 @@ func sortFederationDomainsByName(federationDomains []*configv1alpha1.FederationD return federationDomains[a].GetName() < federationDomains[b].GetName() }) } + +func newTransformationPipeline( + t *testing.T, + consts *celtransformer.TransformationConstants, + transformations ...celtransformer.CELTransformation, +) *idtransform.TransformationPipeline { + pipeline := idtransform.NewTransformationPipeline() + + compiler, err := celtransformer.NewCELTransformer(celTransformerMaxExpressionRuntime) + require.NoError(t, err) + + if consts.StringConstants == nil { + consts.StringConstants = map[string]string{} + } + if consts.StringListConstants == nil { + consts.StringListConstants = map[string][]string{} + } + + for _, transform := range transformations { + compiledTransform, err := compiler.CompileTransformation(transform, consts) + require.NoError(t, err) + pipeline.AppendTransformation(compiledTransform) + } + + return pipeline +} + +func TestTransformationPipelinesCanBeTestedForEqualityUsingSourceToMakeTestingEasier(t *testing.T) { + compiler, err := celtransformer.NewCELTransformer(5 * time.Second) + require.NoError(t, err) + + transforms := []celtransformer.CELTransformation{ + &celtransformer.AllowAuthenticationPolicy{ + Expression: `username == "ryan" || username == "rejectMeWithDefaultMessage"`, + RejectedAuthenticationMessage: "only ryan allowed", + }, + &celtransformer.UsernameTransformation{Expression: `"pre:" + username`}, + &celtransformer.GroupsTransformation{Expression: `groups.map(g, "pre:" + g)`}, + } + + differentTransforms := []celtransformer.CELTransformation{ + &celtransformer.AllowAuthenticationPolicy{ + Expression: `username == "ryan" || username == "different"`, + RejectedAuthenticationMessage: "different", + }, + &celtransformer.UsernameTransformation{Expression: `"different:" + username`}, + &celtransformer.GroupsTransformation{Expression: `groups.map(g, "different:" + g)`}, + } + + consts := &celtransformer.TransformationConstants{ + StringConstants: map[string]string{ + "foo": "bar", + "baz": "bat", + }, + StringListConstants: map[string][]string{ + "foo": {"a", "b"}, + "bar": {"c", "d"}, + }, + } + + differentConsts := &celtransformer.TransformationConstants{ + StringConstants: map[string]string{ + "foo": "barDifferent", + "baz": "bat", + }, + StringListConstants: map[string][]string{ + "foo": {"aDifferent", "b"}, + "bar": {"c", "d"}, + }, + } + + pipeline := idtransform.NewTransformationPipeline() + equalPipeline := idtransform.NewTransformationPipeline() + differentPipeline1 := idtransform.NewTransformationPipeline() + differentPipeline2 := idtransform.NewTransformationPipeline() + expectedSourceList := []interface{}{} + + for i, transform := range transforms { + // Compile and append to a pipeline. + compiledTransform1, err := compiler.CompileTransformation(transform, consts) + require.NoError(t, err) + pipeline.AppendTransformation(compiledTransform1) + + // Recompile the same thing and append it to another pipeline. + // This pipeline should end up being equal to the first one. + compiledTransform2, err := compiler.CompileTransformation(transform, consts) + require.NoError(t, err) + equalPipeline.AppendTransformation(compiledTransform2) + + // Build up a test expectation value. + expectedSourceList = append(expectedSourceList, &celtransformer.CELTransformationSource{Expr: transform, Consts: consts}) + + // Compile a different expression using the same constants and append it to a different pipeline. + // This should not be equal to the other pipelines. + compiledDifferentExpressionSameConsts, err := compiler.CompileTransformation(differentTransforms[i], consts) + require.NoError(t, err) + differentPipeline1.AppendTransformation(compiledDifferentExpressionSameConsts) + + // Compile the same expression using the different constants and append it to a different pipeline. + // This should not be equal to the other pipelines. + compiledSameExpressionDifferentConsts, err := compiler.CompileTransformation(transform, differentConsts) + require.NoError(t, err) + differentPipeline2.AppendTransformation(compiledSameExpressionDifferentConsts) + } + + require.Equal(t, expectedSourceList, pipeline.Source()) + require.Equal(t, expectedSourceList, equalPipeline.Source()) + + // The source of compiled pipelines can be compared to each other in this way for testing purposes. + require.Equal(t, pipeline.Source(), equalPipeline.Source()) + require.NotEqual(t, pipeline.Source(), differentPipeline1.Source()) + require.NotEqual(t, pipeline.Source(), differentPipeline2.Source()) +} diff --git a/internal/idtransform/identity_transformations.go b/internal/idtransform/identity_transformations.go index 133d2601..2518b8e2 100644 --- a/internal/idtransform/identity_transformations.go +++ b/internal/idtransform/identity_transformations.go @@ -25,6 +25,10 @@ type TransformationResult struct { // IdentityTransformation is an individual identity transformation which can be evaluated. type IdentityTransformation interface { Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) + + // Source returns some representation of the original source code of the transformation, which is + // useful for tests to be able to check that a compiled transformation came from the right source. + Source() interface{} } // TransformationPipeline is a list of identity transforms, which can be evaluated in order against some given input @@ -85,6 +89,14 @@ func (p *TransformationPipeline) Evaluate(ctx context.Context, username string, return accumulatedResult, nil } +func (p *TransformationPipeline) Source() []interface{} { + result := []interface{}{} + for _, transform := range p.transforms { + result = append(result, transform.Source()) + } + return result +} + func sortAndUniq(s []string) []string { unique := sets.New(s...).UnsortedList() sort.Strings(unique) diff --git a/internal/idtransform/identity_transformations_test.go b/internal/idtransform/identity_transformations_test.go index f3e8707f..80d8f2df 100644 --- a/internal/idtransform/identity_transformations_test.go +++ b/internal/idtransform/identity_transformations_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/require" ) -type FakeNoopTransformer struct{} +type fakeNoopTransformer struct{} -func (a FakeNoopTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { +func (a fakeNoopTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { return &TransformationResult{ Username: username, Groups: groups, @@ -22,9 +22,13 @@ func (a FakeNoopTransformer) Evaluate(ctx context.Context, username string, grou }, nil } -type FakeNilGroupTransformer struct{} +func (a fakeNoopTransformer) Source() interface{} { + return nil // not needed for this test +} -func (a FakeNilGroupTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { +type fakeNilGroupTransformer struct{} + +func (a fakeNilGroupTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { return &TransformationResult{ Username: username, Groups: nil, @@ -33,9 +37,13 @@ func (a FakeNilGroupTransformer) Evaluate(ctx context.Context, username string, }, nil } -type FakeAppendStringTransformer struct{} +func (a fakeNilGroupTransformer) Source() interface{} { + return nil // not needed for this test +} -func (a FakeAppendStringTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { +type fakeAppendStringTransformer struct{} + +func (a fakeAppendStringTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { newGroups := []string{} for _, group := range groups { newGroups = append(newGroups, group+":transformed") @@ -48,9 +56,13 @@ func (a FakeAppendStringTransformer) Evaluate(ctx context.Context, username stri }, nil } -type FakeDeleteUsernameAndGroupsTransformer struct{} +func (a fakeAppendStringTransformer) Source() interface{} { + return nil // not needed for this test +} -func (d FakeDeleteUsernameAndGroupsTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { +type fakeDeleteUsernameAndGroupsTransformer struct{} + +func (a fakeDeleteUsernameAndGroupsTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { return &TransformationResult{ Username: "", Groups: []string{}, @@ -59,9 +71,13 @@ func (d FakeDeleteUsernameAndGroupsTransformer) Evaluate(ctx context.Context, us }, nil } -type FakeAuthenticationDisallowedTransformer struct{} +func (a fakeDeleteUsernameAndGroupsTransformer) Source() interface{} { + return nil // not needed for this test +} -func (d FakeAuthenticationDisallowedTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { +type fakeAuthenticationDisallowedTransformer struct{} + +func (a fakeAuthenticationDisallowedTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { newGroups := []string{} for _, group := range groups { newGroups = append(newGroups, group+":disallowed") @@ -74,13 +90,33 @@ func (d FakeAuthenticationDisallowedTransformer) Evaluate(ctx context.Context, u }, nil } -type FakeErrorTransformer struct{} +func (a fakeAuthenticationDisallowedTransformer) Source() interface{} { + return nil // not needed for this test +} -func (d FakeErrorTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { +type fakeErrorTransformer struct{} + +func (a fakeErrorTransformer) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { return &TransformationResult{}, errors.New("unexpected catastrophic error") } -func TestTransformationPipeline(t *testing.T) { +func (a fakeErrorTransformer) Source() interface{} { + return nil // not needed for this test +} + +type fakeTransformerWithSource struct { + source string +} + +func (a fakeTransformerWithSource) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) { + return nil, nil // not needed for this test +} + +func (a fakeTransformerWithSource) Source() interface{} { + return a.source +} + +func TestTransformationPipelineEvaluation(t *testing.T) { tests := []struct { name string username string @@ -95,7 +131,7 @@ func TestTransformationPipeline(t *testing.T) { { name: "single transformation applied successfully", transforms: []IdentityTransformation{ - FakeAppendStringTransformer{}, + fakeAppendStringTransformer{}, }, username: "foo", groups: []string{ @@ -113,7 +149,7 @@ func TestTransformationPipeline(t *testing.T) { { name: "group results are sorted and made unique", transforms: []IdentityTransformation{ - FakeAppendStringTransformer{}, + fakeAppendStringTransformer{}, }, username: "foo", groups: []string{ @@ -141,8 +177,8 @@ func TestTransformationPipeline(t *testing.T) { "foobaz", }, transforms: []IdentityTransformation{ - FakeAppendStringTransformer{}, - FakeAppendStringTransformer{}, + fakeAppendStringTransformer{}, + fakeAppendStringTransformer{}, }, wantUsername: "foo:transformed:transformed", wantGroups: []string{ @@ -159,7 +195,7 @@ func TestTransformationPipeline(t *testing.T) { "foobar", }, transforms: []IdentityTransformation{ - FakeAuthenticationDisallowedTransformer{}, + fakeAuthenticationDisallowedTransformer{}, }, wantUsername: "foo:disallowed", wantGroups: []string{"foobar:disallowed"}, @@ -173,10 +209,10 @@ func TestTransformationPipeline(t *testing.T) { "foobar", }, transforms: []IdentityTransformation{ - FakeAppendStringTransformer{}, - FakeAuthenticationDisallowedTransformer{}, + fakeAppendStringTransformer{}, + fakeAuthenticationDisallowedTransformer{}, // this transformation will not be run because the previous exits the pipeline - FakeAppendStringTransformer{}, + fakeAppendStringTransformer{}, }, wantUsername: "foo:transformed:disallowed", wantGroups: []string{"foobar:transformed:disallowed"}, @@ -190,9 +226,9 @@ func TestTransformationPipeline(t *testing.T) { "foobar", }, transforms: []IdentityTransformation{ - FakeAppendStringTransformer{}, - FakeErrorTransformer{}, - FakeAppendStringTransformer{}, + fakeAppendStringTransformer{}, + fakeErrorTransformer{}, + fakeAppendStringTransformer{}, }, wantError: "identity transformation at index 1: unexpected catastrophic error", }, @@ -200,7 +236,7 @@ func TestTransformationPipeline(t *testing.T) { name: "empty username not allowed", username: "foo", transforms: []IdentityTransformation{ - FakeDeleteUsernameAndGroupsTransformer{}, + fakeDeleteUsernameAndGroupsTransformer{}, }, wantError: "identity transformation returned an empty username, which is not allowed", }, @@ -208,7 +244,7 @@ func TestTransformationPipeline(t *testing.T) { name: "whitespace username not allowed", username: " \t\n\r ", transforms: []IdentityTransformation{ - FakeNoopTransformer{}, + fakeNoopTransformer{}, }, wantError: "identity transformation returned an empty username, which is not allowed", }, @@ -217,7 +253,7 @@ func TestTransformationPipeline(t *testing.T) { username: "foo", groups: []string{}, transforms: []IdentityTransformation{ - FakeAppendStringTransformer{}, + fakeAppendStringTransformer{}, }, wantUsername: "foo:transformed", wantGroups: []string{}, @@ -229,7 +265,7 @@ func TestTransformationPipeline(t *testing.T) { username: "foo", groups: nil, transforms: []IdentityTransformation{ - FakeNoopTransformer{}, + fakeNoopTransformer{}, }, wantUsername: "foo", wantGroups: []string{}, @@ -243,7 +279,7 @@ func TestTransformationPipeline(t *testing.T) { "these.will.be.converted.to.nil", }, transforms: []IdentityTransformation{ - FakeNilGroupTransformer{}, + fakeNilGroupTransformer{}, }, wantError: "identity transformation returned a null list of groups, which is not allowed", }, @@ -287,3 +323,18 @@ func TestTransformationPipeline(t *testing.T) { }) } } + +func TestTransformationSource(t *testing.T) { + pipeline := NewTransformationPipeline() + + for _, transform := range []IdentityTransformation{ + &fakeTransformerWithSource{source: "foo"}, + &fakeTransformerWithSource{source: "bar"}, + &fakeTransformerWithSource{source: "baz"}, + } { + pipeline.AppendTransformation(transform) + } + + require.Equal(t, []interface{}{"foo", "bar", "baz"}, pipeline.Source()) + require.NotEqual(t, []interface{}{"foo", "something-else", "baz"}, pipeline.Source()) +}