mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
402 lines
10 KiB
Go
402 lines
10 KiB
Go
/*
|
|
Copyright 2017-2020 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package parse
|
|
|
|
import (
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/gravitational/trace"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestVariable tests variable parsing
|
|
func TestVariable(t *testing.T) {
|
|
t.Parallel()
|
|
var tests = []struct {
|
|
title string
|
|
in string
|
|
err error
|
|
out Expression
|
|
}{
|
|
{
|
|
title: "no curly bracket prefix",
|
|
in: "external.foo}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "invalid syntax",
|
|
in: `{{external.foo("bar")`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "invalid variable syntax",
|
|
in: "{{internal.}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "invalid dot syntax",
|
|
in: "{{external..foo}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "empty variable",
|
|
in: "{{}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "no curly bracket suffix",
|
|
in: "{{internal.foo",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "too many levels of nesting in the variable",
|
|
in: "{{internal.foo.bar}}",
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "regexp function call not allowed",
|
|
in: `{{regexp.match(".*")}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "valid with brackets",
|
|
in: `{{internal["foo"]}}`,
|
|
out: Expression{namespace: "internal", variable: "foo"},
|
|
},
|
|
{
|
|
title: "string literal",
|
|
in: `foo`,
|
|
out: Expression{namespace: LiteralNamespace, variable: "foo"},
|
|
},
|
|
{
|
|
title: "external with no brackets",
|
|
in: "{{external.foo}}",
|
|
out: Expression{namespace: "external", variable: "foo"},
|
|
},
|
|
{
|
|
title: "internal with no brackets",
|
|
in: "{{internal.bar}}",
|
|
out: Expression{namespace: "internal", variable: "bar"},
|
|
},
|
|
{
|
|
title: "internal with spaces removed",
|
|
in: " {{ internal.bar }} ",
|
|
out: Expression{namespace: "internal", variable: "bar"},
|
|
},
|
|
{
|
|
title: "variable with prefix and suffix",
|
|
in: " hello, {{ internal.bar }} there! ",
|
|
out: Expression{prefix: "hello, ", namespace: "internal", variable: "bar", suffix: " there!"},
|
|
},
|
|
{
|
|
title: "variable with local function",
|
|
in: "{{email.local(internal.bar)}}",
|
|
out: Expression{namespace: "internal", variable: "bar", transform: emailLocalTransformer{}},
|
|
},
|
|
{
|
|
title: "regexp replace",
|
|
in: `{{regexp.replace(internal.foo, "bar-(.*)", "$1")}}`,
|
|
out: Expression{
|
|
namespace: "internal",
|
|
variable: "foo",
|
|
transform: ®expReplaceTransformer{
|
|
re: regexp.MustCompile("bar-(.*)"),
|
|
replacement: "$1",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: "regexp replace with variable expression",
|
|
in: `{{regexp.replace(internal.foo, internal.bar, "baz")}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "regexp replace with variable replacement",
|
|
in: `{{regexp.replace(internal.foo, "bar", internal.baz)}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.title, func(t *testing.T) {
|
|
variable, err := NewExpression(tt.in)
|
|
if tt.err != nil {
|
|
require.IsType(t, tt.err, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.out, *variable)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestInterpolate tests variable interpolation
|
|
func TestInterpolate(t *testing.T) {
|
|
t.Parallel()
|
|
type result struct {
|
|
values []string
|
|
err error
|
|
}
|
|
var tests = []struct {
|
|
title string
|
|
in Expression
|
|
traits map[string][]string
|
|
res result
|
|
}{
|
|
{
|
|
title: "mapped traits",
|
|
in: Expression{variable: "foo"},
|
|
traits: map[string][]string{"foo": []string{"a", "b"}, "bar": []string{"c"}},
|
|
res: result{values: []string{"a", "b"}},
|
|
},
|
|
{
|
|
title: "mapped traits with email.local",
|
|
in: Expression{variable: "foo", transform: emailLocalTransformer{}},
|
|
traits: map[string][]string{"foo": []string{"Alice <alice@example.com>", "bob@example.com"}, "bar": []string{"c"}},
|
|
res: result{values: []string{"alice", "bob"}},
|
|
},
|
|
{
|
|
title: "missed traits",
|
|
in: Expression{variable: "baz"},
|
|
traits: map[string][]string{"foo": []string{"a", "b"}, "bar": []string{"c"}},
|
|
res: result{err: trace.NotFound("not found"), values: []string{}},
|
|
},
|
|
{
|
|
title: "traits with prefix and suffix",
|
|
in: Expression{prefix: "IAM#", variable: "foo", suffix: ";"},
|
|
traits: map[string][]string{"foo": []string{"a", "b"}, "bar": []string{"c"}},
|
|
res: result{values: []string{"IAM#a;", "IAM#b;"}},
|
|
},
|
|
{
|
|
title: "error in mapping traits",
|
|
in: Expression{variable: "foo", transform: emailLocalTransformer{}},
|
|
traits: map[string][]string{"foo": []string{"Alice <alice"}},
|
|
res: result{err: trace.BadParameter("")},
|
|
},
|
|
{
|
|
title: "literal expression",
|
|
in: Expression{namespace: LiteralNamespace, variable: "foo"},
|
|
traits: map[string][]string{"foo": []string{"a", "b"}, "bar": []string{"c"}},
|
|
res: result{values: []string{"foo"}},
|
|
},
|
|
{
|
|
title: "regexp replacement with numeric match",
|
|
in: Expression{
|
|
variable: "foo",
|
|
transform: regexpReplaceTransformer{
|
|
re: regexp.MustCompile("bar-(.*)"),
|
|
replacement: "$1",
|
|
},
|
|
},
|
|
traits: map[string][]string{"foo": []string{"bar-baz"}},
|
|
res: result{values: []string{"baz"}},
|
|
},
|
|
{
|
|
title: "regexp replacement with named match",
|
|
in: Expression{
|
|
variable: "foo",
|
|
transform: regexpReplaceTransformer{
|
|
re: regexp.MustCompile("bar-(?P<suffix>.*)"),
|
|
replacement: "${suffix}",
|
|
},
|
|
},
|
|
traits: map[string][]string{"foo": []string{"bar-baz"}},
|
|
res: result{values: []string{"baz"}},
|
|
},
|
|
{
|
|
title: "regexp replacement with multiple matches",
|
|
in: Expression{
|
|
variable: "foo",
|
|
transform: regexpReplaceTransformer{
|
|
re: regexp.MustCompile("foo-(.*)-(.*)"),
|
|
replacement: "$1.$2",
|
|
},
|
|
},
|
|
traits: map[string][]string{"foo": []string{"foo-bar-baz"}},
|
|
res: result{values: []string{"bar.baz"}},
|
|
},
|
|
{
|
|
title: "regexp replacement with no match",
|
|
in: Expression{
|
|
variable: "foo",
|
|
transform: regexpReplaceTransformer{
|
|
re: regexp.MustCompile("^bar-(.*)$"),
|
|
replacement: "$1-matched",
|
|
},
|
|
},
|
|
traits: map[string][]string{"foo": []string{"foo-test1", "bar-test2"}},
|
|
res: result{values: []string{"test2-matched"}},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.title, func(t *testing.T) {
|
|
values, err := tt.in.Interpolate(tt.traits)
|
|
if tt.res.err != nil {
|
|
require.IsType(t, tt.res.err, err)
|
|
require.Empty(t, values)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.res.values, values)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatch(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
title string
|
|
in string
|
|
err error
|
|
out Matcher
|
|
}{
|
|
{
|
|
title: "no curly bracket prefix",
|
|
in: `regexp.match(".*")}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "no curly bracket suffix",
|
|
in: `{{regexp.match(".*")`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "unknown function",
|
|
in: `{{regexp.surprise(".*")}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "bad regexp",
|
|
in: `{{regexp.match("+foo")}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "unknown namespace",
|
|
in: `{{surprise.match(".*")}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "unsupported namespace",
|
|
in: `{{email.local(external.email)}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "unsupported variable syntax",
|
|
in: `{{external.email}}`,
|
|
err: trace.BadParameter(""),
|
|
},
|
|
{
|
|
title: "string literal",
|
|
in: `foo`,
|
|
out: ®expMatcher{re: regexp.MustCompile(`^foo$`)},
|
|
},
|
|
{
|
|
title: "wildcard",
|
|
in: `foo*`,
|
|
out: ®expMatcher{re: regexp.MustCompile(`^foo(.*)$`)},
|
|
},
|
|
{
|
|
title: "raw regexp",
|
|
in: `^foo.*$`,
|
|
out: ®expMatcher{re: regexp.MustCompile(`^foo.*$`)},
|
|
},
|
|
{
|
|
title: "regexp.match call",
|
|
in: `foo-{{regexp.match("bar")}}-baz`,
|
|
out: prefixSuffixMatcher{
|
|
prefix: "foo-",
|
|
suffix: "-baz",
|
|
m: ®expMatcher{re: regexp.MustCompile(`bar`)},
|
|
},
|
|
},
|
|
{
|
|
title: "regexp.not_match call",
|
|
in: `foo-{{regexp.not_match("bar")}}-baz`,
|
|
out: prefixSuffixMatcher{
|
|
prefix: "foo-",
|
|
suffix: "-baz",
|
|
m: notMatcher{®expMatcher{re: regexp.MustCompile(`bar`)}},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.title, func(t *testing.T) {
|
|
matcher, err := NewMatcher(tt.in)
|
|
if tt.err != nil {
|
|
require.IsType(t, tt.err, err, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Empty(t, cmp.Diff(tt.out, matcher, cmp.AllowUnexported(
|
|
regexpMatcher{}, prefixSuffixMatcher{}, notMatcher{}, regexp.Regexp{},
|
|
)))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchers(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
title string
|
|
matcher Matcher
|
|
in string
|
|
want bool
|
|
}{
|
|
{
|
|
title: "regexp matcher positive",
|
|
matcher: regexpMatcher{re: regexp.MustCompile(`foo`)},
|
|
in: "foo",
|
|
want: true,
|
|
},
|
|
{
|
|
title: "regexp matcher negative",
|
|
matcher: regexpMatcher{re: regexp.MustCompile(`bar`)},
|
|
in: "foo",
|
|
want: false,
|
|
},
|
|
{
|
|
title: "not matcher",
|
|
matcher: notMatcher{regexpMatcher{re: regexp.MustCompile(`bar`)}},
|
|
in: "foo",
|
|
want: true,
|
|
},
|
|
{
|
|
title: "prefix/suffix matcher positive",
|
|
matcher: prefixSuffixMatcher{prefix: "foo-", m: regexpMatcher{re: regexp.MustCompile(`bar`)}, suffix: "-baz"},
|
|
in: "foo-bar-baz",
|
|
want: true,
|
|
},
|
|
{
|
|
title: "prefix/suffix matcher negative",
|
|
matcher: prefixSuffixMatcher{prefix: "foo-", m: regexpMatcher{re: regexp.MustCompile(`bar`)}, suffix: "-baz"},
|
|
in: "foo-foo-baz",
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.title, func(t *testing.T) {
|
|
got := tt.matcher.Match(tt.in)
|
|
require.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|