Files
windmill/backend/windmill-api/src/workspace_dependencies.rs
Ruben FiszelandClaude Opus 5.5 3eaf2888c0 fix: scope workspace dependencies create to the path workspace (#11385)
* fix: scope workspace dependencies create to the path workspace

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: state the workspace_id must-match contract in the spec and struct

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 18:15:10 +02:00

190 lines
5.5 KiB
Rust

use axum::{
extract::{Path, Query},
routing::{get, post},
Extension, Json, Router,
};
use http::StatusCode;
use serde::Deserialize;
use windmill_common::{
error::{self, JsonResult},
scripts::ScriptLang,
users::username_to_permissioned_as,
utils::require_admin,
workspace_dependencies::WorkspaceDependencies,
DB,
};
use windmill_dep_map::workspace_dependencies::{
trigger_dependents_to_recompute_dependencies_in_the_background, NewWorkspaceDependencies,
};
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
use crate::db::ApiAuthed;
pub fn workspaced_service() -> Router {
Router::new()
.route("/create", post(create))
.route("/list", get(list))
.route("/archive/{language}", post(archive))
.route("/get_latest/{language}", get(get_latest))
.route("/delete/{language}", post(delete))
}
#[axum::debug_handler]
async fn create(
authed: ApiAuthed,
// Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
Json(nwd): Json<NewWorkspaceDependencies>,
) -> error::Result<(StatusCode, String)> {
tracing::info!(workspace_id = %w_id, name = ?nwd.name, language = ?nwd.language, "create workspace dependencies");
require_admin(authed.is_admin, &authed.username)?;
// `require_admin` vouches for the path workspace only; the body must not target another.
if nwd.workspace_id != w_id {
return Err(error::Error::BadRequest(format!(
"workspace_id `{}` in the request body does not match the workspace `{}` in the path",
nwd.workspace_id, w_id
)));
}
let dep_path = WorkspaceDependencies::to_path(&nwd.name, nwd.language)?;
let email = authed.email.clone();
let username = authed.username.clone();
let id = nwd
.create(
(
authed.email,
username_to_permissioned_as(&authed.username),
authed.username,
),
db.clone(),
)
.await?;
handle_deployment_metadata(
&email,
&username,
&db,
&w_id,
DeployedObject::WorkspaceDependencies { path: dep_path },
None,
true,
None,
)
.await?;
Ok((StatusCode::CREATED, format!("{}", id)))
}
#[axum::debug_handler]
async fn list(
// Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
) -> JsonResult<Vec<WorkspaceDependencies>> {
tracing::info!(workspace_id = %w_id, "list workspace dependencies");
Ok(Json(WorkspaceDependencies::list(&w_id, &db).await?))
}
#[derive(Deserialize)]
pub(super) struct NameQuery {
name: Option<String>,
}
#[axum::debug_handler]
pub(super) async fn get_latest(
Extension(db): Extension<DB>,
Path((w_id, language)): Path<(String, ScriptLang)>,
Query(params): Query<NameQuery>,
) -> JsonResult<Option<WorkspaceDependencies>> {
tracing::info!(workspace_id = %w_id, language = ?language, name = ?params.name, "get latest workspace dependencies");
Ok(Json(
WorkspaceDependencies::get_latest(params.name, language, &w_id, db.into()).await?,
))
}
#[axum::debug_handler]
async fn archive(
authed: ApiAuthed,
// Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path((w_id, language)): Path<(String, ScriptLang)>,
Query(params): Query<NameQuery>,
) -> error::Result<()> {
tracing::info!(workspace_id = %w_id, language = ?language, name = ?params.name, "archive workspace dependencies");
require_admin(authed.is_admin, &authed.username)?;
let db = &db;
let dep_path = WorkspaceDependencies::to_path(&params.name, language)?;
WorkspaceDependencies::archive(params.name.clone(), language, &w_id, db).await?;
handle_deployment_metadata(
&authed.email,
&authed.username,
db,
&w_id,
DeployedObject::WorkspaceDependencies { path: dep_path.clone() },
None,
true,
None,
)
.await?;
trigger_dependents_to_recompute_dependencies_in_the_background(
params.name.is_none(),
w_id,
language,
(
authed.email,
username_to_permissioned_as(&authed.username),
authed.username,
),
dep_path,
db.clone(),
)
.await;
Ok(())
}
#[axum::debug_handler]
async fn delete(
authed: ApiAuthed,
// Extension(user_db): Extension<UserDB>,
Extension(db): Extension<DB>,
Path((w_id, language)): Path<(String, ScriptLang)>,
Query(params): Query<NameQuery>,
) -> error::Result<()> {
tracing::info!(workspace_id = %w_id, language = ?language, name = ?params.name, "delete workspace dependencies");
require_admin(authed.is_admin, &authed.username)?;
let db = &db;
let dep_path = WorkspaceDependencies::to_path(&params.name, language)?;
WorkspaceDependencies::delete(params.name.clone(), language, &w_id, db).await?;
handle_deployment_metadata(
&authed.email,
&authed.username,
db,
&w_id,
DeployedObject::WorkspaceDependencies { path: dep_path.clone() },
None,
true,
None,
)
.await?;
trigger_dependents_to_recompute_dependencies_in_the_background(
params.name.is_none(),
w_id,
language,
(
authed.email,
username_to_permissioned_as(&authed.username),
authed.username,
),
dep_path,
db.clone(),
)
.await;
Ok(())
}