feat(coderd/rbac): make organization-member a per-org system custom role (#21359)

Migrated the built-in organization-member role to DB storage so it can be customized per org.

Closes https://github.com/coder/internal/issues/1073 (part 1)
This commit is contained in:
George K
2026-01-12 18:19:19 -08:00
committed by GitHub
parent 2b448c7178
commit cc2efe9e1f
46 changed files with 1845 additions and 438 deletions
+29
View File
@@ -12,9 +12,12 @@ import (
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/rolestore"
"github.com/coder/coder/v2/codersdk"
)
@@ -265,6 +268,14 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) {
var organization database.Organization
err = api.Database.InTx(func(tx database.Store) error {
// Serialize creation and reconciliation of the org-member
// system role across coderd instances (e.g. during rolling
// restarts).
err := tx.AcquireLock(ctx, database.LockIDReconcileSystemRoles)
if err != nil {
return xerrors.Errorf("acquire system roles reconciliation lock: %w", err)
}
if req.DisplayName == "" {
req.DisplayName = req.Name
}
@@ -281,6 +292,24 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) {
if err != nil {
return xerrors.Errorf("create organization: %w", err)
}
// Populate the placeholder system role(s) that the DB trigger
// created for us.
//nolint:gocritic // ReconcileOrgMemberRole needs the system:update
// permission that user doesn't have.
sysCtx := dbauthz.AsSystemRestricted(ctx)
_, _, err = rolestore.ReconcileOrgMemberRole(sysCtx, tx, database.CustomRole{
Name: rbac.RoleOrgMember(),
OrganizationID: uuid.NullUUID{
UUID: organizationID,
Valid: true,
},
}, organization.WorkspaceSharingDisabled)
if err != nil {
return xerrors.Errorf("reconcile organization-member role for organization %s: %w",
organizationID, err)
}
_, err = tx.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{
OrganizationID: organization.ID,
UserID: apiKey.UserID,