Skip to content

Commit baf4843

Browse files
committed
Implement builtin roles
1 parent be5d045 commit baf4843

File tree

2 files changed

+80
-14
lines changed

2 files changed

+80
-14
lines changed

coderd/rbac/authz_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"fmt"
77
"testing"
88

9+
"github.com/google/uuid"
10+
911
"golang.org/x/xerrors"
1012

1113
"github.com/stretchr/testify/require"
@@ -25,7 +27,7 @@ type subject struct {
2527
// TestAuthorizeDomain test the very basic roles that are commonly used.
2628
func TestAuthorizeDomain(t *testing.T) {
2729
t.Parallel()
28-
defOrg := "default"
30+
defOrg := uuid.New()
2931
wrkID := "1234"
3032

3133
user := subject{

coderd/rbac/builtin.go

+77-13
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ const (
1212
member string = "member"
1313
auditor string = "auditor"
1414

15-
orgAdmin string = "organization-admin"
16-
orgMember string = "organization-member"
15+
orgAdmin string = "organization-admin"
16+
orgMember string = "organization-member"
17+
orgManager string = "organization-manager"
1718
)
1819

1920
// RoleName is a string that represents a registered rbac role. We want to store
@@ -23,18 +24,24 @@ const (
2324
// We use functions to retrieve the name incase we need to add a scope.
2425
type RoleName = string
2526

26-
func RoleAdmin() string {
27+
// The functions below ONLY need to exist for roles that are "defaulted" in some way.
28+
// Any other roles (like auditor), can be listed and let the user select/assigned.
29+
// Once we have a database implementation, the "default" roles be be defined on the
30+
// site and orgs, and these functions can be removed.
31+
32+
func RoleAdmin() RoleName {
2733
return roleName(admin, "")
2834
}
2935

30-
func RoleMember() string {
36+
func RoleMember() RoleName {
3137
return roleName(member, "")
3238
}
3339

3440
func RoleOrgAdmin(organizationID uuid.UUID) RoleName {
3541
return roleName(orgAdmin, organizationID.String())
3642
}
3743

44+
// member:uuid
3845
func RoleOrgMember(organizationID uuid.UUID) RoleName {
3946
return roleName(orgMember, organizationID.String())
4047
}
@@ -46,6 +53,9 @@ var (
4653
// them such that the "org" permissions are dynamically changed by the
4754
// scopeID passed in. This isn't a hard problem to solve, it's just easier
4855
// as a function right now.
56+
//
57+
// This map will be replaced by database storage defined by this ticket.
58+
// https://github.com/coder/coder/issues/1194
4959
builtInRoles = map[string]func(scopeID string) Role{
5060
// admin grants all actions to all resources.
5161
admin: func(_ string) Role {
@@ -110,21 +120,63 @@ var (
110120
},
111121
}
112122
},
123+
124+
orgManager: func(organizationID string) Role {
125+
return Role{
126+
Name: roleName(orgMember, organizationID),
127+
Org: map[string][]Permission{
128+
organizationID: permissions(map[Object][]Action{
129+
ResourceWorkspace: {WildcardSymbol},
130+
}),
131+
},
132+
}
133+
},
113134
}
114135
)
115136

137+
// ListOrgRoles lists all roles that can be applied to an organization user
138+
// in the given organization.
139+
// Note: This should be a list in a database, but until then we build
140+
// the list from the builtins.
141+
func ListOrgRoles(organizationID uuid.UUID) []string {
142+
var roles []string
143+
for role, _ := range builtInRoles {
144+
_, scope, err := roleSplit(role)
145+
if err != nil {
146+
// This should never happen
147+
continue
148+
}
149+
if scope == organizationID.String() {
150+
roles = append(roles, role)
151+
}
152+
}
153+
return roles
154+
}
155+
156+
// ListSiteRoles lists all roles that can be applied to a user.
157+
// Note: This should be a list in a database, but until then we build
158+
// the list from the builtins.
159+
func ListSiteRoles() []string {
160+
var roles []string
161+
for role, _ := range builtInRoles {
162+
_, scope, err := roleSplit(role)
163+
if err != nil {
164+
// This should never happen
165+
continue
166+
}
167+
if scope == "" {
168+
roles = append(roles, role)
169+
}
170+
}
171+
return roles
172+
}
173+
116174
// RoleByName returns the permissions associated with a given role name.
117175
// This allows just the role names to be stored and expanded when required.
118176
func RoleByName(name string) (Role, error) {
119-
arr := strings.Split(name, ":")
120-
if len(arr) > 2 {
121-
return Role{}, xerrors.Errorf("too many semicolons in role name")
122-
}
123-
124-
roleName := arr[0]
125-
var scopeID string
126-
if len(arr) > 1 {
127-
scopeID = arr[1]
177+
roleName, scopeID, err := roleSplit(name)
178+
if err != nil {
179+
return Role{}, xerrors.Errorf(":%w", err)
128180
}
129181

130182
roleFunc, ok := builtInRoles[roleName]
@@ -153,6 +205,18 @@ func roleName(name string, scopeID string) string {
153205
return name + ":" + scopeID
154206
}
155207

208+
func roleSplit(role string) (name string, scopeID string, err error) {
209+
arr := strings.Split(name, ":")
210+
if len(arr) > 2 {
211+
return "", "", xerrors.Errorf("too many colons in role name")
212+
}
213+
214+
if len(arr) == 2 {
215+
return arr[0], arr[1], nil
216+
}
217+
return arr[0], "", nil
218+
}
219+
156220
// permissions is just a helper function to make building roles that list out resources
157221
// and actions a bit easier.
158222
func permissions(perms map[Object][]Action) []Permission {

0 commit comments

Comments
 (0)