mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-29 16:15:51 +00:00
Merge remote-tracking branch 'origin/main' into migrate-session-to-fork
Main dropped the session header's "Acting on" chip for the page breadcrumb, so the move-to-fork picker now opens from a "Move to a fork…" item in the session's menu. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -683,6 +683,9 @@ pub(crate) struct CatalogFacts {
|
||||
pub(crate) former_owner: Option<FormerOwnerDefaults>,
|
||||
/// For a revoke: each grant it takes back.
|
||||
pub(crate) revoked_grants: Vec<RevokedGrant>,
|
||||
/// For a grant on what a schema holds: the role cannot reach the schema yet, so the grant
|
||||
/// would give it nothing it can use.
|
||||
pub(crate) lacks_schema_usage: bool,
|
||||
}
|
||||
|
||||
/// A grant a revoke takes back, as the catalog records it: what `source` gave on `object` (the
|
||||
@@ -1526,7 +1529,24 @@ async fn build_plan(
|
||||
)));
|
||||
}
|
||||
}
|
||||
let facts = CatalogFacts { other_pg_roles, existing_objects, former_owner, revoked_grants };
|
||||
let lacks_schema_usage = match (&change, target) {
|
||||
(AclChange::Grant { scope, .. }, AclTarget::Schema { schema })
|
||||
if !matches!(scope, GrantScope::Target) =>
|
||||
{
|
||||
!has_schema_usage(client, schema, &pg_role).await?
|
||||
}
|
||||
(AclChange::Grant { .. }, AclTarget::Table { schema, .. }) => {
|
||||
!has_schema_usage(client, schema, &pg_role).await?
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let facts = CatalogFacts {
|
||||
other_pg_roles,
|
||||
existing_objects,
|
||||
former_owner,
|
||||
revoked_grants,
|
||||
lacks_schema_usage,
|
||||
};
|
||||
let mut plan =
|
||||
crate::datatable_acl_oss::plan_statements(target, &change, dbname, &pg_role, &facts)?;
|
||||
if matches!(change, AclChange::SetOwner { .. }) {
|
||||
@@ -1540,6 +1560,27 @@ async fn build_plan(
|
||||
Ok(plan)
|
||||
}
|
||||
|
||||
/// Whether `pg_role` may look into `schema`, directly or through a role it inherits from.
|
||||
async fn has_schema_usage(
|
||||
client: &tokio_postgres::Client,
|
||||
schema: &str,
|
||||
pg_role: &str,
|
||||
) -> Result<bool> {
|
||||
Ok(client
|
||||
.query_one(
|
||||
"SELECT has_schema_privilege($1::name, $2::text, 'USAGE')",
|
||||
&[&pg_role, &schema],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::internal_err(format!(
|
||||
"Failed to read the role's privileges on schema {schema}: {}",
|
||||
pg_error_message(&e)
|
||||
))
|
||||
})?
|
||||
.get(0))
|
||||
}
|
||||
|
||||
/// Postgres only hands an object to a role that could have created it: a table to one with
|
||||
/// `CREATE` on its schema, a schema to one with `CREATE` on the database.
|
||||
async fn missing_owner_privilege(
|
||||
|
||||
@@ -880,7 +880,11 @@ struct DatatableMigrationsStatusResult {
|
||||
/// Whether the SQL-migrations feature is enabled for a data table. Honors the
|
||||
/// explicit `migrations_enabled` flag; when unset (data tables predating the
|
||||
/// feature) it is considered enabled only if migrations already exist.
|
||||
async fn datatable_migrations_enabled(db: &DB, w_id: &str, datatable_name: &str) -> Result<bool> {
|
||||
pub(crate) async fn datatable_migrations_enabled(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
datatable_name: &str,
|
||||
) -> Result<bool> {
|
||||
let flag: Option<bool> = sqlx::query_scalar!(
|
||||
"SELECT (ws.datatable->'datatables'->$2->>'migrations_enabled')::boolean \
|
||||
FROM workspace_settings ws WHERE ws.workspace_id = $1",
|
||||
|
||||
@@ -25,11 +25,11 @@ use axum::{
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use lazy_static::lazy_static;
|
||||
use magic_crypt::MagicCrypt256;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use windmill_api_auth::{is_effectively_unscoped, ApiAuthed, Tokened};
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use windmill_common::variables::WorkspaceCrypt;
|
||||
use windmill_common::{
|
||||
auth::hash_token,
|
||||
error::{error_source_chain, Error, Result},
|
||||
@@ -676,8 +676,8 @@ async fn proxy(
|
||||
pub(crate) async fn reencrypt_tokens(
|
||||
conn: &mut sqlx::PgConnection,
|
||||
w_id: &str,
|
||||
old: &MagicCrypt256,
|
||||
new: &MagicCrypt256,
|
||||
old: &WorkspaceCrypt,
|
||||
new: &WorkspaceCrypt,
|
||||
) -> Result<()> {
|
||||
let rows = sqlx::query!(
|
||||
"SELECT email, token FROM remote_deploy_token
|
||||
|
||||
@@ -2325,6 +2325,9 @@ struct DataTableTables {
|
||||
/// What the role the listing connected as may create.
|
||||
can_create_schema: bool,
|
||||
creatable_schemas: Vec<String>,
|
||||
/// Whether its schema is kept as versioned migrations, which a change made with plain SQL
|
||||
/// would bypass.
|
||||
migrations_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -2570,8 +2573,17 @@ async fn list_one_datatable_tables(
|
||||
default_role: windmill_common::datatable_roles::ADMIN_DATATABLE_ROLE.to_string(),
|
||||
can_create_schema: false,
|
||||
creatable_schemas: vec![],
|
||||
migrations_enabled: false,
|
||||
};
|
||||
let result: Result<()> = async {
|
||||
// Read from the workspace settings alone, so it is known even when the database is not
|
||||
// reachable.
|
||||
entry.migrations_enabled = crate::datatable_migrations::datatable_migrations_enabled(
|
||||
db,
|
||||
w_id,
|
||||
&entry.datatable_name,
|
||||
)
|
||||
.await?;
|
||||
let governing = resolve_governing_datatable(db, w_id, &entry.datatable_name).await?;
|
||||
entry.instance = governing.role_cluster().is_some();
|
||||
let usable =
|
||||
@@ -6317,8 +6329,8 @@ async fn set_encryption_key(
|
||||
async fn reencrypt_git_sync_secrets(
|
||||
conn: &mut sqlx::PgConnection,
|
||||
w_id: &str,
|
||||
old: &magic_crypt::MagicCrypt256,
|
||||
new: &magic_crypt::MagicCrypt256,
|
||||
old: &windmill_common::variables::WorkspaceCrypt,
|
||||
new: &windmill_common::variables::WorkspaceCrypt,
|
||||
) -> Result<()> {
|
||||
let Some((mut credentials, mut git_sync)) =
|
||||
sqlx::query_as::<_, (serde_json::Value, Option<serde_json::Value>)>(
|
||||
@@ -11569,10 +11581,13 @@ struct ChangeOperatorSettings {
|
||||
/// withdraw or grant the right on every push.
|
||||
///
|
||||
/// `builder_flows` lets every operator of this workspace compose flows out of already-deployed
|
||||
/// runnables, and makes each of them consume a full author seat instead of half of one.
|
||||
/// runnables, `builder_apps` full-code apps. Either makes each operator consume a full author
|
||||
/// seat instead of half of one.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
builder_flows: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
builder_apps: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
manage_schedules: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
manage_triggers: Option<bool>,
|
||||
@@ -11587,9 +11602,10 @@ async fn update_operator_settings(
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
// Every operator of the workspace turns into a full seat, which an offline license may not
|
||||
// cover. It is a no-op delta when the right is already on.
|
||||
// cover. Either right costs the same seat, so this prices "any", and it is a no-op delta when
|
||||
// one of them is already on.
|
||||
#[cfg(feature = "enterprise")]
|
||||
if settings.builder_flows == Some(true) {
|
||||
if settings.builder_flows == Some(true) || settings.builder_apps == Some(true) {
|
||||
if let Some(msg) =
|
||||
windmill_common::ee_oss::check_seat_cap_for_operator_builder(&db, &w_id).await?
|
||||
{
|
||||
|
||||
@@ -1299,6 +1299,13 @@ pub(crate) async fn delete_workspace(
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM runnable_job_stats WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
// If this workspace is itself a dev workspace, deleting it dissolves the pairing, so also drop
|
||||
// the parent prod's reserved dev_workspace_lock (mirrors detach_dev_workspace) — otherwise prod
|
||||
// stays locked against direct deploy/forking with no dev workspace left to make changes in.
|
||||
|
||||
Reference in New Issue
Block a user