mirror of https://github.com/dexidp/dex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
4.0 KiB
137 lines
4.0 KiB
package cel_test |
|
|
|
import ( |
|
"testing" |
|
|
|
"github.com/stretchr/testify/assert" |
|
"github.com/stretchr/testify/require" |
|
|
|
dexcel "github.com/dexidp/dex/pkg/cel" |
|
) |
|
|
|
func TestEstimateCost(t *testing.T) { |
|
vars := dexcel.IdentityVariables() |
|
compiler, err := dexcel.NewCompiler(vars) |
|
require.NoError(t, err) |
|
|
|
tests := map[string]struct { |
|
expr string |
|
}{ |
|
"simple bool": { |
|
expr: "true", |
|
}, |
|
"string comparison": { |
|
expr: "identity.email == 'test@example.com'", |
|
}, |
|
"group membership": { |
|
expr: "identity.groups.exists(g, g == 'admin')", |
|
}, |
|
} |
|
|
|
for name, tc := range tests { |
|
t.Run(name, func(t *testing.T) { |
|
prog, err := compiler.Compile(tc.expr) |
|
require.NoError(t, err) |
|
|
|
est, err := compiler.EstimateCost(prog) |
|
require.NoError(t, err) |
|
assert.True(t, est.Max >= est.Min, "max cost should be >= min cost") |
|
assert.True(t, est.Max <= dexcel.DefaultCostBudget, |
|
"estimated max cost %d should be within default budget %d", est.Max, dexcel.DefaultCostBudget) |
|
}) |
|
} |
|
} |
|
|
|
func TestCompileTimeCostAcceptsSimpleExpressions(t *testing.T) { |
|
vars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...) |
|
compiler, err := dexcel.NewCompiler(vars) |
|
require.NoError(t, err) |
|
|
|
tests := map[string]string{ |
|
"literal": "true", |
|
"email endsWith": "identity.email.endsWith('@example.com')", |
|
"group check": "'admin' in identity.groups", |
|
"emailDomain": `dex.emailDomain(identity.email)`, |
|
"groupMatches": `dex.groupMatches(identity.groups, "team:*")`, |
|
"groupFilter": `dex.groupFilter(identity.groups, ["admin", "dev"])`, |
|
"combined policy": `identity.email.endsWith('@example.com') && 'admin' in identity.groups`, |
|
"complex policy": `identity.email.endsWith('@example.com') && |
|
identity.groups.exists(g, g == 'admin') && |
|
request.connector_id == 'okta' && |
|
request.scopes.exists(s, s == 'openid')`, |
|
"filter+map chain": `identity.groups |
|
.filter(g, g.startsWith('team:')) |
|
.map(g, g.replace('team:', '')) |
|
.size() > 0`, |
|
} |
|
|
|
for name, expr := range tests { |
|
t.Run(name, func(t *testing.T) { |
|
_, err := compiler.Compile(expr) |
|
assert.NoError(t, err, "expression should compile within default budget") |
|
}) |
|
} |
|
} |
|
|
|
func TestCompileTimeCostRejection(t *testing.T) { |
|
vars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...) |
|
|
|
tests := map[string]struct { |
|
budget uint64 |
|
expr string |
|
}{ |
|
"simple exists exceeds tiny budget": { |
|
budget: 1, |
|
expr: "identity.groups.exists(g, g == 'admin')", |
|
}, |
|
"endsWith exceeds tiny budget": { |
|
budget: 2, |
|
expr: "identity.email.endsWith('@example.com')", |
|
}, |
|
"nested comprehension over groups exceeds moderate budget": { |
|
// Two nested iterations over groups: O(n^2) where n=100 → ~280K |
|
budget: 10_000, |
|
expr: `identity.groups.exists(g1, |
|
identity.groups.exists(g2, |
|
g1 != g2 && g1.startsWith(g2) |
|
) |
|
)`, |
|
}, |
|
"cross-variable comprehension exceeds moderate budget": { |
|
// filter groups then check each against scopes: O(n*m) → ~162K |
|
budget: 10_000, |
|
expr: `identity.groups |
|
.filter(g, g.startsWith('team:')) |
|
.exists(g, request.scopes.exists(s, s == g))`, |
|
}, |
|
"chained filter+map+filter+map exceeds small budget": { |
|
budget: 1000, |
|
expr: `identity.groups |
|
.filter(g, g.startsWith('team:')) |
|
.map(g, g.replace('team:', '')) |
|
.filter(g, g.size() > 3) |
|
.map(g, g.upperAscii()) |
|
.size() > 0`, |
|
}, |
|
"many independent exists exceeds small budget": { |
|
budget: 5000, |
|
expr: `identity.groups.exists(g, g.contains('a')) && |
|
identity.groups.exists(g, g.contains('b')) && |
|
identity.groups.exists(g, g.contains('c')) && |
|
identity.groups.exists(g, g.contains('d')) && |
|
identity.groups.exists(g, g.contains('e'))`, |
|
}, |
|
} |
|
|
|
for name, tc := range tests { |
|
t.Run(name, func(t *testing.T) { |
|
compiler, err := dexcel.NewCompiler(vars, dexcel.WithCostBudget(tc.budget)) |
|
require.NoError(t, err) |
|
|
|
_, err = compiler.Compile(tc.expr) |
|
assert.Error(t, err) |
|
assert.Contains(t, err.Error(), "estimated cost") |
|
assert.Contains(t, err.Error(), "exceeds budget") |
|
}) |
|
} |
|
}
|
|
|