Merge remote-tracking branch 'origin/datatable-roles-redesign-part-2' into datatable-roles-redesign-part-4

# Conflicts:
#	backend/ee-repo-ref.txt
#	backend/windmill-api-workspaces/src/datatable_permissions_oss.rs
This commit is contained in:
Diego Imbert
2026-09-17 09:50:52 +02:00
4 changed files with 47 additions and 14 deletions
+1 -1
View File
@@ -1 +1 @@
4759cf46829760038a8a07ec7c6a8682fc9a81ef
c2e43f5b5ff753d339b70e232fd17b6ffecb054d
@@ -36,13 +36,10 @@ use windmill_common::datatable_roles::{
ADMIN_DATATABLE_ROLE, CUSTOM_INSTANCE_USER,
};
use windmill_common::error::{pg_error_message, Error, JsonResult, Result};
use windmill_common::workspaces::{
get_datatable_resource_from_db_unchecked, resolve_governing_datatable, DataTable,
GoverningDatatable,
};
use windmill_common::workspaces::{resolve_governing_datatable, DataTable, GoverningDatatable};
use windmill_common::{PgDatabase, DB};
use crate::datatable_permissions::{ensure_governs_datatable, ensure_reaches_datatable};
use crate::datatable_permissions::{ensure_governs_datatable, ensure_reaches_governing_datatable};
pub(crate) fn routes() -> Router {
Router::new()
@@ -323,11 +320,20 @@ async fn connect_as_admin_unchecked(
mpsc::UnboundedReceiver<DbError>,
String,
)> {
let resource =
get_datatable_resource_from_db_unchecked(db, &governing.workspace_id, &governing.name)
.await?;
let pg: PgDatabase = serde_json::from_value(resource)
.map_err(|e| Error::internal_err(format!("Failed to parse database credentials: {e}")))?;
ensure_instance(governing)?;
// Built from the authorized entry, never by resolving the settings again: a save in between
// could point the entry at a resource on another server and back, and this connection would
// then alter a database the later checks of the entry never see.
let mut pg = PgDatabase::parse_uri(&windmill_common::get_database_url().await?.as_str().await)?;
pg.dbname = governing
.datatable
.database
.as_ref()
.expect("a governing entry owns a database")
.resource_path
.clone();
pg.user = Some(CUSTOM_INSTANCE_USER.to_string());
pg.password = Some(windmill_common::utils::get_custom_pg_instance_password(db).await?);
let dbname = pg.dbname.clone();
let (client, mut connection) = pg.connect(Some(db)).await?;
// Unbounded: the driver must never wait on the receiver, which only drains once the statement
@@ -1012,8 +1018,8 @@ async fn get_datatable_acl(
) -> JsonResult<DatatableAclInfo> {
crate::datatable_acl_oss::ensure_datatable_acl_available()?;
let target: AclTarget = query.try_into()?;
ensure_reaches_datatable(&db, &w_id, &datatable_name, &authed).await?;
let governing = resolve_governing_datatable(&db, &w_id, &datatable_name).await?;
ensure_reaches_governing_datatable(&db, &w_id, &datatable_name, &governing, &authed).await?;
ensure_instance(&governing)?;
let editable = ensure_governs_datatable(&db, &authed, &w_id, &governing)
.await
@@ -65,3 +65,15 @@ pub(crate) async fn ensure_reaches_datatable(
) -> Result<()> {
roles::ensure_reaches_datatable(db, w_id, datatable_name, authed).await
}
/// [`ensure_reaches_datatable`] against an entry already resolved, for a caller that goes on to
/// connect from that same entry.
pub(crate) async fn ensure_reaches_governing_datatable(
db: &DB,
w_id: &str,
datatable_name: &str,
governing: &GoverningDatatable,
authed: &ApiAuthed,
) -> Result<()> {
roles::ensure_reaches_governing_datatable(db, w_id, datatable_name, governing, authed).await
}
@@ -12,8 +12,9 @@
#[cfg(all(feature = "private", feature = "enterprise"))]
pub(crate) use crate::datatable_permissions_ee::{
ensure_governs_datatable, ensure_reaches_datatable, get_datatable_permissions,
list_usable_datatable_roles, set_datatable_permissions, usable_datatable_roles,
ensure_governs_datatable, ensure_reaches_datatable, ensure_reaches_governing_datatable,
get_datatable_permissions, list_usable_datatable_roles, set_datatable_permissions,
usable_datatable_roles,
};
#[cfg(not(all(feature = "private", feature = "enterprise")))]
@@ -56,6 +57,20 @@ mod ce {
}
}
pub(crate) async fn ensure_reaches_governing_datatable(
_db: &DB,
_w_id: &str,
_datatable_name: &str,
governing: &GoverningDatatable,
_authed: &ApiAuthed,
) -> Result<()> {
if governing.datatable.permissions.is_none() {
Ok(())
} else {
Err(unavailable())
}
}
// The routes stay registered so the API has one shape; each answers after authentication,
// before anything is read.