diff --git a/backend/windmill-api-workspaces/src/workspaces_extra.rs b/backend/windmill-api-workspaces/src/workspaces_extra.rs index ec5e13ebdb..8db19e904c 100644 --- a/backend/windmill-api-workspaces/src/workspaces_extra.rs +++ b/backend/windmill-api-workspaces/src/workspaces_extra.rs @@ -1354,18 +1354,37 @@ pub struct DropForkedDatatableDatabasesRequest { /// was created in. The permissions row stays until the drop succeeds — with its /// logins gone every role is refused meanwhile — and is forgotten by the caller /// once the database is. Returns the key of that database's permissions when -/// roles were dropped. +/// this workspace owns them and nothing stands in the way of forgetting them: +/// a row with no logins to drop included, or the database's next namesake would +/// find it governed by a workspace that is gone. async fn drop_datatable_roles_before_its_database( db: &DB, w_id: &str, dt_name: &str, ) -> Option { - let planned = - crate::datatable_permissions::plan_drop_of_datatable_roles(db, w_id, dt_name).await?; - let key = planned.2.clone(); - crate::datatable_permissions::run_planned_drop_keeping_record(db, w_id, dt_name, planned) + let (_, _, key) = + windmill_common::workspaces::resolve_datatable_database_unchecked(db, w_id, dt_name) + .await + .ok()?; + let record = windmill_common::workspaces::database_permissions_by_key(db, &key) .await - .then_some(key) + .ok() + .flatten()?; + if record.owner_workspace_id.as_deref() != Some(w_id) { + return None; + } + if let Some(planned) = + crate::datatable_permissions::plan_drop_of_datatable_roles(db, w_id, dt_name).await + { + if !crate::datatable_permissions::run_planned_drop_keeping_record( + db, w_id, dt_name, planned, + ) + .await + { + return None; + } + } + Some(key) } /// Drop forked datatable databases. Returns errors per datatable that failed. diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 9d01bbe0c9..a8d6050314 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -1764,8 +1764,12 @@ pub(crate) async fn tarball_workspace( // the database, with roles and tenants — no passwords, which are direct // database logins, and no login names, which a re-save generates. A database // no entry of the workspace reaches any more cannot be named, and is left out. - let permissions = - windmill_common::workspaces::database_permissions_owned_by(&mut *tx, &w_id).await?; + // Admin-only, like the settings and like the drawer that shows the same thing. + let permissions = if include_settings.unwrap_or(false) { + windmill_common::workspaces::database_permissions_owned_by(&mut *tx, &w_id).await? + } else { + vec![] + }; if !permissions.is_empty() { let mut reaching: std::collections::HashMap = std::collections::HashMap::new(); diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index d7af827920..1c4e66ba6e 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -1300,12 +1300,13 @@ pub fn datatable_database_key( use sha2::{Digest, Sha256}; // As the connection reads them, not as the JSON spells them: a port // left out is 5432, a number and its string are one port, a host is - // one host whatever its case. + // one host whatever its case. A database name is taken exactly — `"prod "` + // is a database of its own to Postgres. let text = |field: &str| { resolved .get(field) .map(|v| match v.as_str() { - Some(s) => s.trim().to_string(), + Some(s) => s.to_string(), None => v.to_string(), }) .unwrap_or_default() @@ -3476,7 +3477,11 @@ mod tests { // A host is one host whatever its case, as DNS reads it. assert_eq!( datatable_database_key(&pg("u/a/pg"), &resolved("db.example", "prod", "app")), - datatable_database_key(&pg("u/a/pg"), &resolved(" DB.Example ", "prod", "app")) + datatable_database_key(&pg("u/a/pg"), &resolved("DB.Example", "prod", "app")) + ); + assert_ne!( + datatable_database_key(&pg("u/a/pg"), &resolved("db", "prod", "app")), + datatable_database_key(&pg("u/a/pg"), &resolved("db", "prod ", "app")) ); }