mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-30 08:16:49 +00:00
* feat: connect a data table as the job's own identity unless the runnable opts into its viewer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: key cached results by the data table mode and keep the flag across ai edits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: hash the cache key's mode and viewer as fixed fields so no argument name can forge them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: update ee-repo-ref to 2f74548ad1ec50c8e1faf17e42d5648160e833f1 This commit updates the EE repository reference after PR #878 was merged in windmill-ee-private. Previous ee-repo-ref: 4ad768a777fff9f1ecc86a2803bc27d80f8242d6 New ee-repo-ref: 2f74548ad1ec50c8e1faf17e42d5648160e833f1 Automated by sync-ee-ref workflow. * feat: rename the flag to viewer_data_access and apply it to object storage permission rules Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: describe viewer_data_access as covering object storage rules too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: update ee-repo-ref to 0ffb633473c5465513576afc6f54d18f93c7f1e1 This commit updates the EE repository reference after PR #879 was merged in windmill-ee-private. Previous ee-repo-ref: e9e06a9eb94168f46b378276b01fa0117dc7d8ed New ee-repo-ref: 0ffb633473c5465513576afc6f54d18f93c7f1e1 Automated by sync-ee-ref workflow. * feat: show the viewer data access control as an icon button Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
113 lines
3.9 KiB
Rust
113 lines
3.9 KiB
Rust
//! A job whose `job_perms.viewer_data_access` is set reads workspace object storage as its viewer:
|
|
//! the advanced permission rules are checked against the viewer, not the job's own identity, and
|
|
//! such a job with no viewer is refused rather than checked as itself.
|
|
//!
|
|
//! Advanced S3 permissions are an enterprise feature.
|
|
#![cfg(all(feature = "enterprise", feature = "private", feature = "parquet"))]
|
|
|
|
use serde_json::json;
|
|
use sqlx::{Pool, Postgres};
|
|
use uuid::Uuid;
|
|
use windmill_test_utils::*;
|
|
|
|
/// A job of `test-user-3`, run for `viewer`, reading data as that viewer or not.
|
|
async fn job_token(
|
|
db: &Pool<Postgres>,
|
|
viewer: Option<&str>,
|
|
viewer_data_access: bool,
|
|
) -> anyhow::Result<String> {
|
|
let id = Uuid::new_v4();
|
|
sqlx::query(
|
|
"INSERT INTO v2_job (id, workspace_id, created_by, permissioned_as, permissioned_as_email,
|
|
kind, script_lang, runnable_path, tag)
|
|
VALUES ($1, 'test-workspace', 'test-user-3', 'u/test-user-3', 'test3@windmill.dev',
|
|
'script', 'deno', 'u/test-user-3/app_runnable', 'deno')",
|
|
)
|
|
.bind(id)
|
|
.execute(db)
|
|
.await?;
|
|
sqlx::query(
|
|
"INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups,
|
|
workspace_id, end_user_email, viewer_data_access)
|
|
VALUES ($1, 'test3@windmill.dev', 'test-user-3', false, false, '{}', '{}',
|
|
'test-workspace', $2, $3)",
|
|
)
|
|
.bind(id)
|
|
.bind(viewer)
|
|
.bind(viewer_data_access)
|
|
.execute(db)
|
|
.await?;
|
|
Ok(windmill_common::auth::create_token_for_owner(
|
|
db,
|
|
"test-workspace",
|
|
"u/test-user-3",
|
|
"ephemeral-script",
|
|
300,
|
|
"test3@windmill.dev",
|
|
&id,
|
|
None,
|
|
None,
|
|
)
|
|
.await?)
|
|
}
|
|
|
|
#[sqlx::test(fixtures("base"))]
|
|
async fn a_job_reading_data_as_its_viewer_gets_the_viewers_storage_paths(
|
|
db: Pool<Postgres>,
|
|
) -> anyhow::Result<()> {
|
|
initialize_tracing().await;
|
|
set_jwt_secret().await;
|
|
let server = ApiServer::start(db.clone()).await?;
|
|
let port = server.addr.port();
|
|
|
|
let storage_dir = tempfile::tempdir()?;
|
|
for user in ["test-user-2", "test-user-3"] {
|
|
let dir = storage_dir.path().join("u").join(user);
|
|
std::fs::create_dir_all(&dir)?;
|
|
std::fs::write(dir.join("file.txt"), user)?;
|
|
}
|
|
sqlx::query("UPDATE workspace_settings SET large_file_storage = $1 WHERE workspace_id = $2")
|
|
.bind(json!({
|
|
"type": "FilesystemStorage",
|
|
"root_path": storage_dir.path().to_string_lossy(),
|
|
"public_resource": null,
|
|
"advanced_permissions": [{ "pattern": "u/{username}/**", "allow": "read" }]
|
|
}))
|
|
.bind("test-workspace")
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
let read = |token: String, user: &'static str| async move {
|
|
let resp = reqwest::Client::new()
|
|
.get(format!(
|
|
"http://localhost:{port}/api/w/test-workspace/job_helpers/download_s3_file"
|
|
))
|
|
.query(&[("file_key", format!("u/{user}/file.txt"))])
|
|
.bearer_auth(token)
|
|
.send()
|
|
.await?;
|
|
Ok::<_, anyhow::Error>(resp.status().is_success())
|
|
};
|
|
|
|
let own = job_token(&db, Some("test2@windmill.dev"), false).await?;
|
|
assert!(read(own.clone(), "test-user-3").await?);
|
|
assert!(
|
|
!read(own, "test-user-2").await?,
|
|
"a job not reading as its viewer reached the viewer's paths"
|
|
);
|
|
|
|
let as_viewer = job_token(&db, Some("test2@windmill.dev"), true).await?;
|
|
assert!(read(as_viewer.clone(), "test-user-2").await?);
|
|
assert!(
|
|
!read(as_viewer, "test-user-3").await?,
|
|
"a job reading as its viewer kept its own identity's paths"
|
|
);
|
|
|
|
let no_viewer = job_token(&db, None, true).await?;
|
|
assert!(
|
|
!read(no_viewer, "test-user-3").await?,
|
|
"a job reading as a viewer it does not have was checked as itself"
|
|
);
|
|
Ok(())
|
|
}
|