Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iamcel): add join function #557

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ Tests `caller`s permissions against any `resources`. This test asserts that the

Resolves an ancestor of `resource` using `pattern`. An input of `ancestor("foo/1/bar/2", "foo/{foo}")` will yield the result `"foo/1"`.

#### [`join(parent string, resource string) string`](./iamcel/join.go)

Joins a `resource` name with a `parent` resource name. An input of `join("foo/1", "bar/2")` will yield the result `"foo/1/bar/2"`.

#### [`caller.member(kind string) string`](./iamcel/member.go)

Returns the first IAM member value from the caller's member list which matches the member kind, or fails if there are no such kind.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
cloud.google.com/go/longrunning v0.5.1
cloud.google.com/go/spanner v1.51.0
github.com/google/cel-go v0.18.1
go.einride.tech/aip v0.62.0
go.einride.tech/aip v0.63.0
go.einride.tech/spanner-aip v0.53.0
google.golang.org/api v0.146.0
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
go.einride.tech/aip v0.62.0 h1:DVHT0kgIhHfEqcbTUZ/tKTc+YButvOuTVT4JQFWDGo0=
go.einride.tech/aip v0.62.0/go.mod h1:YVrCQRL7SCB5Mv7i2ZF1R6vkLPh844RQBCLrrLcefaU=
go.einride.tech/aip v0.63.0 h1:1u2ppyKS9hj++bp+q4rOEma7dAbG7ZPl6tSBLf8t+wo=
go.einride.tech/aip v0.63.0/go.mod h1:kK5nO4xh3JoniXp64dxgT474Egmr5L7SlsGD6xvP6fU=
go.einride.tech/spanner-aip v0.53.0 h1:t9thNf7sAA5nDGmRdAXIv9ekerVboUASPSXm67BnC8I=
go.einride.tech/spanner-aip v0.53.0/go.mod h1:ckdDj396+YDxDWjnrifR4pDBnvV7gfgjUGCOznqdq1Q=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
Expand Down
1 change: 1 addition & 0 deletions iamauthz/after.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func NewAfterMethodAuthorization(
iamcel.NewTestAnyFunctionImplementation(options, permissionTester),
iamcel.NewAncestorFunctionImplementation(),
iamcel.NewMemberFunctionImplementation(),
iamcel.NewJoinFunctionImplementation(),
),
)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions iamauthz/before.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func NewBeforeMethodAuthorization(
iamcel.NewTestAnyFunctionImplementation(options, permissionTester),
iamcel.NewAncestorFunctionImplementation(),
iamcel.NewMemberFunctionImplementation(),
iamcel.NewJoinFunctionImplementation(),
),
)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions iamcel/after.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewAfterEnv(method protoreflect.MethodDescriptor) (*cel.Env, error) {
NewTestAnyFunctionDeclaration(),
NewAncestorFunctionDeclaration(),
NewMemberFunctionDeclaration(),
NewJoinFunctionDeclaration(),
),
)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions iamcel/before.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func NewBeforeEnv(method protoreflect.MethodDescriptor) (*cel.Env, error) {
NewTestAnyFunctionDeclaration(),
NewAncestorFunctionDeclaration(),
NewMemberFunctionDeclaration(),
NewJoinFunctionDeclaration(),
),
)
if err != nil {
Expand Down
46 changes: 46 additions & 0 deletions iamcel/join.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package iamcel

import (
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
"go.einride.tech/aip/resourcename"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)

// JoinFunction is the name of the CEL descendant function.
const JoinFunction = "join"

const joinFunctionOverload = "join_string_string"

// NewJoinFunctionDeclaration creates a new declaration for the descendant function.
func NewJoinFunctionDeclaration() *expr.Decl {
return decls.NewFunction(
JoinFunction,
// TODO: if ever possible in CEL-go, declare this as a variadic function.
decls.NewOverload(
joinFunctionOverload,
[]*expr.Type{decls.String, decls.String},
decls.String,
),
)
}

// NewJoinFunctionImplementation creates a new implementation for the descendant function.
func NewJoinFunctionImplementation() *functions.Overload {
return &functions.Overload{
Operator: joinFunctionOverload,
Binary: func(parentVal, childVal ref.Val) ref.Val {
parent, ok := parentVal.Value().(string)
if !ok {
return types.NewErr("parent: unexpected type of arg 1, expected string but got %T", parentVal.Value())
}
child, ok := childVal.Value().(string)
if !ok {
return types.NewErr("child: unexpected type of arg 2, expected string but got %T", childVal.Value())
}
return types.String(resourcename.Join(parent, child))
},
}
}
113 changes: 113 additions & 0 deletions iamcel/join_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package iamcel

import (
"testing"

"github.com/google/cel-go/cel"
"gotest.tools/v3/assert"
)

func TestJoinFunction(t *testing.T) {
env, err := cel.NewEnv(cel.Declarations(NewJoinFunctionDeclaration()))
assert.NilError(t, err)
t.Run("ok", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
t.Run("root parent", func(t *testing.T) {
ast, issues := env.Compile(`join('/', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "child/2", result.Value().(string))
})
t.Run("root child", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', '/')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1", result.Value().(string))
})
t.Run("root parent and child", func(t *testing.T) {
ast, issues := env.Compile(`join('/', '/')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "/", result.Value().(string))
})
t.Run("empty parent", func(t *testing.T) {
ast, issues := env.Compile(`join('', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "child/2", result.Value().(string))
})
t.Run("empty child", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', '')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1", result.Value().(string))
})
t.Run("parent slash suffix", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1/', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
t.Run("child slash suffix", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', 'child/2/')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
t.Run("parent slash prefix", func(t *testing.T) {
ast, issues := env.Compile(`join('/parent/1', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
t.Run("child slash prefix", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', '/child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
}