mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add auditing for groups (#4527)
- Clean up `database.TemplateACL` implementation.
This commit is contained in:
@@ -1457,34 +1457,6 @@ func (q *fakeQuerier) GetTemplates(_ context.Context) ([]database.Template, erro
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateTemplateUserACLByID(_ context.Context, id uuid.UUID, acl database.TemplateACL) error {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for i, t := range q.templates {
|
||||
if t.ID == id {
|
||||
t = t.SetUserACL(acl)
|
||||
q.templates[i] = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateTemplateGroupACLByID(_ context.Context, id uuid.UUID, acl database.TemplateACL) error {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for i, t := range q.templates {
|
||||
if t.ID == id {
|
||||
t = t.SetGroupACL(acl)
|
||||
q.templates[i] = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]database.TemplateUser, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@@ -1501,10 +1473,8 @@ func (q *fakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]d
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
acl := template.UserACL()
|
||||
|
||||
users := make([]database.TemplateUser, 0, len(acl))
|
||||
for k, v := range acl {
|
||||
users := make([]database.TemplateUser, 0, len(template.UserACL))
|
||||
for k, v := range template.UserACL {
|
||||
user, err := q.GetUserByID(context.Background(), uuid.MustParse(k))
|
||||
if err != nil && xerrors.Is(err, sql.ErrNoRows) {
|
||||
return nil, xerrors.Errorf("get user by ID: %w", err)
|
||||
@@ -1544,10 +1514,8 @@ func (q *fakeQuerier) GetTemplateGroupRoles(_ context.Context, id uuid.UUID) ([]
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
acl := template.GroupACL()
|
||||
|
||||
groups := make([]database.TemplateGroup, 0, len(acl))
|
||||
for k, v := range acl {
|
||||
groups := make([]database.TemplateGroup, 0, len(template.GroupACL))
|
||||
for k, v := range template.GroupACL {
|
||||
group, err := q.GetGroupByID(context.Background(), uuid.MustParse(k))
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
return nil, xerrors.Errorf("get group by ID: %w", err)
|
||||
@@ -2047,11 +2015,9 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
|
||||
MaxTtl: arg.MaxTtl,
|
||||
MinAutostartInterval: arg.MinAutostartInterval,
|
||||
CreatedBy: arg.CreatedBy,
|
||||
UserACL: arg.UserACL,
|
||||
GroupACL: arg.GroupACL,
|
||||
}
|
||||
template = template.SetUserACL(database.TemplateACL{})
|
||||
template = template.SetGroupACL(database.TemplateACL{
|
||||
arg.OrganizationID.String(): []rbac.Action{rbac.ActionRead},
|
||||
})
|
||||
q.templates = append(q.templates, template)
|
||||
return template, nil
|
||||
}
|
||||
@@ -2470,6 +2436,23 @@ func (q *fakeQuerier) UpdateTemplateDeletedByID(_ context.Context, arg database.
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.UpdateTemplateACLByIDParams) (database.Template, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, template := range q.templates {
|
||||
if template.ID == arg.ID {
|
||||
template.GroupACL = arg.GroupACL
|
||||
template.UserACL = arg.UserACL
|
||||
|
||||
q.templates[i] = template
|
||||
return template, nil
|
||||
}
|
||||
}
|
||||
|
||||
return database.Template{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.UpdateTemplateVersionByIDParams) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
@@ -24,3 +24,22 @@ func (a *Actions) Scan(src interface{}) error {
|
||||
func (a *Actions) Value() (driver.Value, error) {
|
||||
return json.Marshal(a)
|
||||
}
|
||||
|
||||
// TemplateACL is a map of ids to permissions.
|
||||
type TemplateACL map[string][]rbac.Action
|
||||
|
||||
func (t *TemplateACL) Scan(src interface{}) error {
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
return json.Unmarshal([]byte(v), &t)
|
||||
case []byte, json.RawMessage:
|
||||
//nolint
|
||||
return json.Unmarshal(v.([]byte), &t)
|
||||
}
|
||||
|
||||
return xerrors.Errorf("unexpected type %T", src)
|
||||
}
|
||||
|
||||
func (t TemplateACL) Value() (driver.Value, error) {
|
||||
return json.Marshal(t)
|
||||
}
|
||||
|
||||
Generated
+2
-1
@@ -87,7 +87,8 @@ CREATE TYPE resource_type AS ENUM (
|
||||
'user',
|
||||
'workspace',
|
||||
'git_ssh_key',
|
||||
'api_key'
|
||||
'api_key',
|
||||
'group'
|
||||
);
|
||||
|
||||
CREATE TYPE user_status AS ENUM (
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- You cannot safely remove values from enums https://www.postgresql.org/docs/current/datatype-enum.html
|
||||
-- You cannot create a new type and do a rename because objects depend on this type now.
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'group';
|
||||
|
||||
COMMIT;
|
||||
@@ -1,65 +1,11 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
)
|
||||
|
||||
const AllUsersGroup = "Everyone"
|
||||
|
||||
// TemplateACL is a map of user_ids to permissions.
|
||||
type TemplateACL map[string][]rbac.Action
|
||||
|
||||
func (t Template) UserACL() TemplateACL {
|
||||
var acl TemplateACL
|
||||
if len(t.userACL) == 0 {
|
||||
return acl
|
||||
}
|
||||
|
||||
err := json.Unmarshal(t.userACL, &acl)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to unmarshal template.userACL: %v", err.Error()))
|
||||
}
|
||||
|
||||
return acl
|
||||
}
|
||||
|
||||
func (t Template) GroupACL() TemplateACL {
|
||||
var acl TemplateACL
|
||||
if len(t.groupACL) == 0 {
|
||||
return acl
|
||||
}
|
||||
|
||||
err := json.Unmarshal(t.groupACL, &acl)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to unmarshal template.userACL: %v", err.Error()))
|
||||
}
|
||||
|
||||
return acl
|
||||
}
|
||||
|
||||
func (t Template) SetGroupACL(acl TemplateACL) Template {
|
||||
raw, err := json.Marshal(acl)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("marshal user acl: %v", err))
|
||||
}
|
||||
|
||||
t.groupACL = raw
|
||||
return t
|
||||
}
|
||||
|
||||
func (t Template) SetUserACL(acl TemplateACL) Template {
|
||||
raw, err := json.Marshal(acl)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("marshal user acl: %v", err))
|
||||
}
|
||||
|
||||
t.userACL = raw
|
||||
return t
|
||||
}
|
||||
|
||||
func (s APIKeyScope) ToRBAC() rbac.Scope {
|
||||
switch s {
|
||||
case APIKeyScopeAll:
|
||||
@@ -74,8 +20,8 @@ func (s APIKeyScope) ToRBAC() rbac.Scope {
|
||||
func (t Template) RBACObject() rbac.Object {
|
||||
obj := rbac.ResourceTemplate
|
||||
return obj.InOrg(t.OrganizationID).
|
||||
WithACLUserList(t.UserACL()).
|
||||
WithGroupACL(t.GroupACL())
|
||||
WithACLUserList(t.UserACL).
|
||||
WithGroupACL(t.GroupACL)
|
||||
}
|
||||
|
||||
func (TemplateVersion) RBACObject(template Template) rbac.Object {
|
||||
|
||||
@@ -2,7 +2,6 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -23,8 +22,6 @@ type customQuerier interface {
|
||||
}
|
||||
|
||||
type templateQuerier interface {
|
||||
UpdateTemplateUserACLByID(ctx context.Context, id uuid.UUID, acl TemplateACL) error
|
||||
UpdateTemplateGroupACLByID(ctx context.Context, id uuid.UUID, acl TemplateACL) error
|
||||
GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([]TemplateGroup, error)
|
||||
GetTemplateUserRoles(ctx context.Context, id uuid.UUID) ([]TemplateUser, error)
|
||||
}
|
||||
@@ -34,28 +31,6 @@ type TemplateUser struct {
|
||||
Actions Actions `db:"actions"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateUserACLByID(ctx context.Context, id uuid.UUID, acl TemplateACL) error {
|
||||
raw, err := json.Marshal(acl)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal user acl: %w", err)
|
||||
}
|
||||
|
||||
const query = `
|
||||
UPDATE
|
||||
templates
|
||||
SET
|
||||
user_acl = $2
|
||||
WHERE
|
||||
id = $1`
|
||||
|
||||
_, err = q.db.ExecContext(ctx, query, id.String(), raw)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update user acl: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetTemplateUserRoles(ctx context.Context, id uuid.UUID) ([]TemplateUser, error) {
|
||||
const query = `
|
||||
SELECT
|
||||
@@ -100,28 +75,6 @@ type TemplateGroup struct {
|
||||
Actions Actions `db:"actions"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateGroupACLByID(ctx context.Context, id uuid.UUID, acl TemplateACL) error {
|
||||
raw, err := json.Marshal(acl)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal user acl: %w", err)
|
||||
}
|
||||
|
||||
const query = `
|
||||
UPDATE
|
||||
templates
|
||||
SET
|
||||
group_acl = $2
|
||||
WHERE
|
||||
id = $1`
|
||||
|
||||
_, err = q.db.ExecContext(ctx, query, id.String(), raw)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update user acl: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([]TemplateGroup, error) {
|
||||
const query = `
|
||||
SELECT
|
||||
|
||||
@@ -301,6 +301,7 @@ const (
|
||||
ResourceTypeWorkspace ResourceType = "workspace"
|
||||
ResourceTypeGitSshKey ResourceType = "git_ssh_key"
|
||||
ResourceTypeApiKey ResourceType = "api_key"
|
||||
ResourceTypeGroup ResourceType = "group"
|
||||
)
|
||||
|
||||
func (e *ResourceType) Scan(src interface{}) error {
|
||||
@@ -573,8 +574,8 @@ type Template struct {
|
||||
MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
userACL json.RawMessage `db:"user_acl" json:"user_acl"`
|
||||
groupACL json.RawMessage `db:"group_acl" json:"group_acl"`
|
||||
UserACL TemplateACL `db:"user_acl" json:"user_acl"`
|
||||
GroupACL TemplateACL `db:"group_acl" json:"group_acl"`
|
||||
}
|
||||
|
||||
type TemplateVersion struct {
|
||||
|
||||
@@ -162,6 +162,7 @@ type sqlcQuerier interface {
|
||||
UpdateProvisionerJobWithCancelByID(ctx context.Context, arg UpdateProvisionerJobWithCancelByIDParams) error
|
||||
UpdateProvisionerJobWithCompleteByID(ctx context.Context, arg UpdateProvisionerJobWithCompleteByIDParams) error
|
||||
UpdateReplica(ctx context.Context, arg UpdateReplicaParams) (Replica, error)
|
||||
UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) (Template, error)
|
||||
UpdateTemplateActiveVersionByID(ctx context.Context, arg UpdateTemplateActiveVersionByIDParams) error
|
||||
UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error
|
||||
UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) (Template, error)
|
||||
|
||||
@@ -2899,8 +2899,8 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
&i.UserACL,
|
||||
&i.GroupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -2941,8 +2941,8 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
&i.UserACL,
|
||||
&i.GroupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -2975,8 +2975,8 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
&i.UserACL,
|
||||
&i.GroupACL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3055,8 +3055,8 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
&i.UserACL,
|
||||
&i.GroupACL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3085,10 +3085,12 @@ INSERT INTO
|
||||
max_ttl,
|
||||
min_autostart_interval,
|
||||
created_by,
|
||||
icon
|
||||
icon,
|
||||
user_acl,
|
||||
group_acl
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl
|
||||
`
|
||||
|
||||
type InsertTemplateParams struct {
|
||||
@@ -3104,6 +3106,8 @@ type InsertTemplateParams struct {
|
||||
MinAutostartInterval int64 `db:"min_autostart_interval" json:"min_autostart_interval"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
UserACL TemplateACL `db:"user_acl" json:"user_acl"`
|
||||
GroupACL TemplateACL `db:"group_acl" json:"group_acl"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) (Template, error) {
|
||||
@@ -3120,6 +3124,8 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
|
||||
arg.MinAutostartInterval,
|
||||
arg.CreatedBy,
|
||||
arg.Icon,
|
||||
arg.UserACL,
|
||||
arg.GroupACL,
|
||||
)
|
||||
var i Template
|
||||
err := row.Scan(
|
||||
@@ -3136,8 +3142,49 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
&i.UserACL,
|
||||
&i.GroupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateTemplateACLByID = `-- name: UpdateTemplateACLByID :one
|
||||
UPDATE
|
||||
templates
|
||||
SET
|
||||
group_acl = $1,
|
||||
user_acl = $2
|
||||
WHERE
|
||||
id = $3
|
||||
RETURNING
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon, user_acl, group_acl
|
||||
`
|
||||
|
||||
type UpdateTemplateACLByIDParams struct {
|
||||
GroupACL TemplateACL `db:"group_acl" json:"group_acl"`
|
||||
UserACL TemplateACL `db:"user_acl" json:"user_acl"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateACLByID(ctx context.Context, arg UpdateTemplateACLByIDParams) (Template, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateTemplateACLByID, arg.GroupACL, arg.UserACL, arg.ID)
|
||||
var i Template
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OrganizationID,
|
||||
&i.Deleted,
|
||||
&i.Name,
|
||||
&i.Provisioner,
|
||||
&i.ActiveVersionID,
|
||||
&i.Description,
|
||||
&i.MaxTtl,
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.UserACL,
|
||||
&i.GroupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3235,8 +3282,8 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
&i.UserACL,
|
||||
&i.GroupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -68,10 +68,12 @@ INSERT INTO
|
||||
max_ttl,
|
||||
min_autostart_interval,
|
||||
created_by,
|
||||
icon
|
||||
icon,
|
||||
user_acl,
|
||||
group_acl
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *;
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *;
|
||||
|
||||
-- name: UpdateTemplateActiveVersionByID :exec
|
||||
UPDATE
|
||||
@@ -106,6 +108,17 @@ WHERE
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
-- name: UpdateTemplateACLByID :one
|
||||
UPDATE
|
||||
templates
|
||||
SET
|
||||
group_acl = $1,
|
||||
user_acl = $2
|
||||
WHERE
|
||||
id = $3
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
-- name: GetTemplateAverageBuildTime :one
|
||||
WITH build_times AS (
|
||||
SELECT
|
||||
|
||||
@@ -19,6 +19,12 @@ packages:
|
||||
overrides:
|
||||
- column: "users.rbac_roles"
|
||||
go_type: "github.com/lib/pq.StringArray"
|
||||
- column: "templates.user_acl"
|
||||
go_type:
|
||||
type: "TemplateACL"
|
||||
- column: "templates.group_acl"
|
||||
go_type:
|
||||
type: "TemplateACL"
|
||||
|
||||
rename:
|
||||
api_key: APIKey
|
||||
@@ -39,5 +45,5 @@ rename:
|
||||
ip_addresses: IPAddresses
|
||||
ids: IDs
|
||||
jwt: JWT
|
||||
user_acl: userACL
|
||||
group_acl: groupACL
|
||||
user_acl: UserACL
|
||||
group_acl: GroupACL
|
||||
|
||||
Reference in New Issue
Block a user