mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
chore: Rewrite rbac rego -> SQL clause (#5138)
* chore: Rewrite rbac rego -> SQL clause Previous code was challenging to read with edge cases - bug: OrgAdmin could not make new groups - Also refactor some function names
This commit is contained in:
@ -11,7 +11,9 @@ import (
|
||||
"github.com/coder/coder/coderd/audit"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/cryptorand"
|
||||
"github.com/coder/coder/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/testutil"
|
||||
@ -747,3 +749,210 @@ func TestUpdateTemplateACL(t *testing.T) {
|
||||
require.Equal(t, http.StatusNotFound, cerr.StatusCode())
|
||||
})
|
||||
}
|
||||
|
||||
// TestTemplateAccess tests the rego -> sql conversion. We need to implement
|
||||
// this test on at least 1 table type to ensure that the conversion is correct.
|
||||
// The rbac tests only assert against static SQL queries.
|
||||
// This is a full rbac test of many of the common role combinations.
|
||||
//
|
||||
//nolint:tparallel
|
||||
func TestTemplateAccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
ownerClient := coderdenttest.New(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||
_ = coderdenttest.AddLicense(t, ownerClient, coderdenttest.LicenseOptions{
|
||||
TemplateRBAC: true,
|
||||
})
|
||||
|
||||
type coderUser struct {
|
||||
*codersdk.Client
|
||||
User codersdk.User
|
||||
}
|
||||
|
||||
type orgSetup struct {
|
||||
Admin coderUser
|
||||
MemberInGroup coderUser
|
||||
MemberNoGroup coderUser
|
||||
|
||||
DefaultTemplate codersdk.Template
|
||||
AllRead codersdk.Template
|
||||
UserACL codersdk.Template
|
||||
GroupACL codersdk.Template
|
||||
|
||||
Group codersdk.Group
|
||||
Org codersdk.Organization
|
||||
}
|
||||
|
||||
// Create the following users
|
||||
// - owner: Site wide owner
|
||||
// - template-admin
|
||||
// - org-admin (org 1)
|
||||
// - org-admin (org 2)
|
||||
// - org-member (org 1)
|
||||
// - org-member (org 2)
|
||||
|
||||
// Create the following templates in each org
|
||||
// - template 1, default acls
|
||||
// - template 2, all_user read
|
||||
// - template 3, user_acl read for member
|
||||
// - template 4, group_acl read for groupMember
|
||||
|
||||
templateAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||
|
||||
makeTemplate := func(t *testing.T, client *codersdk.Client, orgID uuid.UUID, acl codersdk.UpdateTemplateACL) codersdk.Template {
|
||||
version := coderdtest.CreateTemplateVersion(t, client, orgID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, orgID, version.ID)
|
||||
|
||||
err := client.UpdateTemplateACL(ctx, template.ID, acl)
|
||||
require.NoError(t, err, "failed to update template acl")
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
makeOrg := func(t *testing.T) orgSetup {
|
||||
// Make org
|
||||
orgName, err := cryptorand.String(5)
|
||||
require.NoError(t, err, "org name")
|
||||
|
||||
// Make users
|
||||
newOrg, err := ownerClient.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{Name: orgName})
|
||||
require.NoError(t, err, "failed to create org")
|
||||
|
||||
adminCli, adminUsr := coderdtest.CreateAnotherUserWithUser(t, ownerClient, newOrg.ID, rbac.RoleOrgAdmin(newOrg.ID))
|
||||
groupMemCli, groupMemUsr := coderdtest.CreateAnotherUserWithUser(t, ownerClient, newOrg.ID, rbac.RoleOrgMember(newOrg.ID))
|
||||
memberCli, memberUsr := coderdtest.CreateAnotherUserWithUser(t, ownerClient, newOrg.ID, rbac.RoleOrgMember(newOrg.ID))
|
||||
|
||||
// Make group
|
||||
group, err := adminCli.CreateGroup(ctx, newOrg.ID, codersdk.CreateGroupRequest{
|
||||
Name: "SingleUser",
|
||||
})
|
||||
require.NoError(t, err, "failed to create group")
|
||||
|
||||
group, err = adminCli.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{
|
||||
AddUsers: []string{groupMemUsr.ID.String()},
|
||||
})
|
||||
require.NoError(t, err, "failed to add user to group")
|
||||
|
||||
// Make templates
|
||||
|
||||
return orgSetup{
|
||||
Admin: coderUser{Client: adminCli, User: adminUsr},
|
||||
MemberInGroup: coderUser{Client: groupMemCli, User: groupMemUsr},
|
||||
MemberNoGroup: coderUser{Client: memberCli, User: memberUsr},
|
||||
Org: newOrg,
|
||||
Group: group,
|
||||
|
||||
DefaultTemplate: makeTemplate(t, adminCli, newOrg.ID, codersdk.UpdateTemplateACL{
|
||||
GroupPerms: map[string]codersdk.TemplateRole{
|
||||
newOrg.ID.String(): codersdk.TemplateRoleDeleted,
|
||||
},
|
||||
}),
|
||||
AllRead: makeTemplate(t, adminCli, newOrg.ID, codersdk.UpdateTemplateACL{
|
||||
GroupPerms: map[string]codersdk.TemplateRole{
|
||||
newOrg.ID.String(): codersdk.TemplateRoleUse,
|
||||
},
|
||||
}),
|
||||
UserACL: makeTemplate(t, adminCli, newOrg.ID, codersdk.UpdateTemplateACL{
|
||||
GroupPerms: map[string]codersdk.TemplateRole{
|
||||
newOrg.ID.String(): codersdk.TemplateRoleDeleted,
|
||||
},
|
||||
UserPerms: map[string]codersdk.TemplateRole{
|
||||
memberUsr.ID.String(): codersdk.TemplateRoleUse,
|
||||
},
|
||||
}),
|
||||
GroupACL: makeTemplate(t, adminCli, newOrg.ID, codersdk.UpdateTemplateACL{
|
||||
GroupPerms: map[string]codersdk.TemplateRole{
|
||||
group.ID.String(): codersdk.TemplateRoleUse,
|
||||
newOrg.ID.String(): codersdk.TemplateRoleDeleted,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Make 2 organizations
|
||||
orgs := []orgSetup{
|
||||
makeOrg(t),
|
||||
makeOrg(t),
|
||||
}
|
||||
|
||||
testTemplateRead := func(t *testing.T, org orgSetup, usr *codersdk.Client, read []codersdk.Template) {
|
||||
found, err := usr.TemplatesByOrganization(ctx, org.Org.ID)
|
||||
require.NoError(t, err, "failed to get templates")
|
||||
|
||||
exp := make(map[uuid.UUID]codersdk.Template)
|
||||
for _, tmpl := range read {
|
||||
exp[tmpl.ID] = tmpl
|
||||
}
|
||||
|
||||
for _, f := range found {
|
||||
if _, ok := exp[f.ID]; !ok {
|
||||
t.Errorf("found unexpected template %q", f.Name)
|
||||
}
|
||||
delete(exp, f.ID)
|
||||
}
|
||||
require.Len(t, exp, 0, "expected templates not found")
|
||||
}
|
||||
|
||||
// nolint:paralleltest
|
||||
t.Run("OwnerReadAll", func(t *testing.T) {
|
||||
for _, o := range orgs {
|
||||
// Owners can read all templates in all orgs
|
||||
exp := []codersdk.Template{o.DefaultTemplate, o.AllRead, o.UserACL, o.GroupACL}
|
||||
testTemplateRead(t, o, ownerClient, exp)
|
||||
}
|
||||
})
|
||||
|
||||
// nolint:paralleltest
|
||||
t.Run("TemplateAdminReadAll", func(t *testing.T) {
|
||||
for _, o := range orgs {
|
||||
// Template Admins can read all templates in all orgs
|
||||
exp := []codersdk.Template{o.DefaultTemplate, o.AllRead, o.UserACL, o.GroupACL}
|
||||
testTemplateRead(t, o, templateAdmin, exp)
|
||||
}
|
||||
})
|
||||
|
||||
// nolint:paralleltest
|
||||
t.Run("OrgAdminReadAllTheirs", func(t *testing.T) {
|
||||
for i, o := range orgs {
|
||||
cli := o.Admin.Client
|
||||
// Only read their own org
|
||||
exp := []codersdk.Template{o.DefaultTemplate, o.AllRead, o.UserACL, o.GroupACL}
|
||||
testTemplateRead(t, o, cli, exp)
|
||||
|
||||
other := orgs[(i+1)%len(orgs)]
|
||||
require.NotEqual(t, other.Org.ID, o.Org.ID, "this test needs at least 2 orgs")
|
||||
testTemplateRead(t, other, cli, []codersdk.Template{})
|
||||
}
|
||||
})
|
||||
|
||||
// nolint:paralleltest
|
||||
t.Run("TestMemberNoGroup", func(t *testing.T) {
|
||||
for i, o := range orgs {
|
||||
cli := o.MemberNoGroup.Client
|
||||
// Only read their own org
|
||||
exp := []codersdk.Template{o.AllRead, o.UserACL}
|
||||
testTemplateRead(t, o, cli, exp)
|
||||
|
||||
other := orgs[(i+1)%len(orgs)]
|
||||
require.NotEqual(t, other.Org.ID, o.Org.ID, "this test needs at least 2 orgs")
|
||||
testTemplateRead(t, other, cli, []codersdk.Template{})
|
||||
}
|
||||
})
|
||||
|
||||
// nolint:paralleltest
|
||||
t.Run("TestMemberInGroup", func(t *testing.T) {
|
||||
for i, o := range orgs {
|
||||
cli := o.MemberInGroup.Client
|
||||
// Only read their own org
|
||||
exp := []codersdk.Template{o.AllRead, o.GroupACL}
|
||||
testTemplateRead(t, o, cli, exp)
|
||||
|
||||
other := orgs[(i+1)%len(orgs)]
|
||||
require.NotEqual(t, other.Org.ID, o.Org.ID, "this test needs at least 2 orgs")
|
||||
testTemplateRead(t, other, cli, []codersdk.Template{})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user