OpenID Connect (OIDC) identity and OAuth 2.0 provider with pluggable connectors
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.
 
 
 
 
 
 

280 lines
6.7 KiB

package cel_test
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dexidp/dex/connector"
dexcel "github.com/dexidp/dex/pkg/cel"
)
func TestCompileBool(t *testing.T) {
compiler, err := dexcel.NewCompiler(nil)
require.NoError(t, err)
tests := map[string]struct {
expr string
wantErr bool
}{
"true literal": {
expr: "true",
},
"comparison": {
expr: "1 == 1",
},
"string type mismatch": {
expr: "'hello'",
wantErr: true,
},
"int type mismatch": {
expr: "42",
wantErr: true,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
result, err := compiler.CompileBool(tc.expr)
if tc.wantErr {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.NotNil(t, result)
}
})
}
}
func TestCompileString(t *testing.T) {
compiler, err := dexcel.NewCompiler(nil)
require.NoError(t, err)
tests := map[string]struct {
expr string
wantErr bool
}{
"string literal": {
expr: "'hello'",
},
"string concatenation": {
expr: "'hello' + ' ' + 'world'",
},
"bool type mismatch": {
expr: "true",
wantErr: true,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
result, err := compiler.CompileString(tc.expr)
if tc.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.NotNil(t, result)
}
})
}
}
func TestCompileStringList(t *testing.T) {
compiler, err := dexcel.NewCompiler(nil)
require.NoError(t, err)
result, err := compiler.CompileStringList("['a', 'b', 'c']")
assert.NoError(t, err)
assert.NotNil(t, result)
_, err = compiler.CompileStringList("'not a list'")
assert.Error(t, err)
}
func TestCompile(t *testing.T) {
compiler, err := dexcel.NewCompiler(nil)
require.NoError(t, err)
// Compile accepts any type
result, err := compiler.Compile("true")
assert.NoError(t, err)
assert.NotNil(t, result)
result, err = compiler.Compile("'hello'")
assert.NoError(t, err)
assert.NotNil(t, result)
result, err = compiler.Compile("42")
assert.NoError(t, err)
assert.NotNil(t, result)
}
func TestCompileErrors(t *testing.T) {
compiler, err := dexcel.NewCompiler(nil)
require.NoError(t, err)
tests := map[string]struct {
expr string
}{
"syntax error": {
expr: "1 +",
},
"undefined variable": {
expr: "undefined_var",
},
"undefined function": {
expr: "undefinedFunc()",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
_, err := compiler.Compile(tc.expr)
assert.Error(t, err)
})
}
}
func TestCompileRejectsUnknownFields(t *testing.T) {
vars := dexcel.IdentityVariables()
compiler, err := dexcel.NewCompiler(vars)
require.NoError(t, err)
// Typo in field name: should fail at compile time with ObjectType
_, err = compiler.CompileBool("identity.emial == 'test@example.com'")
assert.Error(t, err)
assert.Contains(t, err.Error(), "compilation failed")
// Type mismatch: comparing string field to int should fail at compile time
_, err = compiler.CompileBool("identity.email == 123")
assert.Error(t, err)
assert.Contains(t, err.Error(), "compilation failed")
// Valid field: should compile fine
_, err = compiler.CompileBool("identity.email == 'test@example.com'")
assert.NoError(t, err)
}
func TestMaxExpressionLength(t *testing.T) {
compiler, err := dexcel.NewCompiler(nil)
require.NoError(t, err)
longExpr := "'" + strings.Repeat("a", dexcel.MaxExpressionLength) + "'"
_, err = compiler.Compile(longExpr)
assert.Error(t, err)
assert.Contains(t, err.Error(), "maximum length")
}
func TestEvalBool(t *testing.T) {
vars := dexcel.IdentityVariables()
compiler, err := dexcel.NewCompiler(vars)
require.NoError(t, err)
tests := map[string]struct {
expr string
identity dexcel.IdentityVal
want bool
}{
"email endsWith": {
expr: "identity.email.endsWith('@example.com')",
identity: dexcel.IdentityVal{Email: "user@example.com"},
want: true,
},
"email endsWith false": {
expr: "identity.email.endsWith('@example.com')",
identity: dexcel.IdentityVal{Email: "user@other.com"},
want: false,
},
"email_verified": {
expr: "identity.email_verified == true",
identity: dexcel.IdentityVal{EmailVerified: true},
want: true,
},
"group membership": {
expr: "identity.groups.exists(g, g == 'admin')",
identity: dexcel.IdentityVal{Groups: []string{"admin", "dev"}},
want: true,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
prog, err := compiler.CompileBool(tc.expr)
require.NoError(t, err)
result, err := dexcel.EvalBool(context.Background(), prog, map[string]any{
"identity": tc.identity,
})
require.NoError(t, err)
assert.Equal(t, tc.want, result)
})
}
}
func TestEvalString(t *testing.T) {
vars := dexcel.IdentityVariables()
compiler, err := dexcel.NewCompiler(vars)
require.NoError(t, err)
// With ObjectType, identity.email is typed as string, so CompileString works.
prog, err := compiler.CompileString("identity.email")
require.NoError(t, err)
result, err := dexcel.EvalString(context.Background(), prog, map[string]any{
"identity": dexcel.IdentityVal{Email: "user@example.com"},
})
require.NoError(t, err)
assert.Equal(t, "user@example.com", result)
}
func TestEvalWithIdentityAndRequest(t *testing.T) {
vars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...)
compiler, err := dexcel.NewCompiler(vars)
require.NoError(t, err)
prog, err := compiler.CompileBool(
`identity.email.endsWith('@example.com') && 'admin' in identity.groups && request.connector_id == 'okta'`,
)
require.NoError(t, err)
identity := dexcel.IdentityFromConnector(connector.Identity{
UserID: "123",
Username: "john",
Email: "john@example.com",
Groups: []string{"admin", "dev"},
})
request := dexcel.RequestFromContext(dexcel.RequestContext{
ClientID: "my-app",
ConnectorID: "okta",
Scopes: []string{"openid", "email"},
})
result, err := dexcel.EvalBool(context.Background(), prog, map[string]any{
"identity": identity,
"request": request,
})
require.NoError(t, err)
assert.True(t, result)
}
func TestNewCompilerWithVariables(t *testing.T) {
// Claims variable — remains map(string, dyn)
compiler, err := dexcel.NewCompiler(dexcel.ClaimsVariable())
require.NoError(t, err)
// claims.email returns dyn from map access, use Compile (not CompileString)
prog, err := compiler.Compile("claims.email")
require.NoError(t, err)
result, err := dexcel.EvalString(context.Background(), prog, map[string]any{
"claims": map[string]any{
"email": "test@example.com",
},
})
require.NoError(t, err)
assert.Equal(t, "test@example.com", result)
}