feat: configurable default org member roles (#25994)

Refs #25936. 
Adds a configurable per-org default member role set. Unioned into each member's effective roles at read time.

<sub>with Coder Agents on behalf of @Emyrk.</sub>
This commit is contained in:
Steven Masley
2026-06-05 14:33:13 -05:00
committed by GitHub
parent 2ef468ef76
commit 938c2080f3
40 changed files with 635 additions and 174 deletions
+5 -4
View File
@@ -902,10 +902,11 @@ func Organization(organization database.Organization) codersdk.Organization {
DisplayName: organization.DisplayName,
Icon: organization.Icon,
},
Description: organization.Description,
CreatedAt: organization.CreatedAt,
UpdatedAt: organization.UpdatedAt,
IsDefault: organization.IsDefault,
Description: organization.Description,
CreatedAt: organization.CreatedAt,
UpdatedAt: organization.UpdatedAt,
IsDefault: organization.IsDefault,
DefaultOrgMemberRoles: organization.DefaultOrgMemberRoles,
}
}
+63 -3
View File
@@ -1602,6 +1602,19 @@ func (q *querier) authorizeProvisionerJob(ctx context.Context, job database.Prov
return nil
}
// scopedOrgRoleIdentifiers wraps each role name as a RoleIdentifier scoped
// to orgID. Used to feed rbac.ChangeRoleSet from a stored []string.
func scopedOrgRoleIdentifiers(names []string, orgID uuid.UUID) []rbac.RoleIdentifier {
if len(names) == 0 {
return nil
}
out := make([]rbac.RoleIdentifier, len(names))
for i, name := range names {
out[i] = rbac.RoleIdentifier{Name: name, OrganizationID: orgID}
}
return out
}
func (q *querier) AcquireChats(ctx context.Context, arg database.AcquireChatsParams) ([]database.Chat, error) {
// AcquireChats is a system-level operation used by the chat processor.
// Authorization is done at the system level, not per-user.
@@ -5779,9 +5792,23 @@ func (q *querier) InsertOrganizationMember(ctx context.Context, arg database.Ins
return database.OrganizationMember{}, xerrors.Errorf("converting to organization roles: %w", err)
}
// The org's default_org_member_roles are implied at request time by
// GetAuthorizationUserRoles. Include them in canAssignRoles so the
// caller is required to be authorized to grant the full effective set
// (the explicit roles, organization-member, plus the defaults).
org, err := q.db.GetOrganizationByID(ctx, arg.OrganizationID)
if err != nil {
return database.OrganizationMember{}, xerrors.Errorf("get organization: %w", err)
}
defaultRoles, err := q.convertToOrganizationRoles(arg.OrganizationID, org.DefaultOrgMemberRoles)
if err != nil {
return database.OrganizationMember{}, xerrors.Errorf("convert default member roles: %w", err)
}
// All roles are added roles. Org member is always implied.
//nolint:gocritic
addedRoles := append(orgRoles, rbac.ScopedRoleOrgMember(arg.OrganizationID))
addedRoles = append(addedRoles, defaultRoles...)
err = q.canAssignRoles(ctx, arg.OrganizationID, addedRoles, []rbac.RoleIdentifier{})
if err != nil {
return database.OrganizationMember{}, err
@@ -7049,9 +7076,23 @@ func (q *querier) UpdateMemberRoles(ctx context.Context, arg database.UpdateMemb
return database.OrganizationMember{}, err
}
// The org's default_org_member_roles are implied at request time by
// GetAuthorizationUserRoles. Include them in the implied set so
// canAssignRoles validates the caller can grant the full effective set
// (the granted roles, organization-member, plus the defaults).
org, err := q.db.GetOrganizationByID(ctx, arg.OrgID)
if err != nil {
return database.OrganizationMember{}, xerrors.Errorf("get organization: %w", err)
}
defaultRoles, err := q.convertToOrganizationRoles(arg.OrgID, org.DefaultOrgMemberRoles)
if err != nil {
return database.OrganizationMember{}, xerrors.Errorf("convert default member roles: %w", err)
}
// The org member role is always implied.
//nolint:gocritic
impliedTypes := append(scopedGranted, rbac.ScopedRoleOrgMember(arg.OrgID))
impliedTypes = append(impliedTypes, defaultRoles...)
added, removed := rbac.ChangeRoleSet(originalRoles, impliedTypes)
err = q.canAssignRoles(ctx, arg.OrgID, added, removed)
@@ -7092,10 +7133,29 @@ func (q *querier) UpdateOAuth2ProviderAppByID(ctx context.Context, arg database.
}
func (q *querier) UpdateOrganization(ctx context.Context, arg database.UpdateOrganizationParams) (database.Organization, error) {
fetch := func(ctx context.Context, arg database.UpdateOrganizationParams) (database.Organization, error) {
return q.db.GetOrganizationByID(ctx, arg.ID)
existing, err := q.db.GetOrganizationByID(ctx, arg.ID)
if err != nil {
return database.Organization{}, err
}
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateOrganization)(ctx, arg)
if err := q.authorizeContext(ctx, policy.ActionUpdate, existing); err != nil {
return database.Organization{}, err
}
// Treat a change to default_org_member_roles as assigning the added
// roles, and unassigning the removed roles, for every member of the
// org. Mirror the InsertOrganizationMember and UpdateMemberRoles
// guard so the caller cannot grant roles they could not grant
// individually, nor inject a malformed role name that would later
// break RoleNameFromString.
if !slices.Equal(existing.DefaultOrgMemberRoles, arg.DefaultOrgMemberRoles) {
added, removed := rbac.ChangeRoleSet(
scopedOrgRoleIdentifiers(existing.DefaultOrgMemberRoles, arg.ID),
scopedOrgRoleIdentifiers(arg.DefaultOrgMemberRoles, arg.ID),
)
if err := q.canAssignRoles(ctx, arg.ID, added, removed); err != nil {
return database.Organization{}, err
}
}
return q.db.UpdateOrganization(ctx, arg)
}
func (q *querier) UpdateOrganizationDeletedByID(ctx context.Context, arg database.UpdateOrganizationDeletedByIDParams) error {
+12 -5
View File
@@ -2266,9 +2266,10 @@ func (s *MethodTestSuite) TestOrganization() {
check.Args(arg).Asserts(org, policy.ActionUpdate).Returns(org)
}))
s.Run("InsertOrganizationMember", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
o := testutil.Fake(s.T(), faker, database.Organization{})
o := testutil.Fake(s.T(), faker, database.Organization{DefaultOrgMemberRoles: []string{}})
u := testutil.Fake(s.T(), faker, database.User{})
arg := database.InsertOrganizationMemberParams{OrganizationID: o.ID, UserID: u.ID, Roles: []string{codersdk.RoleOrganizationAdmin}}
dbm.EXPECT().GetOrganizationByID(gomock.Any(), o.ID).Return(o, nil).AnyTimes()
dbm.EXPECT().InsertOrganizationMember(gomock.Any(), arg).Return(database.OrganizationMember{OrganizationID: o.ID, UserID: u.ID, Roles: arg.Roles}, nil).AnyTimes()
check.Args(arg).Asserts(
rbac.ResourceAssignOrgRole.InOrg(o.ID), policy.ActionAssign,
@@ -2305,12 +2306,17 @@ func (s *MethodTestSuite) TestOrganization() {
).WithNotAuthorized("no rows").WithCancelled(sql.ErrNoRows.Error())
}))
s.Run("UpdateOrganization", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
o := testutil.Fake(s.T(), faker, database.Organization{Name: "something-unique"})
arg := database.UpdateOrganizationParams{ID: o.ID, Name: "something-different"}
o := testutil.Fake(s.T(), faker, database.Organization{Name: "something-unique", DefaultOrgMemberRoles: []string{}})
// Change DefaultOrgMemberRoles so canAssignRoles fires alongside the
// ActionUpdate check; mirrors the InsertOrganizationMember pattern.
arg := database.UpdateOrganizationParams{ID: o.ID, Name: "something-different", DefaultOrgMemberRoles: []string{codersdk.RoleOrganizationAdmin}}
dbm.EXPECT().GetOrganizationByID(gomock.Any(), o.ID).Return(o, nil).AnyTimes()
dbm.EXPECT().UpdateOrganization(gomock.Any(), arg).Return(o, nil).AnyTimes()
check.Args(arg).Asserts(o, policy.ActionUpdate)
check.Args(arg).Asserts(
o, policy.ActionUpdate,
rbac.ResourceAssignOrgRole.InOrg(o.ID), policy.ActionAssign,
)
}))
s.Run("UpdateOrganizationDeletedByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
o := testutil.Fake(s.T(), faker, database.Organization{Name: "doomed"})
@@ -2347,13 +2353,14 @@ func (s *MethodTestSuite) TestOrganization() {
check.Args(arg).Asserts(rbac.ResourceOrganizationMember.InOrg(o.ID), policy.ActionRead).Returns(rows)
}))
s.Run("UpdateMemberRoles", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
o := testutil.Fake(s.T(), faker, database.Organization{})
o := testutil.Fake(s.T(), faker, database.Organization{DefaultOrgMemberRoles: []string{}})
u := testutil.Fake(s.T(), faker, database.User{})
mem := testutil.Fake(s.T(), faker, database.OrganizationMember{OrganizationID: o.ID, UserID: u.ID, Roles: []string{codersdk.RoleOrganizationAdmin}})
out := mem
out.Roles = []string{}
dbm.EXPECT().OrganizationMembers(gomock.Any(), database.OrganizationMembersParams{OrganizationID: o.ID, UserID: u.ID, IncludeSystem: false}).Return([]database.OrganizationMembersRow{{OrganizationMember: mem}}, nil).AnyTimes()
dbm.EXPECT().GetOrganizationByID(gomock.Any(), o.ID).Return(o, nil).AnyTimes()
arg := database.UpdateMemberRolesParams{GrantedRoles: []string{}, UserID: u.ID, OrgID: o.ID}
dbm.EXPECT().UpdateMemberRoles(gomock.Any(), arg).Return(out, nil).AnyTimes()
+8 -7
View File
@@ -1034,13 +1034,14 @@ func GitSSHKey(t testing.TB, db database.Store, orig database.GitSSHKey) databas
func Organization(t testing.TB, db database.Store, orig database.Organization) database.Organization {
org, err := db.InsertOrganization(genCtx, database.InsertOrganizationParams{
ID: takeFirst(orig.ID, uuid.New()),
Name: takeFirst(orig.Name, testutil.GetRandomName(t)),
DisplayName: takeFirst(orig.Name, testutil.GetRandomName(t)),
Description: takeFirst(orig.Description, testutil.GetRandomName(t)),
Icon: takeFirst(orig.Icon, ""),
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
ID: takeFirst(orig.ID, uuid.New()),
Name: takeFirst(orig.Name, testutil.GetRandomName(t)),
DisplayName: takeFirst(orig.Name, testutil.GetRandomName(t)),
Description: takeFirst(orig.Description, testutil.GetRandomName(t)),
Icon: takeFirst(orig.Icon, ""),
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
DefaultOrgMemberRoles: takeFirstSlice(orig.DefaultOrgMemberRoles, rbac.DefaultOrgMemberRoles()),
})
require.NoError(t, err, "insert organization")
+4 -1
View File
@@ -2375,11 +2375,14 @@ CREATE TABLE organizations (
display_name text NOT NULL,
icon text DEFAULT ''::text NOT NULL,
deleted boolean DEFAULT false NOT NULL,
shareable_workspace_owners shareable_workspace_owners DEFAULT 'everyone'::shareable_workspace_owners NOT NULL
shareable_workspace_owners shareable_workspace_owners DEFAULT 'everyone'::shareable_workspace_owners NOT NULL,
default_org_member_roles text[] NOT NULL
);
COMMENT ON COLUMN organizations.shareable_workspace_owners IS 'Controls whose workspaces can be shared: none, everyone, or service_accounts.';
COMMENT ON COLUMN organizations.default_org_member_roles IS 'Roles granted to every member of this organization at request time. The set is unioned into each member''s effective roles when GetAuthorizationUserRoles runs, so changes propagate to all members on the next request. Deployments can use this column to revoke capabilities that would otherwise be considered normal organization member permissions.';
CREATE TABLE parameter_schemas (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -0,0 +1 @@
ALTER TABLE organizations DROP COLUMN IF EXISTS default_org_member_roles;
@@ -0,0 +1,16 @@
ALTER TABLE organizations
ADD COLUMN default_org_member_roles text[];
UPDATE organizations
SET default_org_member_roles = ARRAY['organization-workspace-access']::text[];
ALTER TABLE organizations
ALTER COLUMN default_org_member_roles SET NOT NULL;
COMMENT ON COLUMN organizations.default_org_member_roles IS
'Roles granted to every member of this organization at request time. '
'The set is unioned into each member''s effective roles when '
'GetAuthorizationUserRoles runs, so changes propagate to all members '
'on the next request. Deployments can use this column to revoke '
'capabilities that would otherwise be considered normal organization '
'member permissions.';
+2
View File
@@ -5202,6 +5202,8 @@ type Organization struct {
Deleted bool `db:"deleted" json:"deleted"`
// Controls whose workspaces can be shared: none, everyone, or service_accounts.
ShareableWorkspaceOwners ShareableWorkspaceOwners `db:"shareable_workspace_owners" json:"shareable_workspace_owners"`
// Roles granted to every member of this organization at request time. The set is unioned into each member's effective roles when GetAuthorizationUserRoles runs, so changes propagate to all members on the next request. Deployments can use this column to revoke capabilities that would otherwise be considered normal organization member permissions.
DefaultOrgMemberRoles []string `db:"default_org_member_roles" json:"default_org_member_roles"`
}
type OrganizationMember struct {
+56
View File
@@ -3036,6 +3036,62 @@ func TestGetAuthorizationUserRolesImpliedOrgRole(t *testing.T) {
require.NotContains(t, saRoles.Roles, wantMember)
}
// TestGetAuthorizationUserRolesUnionsDefaultOrgMemberRoles verifies the
// resolve-at-read semantics for organizations.default_org_member_roles:
// every member's effective roles include the org's defaults, and changes
// to the column propagate on the next request. The union applies to
// regular users and to service accounts; the SQL array_cats the column
// for both code paths.
func TestGetAuthorizationUserRolesUnionsDefaultOrgMemberRoles(t *testing.T) {
t.Parallel()
db, _ := dbtestutil.NewDB(t)
org := dbgen.Organization(t, db, database.Organization{})
user := dbgen.User(t, db, database.User{})
saUser := dbgen.User(t, db, database.User{IsServiceAccount: true})
dbgen.OrganizationMember(t, db, database.OrganizationMember{
OrganizationID: org.ID,
UserID: user.ID,
})
dbgen.OrganizationMember(t, db, database.OrganizationMember{
OrganizationID: org.ID,
UserID: saUser.ID,
})
ctx := testutil.Context(t, testutil.WaitShort)
// New orgs default to organization-workspace-access; both the regular
// user's and the service account's effective roles must include the
// scoped form.
wantWorkspaceAccess := rbac.RoleOrgWorkspaceAccess() + ":" + org.ID.String()
initial, err := db.GetAuthorizationUserRoles(ctx, user.ID)
require.NoError(t, err)
require.Contains(t, initial.Roles, wantWorkspaceAccess)
initialSA, err := db.GetAuthorizationUserRoles(ctx, saUser.ID)
require.NoError(t, err)
require.Contains(t, initialSA.Roles, wantWorkspaceAccess)
// Shrinking the org default to empty must immediately drop the role
// from both effective sets.
_, err = db.UpdateOrganization(ctx, database.UpdateOrganizationParams{
ID: org.ID,
UpdatedAt: dbtime.Now(),
Name: org.Name,
DisplayName: org.DisplayName,
Description: org.Description,
Icon: org.Icon,
DefaultOrgMemberRoles: []string{},
})
require.NoError(t, err)
shrunk, err := db.GetAuthorizationUserRoles(ctx, user.ID)
require.NoError(t, err)
require.NotContains(t, shrunk.Roles, wantWorkspaceAccess)
shrunkSA, err := db.GetAuthorizationUserRoles(ctx, saUser.ID)
require.NoError(t, err)
require.NotContains(t, shrunkSA.Roles, wantWorkspaceAccess)
}
func TestUpdateOrganizationWorkspaceSharingSettings(t *testing.T) {
t.Parallel()
+54 -34
View File
@@ -18319,7 +18319,7 @@ func (q *sqlQuerier) UpdateMemberRoles(ctx context.Context, arg UpdateMemberRole
const getDefaultOrganization = `-- name: GetDefaultOrganization :one
SELECT
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners, default_org_member_roles
FROM
organizations
WHERE
@@ -18342,13 +18342,14 @@ func (q *sqlQuerier) GetDefaultOrganization(ctx context.Context) (Organization,
&i.Icon,
&i.Deleted,
&i.ShareableWorkspaceOwners,
pq.Array(&i.DefaultOrgMemberRoles),
)
return i, err
}
const getOrganizationByID = `-- name: GetOrganizationByID :one
SELECT
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners, default_org_member_roles
FROM
organizations
WHERE
@@ -18369,13 +18370,14 @@ func (q *sqlQuerier) GetOrganizationByID(ctx context.Context, id uuid.UUID) (Org
&i.Icon,
&i.Deleted,
&i.ShareableWorkspaceOwners,
pq.Array(&i.DefaultOrgMemberRoles),
)
return i, err
}
const getOrganizationByName = `-- name: GetOrganizationByName :one
SELECT
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners, default_org_member_roles
FROM
organizations
WHERE
@@ -18405,6 +18407,7 @@ func (q *sqlQuerier) GetOrganizationByName(ctx context.Context, arg GetOrganizat
&i.Icon,
&i.Deleted,
&i.ShareableWorkspaceOwners,
pq.Array(&i.DefaultOrgMemberRoles),
)
return i, err
}
@@ -18475,7 +18478,7 @@ func (q *sqlQuerier) GetOrganizationResourceCountByID(ctx context.Context, organ
const getOrganizations = `-- name: GetOrganizations :many
SELECT
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners, default_org_member_roles
FROM
organizations
WHERE
@@ -18520,6 +18523,7 @@ func (q *sqlQuerier) GetOrganizations(ctx context.Context, arg GetOrganizationsP
&i.Icon,
&i.Deleted,
&i.ShareableWorkspaceOwners,
pq.Array(&i.DefaultOrgMemberRoles),
); err != nil {
return nil, err
}
@@ -18536,7 +18540,7 @@ func (q *sqlQuerier) GetOrganizations(ctx context.Context, arg GetOrganizationsP
const getOrganizationsByUserID = `-- name: GetOrganizationsByUserID :many
SELECT
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners
id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners, default_org_member_roles
FROM
organizations
WHERE
@@ -18582,6 +18586,7 @@ func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, arg GetOrgani
&i.Icon,
&i.Deleted,
&i.ShareableWorkspaceOwners,
pq.Array(&i.DefaultOrgMemberRoles),
); err != nil {
return nil, err
}
@@ -18598,20 +18603,21 @@ func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, arg GetOrgani
const insertOrganization = `-- name: InsertOrganization :one
INSERT INTO
organizations (id, "name", display_name, description, icon, created_at, updated_at, is_default)
organizations (id, "name", display_name, description, icon, created_at, updated_at, is_default, default_org_member_roles)
VALUES
-- If no organizations exist, and this is the first, make it the default.
($1, $2, $3, $4, $5, $6, $7, (SELECT TRUE FROM organizations LIMIT 1) IS NULL) RETURNING id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners
($1, $2, $3, $4, $5, $6, $7, (SELECT TRUE FROM organizations LIMIT 1) IS NULL, $8) RETURNING id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners, default_org_member_roles
`
type InsertOrganizationParams struct {
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Description string `db:"description" json:"description"`
Icon string `db:"icon" json:"icon"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Description string `db:"description" json:"description"`
Icon string `db:"icon" json:"icon"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
DefaultOrgMemberRoles []string `db:"default_org_member_roles" json:"default_org_member_roles"`
}
func (q *sqlQuerier) InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error) {
@@ -18623,6 +18629,7 @@ func (q *sqlQuerier) InsertOrganization(ctx context.Context, arg InsertOrganizat
arg.Icon,
arg.CreatedAt,
arg.UpdatedAt,
pq.Array(arg.DefaultOrgMemberRoles),
)
var i Organization
err := row.Scan(
@@ -18636,6 +18643,7 @@ func (q *sqlQuerier) InsertOrganization(ctx context.Context, arg InsertOrganizat
&i.Icon,
&i.Deleted,
&i.ShareableWorkspaceOwners,
pq.Array(&i.DefaultOrgMemberRoles),
)
return i, err
}
@@ -18648,19 +18656,21 @@ SET
name = $2,
display_name = $3,
description = $4,
icon = $5
icon = $5,
default_org_member_roles = $6
WHERE
id = $6
RETURNING id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners
id = $7
RETURNING id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners, default_org_member_roles
`
type UpdateOrganizationParams struct {
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Description string `db:"description" json:"description"`
Icon string `db:"icon" json:"icon"`
ID uuid.UUID `db:"id" json:"id"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Description string `db:"description" json:"description"`
Icon string `db:"icon" json:"icon"`
DefaultOrgMemberRoles []string `db:"default_org_member_roles" json:"default_org_member_roles"`
ID uuid.UUID `db:"id" json:"id"`
}
func (q *sqlQuerier) UpdateOrganization(ctx context.Context, arg UpdateOrganizationParams) (Organization, error) {
@@ -18670,6 +18680,7 @@ func (q *sqlQuerier) UpdateOrganization(ctx context.Context, arg UpdateOrganizat
arg.DisplayName,
arg.Description,
arg.Icon,
pq.Array(arg.DefaultOrgMemberRoles),
arg.ID,
)
var i Organization
@@ -18684,6 +18695,7 @@ func (q *sqlQuerier) UpdateOrganization(ctx context.Context, arg UpdateOrganizat
&i.Icon,
&i.Deleted,
&i.ShareableWorkspaceOwners,
pq.Array(&i.DefaultOrgMemberRoles),
)
return i, err
}
@@ -18716,7 +18728,7 @@ SET
updated_at = $2
WHERE
id = $3
RETURNING id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners
RETURNING id, name, description, created_at, updated_at, is_default, display_name, icon, deleted, shareable_workspace_owners, default_org_member_roles
`
type UpdateOrganizationWorkspaceSharingSettingsParams struct {
@@ -18739,6 +18751,7 @@ func (q *sqlQuerier) UpdateOrganizationWorkspaceSharingSettings(ctx context.Cont
&i.Icon,
&i.Deleted,
&i.ShareableWorkspaceOwners,
pq.Array(&i.DefaultOrgMemberRoles),
)
return i, err
}
@@ -27898,21 +27911,28 @@ SELECT
-- Concatenating the organization id scopes the organization roles.
array_agg(org_roles || ':' || organization_members.organization_id::text)
FROM
organization_members,
organization_members
JOIN organizations ON organizations.id = organization_members.organization_id,
-- All org members get an implied role for their orgs. Most members
-- get organization-member, but service accounts will get
-- organization-service-account instead. They're largely the same,
-- but having them be distinct means we can allow configuring
-- service-accounts to have slightly broader permissions–such as
-- service-accounts to have slightly broader permissions, such as
-- for workspace sharing.
--
-- organizations.default_org_member_roles is unioned in so changes
-- to org defaults propagate to every member on the next request.
unnest(
array_append(
roles,
CASE WHEN users.is_service_account THEN
'organization-service-account'
ELSE
'organization-member'
END
array_cat(
array_append(
roles,
CASE WHEN users.is_service_account THEN
'organization-service-account'
ELSE
'organization-member'
END
),
organizations.default_org_member_roles
)
) AS org_roles
WHERE
@@ -27933,7 +27953,7 @@ SELECT
FROM
users
WHERE
id = $1
users.id = $1
`
type GetAuthorizationUserRolesRow struct {
+4 -3
View File
@@ -116,10 +116,10 @@ SELECT
-- name: InsertOrganization :one
INSERT INTO
organizations (id, "name", display_name, description, icon, created_at, updated_at, is_default)
organizations (id, "name", display_name, description, icon, created_at, updated_at, is_default, default_org_member_roles)
VALUES
-- If no organizations exist, and this is the first, make it the default.
(@id, @name, @display_name, @description, @icon, @created_at, @updated_at, (SELECT TRUE FROM organizations LIMIT 1) IS NULL) RETURNING *;
(@id, @name, @display_name, @description, @icon, @created_at, @updated_at, (SELECT TRUE FROM organizations LIMIT 1) IS NULL, @default_org_member_roles) RETURNING *;
-- name: UpdateOrganization :one
UPDATE
@@ -129,7 +129,8 @@ SET
name = @name,
display_name = @display_name,
description = @description,
icon = @icon
icon = @icon,
default_org_member_roles = @default_org_member_roles
WHERE
id = @id
RETURNING *;
+17 -10
View File
@@ -609,21 +609,28 @@ SELECT
-- Concatenating the organization id scopes the organization roles.
array_agg(org_roles || ':' || organization_members.organization_id::text)
FROM
organization_members,
organization_members
JOIN organizations ON organizations.id = organization_members.organization_id,
-- All org members get an implied role for their orgs. Most members
-- get organization-member, but service accounts will get
-- organization-service-account instead. They're largely the same,
-- but having them be distinct means we can allow configuring
-- service-accounts to have slightly broader permissions–such as
-- service-accounts to have slightly broader permissions, such as
-- for workspace sharing.
--
-- organizations.default_org_member_roles is unioned in so changes
-- to org defaults propagate to every member on the next request.
unnest(
array_append(
roles,
CASE WHEN users.is_service_account THEN
'organization-service-account'
ELSE
'organization-member'
END
array_cat(
array_append(
roles,
CASE WHEN users.is_service_account THEN
'organization-service-account'
ELSE
'organization-member'
END
),
organizations.default_org_member_roles
)
) AS org_roles
WHERE
@@ -644,7 +651,7 @@ SELECT
FROM
users
WHERE
id = @user_id;
users.id = @user_id;
-- name: UpdateUserQuietHoursSchedule :one
UPDATE