mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add hidden enterprise cmd command to list roles (#13303)
* feat: add hidden enterprise cmd command to list roles This includes custom roles, and has a json ouput option for more granular permissions
This commit is contained in:
Generated
+26
@@ -8335,11 +8335,37 @@ const docTemplate = `{
|
||||
"assignable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"built_in": {
|
||||
"description": "BuiltIn roles are immutable",
|
||||
"type": "boolean"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_permissions": {
|
||||
"description": "map[\u003corg_id\u003e] -\u003e Permissions",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.Permission"
|
||||
}
|
||||
}
|
||||
},
|
||||
"site_permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.Permission"
|
||||
}
|
||||
},
|
||||
"user_permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.Permission"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Generated
+26
@@ -7400,11 +7400,37 @@
|
||||
"assignable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"built_in": {
|
||||
"description": "BuiltIn roles are immutable",
|
||||
"type": "boolean"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_permissions": {
|
||||
"description": "map[\u003corg_id\u003e] -\u003e Permissions",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.Permission"
|
||||
}
|
||||
}
|
||||
},
|
||||
"site_permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.Permission"
|
||||
}
|
||||
},
|
||||
"user_permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.Permission"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -835,11 +835,12 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error {
|
||||
return q.db.CleanTailnetTunnels(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) CustomRolesByName(ctx context.Context, lookupRoles []string) ([]database.CustomRole, error) {
|
||||
// TODO: Handle org scoped lookups
|
||||
func (q *querier) CustomRoles(ctx context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAssignRole); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.CustomRolesByName(ctx, lookupRoles)
|
||||
return q.db.CustomRoles(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteAPIKeyByID(ctx context.Context, id string) error {
|
||||
|
||||
@@ -1177,8 +1177,8 @@ func (s *MethodTestSuite) TestUser() {
|
||||
b := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args().Asserts(rbac.ResourceSystem, policy.ActionRead).Returns(slice.New(a.ID, b.ID))
|
||||
}))
|
||||
s.Run("CustomRolesByName", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args([]string{}).Asserts(rbac.ResourceAssignRole, policy.ActionRead).Returns([]database.CustomRole{})
|
||||
s.Run("CustomRoles", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.CustomRolesParams{}).Asserts(rbac.ResourceAssignRole, policy.ActionRead).Returns([]database.CustomRole{})
|
||||
}))
|
||||
s.Run("Blank/UpsertCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
// Blank is no perms in the role
|
||||
|
||||
@@ -1175,18 +1175,26 @@ func (*FakeQuerier) CleanTailnetTunnels(context.Context) error {
|
||||
return ErrUnimplemented
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) CustomRolesByName(_ context.Context, lookupRoles []string) ([]database.CustomRole, error) {
|
||||
func (q *FakeQuerier) CustomRoles(_ context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
found := make([]database.CustomRole, 0)
|
||||
for _, role := range q.data.customRoles {
|
||||
if slices.ContainsFunc(lookupRoles, func(s string) bool {
|
||||
return strings.EqualFold(s, role.Name)
|
||||
}) {
|
||||
role := role
|
||||
found = append(found, role)
|
||||
role := role
|
||||
if len(arg.LookupRoles) > 0 {
|
||||
if !slices.ContainsFunc(arg.LookupRoles, func(s string) bool {
|
||||
return strings.EqualFold(s, role.Name)
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if arg.ExcludeOrgRoles && role.OrganizationID.Valid {
|
||||
continue
|
||||
}
|
||||
|
||||
found = append(found, role)
|
||||
}
|
||||
|
||||
return found, nil
|
||||
|
||||
@@ -144,10 +144,10 @@ func (m metricsStore) CleanTailnetTunnels(ctx context.Context) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) CustomRolesByName(ctx context.Context, lookupRoles []string) ([]database.CustomRole, error) {
|
||||
func (m metricsStore) CustomRoles(ctx context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.CustomRolesByName(ctx, lookupRoles)
|
||||
m.queryLatencies.WithLabelValues("CustomRolesByName").Observe(time.Since(start).Seconds())
|
||||
r0, r1 := m.s.CustomRoles(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("CustomRoles").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
|
||||
@@ -173,19 +173,19 @@ func (mr *MockStoreMockRecorder) CleanTailnetTunnels(arg0 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanTailnetTunnels", reflect.TypeOf((*MockStore)(nil).CleanTailnetTunnels), arg0)
|
||||
}
|
||||
|
||||
// CustomRolesByName mocks base method.
|
||||
func (m *MockStore) CustomRolesByName(arg0 context.Context, arg1 []string) ([]database.CustomRole, error) {
|
||||
// CustomRoles mocks base method.
|
||||
func (m *MockStore) CustomRoles(arg0 context.Context, arg1 database.CustomRolesParams) ([]database.CustomRole, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CustomRolesByName", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "CustomRoles", arg0, arg1)
|
||||
ret0, _ := ret[0].([]database.CustomRole)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CustomRolesByName indicates an expected call of CustomRolesByName.
|
||||
func (mr *MockStoreMockRecorder) CustomRolesByName(arg0, arg1 any) *gomock.Call {
|
||||
// CustomRoles indicates an expected call of CustomRoles.
|
||||
func (mr *MockStoreMockRecorder) CustomRoles(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CustomRolesByName", reflect.TypeOf((*MockStore)(nil).CustomRolesByName), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CustomRoles", reflect.TypeOf((*MockStore)(nil).CustomRoles), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteAPIKeyByID mocks base method.
|
||||
|
||||
Generated
+4
-1
@@ -411,11 +411,14 @@ CREATE TABLE custom_roles (
|
||||
org_permissions jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
user_permissions jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
organization_id uuid
|
||||
);
|
||||
|
||||
COMMENT ON TABLE custom_roles IS 'Custom roles allow dynamic roles expanded at runtime';
|
||||
|
||||
COMMENT ON COLUMN custom_roles.organization_id IS 'Roles can optionally be scoped to an organization';
|
||||
|
||||
CREATE TABLE dbcrypt_keys (
|
||||
number integer NOT NULL,
|
||||
active_key_digest text,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE custom_roles
|
||||
-- This column is nullable, meaning no organization scope
|
||||
DROP COLUMN organization_id;
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE custom_roles
|
||||
-- This column is nullable, meaning no organization scope
|
||||
ADD COLUMN organization_id uuid;
|
||||
|
||||
COMMENT ON COLUMN custom_roles.organization_id IS 'Roles can optionally be scoped to an organization'
|
||||
@@ -1790,6 +1790,8 @@ type CustomRole struct {
|
||||
UserPermissions json.RawMessage `db:"user_permissions" json:"user_permissions"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
// Roles can optionally be scoped to an organization
|
||||
OrganizationID uuid.NullUUID `db:"organization_id" json:"organization_id"`
|
||||
}
|
||||
|
||||
// A table used to store the keys used to encrypt the database.
|
||||
|
||||
@@ -48,7 +48,7 @@ type sqlcQuerier interface {
|
||||
CleanTailnetCoordinators(ctx context.Context) error
|
||||
CleanTailnetLostPeers(ctx context.Context) error
|
||||
CleanTailnetTunnels(ctx context.Context) error
|
||||
CustomRolesByName(ctx context.Context, lookupRoles []string) ([]CustomRole, error)
|
||||
CustomRoles(ctx context.Context, arg CustomRolesParams) ([]CustomRole, error)
|
||||
DeleteAPIKeyByID(ctx context.Context, id string) error
|
||||
DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteAllTailnetClientSubscriptions(ctx context.Context, arg DeleteAllTailnetClientSubscriptionsParams) error
|
||||
|
||||
@@ -5553,18 +5553,33 @@ func (q *sqlQuerier) UpdateReplica(ctx context.Context, arg UpdateReplicaParams)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const customRolesByName = `-- name: CustomRolesByName :many
|
||||
const customRoles = `-- name: CustomRoles :many
|
||||
SELECT
|
||||
name, display_name, site_permissions, org_permissions, user_permissions, created_at, updated_at
|
||||
name, display_name, site_permissions, org_permissions, user_permissions, created_at, updated_at, organization_id
|
||||
FROM
|
||||
custom_roles
|
||||
WHERE
|
||||
true
|
||||
-- Lookup roles filter
|
||||
AND CASE WHEN array_length($1 :: text[], 1) > 0 THEN
|
||||
-- Case insensitive
|
||||
name ILIKE ANY($1 :: text [])
|
||||
ELSE true
|
||||
END
|
||||
-- Org scoping filter, to only fetch site wide roles
|
||||
AND CASE WHEN $2 :: boolean THEN
|
||||
organization_id IS null
|
||||
ELSE true
|
||||
END
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) CustomRolesByName(ctx context.Context, lookupRoles []string) ([]CustomRole, error) {
|
||||
rows, err := q.db.QueryContext(ctx, customRolesByName, pq.Array(lookupRoles))
|
||||
type CustomRolesParams struct {
|
||||
LookupRoles []string `db:"lookup_roles" json:"lookup_roles"`
|
||||
ExcludeOrgRoles bool `db:"exclude_org_roles" json:"exclude_org_roles"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) CustomRoles(ctx context.Context, arg CustomRolesParams) ([]CustomRole, error) {
|
||||
rows, err := q.db.QueryContext(ctx, customRoles, pq.Array(arg.LookupRoles), arg.ExcludeOrgRoles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -5580,6 +5595,7 @@ func (q *sqlQuerier) CustomRolesByName(ctx context.Context, lookupRoles []string
|
||||
&i.UserPermissions,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OrganizationID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -5622,7 +5638,7 @@ ON CONFLICT (name)
|
||||
org_permissions = $4,
|
||||
user_permissions = $5,
|
||||
updated_at = now()
|
||||
RETURNING name, display_name, site_permissions, org_permissions, user_permissions, created_at, updated_at
|
||||
RETURNING name, display_name, site_permissions, org_permissions, user_permissions, created_at, updated_at, organization_id
|
||||
`
|
||||
|
||||
type UpsertCustomRoleParams struct {
|
||||
@@ -5650,6 +5666,7 @@ func (q *sqlQuerier) UpsertCustomRole(ctx context.Context, arg UpsertCustomRoleP
|
||||
&i.UserPermissions,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OrganizationID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
-- name: CustomRolesByName :many
|
||||
-- name: CustomRoles :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
custom_roles
|
||||
WHERE
|
||||
true
|
||||
-- Lookup roles filter
|
||||
AND CASE WHEN array_length(@lookup_roles :: text[], 1) > 0 THEN
|
||||
-- Case insensitive
|
||||
name ILIKE ANY(@lookup_roles :: text [])
|
||||
ELSE true
|
||||
END
|
||||
-- Org scoping filter, to only fetch site wide roles
|
||||
AND CASE WHEN @exclude_org_roles :: boolean THEN
|
||||
organization_id IS null
|
||||
ELSE true
|
||||
END
|
||||
;
|
||||
|
||||
|
||||
-- name: UpsertCustomRole :one
|
||||
INSERT INTO
|
||||
custom_roles (
|
||||
|
||||
@@ -38,7 +38,7 @@ func UsernameFrom(str string) string {
|
||||
}
|
||||
|
||||
// NameValid returns whether the input string is a valid name.
|
||||
// It is a generic validator for any name (user, workspace, template, etc.).
|
||||
// It is a generic validator for any name (user, workspace, template, role name, etc.).
|
||||
func NameValid(str string) error {
|
||||
if len(str) > 32 {
|
||||
return xerrors.New("must be <= 32 characters")
|
||||
|
||||
@@ -72,7 +72,10 @@ func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles,
|
||||
// If some roles are missing from the database, they are omitted from
|
||||
// the expansion. These roles are no-ops. Should we raise some kind of
|
||||
// warning when this happens?
|
||||
dbroles, err := db.CustomRolesByName(ctx, lookup)
|
||||
dbroles, err := db.CustomRoles(ctx, database.CustomRolesParams{
|
||||
LookupRoles: lookup,
|
||||
ExcludeOrgRoles: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("fetch custom roles: %w", err)
|
||||
}
|
||||
@@ -81,7 +84,7 @@ func Expand(ctx context.Context, db database.Store, names []string) (rbac.Roles,
|
||||
for _, dbrole := range dbroles {
|
||||
converted, err := ConvertDBRole(dbrole)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("convert db role %q: %w", dbrole, err)
|
||||
return nil, xerrors.Errorf("convert db role %q: %w", dbrole.Name, err)
|
||||
}
|
||||
roles = append(roles, converted)
|
||||
cache.Store(dbrole.Name, converted)
|
||||
|
||||
+34
-8
@@ -3,8 +3,11 @@ package coderd
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/coderd/rbac/rolestore"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
@@ -28,8 +31,25 @@ func (api *API) AssignableSiteRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
roles := rbac.SiteRoles()
|
||||
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles))
|
||||
dbCustomRoles, err := api.Database.CustomRoles(ctx, database.CustomRolesParams{
|
||||
// Only site wide custom roles to be included
|
||||
ExcludeOrgRoles: true,
|
||||
LookupRoles: nil,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
customRoles := make([]rbac.Role, 0, len(dbCustomRoles))
|
||||
for _, customRole := range dbCustomRoles {
|
||||
rbacRole, err := rolestore.ConvertDBRole(customRole)
|
||||
if err == nil {
|
||||
customRoles = append(customRoles, rbacRole)
|
||||
}
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, rbac.SiteRoles(), customRoles))
|
||||
}
|
||||
|
||||
// assignableOrgRoles returns all org wide roles that can be assigned.
|
||||
@@ -53,10 +73,10 @@ func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
roles := rbac.OrganizationRoles(organization.ID)
|
||||
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles, []rbac.Role{}))
|
||||
}
|
||||
|
||||
func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role) []codersdk.AssignableRoles {
|
||||
func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role, customRoles []rbac.Role) []codersdk.AssignableRoles {
|
||||
assignable := make([]codersdk.AssignableRoles, 0)
|
||||
for _, role := range roles {
|
||||
// The member role is implied, and not assignable.
|
||||
@@ -66,11 +86,17 @@ func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role) []coder
|
||||
continue
|
||||
}
|
||||
assignable = append(assignable, codersdk.AssignableRoles{
|
||||
SlimRole: codersdk.SlimRole{
|
||||
Name: role.Name,
|
||||
DisplayName: role.DisplayName,
|
||||
},
|
||||
Role: db2sdk.Role(role),
|
||||
Assignable: rbac.CanAssignRole(actorRoles, role.Name),
|
||||
BuiltIn: true,
|
||||
})
|
||||
}
|
||||
|
||||
for _, role := range customRoles {
|
||||
assignable = append(assignable, codersdk.AssignableRoles{
|
||||
Role: db2sdk.Role(role),
|
||||
Assignable: rbac.CanAssignRole(actorRoles, role.Name),
|
||||
BuiltIn: false,
|
||||
})
|
||||
}
|
||||
return assignable
|
||||
|
||||
+17
-7
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
@@ -137,18 +138,27 @@ func TestListRoles(t *testing.T) {
|
||||
require.Contains(t, apiErr.Message, c.AuthorizedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, c.ExpectedRoles, roles)
|
||||
ignorePerms := func(f codersdk.AssignableRoles) codersdk.AssignableRoles {
|
||||
return codersdk.AssignableRoles{
|
||||
Role: codersdk.Role{
|
||||
Name: f.Name,
|
||||
DisplayName: f.DisplayName,
|
||||
},
|
||||
Assignable: f.Assignable,
|
||||
BuiltIn: true,
|
||||
}
|
||||
}
|
||||
expected := db2sdk.List(c.ExpectedRoles, ignorePerms)
|
||||
found := db2sdk.List(roles, ignorePerms)
|
||||
require.ElementsMatch(t, expected, found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func convertRole(roleName string) codersdk.SlimRole {
|
||||
func convertRole(roleName string) codersdk.Role {
|
||||
role, _ := rbac.RoleByName(roleName)
|
||||
return codersdk.SlimRole{
|
||||
DisplayName: role.DisplayName,
|
||||
Name: role.Name,
|
||||
}
|
||||
return db2sdk.Role(role)
|
||||
}
|
||||
|
||||
func convertRoles(assignableRoles map[string]bool) []codersdk.AssignableRoles {
|
||||
@@ -156,7 +166,7 @@ func convertRoles(assignableRoles map[string]bool) []codersdk.AssignableRoles {
|
||||
for roleName, assignable := range assignableRoles {
|
||||
role := convertRole(roleName)
|
||||
converted = append(converted, codersdk.AssignableRoles{
|
||||
SlimRole: role,
|
||||
Role: role,
|
||||
Assignable: assignable,
|
||||
})
|
||||
}
|
||||
|
||||
+9
-7
@@ -19,8 +19,10 @@ type SlimRole struct {
|
||||
}
|
||||
|
||||
type AssignableRoles struct {
|
||||
SlimRole
|
||||
Assignable bool `json:"assignable"`
|
||||
Role `table:"r,recursive_inline"`
|
||||
Assignable bool `json:"assignable" table:"assignable"`
|
||||
// BuiltIn roles are immutable
|
||||
BuiltIn bool `json:"built_in" table:"built_in"`
|
||||
}
|
||||
|
||||
// Permission is the format passed into the rego.
|
||||
@@ -33,12 +35,12 @@ type Permission struct {
|
||||
|
||||
// Role is a longer form of SlimRole used to edit custom roles.
|
||||
type Role struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
SitePermissions []Permission `json:"site_permissions"`
|
||||
Name string `json:"name" table:"name,default_sort"`
|
||||
DisplayName string `json:"display_name" table:"display_name"`
|
||||
SitePermissions []Permission `json:"site_permissions" table:"site_permissions"`
|
||||
// map[<org_id>] -> Permissions
|
||||
OrganizationPermissions map[string][]Permission `json:"organization_permissions"`
|
||||
UserPermissions []Permission `json:"user_permissions"`
|
||||
OrganizationPermissions map[string][]Permission `json:"organization_permissions" table:"org_permissions"`
|
||||
UserPermissions []Permission `json:"user_permissions" table:"user_permissions"`
|
||||
}
|
||||
|
||||
// PatchRole will upsert a custom site wide role
|
||||
|
||||
Generated
+178
-14
@@ -27,8 +27,39 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
|
||||
[
|
||||
{
|
||||
"assignable": true,
|
||||
"built_in": true,
|
||||
"display_name": "string",
|
||||
"name": "string"
|
||||
"name": "string",
|
||||
"organization_permissions": {
|
||||
"property1": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"property2": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -43,12 +74,63 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
|
||||
|
||||
Status Code **200**
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------------- | ------- | -------- | ------------ | ----------- |
|
||||
| `[array item]` | array | false | | |
|
||||
| `» assignable` | boolean | false | | |
|
||||
| `» display_name` | string | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------------------------- | -------------------------------------------------------- | -------- | ------------ | --------------------------------------- |
|
||||
| `[array item]` | array | false | | |
|
||||
| `» assignable` | boolean | false | | |
|
||||
| `» built_in` | boolean | false | | Built in roles are immutable |
|
||||
| `» display_name` | string | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| `» organization_permissions` | object | false | | map[<org_id>] -> Permissions |
|
||||
| `»» [any property]` | array | false | | |
|
||||
| `»»» action` | [codersdk.RBACAction](schemas.md#codersdkrbacaction) | false | | |
|
||||
| `»»» negate` | boolean | false | | Negate makes this a negative permission |
|
||||
| `»»» resource_type` | [codersdk.RBACResource](schemas.md#codersdkrbacresource) | false | | |
|
||||
| `» site_permissions` | array | false | | |
|
||||
| `» user_permissions` | array | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| --------------- | ----------------------- |
|
||||
| `action` | `application_connect` |
|
||||
| `action` | `assign` |
|
||||
| `action` | `create` |
|
||||
| `action` | `delete` |
|
||||
| `action` | `read` |
|
||||
| `action` | `read_personal` |
|
||||
| `action` | `ssh` |
|
||||
| `action` | `update` |
|
||||
| `action` | `update_personal` |
|
||||
| `action` | `use` |
|
||||
| `action` | `view_insights` |
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `resource_type` | `*` |
|
||||
| `resource_type` | `api_key` |
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
| `resource_type` | `deployment_stats` |
|
||||
| `resource_type` | `file` |
|
||||
| `resource_type` | `group` |
|
||||
| `resource_type` | `license` |
|
||||
| `resource_type` | `oauth2_app` |
|
||||
| `resource_type` | `oauth2_app_code_token` |
|
||||
| `resource_type` | `oauth2_app_secret` |
|
||||
| `resource_type` | `organization` |
|
||||
| `resource_type` | `organization_member` |
|
||||
| `resource_type` | `provisioner_daemon` |
|
||||
| `resource_type` | `replicas` |
|
||||
| `resource_type` | `system` |
|
||||
| `resource_type` | `tailnet_coordinator` |
|
||||
| `resource_type` | `template` |
|
||||
| `resource_type` | `user` |
|
||||
| `resource_type` | `workspace` |
|
||||
| `resource_type` | `workspace_dormant` |
|
||||
| `resource_type` | `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -130,8 +212,39 @@ curl -X GET http://coder-server:8080/api/v2/users/roles \
|
||||
[
|
||||
{
|
||||
"assignable": true,
|
||||
"built_in": true,
|
||||
"display_name": "string",
|
||||
"name": "string"
|
||||
"name": "string",
|
||||
"organization_permissions": {
|
||||
"property1": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"property2": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -146,12 +259,63 @@ curl -X GET http://coder-server:8080/api/v2/users/roles \
|
||||
|
||||
Status Code **200**
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------------- | ------- | -------- | ------------ | ----------- |
|
||||
| `[array item]` | array | false | | |
|
||||
| `» assignable` | boolean | false | | |
|
||||
| `» display_name` | string | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------------------------- | -------------------------------------------------------- | -------- | ------------ | --------------------------------------- |
|
||||
| `[array item]` | array | false | | |
|
||||
| `» assignable` | boolean | false | | |
|
||||
| `» built_in` | boolean | false | | Built in roles are immutable |
|
||||
| `» display_name` | string | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| `» organization_permissions` | object | false | | map[<org_id>] -> Permissions |
|
||||
| `»» [any property]` | array | false | | |
|
||||
| `»»» action` | [codersdk.RBACAction](schemas.md#codersdkrbacaction) | false | | |
|
||||
| `»»» negate` | boolean | false | | Negate makes this a negative permission |
|
||||
| `»»» resource_type` | [codersdk.RBACResource](schemas.md#codersdkrbacresource) | false | | |
|
||||
| `» site_permissions` | array | false | | |
|
||||
| `» user_permissions` | array | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| --------------- | ----------------------- |
|
||||
| `action` | `application_connect` |
|
||||
| `action` | `assign` |
|
||||
| `action` | `create` |
|
||||
| `action` | `delete` |
|
||||
| `action` | `read` |
|
||||
| `action` | `read_personal` |
|
||||
| `action` | `ssh` |
|
||||
| `action` | `update` |
|
||||
| `action` | `update_personal` |
|
||||
| `action` | `use` |
|
||||
| `action` | `view_insights` |
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `resource_type` | `*` |
|
||||
| `resource_type` | `api_key` |
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
| `resource_type` | `deployment_stats` |
|
||||
| `resource_type` | `file` |
|
||||
| `resource_type` | `group` |
|
||||
| `resource_type` | `license` |
|
||||
| `resource_type` | `oauth2_app` |
|
||||
| `resource_type` | `oauth2_app_code_token` |
|
||||
| `resource_type` | `oauth2_app_secret` |
|
||||
| `resource_type` | `organization` |
|
||||
| `resource_type` | `organization_member` |
|
||||
| `resource_type` | `provisioner_daemon` |
|
||||
| `resource_type` | `replicas` |
|
||||
| `resource_type` | `system` |
|
||||
| `resource_type` | `tailnet_coordinator` |
|
||||
| `resource_type` | `template` |
|
||||
| `resource_type` | `user` |
|
||||
| `resource_type` | `workspace` |
|
||||
| `resource_type` | `workspace_dormant` |
|
||||
| `resource_type` | `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
|
||||
Generated
+42
-6
@@ -802,18 +802,54 @@
|
||||
```json
|
||||
{
|
||||
"assignable": true,
|
||||
"built_in": true,
|
||||
"display_name": "string",
|
||||
"name": "string"
|
||||
"name": "string",
|
||||
"organization_permissions": {
|
||||
"property1": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"property2": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------------- | ------- | -------- | ------------ | ----------- |
|
||||
| `assignable` | boolean | false | | |
|
||||
| `display_name` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------------------------- | --------------------------------------------------- | -------- | ------------ | ---------------------------- |
|
||||
| `assignable` | boolean | false | | |
|
||||
| `built_in` | boolean | false | | Built in roles are immutable |
|
||||
| `display_name` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `organization_permissions` | object | false | | map[<org_id>] -> Permissions |
|
||||
| » `[any property]` | array of [codersdk.Permission](#codersdkpermission) | false | | |
|
||||
| `site_permissions` | array of [codersdk.Permission](#codersdkpermission) | false | | |
|
||||
| `user_permissions` | array of [codersdk.Permission](#codersdkpermission) | false | | |
|
||||
|
||||
## codersdk.AuditAction
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
// **NOTE** Only covers site wide roles at present. Org scoped roles maybe
|
||||
// should be nested under some command that scopes to an org??
|
||||
|
||||
func (r *RootCmd) roles() *serpent.Command {
|
||||
cmd := &serpent.Command{
|
||||
Use: "roles",
|
||||
Short: "Manage site-wide roles.",
|
||||
Aliases: []string{"role"},
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
return inv.Command.HelpHandler(inv)
|
||||
},
|
||||
Hidden: true,
|
||||
Children: []*serpent.Command{
|
||||
r.showRole(),
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (r *RootCmd) showRole() *serpent.Command {
|
||||
formatter := cliui.NewOutputFormatter(
|
||||
cliui.ChangeFormatterData(
|
||||
cliui.TableFormat([]assignableRolesTableRow{}, []string{"name", "display_name", "built_in", "site_permissions", "org_permissions", "user_permissions"}),
|
||||
func(data any) (any, error) {
|
||||
input, ok := data.([]codersdk.AssignableRoles)
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("expected []codersdk.AssignableRoles got %T", data)
|
||||
}
|
||||
rows := make([]assignableRolesTableRow, 0, len(input))
|
||||
for _, role := range input {
|
||||
rows = append(rows, assignableRolesTableRow{
|
||||
Name: role.Name,
|
||||
DisplayName: role.DisplayName,
|
||||
SitePermissions: fmt.Sprintf("%d permissions", len(role.SitePermissions)),
|
||||
OrganizationPermissions: fmt.Sprintf("%d organizations", len(role.OrganizationPermissions)),
|
||||
UserPermissions: fmt.Sprintf("%d permissions", len(role.UserPermissions)),
|
||||
Assignable: role.Assignable,
|
||||
BuiltIn: role.BuiltIn,
|
||||
})
|
||||
}
|
||||
return rows, nil
|
||||
},
|
||||
),
|
||||
cliui.JSONFormat(),
|
||||
)
|
||||
|
||||
client := new(codersdk.Client)
|
||||
cmd := &serpent.Command{
|
||||
Use: "show [role_names ...]",
|
||||
Short: "Show role(s)",
|
||||
Middleware: serpent.Chain(
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
ctx := inv.Context()
|
||||
roles, err := client.ListSiteRoles(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("listing roles: %w", err)
|
||||
}
|
||||
|
||||
if len(inv.Args) > 0 {
|
||||
// filter roles
|
||||
filtered := make([]codersdk.AssignableRoles, 0)
|
||||
for _, role := range roles {
|
||||
if slices.ContainsFunc(inv.Args, func(s string) bool {
|
||||
return strings.EqualFold(s, role.Name)
|
||||
}) {
|
||||
filtered = append(filtered, role)
|
||||
}
|
||||
}
|
||||
roles = filtered
|
||||
}
|
||||
|
||||
out, err := formatter.Format(inv.Context(), roles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(inv.Stdout, out)
|
||||
return err
|
||||
},
|
||||
}
|
||||
formatter.AttachOptions(&cmd.Options)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type assignableRolesTableRow struct {
|
||||
Name string `table:"name,default_sort"`
|
||||
DisplayName string `table:"display_name"`
|
||||
SitePermissions string ` table:"site_permissions"`
|
||||
// map[<org_id>] -> Permissions
|
||||
OrganizationPermissions string `table:"org_permissions"`
|
||||
UserPermissions string `table:"user_permissions"`
|
||||
Assignable bool `table:"assignable"`
|
||||
BuiltIn bool `table:"built_in"`
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||
"github.com/coder/coder/v2/pty/ptytest"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestShowRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentCustomRoles)}
|
||||
owner, admin := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureCustomRoles: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Requires an owner
|
||||
client, _ := coderdtest.CreateAnotherUser(t, owner, admin.OrganizationID, rbac.RoleOwner())
|
||||
|
||||
const expectedRole = "test-role"
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
_, err := client.PatchRole(ctx, codersdk.Role{
|
||||
Name: expectedRole,
|
||||
DisplayName: "Test Role",
|
||||
SitePermissions: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceWorkspace: {codersdk.ActionRead, codersdk.ActionUpdate},
|
||||
}),
|
||||
})
|
||||
require.NoError(t, err, "create role")
|
||||
|
||||
inv, conf := newCLI(t, "roles", "show", "test-role")
|
||||
|
||||
pty := ptytest.New(t)
|
||||
inv.Stdout = pty.Output()
|
||||
clitest.SetupConfig(t, client, conf)
|
||||
|
||||
err = inv.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
matches := []string{
|
||||
"test-role", "2 permissions",
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
pty.ExpectMatch(match)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -17,6 +17,7 @@ func (r *RootCmd) enterpriseOnly() []*serpent.Command {
|
||||
r.licenses(),
|
||||
r.groups(),
|
||||
r.provisionerDaemons(),
|
||||
r.roles(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,14 @@ func (api *API) patchRole(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := httpapi.NameValid(req.Name); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid role name",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.OrganizationPermissions) > 0 {
|
||||
// Org perms should be assigned only in org specific roles. Otherwise,
|
||||
// it gets complicated to keep track of who can do what.
|
||||
|
||||
@@ -2,6 +2,7 @@ package coderd_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -63,13 +64,12 @@ func TestCustomRole(t *testing.T) {
|
||||
coderdtest.CreateTemplateVersion(t, tmplAdmin, first.OrganizationID, nil)
|
||||
|
||||
// Verify the role exists in the list
|
||||
// TODO: Turn this assertion back on when the cli api experience is created.
|
||||
//allRoles, err := tmplAdmin.ListSiteRoles(ctx)
|
||||
//require.NoError(t, err)
|
||||
//
|
||||
//require.True(t, slices.ContainsFunc(allRoles, func(selected codersdk.AssignableRoles) bool {
|
||||
// return selected.Name == role.Name
|
||||
//}), "role missing from site role list")
|
||||
allRoles, err := tmplAdmin.ListSiteRoles(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, slices.ContainsFunc(allRoles, func(selected codersdk.AssignableRoles) bool {
|
||||
return selected.Name == role.Name
|
||||
}), "role missing from site role list")
|
||||
})
|
||||
|
||||
// Revoked licenses cannot modify/create custom roles, but they can
|
||||
@@ -167,4 +167,37 @@ func TestCustomRole(t *testing.T) {
|
||||
})
|
||||
require.ErrorContains(t, err, "forbidden")
|
||||
})
|
||||
|
||||
t.Run("InvalidName", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentCustomRoles)}
|
||||
owner, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureCustomRoles: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
|
||||
//nolint:gocritic // owner is required for this
|
||||
_, err := owner.PatchRole(ctx, codersdk.Role{
|
||||
Name: "Bad_Name", // No underscores allowed
|
||||
DisplayName: "Testing Purposes",
|
||||
// Basically creating a template admin manually
|
||||
SitePermissions: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceTemplate: {codersdk.ActionCreate, codersdk.ActionRead, codersdk.ActionUpdate, codersdk.ActionViewInsights},
|
||||
codersdk.ResourceFile: {codersdk.ActionCreate, codersdk.ActionRead},
|
||||
codersdk.ResourceWorkspace: {codersdk.ActionRead},
|
||||
}),
|
||||
OrganizationPermissions: nil,
|
||||
UserPermissions: nil,
|
||||
})
|
||||
require.ErrorContains(t, err, "Invalid role name")
|
||||
})
|
||||
}
|
||||
|
||||
Generated
+2
-1
@@ -65,8 +65,9 @@ export interface ArchiveTemplateVersionsResponse {
|
||||
}
|
||||
|
||||
// From codersdk/roles.go
|
||||
export interface AssignableRoles extends SlimRole {
|
||||
export interface AssignableRoles extends Role {
|
||||
readonly assignable: boolean;
|
||||
readonly built_in: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/audit.go
|
||||
|
||||
@@ -229,19 +229,28 @@ export const MockUpdateCheck: TypesGen.UpdateCheckResponse = {
|
||||
version: "v99.999.9999+c9cdf14",
|
||||
};
|
||||
|
||||
export const MockOwnerRole: TypesGen.SlimRole = {
|
||||
export const MockOwnerRole: TypesGen.Role = {
|
||||
name: "owner",
|
||||
display_name: "Owner",
|
||||
site_permissions: [],
|
||||
organization_permissions: {},
|
||||
user_permissions: [],
|
||||
};
|
||||
|
||||
export const MockUserAdminRole: TypesGen.SlimRole = {
|
||||
export const MockUserAdminRole: TypesGen.Role = {
|
||||
name: "user_admin",
|
||||
display_name: "User Admin",
|
||||
site_permissions: [],
|
||||
organization_permissions: {},
|
||||
user_permissions: [],
|
||||
};
|
||||
|
||||
export const MockTemplateAdminRole: TypesGen.SlimRole = {
|
||||
export const MockTemplateAdminRole: TypesGen.Role = {
|
||||
name: "template_admin",
|
||||
display_name: "Template Admin",
|
||||
site_permissions: [],
|
||||
organization_permissions: {},
|
||||
user_permissions: [],
|
||||
};
|
||||
|
||||
export const MockMemberRole: TypesGen.SlimRole = {
|
||||
@@ -249,20 +258,24 @@ export const MockMemberRole: TypesGen.SlimRole = {
|
||||
display_name: "Member",
|
||||
};
|
||||
|
||||
export const MockAuditorRole: TypesGen.SlimRole = {
|
||||
export const MockAuditorRole: TypesGen.Role = {
|
||||
name: "auditor",
|
||||
display_name: "Auditor",
|
||||
site_permissions: [],
|
||||
organization_permissions: {},
|
||||
user_permissions: [],
|
||||
};
|
||||
|
||||
// assignableRole takes a role and a boolean. The boolean implies if the
|
||||
// actor can assign (add/remove) the role from other users.
|
||||
export function assignableRole(
|
||||
role: TypesGen.SlimRole,
|
||||
role: TypesGen.Role,
|
||||
assignable: boolean,
|
||||
): TypesGen.AssignableRoles {
|
||||
return {
|
||||
...role,
|
||||
assignable: assignable,
|
||||
built_in: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user