mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-23 08:03:33 +00:00
* feat: configurable workspace roles with per-workspace overrides Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: close role review gaps in pickers, declarative sync and removal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: keep resolve_role_name doc on its function Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: validate overrides on instance role changes and bill admins fully Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: allow pipeline drafts and keep role checks on one connection Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: list data pipelines in operator_can_draft Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: expect a 403 role refusal when an operator resolves jobs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: require a valid license for custom workspace roles Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: freeze workspace roles when the license lapses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: pin the ee companion for workspace roles Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: broadcast role override changes from the write path, not a trigger Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: update ee-repo-ref to b5294d38db082bdd104d5358a47695390694c05d This commit updates the EE repository reference after PR #891 was merged in windmill-ee-private. Previous ee-repo-ref: 688e00c5982865b14098f530d54280e2311994c0 New ee-repo-ref: b5294d38db082bdd104d5358a47695390694c05d Automated by sync-ee-ref workflow. * fix: let an admin row that kept the operator flag hold full rights everywhere Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: treat superadmin members as admins and return role names from get user Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
67 lines
4.1 KiB
Markdown
67 lines
4.1 KiB
Markdown
# Workspace roles
|
|
|
|
What a non-admin member may author, manage and see is its **role**. `developer` and `operator` are
|
|
the default roles; a licensed EE instance defines its own list in the `workspace_roles` global
|
|
setting (at least one role), and any workspace may override a role's capabilities in
|
|
`workspace_settings.role_overrides`, or, when licensed, add roles of its own there. A lapsed
|
|
license freezes them rather than dropping them (`custom_roles_enabled`): the stored list and the
|
|
workspace-only roles keep applying, writes that change them are refused, and resetting the list to
|
|
the defaults or removing a workspace-only role stays open. Falling back to the defaults instead
|
|
would hand a restricted `developer` everything. Workspace admin is not a
|
|
role: it is the `is_admin` flag on top of whichever role a member holds, and holds everything. The
|
|
code is `windmill_common::roles`.
|
|
|
|
## Capabilities
|
|
|
|
| Capability | Values | Enforced |
|
|
|---|---|---|
|
|
| `code` | bool | yes: scripts, previews, dependency jobs, dynamic selects, resolving jobs, data table migrations, eval datasets |
|
|
| `flows` | `none` / `compose` / `full` | yes |
|
|
| `apps` | `none` / `compose` / `full` | yes; low-code apps and worker-built apps need `full` |
|
|
| `manage_schedules`, `manage_triggers` | bool | yes, see `docs/operator-write-rights.md` |
|
|
| `visibility.*` | bool per page | no, UI only |
|
|
|
|
`compose` is the former operator builder right: the composition-only checks of
|
|
`docs/operator-builder-rights.md` run for it and are skipped only at `full`. `full` flows or apps
|
|
without `code` is refused at write, since both carry inline code.
|
|
|
|
Stored values are **patches**: an unset key falls through to the layer below, so a key added later
|
|
reads as "never configured". The layers, top first: the workspace override, the instance
|
|
definition, then `RoleCapabilities::base_for` (`FULL` for `developer`, the legacy operator for any
|
|
other name). A role name no longer defined resolves to `NONE`, so deleting a role fails closed;
|
|
deleting one that a member, invite, auto-invite or instance group mapping still names is refused,
|
|
and so is deleting an instance role that a workspace still overrides (the override would fall onto
|
|
`base_for` instead of the definition it was written against).
|
|
|
|
## Who is restricted
|
|
|
|
`ApiAuthed::is_operator` means "this member's role is restricted", computed at authentication from
|
|
the role, never from `usr.operator` alone. Every gate fast-paths on it (`caller_capabilities`
|
|
returns `FULL` for an unrestricted or admin caller without reading the role), so it must stay
|
|
derived from the role: a stale flag lets a restricted member through. The authentication cache
|
|
holds it for up to 120s.
|
|
|
|
`usr.operator` is kept as "this role cannot write code", which is what clients that predate roles
|
|
read it as. A member's role is `usr.workspace_role`, or, while that is NULL, `operator` /
|
|
`developer` from `usr.operator`: rows written by paths that know nothing of roles stay meaningful.
|
|
The old `usr.role` column is unrelated and holds legacy display strings.
|
|
|
|
## Seats
|
|
|
|
A role that writes or composes anything costs a full seat; anything else half. Billing SQL cannot
|
|
merge capability patches, so `role_seat` materializes the author flag per role
|
|
(`workspace_id = ''` for the instance, a row per workspace override), rewritten by
|
|
`sync_role_seats` after every write to either setting. Every count reads it through
|
|
`AUTHOR_SEAT_SQL`, and usage telemetry reports builders (authors anywhere) against non-builders the
|
|
same way. A change that adds seats under an offline license is priced inside the transaction that
|
|
writes it, by `check_seat_cap_for_role_change`.
|
|
|
|
## Legacy `operator_settings`
|
|
|
|
`/operator_settings` still works: it is applied as an override of the `operator` role, dropping any
|
|
key equal to what the instance gives that role, and the payload is mirrored into the
|
|
`operator_settings` column for git sync and older servers. The migration moved every key a
|
|
workspace had changed from its default into `role_overrides.operator` the same way. The workspace
|
|
list still returns an `operator_settings` object for a restricted member, synthesized from its
|
|
role, alongside `role` and `capabilities`.
|