Skip to content

chore: Rewrite rbac rego -> SQL clause #5138

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

Merged
merged 23 commits into from
Nov 28, 2022
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add test vectors
  • Loading branch information
Emyrk committed Nov 20, 2022
commit de198c515e514b258e9b1e6ff130fd389766ce57
281 changes: 59 additions & 222 deletions coderd/rbac/regosql/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,228 +12,6 @@ import (
"github.com/coder/coder/coderd/rbac/regosql/sqltypes"
)

//func TestSomething(t *testing.T) {
// ConvertRegoAst(ConvertConfig{
// ConvertVariable: func(rego ast.Ref) (sqltypes.Node, error) {
// return nil, fmt.Errorf("not implemented")
// },
// }, &rego.PartialQueries{})
//}

//func TestPartialQueriesWithVariables(t *testing.T) {
// cfg := regosql.CompileConfig{
// VariableTypes: NewTree().
// AddElement(strings.Split("input.post.deleted", "."), Boolean{}, StaticName("deleted")).
// AddElement(strings.Split("input.post.author", "."), String{}, StaticName("author")).
// AddElement(strings.Split("input.post.can", "."), String{}, StaticName("can")).
// AddElement(strings.Split("input.post.authors", "."), Map{ValueType: String{}},
// RegexColumnNameReplace(`input\.post\.authors\.(.*)`, "authors->$1")).
// AddElement(strings.Split("input.post.posts", "."), Array{elemType: String{}}, StaticName("posts")).
// AddElement(strings.Split("input.post.can_list", "."), Array{elemType: String{}}, StaticName("can_list")).
// AddElement(strings.Split("input.post.list", "."), Array{elemType: String{}}, StaticName("list")).
// AddElement(strings.Split("input.post.moderators", "."), Array{elemType: String{}}, StaticName("moderators")),
// }
// //opts := ast.ParserOptions{AllFutureKeywords: true}
// testCases := []struct {
// Name string
// Input map[string]interface{}
// Unknowns []string
// Rego string
// ExpectedSQL string
// ExpectError bool
// }{
// {
// Name: "AlwaysFalse",
// Rego: `
// package example
// allow = true {
// input.method = "GET"
// input.path = ["posts"]
// }`,
// Input: map[string]interface{}{
// "method": "GET",
// "path": []string{"users"},
// "user": "bob",
// },
// ExpectedSQL: "false",
// Unknowns: []string{"none"},
// },
// {
// Name: "AlwaysTrue",
// Rego: `
// package example
// allow = true {
// input.method = "GET"
// input.path = ["posts"]
// }`,
// Input: map[string]interface{}{
// "method": "GET",
// "path": []string{"posts"},
// "user": "bob",
// },
// ExpectedSQL: "true",
// Unknowns: []string{"none"},
// },
// {
// Name: "SingleObject",
// // "bob" = input.post.author
// Rego: `
// package example
// allow {
// input.post.author = input.user
// }
// `,
// Input: map[string]interface{}{
// "user": "bob",
// },
// ExpectedSQL: "'bob' = author",
// Unknowns: []string{"input.post.author"},
// },
// {
// Name: "RefBoolean",
// // input.post.deleted
// Rego: `
// package example
// allow {
// input.post.deleted
// }
// `,
// Input: map[string]interface{}{},
// ExpectedSQL: "deleted",
// Unknowns: []string{"input.post.deleted"},
// },
// {
// Name: "RefWithNumber",
// // Query 0: "bob" = input.post.authors.name; "bob" = input.post.list[0]
// Rego: `
// package example
// allow {
// input.post.authors["name"] = input.user
// input.post.list[0] = input.user
// }
// `,
// Input: map[string]interface{}{
// "user": "bob",
// },
// ExpectedSQL: "authors->>name = 'bob AND list[0] = 'bob",
// Unknowns: []string{"input.post.authors", "input.post.list"},
// },
// {
// Name: "Array",
// // Query 0: "bob" = input.post.author
// // Query 1: "bob" = input.post.moderators[_]
// Rego: `
// package example
// allow {
// can_edit
// }
//
// can_edit {
// input.post.author = input.user
// }
// can_edit {
// input.post.moderators[_] = input.user
// }
//
// `,
// Input: map[string]interface{}{
// "user": "bob",
// },
// ExpectedSQL: "'bob' = author OR 'bob' = ANY(moderators)",
// Unknowns: []string{"input.post.author", "input.post.moderators"},
// },
// {
// Name: "ArrayIntersection",
// // Query 0: internal.member_2(input.can_list[_], ["edit", "*"])
// // Query 1: internal.member_2(input.can, ["edit", "*"])
// Rego: `
// package example
// import future.keywords.in
// allow {
// input.can in ["edit", "*"]
// }
//
// allow {
// input.can_list[_] in ["edit", "*"]
// }
// `,
// Input: map[string]interface{}{},
// // TODO: Convert vars to columns
// ExpectedSQL: "input.can_list && ARRAY['edit', '*'] OR input.can = ANY(ARRAY ['edit', '*'])",
// Unknowns: []string{"input.can_list", "input.can"},
// },
// {
// Name: "EveryTerm",
// // "bob" = input.posts[_].author; input.posts[_]
// Rego: `
// package example
// allow = true {
// input.method = "GET"
// input.path = ["posts"]
// allowed[x]
// }
//
// allowed[x] {
// x := input.posts[_]
// x.author == input.user
// }
// `,
// Input: map[string]interface{}{
// "method": "GET",
// "path": []string{"posts"},
// "user": "bob",
// },
// ExpectedSQL: "true",
// Unknowns: []string{"input.posts"},
// },
//
// // Failures
// {
// Name: "RefString",
// Rego: `
// package example
// allow {
// input.post.author
// }
// `,
// Input: map[string]interface{}{},
// Unknowns: []string{"input.post.author"},
// ExpectError: true,
// },
// }
//
// for _, tc := range testCases {
// tc := tc
// t.Run(tc.Name, func(t *testing.T) {
// t.Parallel()
// ctx := context.Background()
//
// part, err := rego.New(
// rego.Query("data.example.allow == true"),
// rego.Module("policy.rego", tc.Rego),
// rego.Input(tc.Input),
// rego.Unknowns(tc.Unknowns),
// ).Partial(ctx)
// require.NoError(t, err)
//
// for i, q := range part.Queries {
// t.Logf("Query %d: %s", i, q.String())
// }
// for i, s := range part.Support {
// t.Logf("Support %d: %s", i, s.String())
// }
//
// sql, err := CompileSQL(cfg, part)
// if tc.ExpectError {
// require.Error(t, err)
// } else {
// require.NoError(t, err, "compile")
// require.Equal(t, tc.ExpectedSQL, sql, "sql match")
// }
// })
// }
//}

// TestRegoQueriesNoVariables handles cases without variables. These should be
// very simple and straight forward.
func TestRegoQueries(t *testing.T) {
Expand Down Expand Up @@ -359,6 +137,65 @@ func TestRegoQueries(t *testing.T) {
ExpectedSQL: "((false) OR (false))",
VariableConverter: regosql.NoACLConverter(),
},
{
Name: "TwoExpressions",
Queries: []string{
`true; true`,
},
// Special case where the bool is wrapped
ExpectedSQL: "(true AND true)",
VariableConverter: regosql.DefaultVariableConverter(),
},

// Actual vectors from production
{
Name: "FromOwner",
Queries: []string{
``,
`"05f58202-4bfc-43ce-9ba4-5ff6e0174a71" = input.object.org_owner`,
`"read" in input.object.acl_user_list["d5389ccc-57a4-4b13-8c3f-31747bcdc9f1"]`,
},
// Special case where the bool is wrapped
ExpectedSQL: "true",
VariableConverter: regosql.NoACLConverter(),
},
{
Name: "OrgAdmin",
Queries: []string{
`input.object.org_owner != "";
input.object.org_owner in {"05f58202-4bfc-43ce-9ba4-5ff6e0174a71"};
input.object.owner != "";
"d5389ccc-57a4-4b13-8c3f-31747bcdc9f1" = input.object.owner`,
},
// Special case where the bool is wrapped
ExpectedSQL: "((organization_id :: text != '') AND " +
"(organization_id :: text = ANY(ARRAY ['05f58202-4bfc-43ce-9ba4-5ff6e0174a71'])) AND " +
"(owner_id :: text != '') AND " +
"('d5389ccc-57a4-4b13-8c3f-31747bcdc9f1' = owner_id :: text))",
VariableConverter: regosql.DefaultVariableConverter(),
},
{
Name: "UserACLAllow",
Queries: []string{
`"read" in input.object.acl_user_list["d5389ccc-57a4-4b13-8c3f-31747bcdc9f1"]`,
`"*" in input.object.acl_user_list["d5389ccc-57a4-4b13-8c3f-31747bcdc9f1"]`,
},
// Special case where the bool is wrapped
ExpectedSQL: "((user_acl->'d5389ccc-57a4-4b13-8c3f-31747bcdc9f1' ? 'read') OR " +
"(user_acl->'d5389ccc-57a4-4b13-8c3f-31747bcdc9f1' ? '*'))",
VariableConverter: regosql.DefaultVariableConverter(),
},
{
Name: "NoACLConfig",
Queries: []string{
`input.object.org_owner != "";
input.object.org_owner in {"05f58202-4bfc-43ce-9ba4-5ff6e0174a71"};
"read" in input.object.acl_group_list[input.object.org_owner]`,
},
// Special case where the bool is wrapped
ExpectedSQL: "((organization_id :: text != '') AND (organization_id :: text = ANY(ARRAY ['05f58202-4bfc-43ce-9ba4-5ff6e0174a71'])) AND (false))",
VariableConverter: regosql.NoACLConverter(),
},
}

for _, tc := range testCases {
Expand Down