mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 00:03:00 +00:00
3.4 KiB
3.4 KiB
Data table connections as the viewer
A data table under roles (Enterprise Edition) normally resolves to one role: the one a caller
names, or the data table's default. ?as=viewer resolves to every role the job's viewer is a
tenant of, so Postgres row-level security can answer per viewer from a backend runnable that
runs as the app's publisher.
wmill.datatable('main', { as: 'viewer' }) // TypeScript
wmill.datatable('main', as_viewer=True) # Python
ATTACH 'datatable://main?as=viewer' AS dt; -- DuckDB
-- PostgreSQL script: `datatable://main?as=viewer` as its `database` argument
Policies test membership, which is true for a role's own login too, so one policy serves both kinds of connection:
CREATE POLICY emea ON orders FOR SELECT
USING (region = 'emea' AND pg_has_role(current_user, 'sales_emea', 'member'));
A policy names its role as text, so renaming the role breaks it.
Code
| Where | What |
|---|---|
windmill-common/src/workspaces.rs |
DATATABLE_VIEWER_ROLE, parse_datatable_ref, DatatableAccess |
windmill-common/src/datatable_roles_ee.rs |
resolve_viewer_login, ensure_role_set_login, retire_role_set_logins_of |
windmill-api-jobs/src/execution.rs |
run_end_user_email, where a run's viewer is recorded |
What holds it together
- The viewer is
job_perms.end_user_email, and nothing else. The server records it when the run starts: the session behind an app request, the caller of a direct run or preview, and for a job started with another job's token, that job's viewer. No argument reaches it. A job without one (anonymous app viewer, schedule, trigger) is refused rather than resolved as its owner, which in a publisher-mode app would be the publisher. - Only a tenant list naming the viewer counts. A workspace admin may use every role,
adminincluded, andadminis the connection that owns the tables and bypasses row-level security. The viewer's roles are those whose tenants name them (user, group, folder,*), and neveradmin. - One role is that role's login. Several are a login per distinct set.
wm_set_<hash>is a member of exactly those roles, withINHERIT; the hash is over their catalog ids, so a rename moves nothing and a change in someone's groups resolves them to another login instead of editing one. Its privileges are its memberships, soSET ROLEonly narrows andRESET ROLEcomes back to it. The prefix is reserved invalidate_role_name. - A
datatable_role_setrow always names a login that can authenticate. The row is written after the login exists and removed, in its own commit, before the login is dropped or madeNOLOGIN. A login without a row is taken back with a new password on next use. - Disabling or dropping a role retires every set login holding it.
NOLOGINon a role does not reach a login that is a member of it. - It travels in the reference, not in an annotation. A worker that predates
?as=refuses the reference as malformed. It would skip an unknown--annotation and connect as the default role, which isadminunless changed. For the same reason the request reaches an agent worker's server in theroleparameter, as a name no role can have.
Trust
A runnable connecting as the viewer runs its author's code with the viewer's reach into the data table, the same trust as an app in viewer execution mode or a script someone else wrote and you run.