mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-16 00:03:02 +00:00
* feat: connect to a data table with every role of the job's viewer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: key a cached result by the run's recorded viewer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: pin the per-viewer cache key and fix a stale doc sentence Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: length-prefix the viewer in the result cache key Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: drop data table role set logins unused for 30 days Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: pin the ee ref with the set login sweep fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: pin the ee ref with the sweep retry fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: give the cache key test its own migrated database Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: add the viewer's roles to a data table connection that names none The default role of a data table under roles is now a floor: a connection naming no role also holds the roles its viewer is a tenant of, unless the default is admin. This replaces the explicit as=viewer option, which is removed from the reference parser, the executors and both clients. The roles drawer warns while the default is admin and confirms before removing the default role. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: let a data table have no default role A connection naming no role then holds its viewer's roles only and is refused without any. The roles drawer offers the choice, states what an unnamed connection gets for the current settings, says who gets admin while it is the default, and notes who owns a table created over several roles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: start a data table's roles with admin open to everyone Turning roles on from the drawer seeded admin with no tenant, which refused every non-admin member's job as soon as it was saved. It now starts open to everyone, so the toggle changes nothing until the roles are edited, and the drawer warns for as long as everyone can connect as admin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: update ee-repo-ref to 40ab35596e6fcb82a4dfc56e186befb807f8e2af This commit updates the EE repository reference after PR #873 was merged in windmill-ee-private. Previous ee-repo-ref: a5def184a29fffab8ed42c70c5a7445b2b3cbe98 New ee-repo-ref: 40ab35596e6fcb82a4dfc56e186befb807f8e2af Automated by sync-ee-ref workflow. --------- 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>
251 lines
7.7 KiB
Rust
251 lines
7.7 KiB
Rust
/*
|
|
* Author: Ruben Fiszel
|
|
* Copyright: Windmill Labs, Inc 2022
|
|
* This file and its contents are licensed under the AGPLv3 License.
|
|
* Please see the included NOTICE for copyright information and
|
|
* LICENSE-AGPL for a copy of the license.
|
|
*/
|
|
|
|
//! Where data table roles come from: the enterprise implementation, or a refusal.
|
|
//!
|
|
//! Roles are an Enterprise Edition feature. An edition without them creates, grants and connects
|
|
//! as none, and a data table saved under roles — by an enterprise build, before a downgrade — is
|
|
//! refused rather than resolved as `admin`. A data table not under roles, asked for no role,
|
|
//! resolves as it always has. `private` alone is not that edition: community builds carry it.
|
|
|
|
use crate::error::Error;
|
|
|
|
/// What every roles path answers without the Enterprise Edition. The frontend matches this exact
|
|
/// sentence (`datatableUsableRoles.ts`) to read the refusal as "not under roles": reword both.
|
|
pub fn datatable_roles_unavailable() -> Error {
|
|
Error::BadRequest("Data table roles are a Windmill Enterprise Edition feature".to_string())
|
|
}
|
|
|
|
#[cfg(all(feature = "private", feature = "enterprise"))]
|
|
pub(crate) use crate::datatable_roles_ee::{
|
|
can_use_datatable_role, can_use_datatable_role_in_governing_workspace, converge_connect_grants,
|
|
converge_connect_grants_with, create_datatable_role, delete_role_catalog_entry,
|
|
drop_datatable_role, ensure_can_use_datatable_role, ensure_datatable_admin_access,
|
|
ensure_instance_db_grant_options_unchecked, forget_datatable_role_everywhere,
|
|
insert_role_catalog_entry, read_role_catalog, read_role_catalog_tx,
|
|
registered_instance_databases, rename_datatable_role, resolve_datatable_role_connection,
|
|
role_cluster, set_datatable_role_login, sweep_unused_role_set_logins,
|
|
update_role_catalog_entry,
|
|
};
|
|
|
|
#[cfg(not(all(feature = "private", feature = "enterprise")))]
|
|
pub(crate) use ce::*;
|
|
|
|
#[cfg(not(all(feature = "private", feature = "enterprise")))]
|
|
mod ce {
|
|
use super::datatable_roles_unavailable as unavailable;
|
|
use crate::{
|
|
datatable_roles::{DatatableRoleCatalog, DatatableRoleCluster, InstanceDatatableRole},
|
|
db::AuthedRef,
|
|
error::Result,
|
|
workspaces::{
|
|
resolve_governing_datatable, DataTableRoleTenants, DatatableAccess, GoverningDatatable,
|
|
},
|
|
DB,
|
|
};
|
|
|
|
type Tx<'a> = sqlx::Transaction<'a, sqlx::Postgres>;
|
|
|
|
pub(crate) async fn read_role_catalog(
|
|
_db: &DB,
|
|
_cluster: DatatableRoleCluster,
|
|
) -> Result<DatatableRoleCatalog> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn read_role_catalog_tx(
|
|
_tx: &mut Tx<'_>,
|
|
_cluster: DatatableRoleCluster,
|
|
) -> Result<DatatableRoleCatalog> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn role_cluster(
|
|
_tx: &mut Tx<'_>,
|
|
_id: &str,
|
|
) -> Result<Option<DatatableRoleCluster>> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn insert_role_catalog_entry(
|
|
_tx: &mut Tx<'_>,
|
|
_id: &str,
|
|
_cluster: DatatableRoleCluster,
|
|
_role: &InstanceDatatableRole,
|
|
) -> Result<()> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn update_role_catalog_entry(
|
|
_tx: &mut Tx<'_>,
|
|
_id: &str,
|
|
_role: &InstanceDatatableRole,
|
|
) -> Result<()> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn delete_role_catalog_entry(_tx: &mut Tx<'_>, _id: &str) -> Result<()> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn registered_instance_databases(
|
|
_db: &DB,
|
|
_cluster: DatatableRoleCluster,
|
|
) -> Result<Vec<String>> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
/// Nothing to converge: with no roles to admit, a managed database keeps the `CONNECT` grants
|
|
/// it was created with, as it did before roles existed.
|
|
pub(crate) async fn converge_connect_grants(
|
|
_db: &DB,
|
|
_cluster: DatatableRoleCluster,
|
|
_dbname: &str,
|
|
) -> Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
/// As [`converge_connect_grants`].
|
|
pub(crate) async fn converge_connect_grants_with(
|
|
_db: &DB,
|
|
_cluster: DatatableRoleCluster,
|
|
_dbname: &str,
|
|
_catalog: &DatatableRoleCatalog,
|
|
) -> Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) async fn create_datatable_role(
|
|
_db: &DB,
|
|
_tx: &mut Tx<'_>,
|
|
_cluster: DatatableRoleCluster,
|
|
_name: &str,
|
|
_password: &str,
|
|
_take_over: bool,
|
|
) -> Result<bool> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn set_datatable_role_login(
|
|
_db: &DB,
|
|
_tx: &mut Tx<'_>,
|
|
_cluster: DatatableRoleCluster,
|
|
_name: &str,
|
|
_enabled: bool,
|
|
) -> Result<()> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn rename_datatable_role(
|
|
_db: &DB,
|
|
_tx: &mut Tx<'_>,
|
|
_cluster: DatatableRoleCluster,
|
|
_from: &str,
|
|
_to: &str,
|
|
_password: &str,
|
|
) -> Result<()> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn drop_datatable_role(
|
|
_db: &DB,
|
|
_tx: &mut Tx<'_>,
|
|
_cluster: DatatableRoleCluster,
|
|
_name: &str,
|
|
) -> Result<()> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
pub(crate) async fn ensure_instance_db_grant_options_unchecked(
|
|
_db: &DB,
|
|
_cluster: DatatableRoleCluster,
|
|
_dbname: &str,
|
|
) -> Result<()> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
/// No tenant list covers anyone: there is no role to connect as.
|
|
pub(crate) fn can_use_datatable_role(
|
|
_tenants: &DataTableRoleTenants,
|
|
_authed: &AuthedRef<'_>,
|
|
) -> bool {
|
|
false
|
|
}
|
|
|
|
pub(crate) async fn can_use_datatable_role_in_governing_workspace(
|
|
_db: &DB,
|
|
_governing_w_id: &str,
|
|
_w_id: &str,
|
|
_tenants: &DataTableRoleTenants,
|
|
_access: &DatatableAccess<'_>,
|
|
) -> Result<bool> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
/// Reached only for a data table under roles or a caller naming a role: both are refused.
|
|
pub(crate) async fn resolve_datatable_role_connection(
|
|
_db: &DB,
|
|
_w_id: &str,
|
|
_name: &str,
|
|
_governing: &GoverningDatatable,
|
|
_db_resource: serde_json::Value,
|
|
_role: Option<&str>,
|
|
_access: DatatableAccess<'_>,
|
|
) -> Result<serde_json::Value> {
|
|
Err(unavailable())
|
|
}
|
|
|
|
/// A data table not under roles, asked for no role or for `admin`, is not a role decision and
|
|
/// passes, as it did before roles existed. Anything else is refused.
|
|
pub(crate) async fn ensure_can_use_datatable_role(
|
|
db: &DB,
|
|
w_id: &str,
|
|
name: &str,
|
|
role: Option<&str>,
|
|
_access: &DatatableAccess<'_>,
|
|
_context: &str,
|
|
) -> Result<()> {
|
|
let governing = resolve_governing_datatable(db, w_id, name).await?;
|
|
if governing.datatable.permissions.is_none()
|
|
&& role.is_none_or(|r| r == crate::datatable_roles::ADMIN_DATATABLE_ROLE)
|
|
{
|
|
Ok(())
|
|
} else {
|
|
Err(unavailable())
|
|
}
|
|
}
|
|
|
|
/// A data table not under roles is the `admin` connection for anyone who reaches it, as before
|
|
/// roles existed. One under roles is refused.
|
|
pub(crate) async fn ensure_datatable_admin_access(
|
|
db: &DB,
|
|
w_id: &str,
|
|
name: &str,
|
|
_access: &DatatableAccess<'_>,
|
|
) -> Result<()> {
|
|
let governing = resolve_governing_datatable(db, w_id, name).await?;
|
|
if governing.datatable.permissions.is_none() {
|
|
Ok(())
|
|
} else {
|
|
Err(unavailable())
|
|
}
|
|
}
|
|
|
|
/// No roles, so no set login to sweep.
|
|
pub(crate) async fn sweep_unused_role_set_logins(_db: &DB) -> Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) async fn forget_datatable_role_everywhere(
|
|
_tx: &mut Tx<'_>,
|
|
_role_id: &str,
|
|
) -> Result<()> {
|
|
Err(unavailable())
|
|
}
|
|
}
|