diff --git a/backend/windmill-api-integration-tests/tests/datatable_roles.rs b/backend/windmill-api-integration-tests/tests/datatable_roles.rs index a15439455f..08790386a4 100644 --- a/backend/windmill-api-integration-tests/tests/datatable_roles.rs +++ b/backend/windmill-api-integration-tests/tests/datatable_roles.rs @@ -1098,22 +1098,25 @@ async fn without_the_enterprise_edition_a_data_table_under_roles_is_refused_a_co assert!(err.to_string().contains(ENTERPRISE_REFUSAL), "{err}"); } - // Not under roles, it resolves as it always has; naming a role on it is refused. + // Not under roles, it resolves as it always has, including when `admin` is named — which every + // migration does; naming any other role on it is refused. sqlx::query( "UPDATE workspace_settings SET datatable = datatable #- '{datatables,main,permissions}' WHERE workspace_id = 'test-workspace'", ) .execute(&db) .await?; - let resolved = get_datatable_resource_from_db( - &db, - "test-workspace", - "main", - None, - DatatableAccess::NoIdentity, - ) - .await?; - assert_eq!(resolved["dbname"], "dt_main", "{resolved}"); + for role in [None, Some("admin")] { + let resolved = get_datatable_resource_from_db( + &db, + "test-workspace", + "main", + role, + DatatableAccess::NoIdentity, + ) + .await?; + assert_eq!(resolved["dbname"], "dt_main", "{resolved}"); + } let err = get_datatable_resource_from_db( &db, "test-workspace", diff --git a/backend/windmill-common/src/datatable_roles_oss.rs b/backend/windmill-common/src/datatable_roles_oss.rs index 98fef4260f..057a739bd4 100644 --- a/backend/windmill-common/src/datatable_roles_oss.rs +++ b/backend/windmill-common/src/datatable_roles_oss.rs @@ -163,8 +163,8 @@ mod ce { Err(unavailable()) } - /// A data table not under roles, asked for no role, is not a role decision and passes, as it - /// did before roles existed. Anything else is refused. + /// 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, @@ -174,7 +174,9 @@ mod ce { _context: &str, ) -> Result<()> { let governing = resolve_governing_datatable(db, w_id, name).await?; - if governing.datatable.permissions.is_none() && role.is_none() { + if governing.datatable.permissions.is_none() + && role.is_none_or(|r| r == crate::datatable_roles::ADMIN_DATATABLE_ROLE) + { Ok(()) } else { Err(unavailable()) diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index e8974da0e8..3e8dd0aae5 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -1664,9 +1664,10 @@ pub async fn get_datatable_resource_from_db( ) -> Result { let governing = resolve_governing_datatable(db, w_id, name).await?; let db_resource = resolve_datatable_connection_unchecked(db, &governing, false).await?; - // Not under roles and asked for none: the `admin` connection, as before roles existed, in - // every edition. Anything else is a role decision. - if governing.datatable.permissions.is_none() && role.is_none() { + // Not under roles and asked for none, or for `admin` by name: the `admin` connection, as before + // roles existed, in every edition. Anything else is a role decision. Every migration names + // `admin` explicitly, so an edition without roles must not treat that as one. + if governing.datatable.permissions.is_none() && role.is_none_or(|r| r == ADMIN_DATATABLE_ROLE) { return Ok(db_resource); } crate::datatable_roles_oss::resolve_datatable_role_connection(