mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08: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:
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user