mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 00:03:07 +00:00
clear orphaned usr_to_group rows on service account creation (#10660)
* fix: clear orphaned usr_to_group rows on service account creation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin service account creation over orphaned usr_to_group rows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 60c20e686cead73ff075512b15c6e2d6232beca6 This commit updates the EE repository reference after PR #723 was merged in windmill-ee-private. Previous ee-repo-ref: 5c2c553f960abcd7988fdac8830dd36c066160ad New ee-repo-ref: 60c20e686cead73ff075512b15c6e2d6232beca6 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 5
windmill-internal-app[bot]
parent
83bdff89d5
commit
9cd2bcea25
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "add01e9e31d64e88b84c9505fe3de553031e581b1bb173413a9a3e3eb0817b43"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
88568d11162ffa11723e7955e613224bab4f0568
|
||||
60c20e686cead73ff075512b15c6e2d6232beca6
|
||||
|
||||
@@ -1007,3 +1007,108 @@ async fn test_dbt_warehouses(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A service account created over an orphaned `usr_to_group` row must start with
|
||||
/// exactly the memberships that were asked for.
|
||||
///
|
||||
/// `usr_to_group` has no FK to `usr`, so `leave_workspace` leaves group rows behind
|
||||
/// for a deleted username. Recreating that username used to fail outright on the
|
||||
/// duplicate `all` row; tolerating the duplicate alone would instead have handed the
|
||||
/// new account every stale membership, including privileged ones.
|
||||
///
|
||||
/// Gated on `private` because `create_service_account` is EE-only — the OSS shim
|
||||
/// (`windmill-api-workspaces/src/workspaces_oss.rs`) rejects the request outright.
|
||||
#[cfg(feature = "private")]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_create_service_account_drops_orphaned_group_memberships(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO group_ (workspace_id, name, summary) VALUES
|
||||
('test-workspace', 'wm_deployers', 'deployers'),
|
||||
('test-workspace', 'secrets', 'privileged')",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Stale rows from a prior user of this username: no matching `usr` row exists.
|
||||
sqlx::query(
|
||||
"INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES
|
||||
('test-workspace', 'svc_acct', 'all'),
|
||||
('test-workspace', 'svc_acct', 'wm_deployers'),
|
||||
('test-workspace', 'svc_acct', 'secrets')",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// Same username, different workspace, and very much alive — must not be touched.
|
||||
sqlx::query("INSERT INTO workspace (id, name, owner) VALUES ('other-workspace', 'other', 'svc_acct')")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO group_ (workspace_id, name, summary) VALUES
|
||||
('other-workspace', 'all', 'All users'),
|
||||
('other-workspace', 'secrets', 'privileged')",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO usr (workspace_id, email, username, is_admin, role)
|
||||
VALUES ('other-workspace', 'other@windmill.dev', 'svc_acct', false, 'User')",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES
|
||||
('other-workspace', 'svc_acct', 'all'),
|
||||
('other-workspace', 'svc_acct', 'secrets')",
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let base = format!("http://localhost:{port}/api/w/test-workspace/workspaces");
|
||||
|
||||
let resp = authed(client().post(format!("{base}/create_service_account")))
|
||||
.json(&json!({"username": "svc_acct", "is_admin": false, "operator": true, "add_to_deployers": false}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
201,
|
||||
"creation over an orphaned row failed: {}",
|
||||
resp.text().await?
|
||||
);
|
||||
|
||||
let groups: Vec<String> = sqlx::query_scalar(
|
||||
"SELECT group_ FROM usr_to_group WHERE workspace_id = $1 AND usr = $2 ORDER BY group_",
|
||||
)
|
||||
.bind("test-workspace")
|
||||
.bind("svc_acct")
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
groups,
|
||||
vec!["all".to_string()],
|
||||
"service account inherited stale memberships"
|
||||
);
|
||||
|
||||
let other: Vec<String> = sqlx::query_scalar(
|
||||
"SELECT group_ FROM usr_to_group WHERE workspace_id = $1 AND usr = $2 ORDER BY group_",
|
||||
)
|
||||
.bind("other-workspace")
|
||||
.bind("svc_acct")
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
other,
|
||||
vec!["all".to_string(), "secrets".to_string()],
|
||||
"cleanup escaped the workspace it was scoped to"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user