Skip to content

Commit e320661

Browse files
authored
chore: implement typed database for custom permissions (breaks existing custom roles) (#13457)
* chore: typed database custom permissions * add migration to fix any custom roles out there
1 parent 168d2d6 commit e320661

File tree

17 files changed

+256
-266
lines changed

17 files changed

+256
-266
lines changed

cli/organizationroles.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func (r *RootCmd) organizationRoles() *serpent.Command {
3636
func (r *RootCmd) showOrganizationRoles() *serpent.Command {
3737
formatter := cliui.NewOutputFormatter(
3838
cliui.ChangeFormatterData(
39-
cliui.TableFormat([]roleTableRow{}, []string{"name", "display_name", "site_permissions", "org_permissions", "user_permissions"}),
39+
cliui.TableFormat([]roleTableRow{}, []string{"name", "display_name", "site_permissions", "organization_permissions", "user_permissions"}),
4040
func(data any) (any, error) {
4141
inputs, ok := data.([]codersdk.AssignableRoles)
4242
if !ok {
@@ -103,7 +103,7 @@ func (r *RootCmd) showOrganizationRoles() *serpent.Command {
103103
func (r *RootCmd) editOrganizationRole() *serpent.Command {
104104
formatter := cliui.NewOutputFormatter(
105105
cliui.ChangeFormatterData(
106-
cliui.TableFormat([]roleTableRow{}, []string{"name", "display_name", "site_permissions", "org_permissions", "user_permissions"}),
106+
cliui.TableFormat([]roleTableRow{}, []string{"name", "display_name", "site_permissions", "organization_permissions", "user_permissions"}),
107107
func(data any) (any, error) {
108108
typed, _ := data.(codersdk.Role)
109109
return []roleTableRow{roleToTableView(typed)}, nil
@@ -391,6 +391,6 @@ type roleTableRow struct {
391391
OrganizationID string `table:"organization_id"`
392392
SitePermissions string ` table:"site_permissions"`
393393
// map[<org_id>] -> Permissions
394-
OrganizationPermissions string `table:"org_permissions"`
394+
OrganizationPermissions string `table:"organization_permissions"`
395395
UserPermissions string `table:"user_permissions"`
396396
}

coderd/database/db2sdk/db2sdk.go

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/coder/coder/v2/coderd/database"
1919
"github.com/coder/coder/v2/coderd/parameter"
2020
"github.com/coder/coder/v2/coderd/rbac"
21-
"github.com/coder/coder/v2/coderd/rbac/policy"
2221
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
2322
"github.com/coder/coder/v2/codersdk"
2423
"github.com/coder/coder/v2/provisionersdk/proto"
@@ -526,54 +525,51 @@ func ProvisionerDaemon(dbDaemon database.ProvisionerDaemon) codersdk.Provisioner
526525
return result
527526
}
528527

529-
func Role(role rbac.Role) codersdk.Role {
528+
func RBACRole(role rbac.Role) codersdk.Role {
530529
roleName, orgIDStr, err := rbac.RoleSplit(role.Name)
531530
if err != nil {
532531
roleName = role.Name
533532
}
533+
orgPerms := role.Org[orgIDStr]
534534

535535
return codersdk.Role{
536-
Name: roleName,
537-
OrganizationID: orgIDStr,
538-
DisplayName: role.DisplayName,
539-
SitePermissions: List(role.Site, Permission),
540-
// This is not perfect. If there are organization permissions in another
541-
// organization, they will be omitted. This should not be allowed, so
542-
// should never happen.
543-
OrganizationPermissions: List(role.Org[orgIDStr], Permission),
544-
UserPermissions: List(role.User, Permission),
536+
Name: roleName,
537+
OrganizationID: orgIDStr,
538+
DisplayName: role.DisplayName,
539+
SitePermissions: List(role.Site, RBACPermission),
540+
OrganizationPermissions: List(orgPerms, RBACPermission),
541+
UserPermissions: List(role.User, RBACPermission),
545542
}
546543
}
547544

548-
func Permission(permission rbac.Permission) codersdk.Permission {
549-
return codersdk.Permission{
550-
Negate: permission.Negate,
551-
ResourceType: codersdk.RBACResource(permission.ResourceType),
552-
Action: codersdk.RBACAction(permission.Action),
545+
func Role(role database.CustomRole) codersdk.Role {
546+
orgID := ""
547+
if role.OrganizationID.UUID != uuid.Nil {
548+
orgID = role.OrganizationID.UUID.String()
553549
}
554-
}
555550

556-
func RoleToRBAC(role codersdk.Role) rbac.Role {
557-
orgPerms := map[string][]rbac.Permission{}
558-
if role.OrganizationID != "" {
559-
orgPerms = map[string][]rbac.Permission{
560-
role.OrganizationID: List(role.OrganizationPermissions, PermissionToRBAC),
561-
}
551+
return codersdk.Role{
552+
Name: role.Name,
553+
OrganizationID: orgID,
554+
DisplayName: role.DisplayName,
555+
SitePermissions: List(role.SitePermissions, Permission),
556+
OrganizationPermissions: List(role.OrgPermissions, Permission),
557+
UserPermissions: List(role.UserPermissions, Permission),
562558
}
559+
}
563560

564-
return rbac.Role{
565-
Name: rbac.RoleName(role.Name, role.OrganizationID),
566-
DisplayName: role.DisplayName,
567-
Site: List(role.SitePermissions, PermissionToRBAC),
568-
Org: orgPerms,
569-
User: List(role.UserPermissions, PermissionToRBAC),
561+
func Permission(permission database.CustomRolePermission) codersdk.Permission {
562+
return codersdk.Permission{
563+
Negate: permission.Negate,
564+
ResourceType: codersdk.RBACResource(permission.ResourceType),
565+
Action: codersdk.RBACAction(permission.Action),
570566
}
571567
}
572568

573-
func PermissionToRBAC(permission codersdk.Permission) rbac.Permission {
574-
return rbac.Permission{
569+
func RBACPermission(permission rbac.Permission) codersdk.Permission {
570+
return codersdk.Permission{
575571
Negate: permission.Negate,
576-
ResourceType: string(permission.ResourceType),
577-
Action: policy.Action(permission.Action),
572+
ResourceType: codersdk.RBACResource(permission.ResourceType),
573+
Action: codersdk.RBACAction(permission.Action),
578574
}
579575
}
Lines changed: 72 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package dbauthz_test
22

33
import (
4-
"encoding/json"
54
"testing"
65

76
"github.com/google/uuid"
@@ -11,10 +10,12 @@ import (
1110
"cdr.dev/slog"
1211
"github.com/coder/coder/v2/coderd/coderdtest"
1312
"github.com/coder/coder/v2/coderd/database"
13+
"github.com/coder/coder/v2/coderd/database/db2sdk"
1414
"github.com/coder/coder/v2/coderd/database/dbauthz"
1515
"github.com/coder/coder/v2/coderd/database/dbmem"
1616
"github.com/coder/coder/v2/coderd/rbac"
1717
"github.com/coder/coder/v2/coderd/rbac/policy"
18+
"github.com/coder/coder/v2/codersdk"
1819
"github.com/coder/coder/v2/testutil"
1920
)
2021

@@ -60,17 +61,21 @@ func TestUpsertCustomRoles(t *testing.T) {
6061
return all
6162
}
6263

63-
orgID := uuid.New()
64+
orgID := uuid.NullUUID{
65+
UUID: uuid.New(),
66+
Valid: true,
67+
}
6468
testCases := []struct {
6569
name string
6670

6771
subject rbac.ExpandableRoles
6872

6973
// Perms to create on new custom role
70-
site []rbac.Permission
71-
org map[string][]rbac.Permission
72-
user []rbac.Permission
73-
errorContains string
74+
organizationID uuid.NullUUID
75+
site []codersdk.Permission
76+
org []codersdk.Permission
77+
user []codersdk.Permission
78+
errorContains string
7479
}{
7580
{
7681
// No roles, so no assign role
@@ -84,144 +89,129 @@ func TestUpsertCustomRoles(t *testing.T) {
8489
subject: merge(canAssignRole),
8590
},
8691
{
87-
name: "mixed-scopes",
88-
subject: merge(canAssignRole, rbac.RoleOwner()),
89-
site: rbac.Permissions(map[string][]policy.Action{
90-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
92+
name: "mixed-scopes",
93+
subject: merge(canAssignRole, rbac.RoleOwner()),
94+
organizationID: orgID,
95+
site: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
96+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
97+
}),
98+
org: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
99+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
91100
}),
92-
org: map[string][]rbac.Permission{
93-
uuid.New().String(): rbac.Permissions(map[string][]policy.Action{
94-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
95-
}),
96-
},
97101
errorContains: "cannot assign both org and site permissions",
98102
},
99-
{
100-
name: "multiple-org",
101-
subject: merge(canAssignRole, rbac.RoleOwner()),
102-
org: map[string][]rbac.Permission{
103-
uuid.New().String(): rbac.Permissions(map[string][]policy.Action{
104-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
105-
}),
106-
uuid.New().String(): rbac.Permissions(map[string][]policy.Action{
107-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
108-
}),
109-
},
110-
errorContains: "cannot assign permissions to more than 1",
111-
},
112103
{
113104
name: "invalid-action",
114105
subject: merge(canAssignRole, rbac.RoleOwner()),
115-
site: rbac.Permissions(map[string][]policy.Action{
106+
site: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
116107
// Action does not go with resource
117-
rbac.ResourceWorkspace.Type: {policy.ActionViewInsights},
108+
codersdk.ResourceWorkspace: {codersdk.ActionViewInsights},
118109
}),
119110
errorContains: "invalid action",
120111
},
121112
{
122113
name: "invalid-resource",
123114
subject: merge(canAssignRole, rbac.RoleOwner()),
124-
site: rbac.Permissions(map[string][]policy.Action{
125-
"foobar": {policy.ActionViewInsights},
115+
site: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
116+
"foobar": {codersdk.ActionViewInsights},
126117
}),
127118
errorContains: "invalid resource",
128119
},
129120
{
130121
// Not allowing these at this time.
131122
name: "negative-permission",
132123
subject: merge(canAssignRole, rbac.RoleOwner()),
133-
site: []rbac.Permission{
124+
site: []codersdk.Permission{
134125
{
135126
Negate: true,
136-
ResourceType: rbac.ResourceWorkspace.Type,
137-
Action: policy.ActionRead,
127+
ResourceType: codersdk.ResourceWorkspace,
128+
Action: codersdk.ActionRead,
138129
},
139130
},
140131
errorContains: "no negative permissions",
141132
},
142133
{
143134
name: "wildcard", // not allowed
144135
subject: merge(canAssignRole, rbac.RoleOwner()),
145-
site: rbac.Permissions(map[string][]policy.Action{
146-
rbac.ResourceWorkspace.Type: {policy.WildcardSymbol},
136+
site: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
137+
codersdk.ResourceWorkspace: {"*"},
147138
}),
148139
errorContains: "no wildcard symbols",
149140
},
150141
// escalation checks
151142
{
152143
name: "read-workspace-escalation",
153144
subject: merge(canAssignRole),
154-
site: rbac.Permissions(map[string][]policy.Action{
155-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
145+
site: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
146+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
156147
}),
157148
errorContains: "not allowed to grant this permission",
158149
},
159150
{
160-
name: "read-workspace-outside-org",
161-
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID)),
162-
org: map[string][]rbac.Permission{
163-
// The org admin is for a different org
164-
uuid.NewString(): rbac.Permissions(map[string][]policy.Action{
165-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
166-
}),
151+
name: "read-workspace-outside-org",
152+
organizationID: uuid.NullUUID{
153+
UUID: uuid.New(),
154+
Valid: true,
167155
},
156+
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID.UUID)),
157+
org: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
158+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
159+
}),
168160
errorContains: "not allowed to grant this permission",
169161
},
170162
{
171163
name: "user-escalation",
172164
// These roles do not grant user perms
173-
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID)),
174-
user: rbac.Permissions(map[string][]policy.Action{
175-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
165+
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID.UUID)),
166+
user: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
167+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
176168
}),
177169
errorContains: "not allowed to grant this permission",
178170
},
179171
{
180172
name: "template-admin-escalation",
181173
subject: merge(canAssignRole, rbac.RoleTemplateAdmin()),
182-
site: rbac.Permissions(map[string][]policy.Action{
183-
rbac.ResourceWorkspace.Type: {policy.ActionRead}, // ok!
184-
rbac.ResourceDeploymentConfig.Type: {policy.ActionUpdate}, // not ok!
174+
site: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
175+
codersdk.ResourceWorkspace: {codersdk.ActionRead}, // ok!
176+
codersdk.ResourceDeploymentConfig: {codersdk.ActionUpdate}, // not ok!
185177
}),
186-
user: rbac.Permissions(map[string][]policy.Action{
187-
rbac.ResourceWorkspace.Type: {policy.ActionRead}, // ok!
178+
user: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
179+
codersdk.ResourceWorkspace: {codersdk.ActionRead}, // ok!
188180
}),
189181
errorContains: "deployment_config",
190182
},
191183
// ok!
192184
{
193185
name: "read-workspace-template-admin",
194186
subject: merge(canAssignRole, rbac.RoleTemplateAdmin()),
195-
site: rbac.Permissions(map[string][]policy.Action{
196-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
187+
site: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
188+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
197189
}),
198190
},
199191
{
200-
name: "read-workspace-in-org",
201-
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID)),
202-
org: map[string][]rbac.Permission{
203-
// Org admin of this org, this is ok!
204-
orgID.String(): rbac.Permissions(map[string][]policy.Action{
205-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
206-
}),
207-
},
192+
name: "read-workspace-in-org",
193+
subject: merge(canAssignRole, rbac.RoleOrgAdmin(orgID.UUID)),
194+
organizationID: orgID,
195+
org: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
196+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
197+
}),
208198
},
209199
{
210200
name: "user-perms",
211201
// This is weird, but is ok
212202
subject: merge(canAssignRole, rbac.RoleMember()),
213-
user: rbac.Permissions(map[string][]policy.Action{
214-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
203+
user: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
204+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
215205
}),
216206
},
217207
{
218208
name: "site+user-perms",
219209
subject: merge(canAssignRole, rbac.RoleMember(), rbac.RoleTemplateAdmin()),
220-
site: rbac.Permissions(map[string][]policy.Action{
221-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
210+
site: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
211+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
222212
}),
223-
user: rbac.Permissions(map[string][]policy.Action{
224-
rbac.ResourceWorkspace.Type: {policy.ActionRead},
213+
user: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
214+
codersdk.ResourceWorkspace: {codersdk.ActionRead},
225215
}),
226216
},
227217
}
@@ -244,9 +234,10 @@ func TestUpsertCustomRoles(t *testing.T) {
244234
_, err := az.UpsertCustomRole(ctx, database.UpsertCustomRoleParams{
245235
Name: "test-role",
246236
DisplayName: "",
247-
SitePermissions: must(json.Marshal(tc.site)),
248-
OrgPermissions: must(json.Marshal(tc.org)),
249-
UserPermissions: must(json.Marshal(tc.user)),
237+
OrganizationID: tc.organizationID,
238+
SitePermissions: db2sdk.List(tc.site, convertSDKPerm),
239+
OrgPermissions: db2sdk.List(tc.org, convertSDKPerm),
240+
UserPermissions: db2sdk.List(tc.user, convertSDKPerm),
250241
})
251242
if tc.errorContains != "" {
252243
require.ErrorContains(t, err, tc.errorContains)
@@ -256,3 +247,11 @@ func TestUpsertCustomRoles(t *testing.T) {
256247
})
257248
}
258249
}
250+
251+
func convertSDKPerm(perm codersdk.Permission) database.CustomRolePermission {
252+
return database.CustomRolePermission{
253+
Negate: perm.Negate,
254+
ResourceType: string(perm.ResourceType),
255+
Action: policy.Action(perm.Action),
256+
}
257+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy