mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat: add template RBAC/groups (#4235)
This commit is contained in:
@ -1,62 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
)
|
||||
|
||||
type customQuerier interface {
|
||||
GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspacesParams, authorizedFilter rbac.AuthorizeFilter) ([]Workspace, error)
|
||||
}
|
||||
|
||||
// GetAuthorizedWorkspaces returns all workspaces that the user is authorized to access.
|
||||
// This code is copied from `GetWorkspaces` and adds the authorized filter WHERE
|
||||
// clause.
|
||||
func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspacesParams, authorizedFilter rbac.AuthorizeFilter) ([]Workspace, error) {
|
||||
// The name comment is for metric tracking
|
||||
query := fmt.Sprintf("-- name: GetAuthorizedWorkspaces :many\n%s AND %s", getWorkspaces, authorizedFilter.SQLString(rbac.DefaultConfig()))
|
||||
rows, err := q.db.QueryContext(ctx, query,
|
||||
arg.Deleted,
|
||||
arg.OwnerID,
|
||||
arg.OwnerUsername,
|
||||
arg.TemplateName,
|
||||
pq.Array(arg.TemplateIds),
|
||||
arg.Name,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get authorized workspaces: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Workspace
|
||||
for rows.Next() {
|
||||
var i Workspace
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
&i.OrganizationID,
|
||||
&i.TemplateID,
|
||||
&i.Deleted,
|
||||
&i.Name,
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
@ -12,23 +12,30 @@ import (
|
||||
"github.com/lib/pq"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/util/slice"
|
||||
)
|
||||
|
||||
var errDuplicateKey = &pq.Error{
|
||||
Code: "23505",
|
||||
Message: "duplicate key value violates unique constraint",
|
||||
}
|
||||
|
||||
// New returns an in-memory fake of the database.
|
||||
func New() database.Store {
|
||||
return &fakeQuerier{
|
||||
mutex: &sync.RWMutex{},
|
||||
data: &data{
|
||||
apiKeys: make([]database.APIKey, 0),
|
||||
agentStats: make([]database.AgentStat, 0),
|
||||
organizationMembers: make([]database.OrganizationMember, 0),
|
||||
organizations: make([]database.Organization, 0),
|
||||
users: make([]database.User, 0),
|
||||
|
||||
apiKeys: make([]database.APIKey, 0),
|
||||
agentStats: make([]database.AgentStat, 0),
|
||||
organizationMembers: make([]database.OrganizationMember, 0),
|
||||
organizations: make([]database.Organization, 0),
|
||||
users: make([]database.User, 0),
|
||||
groups: make([]database.Group, 0),
|
||||
groupMembers: make([]database.GroupMember, 0),
|
||||
auditLogs: make([]database.AuditLog, 0),
|
||||
files: make([]database.File, 0),
|
||||
gitSSHKey: make([]database.GitSSHKey, 0),
|
||||
@ -84,6 +91,8 @@ type data struct {
|
||||
auditLogs []database.AuditLog
|
||||
files []database.File
|
||||
gitSSHKey []database.GitSSHKey
|
||||
groups []database.Group
|
||||
groupMembers []database.GroupMember
|
||||
parameterSchemas []database.ParameterSchema
|
||||
parameterValues []database.ParameterValue
|
||||
provisionerDaemons []database.ProvisionerDaemon
|
||||
@ -518,6 +527,13 @@ func (q *fakeQuerier) GetAuthorizationUserRoles(_ context.Context, userID uuid.U
|
||||
}
|
||||
}
|
||||
|
||||
var groups []string
|
||||
for _, member := range q.groupMembers {
|
||||
if member.UserID == userID {
|
||||
groups = append(groups, member.GroupID.String())
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
return database.GetAuthorizationUserRolesRow{}, sql.ErrNoRows
|
||||
}
|
||||
@ -527,6 +543,7 @@ func (q *fakeQuerier) GetAuthorizationUserRoles(_ context.Context, userID uuid.U
|
||||
Username: user.Username,
|
||||
Status: user.Status,
|
||||
Roles: roles,
|
||||
Groups: groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -1269,6 +1286,116 @@ 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()
|
||||
|
||||
var template database.Template
|
||||
for _, t := range q.templates {
|
||||
if t.ID == id {
|
||||
template = t
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if template.ID == uuid.Nil {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
acl := template.UserACL()
|
||||
|
||||
users := make([]database.TemplateUser, 0, len(acl))
|
||||
for k, v := range acl {
|
||||
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)
|
||||
}
|
||||
// We don't delete users from the map if they
|
||||
// get deleted so just skip.
|
||||
if xerrors.Is(err, sql.ErrNoRows) {
|
||||
continue
|
||||
}
|
||||
|
||||
if user.Deleted || user.Status == database.UserStatusSuspended {
|
||||
continue
|
||||
}
|
||||
|
||||
users = append(users, database.TemplateUser{
|
||||
User: user,
|
||||
Actions: v,
|
||||
})
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetTemplateGroupRoles(_ context.Context, id uuid.UUID) ([]database.TemplateGroup, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
var template database.Template
|
||||
for _, t := range q.templates {
|
||||
if t.ID == id {
|
||||
template = t
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if template.ID == uuid.Nil {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
acl := template.GroupACL()
|
||||
|
||||
groups := make([]database.TemplateGroup, 0, len(acl))
|
||||
for k, v := range acl {
|
||||
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)
|
||||
}
|
||||
// We don't delete groups from the map if they
|
||||
// get deleted so just skip.
|
||||
if xerrors.Is(err, sql.ErrNoRows) {
|
||||
continue
|
||||
}
|
||||
|
||||
groups = append(groups, database.TemplateGroup{
|
||||
Group: group,
|
||||
Actions: v,
|
||||
})
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetOrganizationMemberByUserID(_ context.Context, arg database.GetOrganizationMemberByUserIDParams) (database.OrganizationMember, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@ -1749,6 +1876,10 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
|
||||
MinAutostartInterval: arg.MinAutostartInterval,
|
||||
CreatedBy: arg.CreatedBy,
|
||||
}
|
||||
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
|
||||
}
|
||||
@ -2299,7 +2430,7 @@ func (q *fakeQuerier) UpdateWorkspace(_ context.Context, arg database.UpdateWork
|
||||
continue
|
||||
}
|
||||
if other.Name == arg.Name {
|
||||
return database.Workspace{}, &pq.Error{Code: "23505", Message: "duplicate key value violates unique constraint"}
|
||||
return database.Workspace{}, errDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
@ -2437,6 +2568,52 @@ func (q *fakeQuerier) UpdateGitSSHKey(_ context.Context, arg database.UpdateGitS
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertGroupMember(_ context.Context, arg database.InsertGroupMemberParams) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for _, member := range q.groupMembers {
|
||||
if member.GroupID == arg.GroupID &&
|
||||
member.UserID == arg.UserID {
|
||||
return errDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gosimple
|
||||
q.groupMembers = append(q.groupMembers, database.GroupMember{
|
||||
GroupID: arg.GroupID,
|
||||
UserID: arg.UserID,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) DeleteGroupMember(_ context.Context, userID uuid.UUID) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, member := range q.groupMembers {
|
||||
if member.UserID == userID {
|
||||
q.groupMembers = append(q.groupMembers[:i], q.groupMembers[i+1:]...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateGroupByID(_ context.Context, arg database.UpdateGroupByIDParams) (database.Group, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, group := range q.groups {
|
||||
if group.ID == arg.ID {
|
||||
group.Name = arg.Name
|
||||
q.groups[i] = group
|
||||
return group, nil
|
||||
}
|
||||
}
|
||||
return database.Group{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) DeleteGitSSHKey(_ context.Context, userID uuid.UUID) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
@ -2714,3 +2891,137 @@ func (q *fakeQuerier) UpdateUserLink(_ context.Context, params database.UpdateUs
|
||||
|
||||
return database.UserLink{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetGroupByID(_ context.Context, id uuid.UUID) (database.Group, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, group := range q.groups {
|
||||
if group.ID == id {
|
||||
return group, nil
|
||||
}
|
||||
}
|
||||
|
||||
return database.Group{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetGroupByOrgAndName(_ context.Context, arg database.GetGroupByOrgAndNameParams) (database.Group, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, group := range q.groups {
|
||||
if group.OrganizationID == arg.OrganizationID &&
|
||||
group.Name == arg.Name {
|
||||
return group, nil
|
||||
}
|
||||
}
|
||||
|
||||
return database.Group{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertAllUsersGroup(ctx context.Context, orgID uuid.UUID) (database.Group, error) {
|
||||
return q.InsertGroup(ctx, database.InsertGroupParams{
|
||||
ID: orgID,
|
||||
Name: database.AllUsersGroup,
|
||||
OrganizationID: orgID,
|
||||
})
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertGroup(_ context.Context, arg database.InsertGroupParams) (database.Group, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, group := range q.groups {
|
||||
if group.OrganizationID.String() == arg.OrganizationID.String() &&
|
||||
group.Name == arg.Name {
|
||||
return database.Group{}, errDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gosimple
|
||||
group := database.Group{
|
||||
ID: arg.ID,
|
||||
Name: arg.Name,
|
||||
OrganizationID: arg.OrganizationID,
|
||||
}
|
||||
|
||||
q.groups = append(q.groups, group)
|
||||
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func (*fakeQuerier) GetUserGroups(_ context.Context, _ uuid.UUID) ([]database.Group, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetGroupMembers(_ context.Context, groupID uuid.UUID) ([]database.User, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
var members []database.GroupMember
|
||||
for _, member := range q.groupMembers {
|
||||
if member.GroupID == groupID {
|
||||
members = append(members, member)
|
||||
}
|
||||
}
|
||||
|
||||
users := make([]database.User, 0, len(members))
|
||||
|
||||
for _, member := range members {
|
||||
for _, user := range q.users {
|
||||
if user.ID == member.UserID && user.Status == database.UserStatusActive && !user.Deleted {
|
||||
users = append(users, user)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetGroupsByOrganizationID(_ context.Context, organizationID uuid.UUID) ([]database.Group, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
var groups []database.Group
|
||||
for _, group := range q.groups {
|
||||
// Omit the allUsers group.
|
||||
if group.OrganizationID == organizationID && group.ID != organizationID {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetAllOrganizationMembers(_ context.Context, organizationID uuid.UUID) ([]database.User, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
var users []database.User
|
||||
for _, member := range q.organizationMembers {
|
||||
if member.OrganizationID == organizationID {
|
||||
for _, user := range q.users {
|
||||
if user.ID == member.UserID {
|
||||
users = append(users, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) DeleteGroupByID(_ context.Context, id uuid.UUID) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, group := range q.groups {
|
||||
if group.ID == id {
|
||||
q.groups = append(q.groups[:i], q.groups[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
@ -32,24 +33,34 @@ type DBTX interface {
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
|
||||
GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
|
||||
}
|
||||
|
||||
// New creates a new database store using a SQL database connection.
|
||||
func New(sdb *sql.DB) Store {
|
||||
dbx := sqlx.NewDb(sdb, "postgres")
|
||||
return &sqlQuerier{
|
||||
db: sdb,
|
||||
sdb: sdb,
|
||||
db: dbx,
|
||||
sdb: dbx,
|
||||
}
|
||||
}
|
||||
|
||||
// queries encompasses both are sqlc generated
|
||||
// queries and our custom queries.
|
||||
type querier interface {
|
||||
sqlcQuerier
|
||||
customQuerier
|
||||
}
|
||||
|
||||
type sqlQuerier struct {
|
||||
sdb *sql.DB
|
||||
sdb *sqlx.DB
|
||||
db DBTX
|
||||
}
|
||||
|
||||
// InTx performs database operations inside a transaction.
|
||||
func (q *sqlQuerier) InTx(function func(Store) error) error {
|
||||
if _, ok := q.db.(*sql.Tx); ok {
|
||||
if _, ok := q.db.(*sqlx.Tx); ok {
|
||||
// If the current inner "db" is already a transaction, we just reuse it.
|
||||
// We do not need to handle commit/rollback as the outer tx will handle
|
||||
// that.
|
||||
@ -60,7 +71,7 @@ func (q *sqlQuerier) InTx(function func(Store) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
transaction, err := q.sdb.Begin()
|
||||
transaction, err := q.sdb.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("begin transaction: %w", err)
|
||||
}
|
||||
|
40
coderd/database/dbtestutil/db.go
Normal file
40
coderd/database/dbtestutil/db.go
Normal file
@ -0,0 +1,40 @@
|
||||
package dbtestutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/databasefake"
|
||||
"github.com/coder/coder/coderd/database/postgres"
|
||||
)
|
||||
|
||||
func NewDB(t *testing.T) (database.Store, database.Pubsub) {
|
||||
t.Helper()
|
||||
|
||||
db := databasefake.New()
|
||||
pubsub := database.NewPubsubInMemory()
|
||||
if os.Getenv("DB") != "" {
|
||||
connectionURL, closePg, err := postgres.Open()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(closePg)
|
||||
sqlDB, err := sql.Open("postgres", connectionURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = sqlDB.Close()
|
||||
})
|
||||
db = database.New(sqlDB)
|
||||
|
||||
pubsub, err = database.NewPubsub(context.Background(), sqlDB, connectionURL)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = pubsub.Close()
|
||||
})
|
||||
}
|
||||
|
||||
return db, pubsub
|
||||
}
|
26
coderd/database/drivers.go
Normal file
26
coderd/database/drivers.go
Normal file
@ -0,0 +1,26 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
)
|
||||
|
||||
type Actions []rbac.Action
|
||||
|
||||
func (a *Actions) Scan(src interface{}) error {
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
return json.Unmarshal([]byte(v), &a)
|
||||
case []byte:
|
||||
return json.Unmarshal(v, &a)
|
||||
}
|
||||
return xerrors.Errorf("unexpected type %T", src)
|
||||
}
|
||||
|
||||
func (a *Actions) Value() (driver.Value, error) {
|
||||
return json.Marshal(a)
|
||||
}
|
33
coderd/database/dump.sql
generated
33
coderd/database/dump.sql
generated
@ -162,6 +162,17 @@ CREATE TABLE gitsshkeys (
|
||||
public_key text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE group_members (
|
||||
user_id uuid NOT NULL,
|
||||
group_id uuid NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE groups (
|
||||
id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
organization_id uuid NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE licenses (
|
||||
id integer NOT NULL,
|
||||
uploaded_at timestamp with time zone NOT NULL,
|
||||
@ -295,7 +306,9 @@ CREATE TABLE templates (
|
||||
max_ttl bigint DEFAULT '604800000000000'::bigint NOT NULL,
|
||||
min_autostart_interval bigint DEFAULT '3600000000000'::bigint NOT NULL,
|
||||
created_by uuid NOT NULL,
|
||||
icon character varying(256) DEFAULT ''::character varying NOT NULL
|
||||
icon character varying(256) DEFAULT ''::character varying NOT NULL,
|
||||
user_acl jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
group_acl jsonb DEFAULT '{}'::jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE user_links (
|
||||
@ -424,6 +437,15 @@ ALTER TABLE ONLY files
|
||||
ALTER TABLE ONLY gitsshkeys
|
||||
ADD CONSTRAINT gitsshkeys_pkey PRIMARY KEY (user_id);
|
||||
|
||||
ALTER TABLE ONLY group_members
|
||||
ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id);
|
||||
|
||||
ALTER TABLE ONLY groups
|
||||
ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id);
|
||||
|
||||
ALTER TABLE ONLY groups
|
||||
ADD CONSTRAINT groups_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY licenses
|
||||
ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt);
|
||||
|
||||
@ -545,6 +567,15 @@ ALTER TABLE ONLY api_keys
|
||||
ALTER TABLE ONLY gitsshkeys
|
||||
ADD CONSTRAINT gitsshkeys_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
|
||||
ALTER TABLE ONLY group_members
|
||||
ADD CONSTRAINT group_members_group_id_fkey FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY group_members
|
||||
ADD CONSTRAINT group_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY groups
|
||||
ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY organization_members
|
||||
ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
|
@ -42,7 +42,7 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
rm -f queries/*.go
|
||||
|
||||
# Fix struct/interface names.
|
||||
gofmt -w -r 'Querier -> querier' -- *.go
|
||||
gofmt -w -r 'Querier -> sqlcQuerier' -- *.go
|
||||
gofmt -w -r 'Queries -> sqlQuerier' -- *.go
|
||||
|
||||
# Ensure correct imports exist. Modules must all be downloaded so we get correct
|
||||
|
8
coderd/database/migrations/000058_template_acl.down.sql
Normal file
8
coderd/database/migrations/000058_template_acl.down.sql
Normal file
@ -0,0 +1,8 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE group_members;
|
||||
DROP TABLE groups;
|
||||
ALTER TABLE templates DROP COLUMN group_acl;
|
||||
ALTER TABLE templates DROP COLUMN user_acl;
|
||||
|
||||
COMMIT;
|
48
coderd/database/migrations/000058_template_acl.up.sql
Normal file
48
coderd/database/migrations/000058_template_acl.up.sql
Normal file
@ -0,0 +1,48 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE templates ADD COLUMN user_acl jsonb NOT NULL default '{}';
|
||||
ALTER TABLE templates ADD COLUMN group_acl jsonb NOT NULL default '{}';
|
||||
|
||||
CREATE TABLE groups (
|
||||
id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE(name, organization_id)
|
||||
);
|
||||
|
||||
CREATE TABLE group_members (
|
||||
user_id uuid NOT NULL,
|
||||
group_id uuid NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE,
|
||||
UNIQUE(user_id, group_id)
|
||||
);
|
||||
|
||||
-- Insert a group for every organization (which should just be 1).
|
||||
INSERT INTO groups (
|
||||
id,
|
||||
name,
|
||||
organization_id
|
||||
) SELECT
|
||||
id, 'Everyone' as name, id
|
||||
FROM
|
||||
organizations;
|
||||
|
||||
-- Insert allUsers groups into every existing template to avoid breaking
|
||||
-- existing deployments.
|
||||
UPDATE
|
||||
templates
|
||||
SET
|
||||
group_acl = (
|
||||
SELECT
|
||||
json_build_object(
|
||||
organizations.id, array_to_json('{"read"}'::text[])
|
||||
)
|
||||
FROM
|
||||
organizations
|
||||
WHERE
|
||||
templates.organization_id = organizations.id
|
||||
);
|
||||
|
||||
COMMIT;
|
@ -1,9 +1,65 @@
|
||||
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:
|
||||
@ -16,12 +72,19 @@ func (s APIKeyScope) ToRBAC() rbac.Scope {
|
||||
}
|
||||
|
||||
func (t Template) RBACObject() rbac.Object {
|
||||
return rbac.ResourceTemplate.InOrg(t.OrganizationID)
|
||||
obj := rbac.ResourceTemplate
|
||||
return obj.InOrg(t.OrganizationID).
|
||||
WithACLUserList(t.UserACL()).
|
||||
WithGroupACL(t.GroupACL())
|
||||
}
|
||||
|
||||
func (t TemplateVersion) RBACObject() rbac.Object {
|
||||
func (TemplateVersion) RBACObject(template Template) rbac.Object {
|
||||
// Just use the parent template resource for controlling versions
|
||||
return rbac.ResourceTemplate.InOrg(t.OrganizationID)
|
||||
return template.RBACObject()
|
||||
}
|
||||
|
||||
func (g Group) RBACObject() rbac.Object {
|
||||
return rbac.ResourceGroup.InOrg(g.OrganizationID)
|
||||
}
|
||||
|
||||
func (w Workspace) RBACObject() rbac.Object {
|
||||
|
208
coderd/database/modelqueries.go
Normal file
208
coderd/database/modelqueries.go
Normal file
@ -0,0 +1,208 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// customQuerier encompasses all non-generated queries.
|
||||
// It provides a flexible way to write queries for cases
|
||||
// where sqlc proves inadequate.
|
||||
type customQuerier interface {
|
||||
templateQuerier
|
||||
workspaceQuerier
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
type TemplateUser struct {
|
||||
User
|
||||
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
|
||||
perms.value as actions, users.*
|
||||
FROM
|
||||
users
|
||||
JOIN
|
||||
(
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
jsonb_each_text(
|
||||
(
|
||||
SELECT
|
||||
templates.user_acl
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
id = $1
|
||||
)
|
||||
)
|
||||
) AS perms
|
||||
ON
|
||||
users.id::text = perms.key
|
||||
WHERE
|
||||
users.deleted = false
|
||||
AND
|
||||
users.status = 'active';
|
||||
`
|
||||
|
||||
var tus []TemplateUser
|
||||
err := q.db.SelectContext(ctx, &tus, query, id.String())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("select user actions: %w", err)
|
||||
}
|
||||
|
||||
return tus, nil
|
||||
}
|
||||
|
||||
type TemplateGroup struct {
|
||||
Group
|
||||
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
|
||||
perms.value as actions, groups.*
|
||||
FROM
|
||||
groups
|
||||
JOIN
|
||||
(
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
jsonb_each_text(
|
||||
(
|
||||
SELECT
|
||||
templates.group_acl
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
id = $1
|
||||
)
|
||||
)
|
||||
) AS perms
|
||||
ON
|
||||
groups.id::text = perms.key;
|
||||
`
|
||||
|
||||
var tgs []TemplateGroup
|
||||
err := q.db.SelectContext(ctx, &tgs, query, id.String())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("select group roles: %w", err)
|
||||
}
|
||||
|
||||
return tgs, nil
|
||||
}
|
||||
|
||||
type workspaceQuerier interface {
|
||||
GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspacesParams, authorizedFilter rbac.AuthorizeFilter) ([]Workspace, error)
|
||||
}
|
||||
|
||||
// GetAuthorizedWorkspaces returns all workspaces that the user is authorized to access.
|
||||
// This code is copied from `GetWorkspaces` and adds the authorized filter WHERE
|
||||
// clause.
|
||||
func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspacesParams, authorizedFilter rbac.AuthorizeFilter) ([]Workspace, error) {
|
||||
// The name comment is for metric tracking
|
||||
query := fmt.Sprintf("-- name: GetAuthorizedWorkspaces :many\n%s AND %s", getWorkspaces, authorizedFilter.SQLString(rbac.NoACLConfig()))
|
||||
rows, err := q.db.QueryContext(ctx, query,
|
||||
arg.Deleted,
|
||||
arg.OwnerID,
|
||||
arg.OwnerUsername,
|
||||
arg.TemplateName,
|
||||
pq.Array(arg.TemplateIds),
|
||||
arg.Name,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get authorized workspaces: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Workspace
|
||||
for rows.Next() {
|
||||
var i Workspace
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
&i.OrganizationID,
|
||||
&i.TemplateID,
|
||||
&i.Deleted,
|
||||
&i.Name,
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
"github.com/tabbed/pqtype"
|
||||
)
|
||||
|
||||
@ -413,6 +414,17 @@ type GitSSHKey struct {
|
||||
PublicKey string `db:"public_key" json:"public_key"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
}
|
||||
|
||||
type GroupMember struct {
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
GroupID uuid.UUID `db:"group_id" json:"group_id"`
|
||||
}
|
||||
|
||||
type License struct {
|
||||
ID int32 `db:"id" json:"id"`
|
||||
UploadedAt time.Time `db:"uploaded_at" json:"uploaded_at"`
|
||||
@ -524,6 +536,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"`
|
||||
}
|
||||
|
||||
type TemplateVersion struct {
|
||||
@ -546,7 +560,7 @@ type User struct {
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Status UserStatus `db:"status" json:"status"`
|
||||
RBACRoles []string `db:"rbac_roles" json:"rbac_roles"`
|
||||
RBACRoles pq.StringArray `db:"rbac_roles" json:"rbac_roles"`
|
||||
LoginType LoginType `db:"login_type" json:"login_type"`
|
||||
AvatarURL sql.NullString `db:"avatar_url" json:"avatar_url"`
|
||||
Deleted bool `db:"deleted" json:"deleted"`
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type querier interface {
|
||||
type sqlcQuerier interface {
|
||||
// Acquires the lock for a single job that isn't started, completed,
|
||||
// canceled, and that matches an array of provisioner types.
|
||||
//
|
||||
@ -21,6 +21,8 @@ type querier interface {
|
||||
AcquireProvisionerJob(ctx context.Context, arg AcquireProvisionerJobParams) (ProvisionerJob, error)
|
||||
DeleteAPIKeyByID(ctx context.Context, id string) error
|
||||
DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteGroupByID(ctx context.Context, id uuid.UUID) error
|
||||
DeleteGroupMember(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteLicense(ctx context.Context, id int32) (int32, error)
|
||||
DeleteOldAgentStats(ctx context.Context) error
|
||||
DeleteParameterValueByID(ctx context.Context, id uuid.UUID) error
|
||||
@ -28,6 +30,7 @@ type querier interface {
|
||||
GetAPIKeysByLoginType(ctx context.Context, loginType LoginType) ([]APIKey, error)
|
||||
GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error)
|
||||
GetActiveUserCount(ctx context.Context) (int64, error)
|
||||
GetAllOrganizationMembers(ctx context.Context, organizationID uuid.UUID) ([]User, error)
|
||||
GetAuditLogCount(ctx context.Context, arg GetAuditLogCountParams) (int64, error)
|
||||
// GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
|
||||
// ID.
|
||||
@ -38,6 +41,10 @@ type querier interface {
|
||||
GetDeploymentID(ctx context.Context) (string, error)
|
||||
GetFileByHash(ctx context.Context, hash string) (File, error)
|
||||
GetGitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSHKey, error)
|
||||
GetGroupByID(ctx context.Context, id uuid.UUID) (Group, error)
|
||||
GetGroupByOrgAndName(ctx context.Context, arg GetGroupByOrgAndNameParams) (Group, error)
|
||||
GetGroupMembers(ctx context.Context, groupID uuid.UUID) ([]User, error)
|
||||
GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]Group, error)
|
||||
GetLatestAgentStat(ctx context.Context, agentID uuid.UUID) (AgentStat, error)
|
||||
GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error)
|
||||
GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error)
|
||||
@ -73,6 +80,7 @@ type querier interface {
|
||||
GetUserByEmailOrUsername(ctx context.Context, arg GetUserByEmailOrUsernameParams) (User, error)
|
||||
GetUserByID(ctx context.Context, id uuid.UUID) (User, error)
|
||||
GetUserCount(ctx context.Context) (int64, error)
|
||||
GetUserGroups(ctx context.Context, userID uuid.UUID) ([]Group, error)
|
||||
GetUserLinkByLinkedID(ctx context.Context, linkedID string) (UserLink, error)
|
||||
GetUserLinkByUserIDLoginType(ctx context.Context, arg GetUserLinkByUserIDLoginTypeParams) (UserLink, error)
|
||||
GetUsers(ctx context.Context, arg GetUsersParams) ([]User, error)
|
||||
@ -108,10 +116,16 @@ type querier interface {
|
||||
GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) ([]Workspace, error)
|
||||
InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error)
|
||||
InsertAgentStat(ctx context.Context, arg InsertAgentStatParams) (AgentStat, error)
|
||||
// We use the organization_id as the id
|
||||
// for simplicity since all users is
|
||||
// every member of the org.
|
||||
InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error)
|
||||
InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error)
|
||||
InsertDeploymentID(ctx context.Context, value string) error
|
||||
InsertFile(ctx context.Context, arg InsertFileParams) (File, error)
|
||||
InsertGitSSHKey(ctx context.Context, arg InsertGitSSHKeyParams) (GitSSHKey, error)
|
||||
InsertGroup(ctx context.Context, arg InsertGroupParams) (Group, error)
|
||||
InsertGroupMember(ctx context.Context, arg InsertGroupMemberParams) error
|
||||
InsertLicense(ctx context.Context, arg InsertLicenseParams) (License, error)
|
||||
InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error)
|
||||
InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error)
|
||||
@ -134,6 +148,7 @@ type querier interface {
|
||||
ParameterValues(ctx context.Context, arg ParameterValuesParams) ([]ParameterValue, error)
|
||||
UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error
|
||||
UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) error
|
||||
UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error)
|
||||
UpdateMemberRoles(ctx context.Context, arg UpdateMemberRolesParams) (OrganizationMember, error)
|
||||
UpdateProvisionerDaemonByID(ctx context.Context, arg UpdateProvisionerDaemonByIDParams) error
|
||||
UpdateProvisionerJobByID(ctx context.Context, arg UpdateProvisionerJobByIDParams) error
|
||||
@ -163,4 +178,4 @@ type querier interface {
|
||||
UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error
|
||||
}
|
||||
|
||||
var _ querier = (*sqlQuerier)(nil)
|
||||
var _ sqlcQuerier = (*sqlQuerier)(nil)
|
||||
|
@ -807,6 +807,328 @@ func (q *sqlQuerier) UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyPar
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteGroupByID = `-- name: DeleteGroupByID :exec
|
||||
DELETE FROM
|
||||
groups
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) DeleteGroupByID(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteGroupByID, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteGroupMember = `-- name: DeleteGroupMember :exec
|
||||
DELETE FROM
|
||||
group_members
|
||||
WHERE
|
||||
user_id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) DeleteGroupMember(ctx context.Context, userID uuid.UUID) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteGroupMember, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getAllOrganizationMembers = `-- name: GetAllOrganizationMembers :many
|
||||
SELECT
|
||||
users.id, users.email, users.username, users.hashed_password, users.created_at, users.updated_at, users.status, users.rbac_roles, users.login_type, users.avatar_url, users.deleted, users.last_seen_at
|
||||
FROM
|
||||
users
|
||||
JOIN
|
||||
organization_members
|
||||
ON
|
||||
users.id = organization_members.user_id
|
||||
WHERE
|
||||
organization_members.organization_id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetAllOrganizationMembers(ctx context.Context, organizationID uuid.UUID) ([]User, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getAllOrganizationMembers, organizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []User
|
||||
for rows.Next() {
|
||||
var i User
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Email,
|
||||
&i.Username,
|
||||
&i.HashedPassword,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
&i.LastSeenAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getGroupByID = `-- name: GetGroupByID :one
|
||||
SELECT
|
||||
id, name, organization_id
|
||||
FROM
|
||||
groups
|
||||
WHERE
|
||||
id = $1
|
||||
LIMIT
|
||||
1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetGroupByID(ctx context.Context, id uuid.UUID) (Group, error) {
|
||||
row := q.db.QueryRowContext(ctx, getGroupByID, id)
|
||||
var i Group
|
||||
err := row.Scan(&i.ID, &i.Name, &i.OrganizationID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getGroupByOrgAndName = `-- name: GetGroupByOrgAndName :one
|
||||
SELECT
|
||||
id, name, organization_id
|
||||
FROM
|
||||
groups
|
||||
WHERE
|
||||
organization_id = $1
|
||||
AND
|
||||
name = $2
|
||||
LIMIT
|
||||
1
|
||||
`
|
||||
|
||||
type GetGroupByOrgAndNameParams struct {
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetGroupByOrgAndName(ctx context.Context, arg GetGroupByOrgAndNameParams) (Group, error) {
|
||||
row := q.db.QueryRowContext(ctx, getGroupByOrgAndName, arg.OrganizationID, arg.Name)
|
||||
var i Group
|
||||
err := row.Scan(&i.ID, &i.Name, &i.OrganizationID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getGroupMembers = `-- name: GetGroupMembers :many
|
||||
SELECT
|
||||
users.id, users.email, users.username, users.hashed_password, users.created_at, users.updated_at, users.status, users.rbac_roles, users.login_type, users.avatar_url, users.deleted, users.last_seen_at
|
||||
FROM
|
||||
users
|
||||
JOIN
|
||||
group_members
|
||||
ON
|
||||
users.id = group_members.user_id
|
||||
WHERE
|
||||
group_members.group_id = $1
|
||||
AND
|
||||
users.status = 'active'
|
||||
AND
|
||||
users.deleted = 'false'
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetGroupMembers(ctx context.Context, groupID uuid.UUID) ([]User, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getGroupMembers, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []User
|
||||
for rows.Next() {
|
||||
var i User
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Email,
|
||||
&i.Username,
|
||||
&i.HashedPassword,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
&i.LastSeenAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getGroupsByOrganizationID = `-- name: GetGroupsByOrganizationID :many
|
||||
SELECT
|
||||
id, name, organization_id
|
||||
FROM
|
||||
groups
|
||||
WHERE
|
||||
organization_id = $1
|
||||
AND
|
||||
id != $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]Group, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getGroupsByOrganizationID, organizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Group
|
||||
for rows.Next() {
|
||||
var i Group
|
||||
if err := rows.Scan(&i.ID, &i.Name, &i.OrganizationID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getUserGroups = `-- name: GetUserGroups :many
|
||||
SELECT
|
||||
groups.id, groups.name, groups.organization_id
|
||||
FROM
|
||||
groups
|
||||
JOIN
|
||||
group_members
|
||||
ON
|
||||
groups.id = group_members.group_id
|
||||
WHERE
|
||||
group_members.user_id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetUserGroups(ctx context.Context, userID uuid.UUID) ([]Group, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getUserGroups, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Group
|
||||
for rows.Next() {
|
||||
var i Group
|
||||
if err := rows.Scan(&i.ID, &i.Name, &i.OrganizationID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertAllUsersGroup = `-- name: InsertAllUsersGroup :one
|
||||
INSERT INTO groups (
|
||||
id,
|
||||
name,
|
||||
organization_id
|
||||
)
|
||||
VALUES
|
||||
( $1, 'Everyone', $1) RETURNING id, name, organization_id
|
||||
`
|
||||
|
||||
// We use the organization_id as the id
|
||||
// for simplicity since all users is
|
||||
// every member of the org.
|
||||
func (q *sqlQuerier) InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertAllUsersGroup, organizationID)
|
||||
var i Group
|
||||
err := row.Scan(&i.ID, &i.Name, &i.OrganizationID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertGroup = `-- name: InsertGroup :one
|
||||
INSERT INTO groups (
|
||||
id,
|
||||
name,
|
||||
organization_id
|
||||
)
|
||||
VALUES
|
||||
( $1, $2, $3) RETURNING id, name, organization_id
|
||||
`
|
||||
|
||||
type InsertGroupParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertGroup(ctx context.Context, arg InsertGroupParams) (Group, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertGroup, arg.ID, arg.Name, arg.OrganizationID)
|
||||
var i Group
|
||||
err := row.Scan(&i.ID, &i.Name, &i.OrganizationID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertGroupMember = `-- name: InsertGroupMember :exec
|
||||
INSERT INTO group_members (
|
||||
user_id,
|
||||
group_id
|
||||
)
|
||||
VALUES ( $1, $2)
|
||||
`
|
||||
|
||||
type InsertGroupMemberParams struct {
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
GroupID uuid.UUID `db:"group_id" json:"group_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertGroupMember(ctx context.Context, arg InsertGroupMemberParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertGroupMember, arg.UserID, arg.GroupID)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateGroupByID = `-- name: UpdateGroupByID :one
|
||||
UPDATE
|
||||
groups
|
||||
SET
|
||||
name = $1
|
||||
WHERE
|
||||
id = $2
|
||||
RETURNING id, name, organization_id
|
||||
`
|
||||
|
||||
type UpdateGroupByIDParams struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateGroupByID, arg.Name, arg.ID)
|
||||
var i Group
|
||||
err := row.Scan(&i.ID, &i.Name, &i.OrganizationID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteLicense = `-- name: DeleteLicense :one
|
||||
DELETE
|
||||
FROM licenses
|
||||
@ -2231,7 +2553,7 @@ func (q *sqlQuerier) InsertDeploymentID(ctx context.Context, value string) error
|
||||
|
||||
const getTemplateByID = `-- name: GetTemplateByID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon
|
||||
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
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
@ -2257,13 +2579,15 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon
|
||||
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
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
@ -2297,12 +2621,14 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTemplates = `-- name: GetTemplates :many
|
||||
SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon FROM templates
|
||||
SELECT 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 FROM templates
|
||||
ORDER BY (name, id) ASC
|
||||
`
|
||||
|
||||
@ -2329,6 +2655,8 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -2345,7 +2673,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) {
|
||||
|
||||
const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many
|
||||
SELECT
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon
|
||||
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
|
||||
FROM
|
||||
templates
|
||||
WHERE
|
||||
@ -2407,6 +2735,8 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -2438,7 +2768,7 @@ INSERT INTO
|
||||
icon
|
||||
)
|
||||
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
|
||||
($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
|
||||
`
|
||||
|
||||
type InsertTemplateParams struct {
|
||||
@ -2486,6 +2816,8 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -2545,7 +2877,7 @@ SET
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING
|
||||
id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, max_ttl, min_autostart_interval, created_by, icon
|
||||
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 UpdateTemplateMetaByIDParams struct {
|
||||
@ -2583,6 +2915,8 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl
|
||||
&i.MinAutostartInterval,
|
||||
&i.CreatedBy,
|
||||
&i.Icon,
|
||||
&i.userACL,
|
||||
&i.groupACL,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -3071,16 +3405,36 @@ SELECT
|
||||
-- status is used to enforce 'suspended' users, as all roles are ignored
|
||||
-- when suspended.
|
||||
id, username, status,
|
||||
-- All user roles, including their org roles.
|
||||
array_cat(
|
||||
-- All users are members
|
||||
array_append(users.rbac_roles, 'member'),
|
||||
-- All org_members get the org-member role for their orgs
|
||||
array_append(organization_members.roles, 'organization-member:'||organization_members.organization_id::text)) :: text[]
|
||||
AS roles
|
||||
array_append(users.rbac_roles, 'member'),
|
||||
(
|
||||
SELECT
|
||||
array_agg(org_roles)
|
||||
FROM
|
||||
organization_members,
|
||||
-- All org_members get the org-member role for their orgs
|
||||
unnest(
|
||||
array_append(roles, 'organization-member:' || organization_members.organization_id::text)
|
||||
) AS org_roles
|
||||
WHERE
|
||||
user_id = users.id
|
||||
)
|
||||
) :: text[] AS roles,
|
||||
-- All groups the user is in.
|
||||
(
|
||||
SELECT
|
||||
array_agg(
|
||||
group_members.group_id :: text
|
||||
)
|
||||
FROM
|
||||
group_members
|
||||
WHERE
|
||||
user_id = users.id
|
||||
) :: text[] AS groups
|
||||
FROM
|
||||
users
|
||||
LEFT JOIN organization_members
|
||||
ON id = user_id
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
@ -3090,6 +3444,7 @@ type GetAuthorizationUserRolesRow struct {
|
||||
Username string `db:"username" json:"username"`
|
||||
Status UserStatus `db:"status" json:"status"`
|
||||
Roles []string `db:"roles" json:"roles"`
|
||||
Groups []string `db:"groups" json:"groups"`
|
||||
}
|
||||
|
||||
// This function returns roles for authorization purposes. Implied member roles
|
||||
@ -3102,6 +3457,7 @@ func (q *sqlQuerier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.
|
||||
&i.Username,
|
||||
&i.Status,
|
||||
pq.Array(&i.Roles),
|
||||
pq.Array(&i.Groups),
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -3135,7 +3491,7 @@ func (q *sqlQuerier) GetUserByEmailOrUsername(ctx context.Context, arg GetUserBy
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
@ -3166,7 +3522,7 @@ func (q *sqlQuerier) GetUserByID(ctx context.Context, id uuid.UUID) (User, error
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
@ -3285,7 +3641,7 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]User,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
@ -3328,7 +3684,7 @@ func (q *sqlQuerier) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]User
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
@ -3364,14 +3720,14 @@ VALUES
|
||||
`
|
||||
|
||||
type InsertUserParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Email string `db:"email" json:"email"`
|
||||
Username string `db:"username" json:"username"`
|
||||
HashedPassword []byte `db:"hashed_password" json:"hashed_password"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
RBACRoles []string `db:"rbac_roles" json:"rbac_roles"`
|
||||
LoginType LoginType `db:"login_type" json:"login_type"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Email string `db:"email" json:"email"`
|
||||
Username string `db:"username" json:"username"`
|
||||
HashedPassword []byte `db:"hashed_password" json:"hashed_password"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
RBACRoles pq.StringArray `db:"rbac_roles" json:"rbac_roles"`
|
||||
LoginType LoginType `db:"login_type" json:"login_type"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User, error) {
|
||||
@ -3382,7 +3738,7 @@ func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User
|
||||
arg.HashedPassword,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
pq.Array(arg.RBACRoles),
|
||||
arg.RBACRoles,
|
||||
arg.LoginType,
|
||||
)
|
||||
var i User
|
||||
@ -3394,7 +3750,7 @@ func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
@ -3468,7 +3824,7 @@ func (q *sqlQuerier) UpdateUserLastSeenAt(ctx context.Context, arg UpdateUserLas
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
@ -3514,7 +3870,7 @@ func (q *sqlQuerier) UpdateUserProfile(ctx context.Context, arg UpdateUserProfil
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
@ -3550,7 +3906,7 @@ func (q *sqlQuerier) UpdateUserRoles(ctx context.Context, arg UpdateUserRolesPar
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
@ -3586,7 +3942,7 @@ func (q *sqlQuerier) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusP
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Status,
|
||||
pq.Array(&i.RBACRoles),
|
||||
&i.RBACRoles,
|
||||
&i.LoginType,
|
||||
&i.AvatarURL,
|
||||
&i.Deleted,
|
||||
|
122
coderd/database/queries/groups.sql
Normal file
122
coderd/database/queries/groups.sql
Normal file
@ -0,0 +1,122 @@
|
||||
-- name: GetGroupByID :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
groups
|
||||
WHERE
|
||||
id = $1
|
||||
LIMIT
|
||||
1;
|
||||
|
||||
-- name: GetGroupByOrgAndName :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
groups
|
||||
WHERE
|
||||
organization_id = $1
|
||||
AND
|
||||
name = $2
|
||||
LIMIT
|
||||
1;
|
||||
|
||||
-- name: GetUserGroups :many
|
||||
SELECT
|
||||
groups.*
|
||||
FROM
|
||||
groups
|
||||
JOIN
|
||||
group_members
|
||||
ON
|
||||
groups.id = group_members.group_id
|
||||
WHERE
|
||||
group_members.user_id = $1;
|
||||
|
||||
-- name: GetGroupMembers :many
|
||||
SELECT
|
||||
users.*
|
||||
FROM
|
||||
users
|
||||
JOIN
|
||||
group_members
|
||||
ON
|
||||
users.id = group_members.user_id
|
||||
WHERE
|
||||
group_members.group_id = $1
|
||||
AND
|
||||
users.status = 'active'
|
||||
AND
|
||||
users.deleted = 'false';
|
||||
|
||||
-- name: GetAllOrganizationMembers :many
|
||||
SELECT
|
||||
users.*
|
||||
FROM
|
||||
users
|
||||
JOIN
|
||||
organization_members
|
||||
ON
|
||||
users.id = organization_members.user_id
|
||||
WHERE
|
||||
organization_members.organization_id = $1;
|
||||
|
||||
-- name: GetGroupsByOrganizationID :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
groups
|
||||
WHERE
|
||||
organization_id = $1
|
||||
AND
|
||||
id != $1;
|
||||
|
||||
-- name: InsertGroup :one
|
||||
INSERT INTO groups (
|
||||
id,
|
||||
name,
|
||||
organization_id
|
||||
)
|
||||
VALUES
|
||||
( $1, $2, $3) RETURNING *;
|
||||
|
||||
-- We use the organization_id as the id
|
||||
-- for simplicity since all users is
|
||||
-- every member of the org.
|
||||
-- name: InsertAllUsersGroup :one
|
||||
INSERT INTO groups (
|
||||
id,
|
||||
name,
|
||||
organization_id
|
||||
)
|
||||
VALUES
|
||||
( sqlc.arg(organization_id), 'Everyone', sqlc.arg(organization_id)) RETURNING *;
|
||||
|
||||
-- name: UpdateGroupByID :one
|
||||
UPDATE
|
||||
groups
|
||||
SET
|
||||
name = $1
|
||||
WHERE
|
||||
id = $2
|
||||
RETURNING *;
|
||||
|
||||
-- name: InsertGroupMember :exec
|
||||
INSERT INTO group_members (
|
||||
user_id,
|
||||
group_id
|
||||
)
|
||||
VALUES ( $1, $2);
|
||||
|
||||
-- name: DeleteGroupMember :exec
|
||||
DELETE FROM
|
||||
group_members
|
||||
WHERE
|
||||
user_id = $1;
|
||||
|
||||
-- name: DeleteGroupByID :exec
|
||||
DELETE FROM
|
||||
groups
|
||||
WHERE
|
||||
id = $1;
|
||||
|
||||
|
@ -178,15 +178,35 @@ SELECT
|
||||
-- status is used to enforce 'suspended' users, as all roles are ignored
|
||||
-- when suspended.
|
||||
id, username, status,
|
||||
-- All user roles, including their org roles.
|
||||
array_cat(
|
||||
-- All users are members
|
||||
array_append(users.rbac_roles, 'member'),
|
||||
-- All org_members get the org-member role for their orgs
|
||||
array_append(organization_members.roles, 'organization-member:'||organization_members.organization_id::text)) :: text[]
|
||||
AS roles
|
||||
array_append(users.rbac_roles, 'member'),
|
||||
(
|
||||
SELECT
|
||||
array_agg(org_roles)
|
||||
FROM
|
||||
organization_members,
|
||||
-- All org_members get the org-member role for their orgs
|
||||
unnest(
|
||||
array_append(roles, 'organization-member:' || organization_members.organization_id::text)
|
||||
) AS org_roles
|
||||
WHERE
|
||||
user_id = users.id
|
||||
)
|
||||
) :: text[] AS roles,
|
||||
-- All groups the user is in.
|
||||
(
|
||||
SELECT
|
||||
array_agg(
|
||||
group_members.group_id :: text
|
||||
)
|
||||
FROM
|
||||
group_members
|
||||
WHERE
|
||||
user_id = users.id
|
||||
) :: text[] AS groups
|
||||
FROM
|
||||
users
|
||||
LEFT JOIN organization_members
|
||||
ON id = user_id
|
||||
WHERE
|
||||
id = @user_id;
|
||||
|
@ -16,6 +16,10 @@ packages:
|
||||
# deleted after generation.
|
||||
output_db_file_name: db_tmp.go
|
||||
|
||||
overrides:
|
||||
- column: "users.rbac_roles"
|
||||
go_type: "github.com/lib/pq.StringArray"
|
||||
|
||||
rename:
|
||||
api_key: APIKey
|
||||
api_key_scope: APIKeyScope
|
||||
@ -35,3 +39,5 @@ rename:
|
||||
ip_addresses: IPAddresses
|
||||
ids: IDs
|
||||
jwt: JWT
|
||||
user_acl: userACL
|
||||
group_acl: groupACL
|
||||
|
@ -6,6 +6,8 @@ type UniqueConstraint string
|
||||
|
||||
// UniqueConstraint enums.
|
||||
const (
|
||||
UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id);
|
||||
UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id);
|
||||
UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt);
|
||||
UniqueParameterSchemasJobIDNameKey UniqueConstraint = "parameter_schemas_job_id_name_key" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_name_key UNIQUE (job_id, name);
|
||||
UniqueParameterValuesScopeIDNameKey UniqueConstraint = "parameter_values_scope_id_name_key" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name);
|
||||
|
Reference in New Issue
Block a user