mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 08:01:25 +00:00
feat: improve premissioned audit logs (#3793)
* feat: improve premissioned audit logs * chore: update ee ref * fix: build * fix: sqlx
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM usr WHERE email = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3da14e6c89a33871e4bd40524e2fd9222aa707ee76de6ce348337c3e403eb955"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM usr WHERE email = $1 AND workspace_id = $2 RETURNING username",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "62cd69e3dc0f8757db3481d078921f4722bbc93d52e05656b82cb42ba9803ef6"
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT username FROM usr WHERE workspace_id = $1 AND email = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "da5da57ea48ddc6ab271b6c18baa5f7360008e082e2fb8d58faff4461e18c83a"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
229b7acc60691f7e6577c44f782676d6871defdd
|
||||
cb38edaf31affeda48317433d10903245aa518f2
|
||||
@@ -1079,11 +1079,16 @@ async fn handle_zombie_jobs<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
|
||||
mpsc::channel::<SameWorkerPayload>(1);
|
||||
let (send_result_never_used, _send_result_rx_never_used) = mpsc::channel::<SendResult>(1);
|
||||
|
||||
let label = if job.permissioned_as != format!("u/{}", job.created_by) && job.permissioned_as != job.created_by {
|
||||
format!("ephemeral-script-end-user-{}", job.created_by)
|
||||
} else {
|
||||
"ephemeral-script".to_string()
|
||||
};
|
||||
let token = create_token_for_owner(
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
&job.permissioned_as,
|
||||
"ephemeral-script",
|
||||
&label,
|
||||
*SCRIPT_TOKEN_EXPIRY,
|
||||
&job.email,
|
||||
&job.id,
|
||||
|
||||
@@ -2025,6 +2025,27 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/users/email_from_username/{username}:
|
||||
get:
|
||||
summary: get email from username
|
||||
operationId: emailFromUsername
|
||||
tags:
|
||||
- user
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: username
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: email
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/users/tokens/create:
|
||||
post:
|
||||
summary: create token
|
||||
|
||||
@@ -585,7 +585,7 @@ async fn create_app(
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"apps.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -701,7 +701,7 @@ async fn delete_app(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"apps.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -873,7 +873,7 @@ async fn update_app(
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"apps.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
|
||||
@@ -122,7 +122,7 @@ async fn update_config(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"worker_config.update",
|
||||
ActionKind::Update,
|
||||
"global",
|
||||
@@ -149,7 +149,7 @@ async fn delete_config(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"worker_config.delete",
|
||||
ActionKind::Delete,
|
||||
"global",
|
||||
|
||||
@@ -15,6 +15,7 @@ use sqlx::{
|
||||
pool::PoolConnection,
|
||||
PgConnection, Pool, Postgres,
|
||||
};
|
||||
use windmill_audit::audit_ee::{AuditAuthor, AuditAuthorable};
|
||||
use windmill_common::{
|
||||
db::{Authable, Authed},
|
||||
error::Error,
|
||||
@@ -246,12 +247,34 @@ impl From<ApiAuthed> for Authed {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ApiAuthed> for AuditAuthor {
|
||||
fn from(value: &ApiAuthed) -> Self {
|
||||
Self {
|
||||
email: value.email.clone(),
|
||||
username: value.username.clone(),
|
||||
username_override: value.username_override.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiAuthed {
|
||||
pub fn display_username(&self) -> &str {
|
||||
self.username_override.as_ref().unwrap_or(&self.username)
|
||||
}
|
||||
}
|
||||
|
||||
impl AuditAuthorable for ApiAuthed {
|
||||
fn username(&self) -> &str {
|
||||
self.username.as_str()
|
||||
}
|
||||
fn email(&self) -> &str {
|
||||
self.email.as_str()
|
||||
}
|
||||
fn username_override(&self) -> Option<&str> {
|
||||
self.username_override.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Authable for ApiAuthed {
|
||||
fn is_admin(&self) -> bool {
|
||||
self.is_admin
|
||||
|
||||
@@ -348,7 +348,7 @@ async fn create_flow(
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"flows.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -553,7 +553,7 @@ async fn update_flow(
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"flows.update",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -746,7 +746,7 @@ async fn archive_flow_by_path(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"flows.archive",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -813,7 +813,7 @@ async fn delete_flow_by_path(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"flows.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
|
||||
@@ -223,7 +223,7 @@ async fn create_folder(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"folder.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -356,7 +356,7 @@ async fn update_folder(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"folder.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -522,7 +522,7 @@ async fn delete_folder(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"folder.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -585,7 +585,7 @@ async fn add_owner(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"folder.add_owner",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -664,7 +664,7 @@ async fn remove_owner(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"folder.remove_owner",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
|
||||
@@ -252,7 +252,7 @@ async fn create_group(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"group.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -296,7 +296,7 @@ async fn create_igroup(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"igroup.create",
|
||||
ActionKind::Create,
|
||||
"global",
|
||||
@@ -337,7 +337,7 @@ async fn update_igroup(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"igroup.updated",
|
||||
ActionKind::Delete,
|
||||
"global",
|
||||
@@ -367,7 +367,7 @@ async fn delete_igroup(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"igroup.delete",
|
||||
ActionKind::Delete,
|
||||
"global",
|
||||
@@ -472,7 +472,7 @@ async fn delete_group(
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"group.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -522,7 +522,7 @@ async fn update_group(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"group.edit",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -573,7 +573,7 @@ async fn add_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"group.adduser",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -624,7 +624,7 @@ async fn add_user_igroup(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"igroup.adduser",
|
||||
ActionKind::Update,
|
||||
"global",
|
||||
@@ -693,7 +693,7 @@ async fn remove_user_igroup(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"igroup.removeuser",
|
||||
ActionKind::Update,
|
||||
"global",
|
||||
@@ -733,7 +733,7 @@ async fn remove_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"group.removeuser",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
|
||||
@@ -51,7 +51,7 @@ use sqlx::types::JsonRawValue;
|
||||
use sqlx::{types::Uuid, FromRow, Postgres, Transaction};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use urlencoding::encode;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE, SERVER_CONFIG};
|
||||
use windmill_common::{
|
||||
@@ -317,18 +317,31 @@ async fn cancel_job_api(
|
||||
) -> error::Result<String> {
|
||||
let tx = db.begin().await?;
|
||||
|
||||
let username = match opt_authed {
|
||||
Some(authed) => authed.username,
|
||||
None => "anonymous".to_string(),
|
||||
let audit_author = match opt_authed {
|
||||
Some(authed) => (&authed).into(),
|
||||
None => AuditAuthor {
|
||||
email: "anonymous".to_string(),
|
||||
username: "anonymous".to_string(),
|
||||
username_override: None,
|
||||
},
|
||||
};
|
||||
|
||||
let (mut tx, job_option) =
|
||||
windmill_queue::cancel_job(&username, reason, id, &w_id, tx, &db, rsmq, false).await?;
|
||||
let (mut tx, job_option) = windmill_queue::cancel_job(
|
||||
&audit_author.username,
|
||||
reason,
|
||||
id,
|
||||
&w_id,
|
||||
tx,
|
||||
&db,
|
||||
rsmq,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(id) = job_option {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&audit_author,
|
||||
"jobs.cancel",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -358,13 +371,17 @@ async fn cancel_persistent_script_api(
|
||||
Path((w_id, script_path)): Path<(String, StripPath)>,
|
||||
Json(CancelJob { reason }): Json<CancelJob>,
|
||||
) -> error::Result<()> {
|
||||
let username = match opt_authed {
|
||||
Some(authed) => authed.username,
|
||||
None => "anonymous".to_string(),
|
||||
let audit_author = match opt_authed {
|
||||
Some(authed) => (&authed).into(),
|
||||
None => AuditAuthor {
|
||||
email: "anonymous".to_string(),
|
||||
username: "anonymous".to_string(),
|
||||
username_override: None,
|
||||
},
|
||||
};
|
||||
|
||||
let cancelled_job_ids = windmill_queue::cancel_persistent_script_jobs(
|
||||
&username,
|
||||
&audit_author.username,
|
||||
reason,
|
||||
script_path.to_path(),
|
||||
&w_id,
|
||||
@@ -375,7 +392,7 @@ async fn cancel_persistent_script_api(
|
||||
|
||||
audit_log(
|
||||
&db,
|
||||
&username,
|
||||
&audit_author,
|
||||
"jobs.cancel_persistent",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -406,18 +423,31 @@ async fn force_cancel(
|
||||
) -> error::Result<String> {
|
||||
let tx = db.begin().await?;
|
||||
|
||||
let username = match opt_authed {
|
||||
Some(authed) => authed.username,
|
||||
None => "anonymous".to_string(),
|
||||
let audit_author = match opt_authed {
|
||||
Some(authed) => (&authed).into(),
|
||||
None => AuditAuthor {
|
||||
email: "anonymous".to_string(),
|
||||
username: "anonymous".to_string(),
|
||||
username_override: None,
|
||||
},
|
||||
};
|
||||
|
||||
let (mut tx, job_option) =
|
||||
windmill_queue::cancel_job(&username, reason, id, &w_id, tx, &db, rsmq, true).await?;
|
||||
let (mut tx, job_option) = windmill_queue::cancel_job(
|
||||
&audit_author.username,
|
||||
reason,
|
||||
id,
|
||||
&w_id,
|
||||
tx,
|
||||
&db,
|
||||
rsmq,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(id) = job_option {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&audit_author,
|
||||
"jobs.force_cancel",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -933,7 +963,6 @@ impl From<ListCompletedQuery> for ListQueueQuery {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn filter_list_queue_query(
|
||||
mut sqlb: SqlBuilder,
|
||||
lq: &ListQueueQuery,
|
||||
@@ -1396,7 +1425,7 @@ async fn resume_suspended_job_internal(
|
||||
{
|
||||
approver.approver
|
||||
} else {
|
||||
authed.map(|x| x.username)
|
||||
authed.as_ref().map(|x| x.username.clone())
|
||||
};
|
||||
insert_resume_job(
|
||||
resume_id,
|
||||
@@ -1419,9 +1448,17 @@ async fn resume_suspended_job_internal(
|
||||
} else {
|
||||
resume_immediately_if_relevant(parent_flow_info, job_id, &mut tx).await?;
|
||||
}
|
||||
|
||||
let audit_author = match authed {
|
||||
Some(authed) => (&authed).into(),
|
||||
None => {
|
||||
let approver = approver.unwrap_or_else(|| "anonymous".to_string());
|
||||
AuditAuthor { email: approver.clone(), username: approver, username_override: None }
|
||||
}
|
||||
};
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&approver.unwrap_or_else(|| "anonymous".to_string()),
|
||||
&audit_author,
|
||||
"jobs.approved",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -2047,76 +2084,75 @@ pub struct UnifiedJob {
|
||||
}
|
||||
|
||||
const CJ_FIELDS: &[&str] = &[
|
||||
"'CompletedJob' as typ",
|
||||
"id",
|
||||
"workspace_id",
|
||||
"parent_job",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"started_at",
|
||||
"null as scheduled_for",
|
||||
"null as running",
|
||||
"script_hash",
|
||||
"script_path",
|
||||
"null as args",
|
||||
"duration_ms",
|
||||
"success",
|
||||
"deleted",
|
||||
"canceled",
|
||||
"canceled_by",
|
||||
"job_kind",
|
||||
"schedule_path",
|
||||
"permissioned_as",
|
||||
"is_flow_step",
|
||||
"language",
|
||||
"is_skipped",
|
||||
"email",
|
||||
"visible_to_owner",
|
||||
"null as suspend",
|
||||
"mem_peak",
|
||||
"tag",
|
||||
"null as concurrent_limit",
|
||||
"null as concurrency_time_window_s",
|
||||
"priority",
|
||||
"result->'wm_labels' as labels",
|
||||
];
|
||||
"'CompletedJob' as typ",
|
||||
"id",
|
||||
"workspace_id",
|
||||
"parent_job",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"started_at",
|
||||
"null as scheduled_for",
|
||||
"null as running",
|
||||
"script_hash",
|
||||
"script_path",
|
||||
"null as args",
|
||||
"duration_ms",
|
||||
"success",
|
||||
"deleted",
|
||||
"canceled",
|
||||
"canceled_by",
|
||||
"job_kind",
|
||||
"schedule_path",
|
||||
"permissioned_as",
|
||||
"is_flow_step",
|
||||
"language",
|
||||
"is_skipped",
|
||||
"email",
|
||||
"visible_to_owner",
|
||||
"null as suspend",
|
||||
"mem_peak",
|
||||
"tag",
|
||||
"null as concurrent_limit",
|
||||
"null as concurrency_time_window_s",
|
||||
"priority",
|
||||
"result->'wm_labels' as labels",
|
||||
];
|
||||
const QJ_FIELDS: &[&str] = &[
|
||||
"'QueuedJob' as typ",
|
||||
"id",
|
||||
"workspace_id",
|
||||
"parent_job",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"started_at",
|
||||
"scheduled_for",
|
||||
"running",
|
||||
"script_hash",
|
||||
"script_path",
|
||||
"null as args",
|
||||
"null as duration_ms",
|
||||
"null as success",
|
||||
"false as deleted",
|
||||
"canceled",
|
||||
"canceled_by",
|
||||
"job_kind",
|
||||
"schedule_path",
|
||||
"permissioned_as",
|
||||
"is_flow_step",
|
||||
"language",
|
||||
"false as is_skipped",
|
||||
"email",
|
||||
"visible_to_owner",
|
||||
"suspend",
|
||||
"mem_peak",
|
||||
"tag",
|
||||
"concurrent_limit",
|
||||
"concurrency_time_window_s",
|
||||
"priority",
|
||||
"null as labels",
|
||||
];
|
||||
"'QueuedJob' as typ",
|
||||
"id",
|
||||
"workspace_id",
|
||||
"parent_job",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"started_at",
|
||||
"scheduled_for",
|
||||
"running",
|
||||
"script_hash",
|
||||
"script_path",
|
||||
"null as args",
|
||||
"null as duration_ms",
|
||||
"null as success",
|
||||
"false as deleted",
|
||||
"canceled",
|
||||
"canceled_by",
|
||||
"job_kind",
|
||||
"schedule_path",
|
||||
"permissioned_as",
|
||||
"is_flow_step",
|
||||
"language",
|
||||
"false as is_skipped",
|
||||
"email",
|
||||
"visible_to_owner",
|
||||
"suspend",
|
||||
"mem_peak",
|
||||
"tag",
|
||||
"concurrent_limit",
|
||||
"concurrency_time_window_s",
|
||||
"priority",
|
||||
"null as labels",
|
||||
];
|
||||
|
||||
impl UnifiedJob {
|
||||
|
||||
pub fn completed_job_fields() -> &'static [&'static str] {
|
||||
CJ_FIELDS
|
||||
}
|
||||
@@ -4318,7 +4354,7 @@ async fn delete_completed_job<'a>(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"jobs.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
|
||||
@@ -317,7 +317,7 @@ async fn proxy(
|
||||
let mut tx = db.begin().await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"openai.request",
|
||||
ActionKind::Execute,
|
||||
&w_id,
|
||||
|
||||
@@ -134,9 +134,7 @@ async fn get_data(
|
||||
let app = not_found_if_none(app_o, "App", path)?;
|
||||
let res = Response::builder().header(header::CONTENT_TYPE, "text/javascript");
|
||||
|
||||
Ok(res
|
||||
.body(Body::from(app))
|
||||
.unwrap())
|
||||
Ok(res.body(Body::from(app)).unwrap())
|
||||
}
|
||||
|
||||
async fn create_app(
|
||||
@@ -181,7 +179,7 @@ async fn create_app(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"apps.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -217,7 +215,7 @@ async fn delete_app(
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"apps.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -294,7 +292,7 @@ async fn update_app(
|
||||
let npath = app.path.clone().unwrap_or_else(|| path.to_owned());
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"apps.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
|
||||
@@ -24,7 +24,7 @@ use serde_json::{value::RawValue, Value};
|
||||
use sql_builder::{bind::Bind, quote, SqlBuilder};
|
||||
use sqlx::{FromRow, Postgres, Transaction};
|
||||
use uuid::Uuid;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
@@ -473,15 +473,20 @@ pub async fn transform_json_value<'c>(
|
||||
let path = y.strip_prefix("$var:").unwrap();
|
||||
let tx: Transaction<'_, Postgres> =
|
||||
authed_transaction_or_default(authed, user_db.clone(), db).await?;
|
||||
|
||||
let v = crate::variables::get_value_internal(
|
||||
tx,
|
||||
db,
|
||||
workspace,
|
||||
path,
|
||||
user_db
|
||||
&user_db
|
||||
.clone()
|
||||
.map(|_| authed.username.as_str())
|
||||
.unwrap_or("backend"),
|
||||
.map(|_| authed.into())
|
||||
.unwrap_or(AuditAuthor {
|
||||
email: "backend".to_string(),
|
||||
username: "backend".to_string(),
|
||||
username_override: None,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
Ok(Value::String(v))
|
||||
@@ -650,7 +655,7 @@ async fn create_resource(
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"resources.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -710,7 +715,7 @@ async fn delete_resource(
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"resources.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -796,7 +801,7 @@ async fn update_resource(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"resources.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -857,7 +862,7 @@ async fn update_resource_value(
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"resources.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -1001,7 +1006,7 @@ async fn create_resource_type(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"resource_types.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -1065,7 +1070,7 @@ async fn delete_resource_type(
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"resource_types.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -1121,7 +1126,7 @@ async fn update_resource_type(
|
||||
sqlx::query(&sql).execute(&mut *tx).await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"resource_types.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
|
||||
@@ -201,7 +201,7 @@ async fn create_schedule(
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"schedule.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -284,7 +284,7 @@ async fn edit_schedule(
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"schedule.edit",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -496,7 +496,7 @@ pub async fn set_enabled(
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"schedule.setenabled",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -544,7 +544,7 @@ pub async fn set_enabled(
|
||||
|
||||
// audit_log(
|
||||
// &mut tx,
|
||||
// &authed.username,
|
||||
// &authed,
|
||||
// "schedule.setenabled",
|
||||
// ActionKind::Update,
|
||||
// &w_id,
|
||||
@@ -621,7 +621,7 @@ async fn delete_schedule(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"schedule.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
|
||||
@@ -663,7 +663,7 @@ async fn create_script_internal<'c>(
|
||||
if p_hashes.is_some() && !p_hashes.unwrap().is_empty() {
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"scripts.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -682,7 +682,7 @@ async fn create_script_internal<'c>(
|
||||
} else {
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"scripts.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -1163,7 +1163,7 @@ async fn archive_script_by_path(
|
||||
.map_err(|e| Error::InternalErr(format!("archiving script in {w_id}: {e}")))?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"scripts.archive",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -1215,7 +1215,7 @@ async fn archive_script_by_hash(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"scripts.archive",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -1255,7 +1255,7 @@ async fn delete_script_by_hash(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"scripts.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -1322,7 +1322,7 @@ async fn delete_script_by_path(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"scripts.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
|
||||
@@ -40,7 +40,7 @@ use sqlx::FromRow;
|
||||
use time::OffsetDateTime;
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
use tracing::{Instrument, Span};
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::global_settings::AUTOMATE_USERNAME_CREATION_SETTING;
|
||||
use windmill_common::users::truncate_token;
|
||||
@@ -72,6 +72,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/whois/:username", get(whois))
|
||||
.route("/whoami", get(whoami))
|
||||
.route("/leave", post(leave_workspace))
|
||||
.route("/email_from_username/:username", get(email_from_username))
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -118,6 +119,16 @@ pub fn make_unauthed_service() -> Router {
|
||||
fn username_override_from_label(label: Option<String>) -> Option<String> {
|
||||
if label.as_ref().is_some_and(|x| x.starts_with("webhook-")) {
|
||||
label
|
||||
} else if label
|
||||
.as_ref()
|
||||
.is_some_and(|x| x.starts_with("ephemeral-script-end-user-"))
|
||||
{
|
||||
Some(
|
||||
label
|
||||
.unwrap()
|
||||
.trim_start_matches("ephemeral-script-end-user-")
|
||||
.to_string(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -919,9 +930,10 @@ async fn logout(
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
if let Some(email) = email {
|
||||
let email = email.unwrap_or("noemail".to_string());
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email.unwrap_or("noemail".to_string()),
|
||||
&AuditAuthor { email: email.clone(), username: email, username_override: None },
|
||||
"users.logout",
|
||||
ActionKind::Delete,
|
||||
"global",
|
||||
@@ -1275,7 +1287,7 @@ async fn whois(
|
||||
// }
|
||||
|
||||
async fn decline_invite(
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Json(nu): Json<DeclineInvite>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
@@ -1284,18 +1296,18 @@ async fn decline_invite(
|
||||
let is_admin = sqlx::query_scalar!(
|
||||
"DELETE FROM workspace_invite WHERE workspace_id = $1 AND email = $2 RETURNING is_admin",
|
||||
nu.workspace_id,
|
||||
email,
|
||||
authed.email,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email,
|
||||
&authed,
|
||||
"users.decline_invite",
|
||||
ActionKind::Delete,
|
||||
&nu.workspace_id,
|
||||
Some(&email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -1306,11 +1318,14 @@ async fn decline_invite(
|
||||
StatusCode::OK,
|
||||
format!(
|
||||
"user {} declined invite to workspace {}",
|
||||
&email, nu.workspace_id
|
||||
&authed.email, nu.workspace_id
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Err(Error::NotFound(format!("invite for {email} not found")))
|
||||
Err(Error::NotFound(format!(
|
||||
"invite for {} not found",
|
||||
authed.email
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1319,7 +1334,7 @@ lazy_static! {
|
||||
}
|
||||
|
||||
async fn accept_invite(
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
@@ -1330,7 +1345,7 @@ async fn accept_invite(
|
||||
let r = sqlx::query!(
|
||||
"DELETE FROM workspace_invite WHERE workspace_id = $1 AND email = $2 RETURNING is_admin, operator",
|
||||
nu.workspace_id,
|
||||
email,
|
||||
authed.email,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
@@ -1339,7 +1354,7 @@ async fn accept_invite(
|
||||
let already_in_workspace = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE workspace_id = $1 AND email = $2)",
|
||||
&nu.workspace_id,
|
||||
&email,
|
||||
&authed.email,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
@@ -1351,14 +1366,14 @@ async fn accept_invite(
|
||||
StatusCode::CREATED,
|
||||
format!(
|
||||
"user {} accepted invite to workspace {}",
|
||||
&email, nu.workspace_id
|
||||
&authed.email, nu.workspace_id
|
||||
),
|
||||
));
|
||||
}
|
||||
let username;
|
||||
(tx, username) = add_user_to_workspace(
|
||||
(tx, username) = join_workspace(
|
||||
&nu.workspace_id,
|
||||
&email,
|
||||
&authed,
|
||||
nu.username,
|
||||
r.is_admin,
|
||||
r.operator,
|
||||
@@ -1368,29 +1383,29 @@ async fn accept_invite(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&ApiAuthed { username: username.clone(), ..authed.clone() },
|
||||
"users.accept_invite",
|
||||
ActionKind::Create,
|
||||
&nu.workspace_id,
|
||||
Some(&email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&email,
|
||||
&authed.email,
|
||||
&username,
|
||||
&db,
|
||||
&nu.workspace_id,
|
||||
windmill_git_sync::DeployedObject::User { email: email.clone() },
|
||||
Some(format!("User '{}' accepted invite", &email)),
|
||||
windmill_git_sync::DeployedObject::User { email: authed.email.clone() },
|
||||
Some(format!("User '{}' accepted invite", &authed.email)),
|
||||
rsmq,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
webhook.send_instance_event(InstanceEvent::UserJoinedWorkspace {
|
||||
email: email.clone(),
|
||||
email: authed.email.clone(),
|
||||
workspace: nu.workspace_id.clone(),
|
||||
username: username,
|
||||
});
|
||||
@@ -1398,17 +1413,20 @@ async fn accept_invite(
|
||||
StatusCode::CREATED,
|
||||
format!(
|
||||
"user {} accepted invite to workspace {}",
|
||||
&email, nu.workspace_id
|
||||
&authed.email, nu.workspace_id
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Err(Error::NotFound(format!("invite for {email} not found")))
|
||||
Err(Error::NotFound(format!(
|
||||
"invite for {} not found",
|
||||
authed.email
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_user_to_workspace<'c>(
|
||||
async fn join_workspace<'c>(
|
||||
w_id: &str,
|
||||
email: &str,
|
||||
authed: &ApiAuthed,
|
||||
username: Option<String>,
|
||||
is_admin: bool,
|
||||
operator: bool,
|
||||
@@ -1430,7 +1448,7 @@ async fn add_user_to_workspace<'c>(
|
||||
"username is not allowed when username creation is automated".to_string(),
|
||||
));
|
||||
}
|
||||
get_instance_username_or_create_pending(&mut tx, &email).await?
|
||||
get_instance_username_or_create_pending(&mut tx, &authed.email).await?
|
||||
} else {
|
||||
let username = username.ok_or(Error::BadRequest("username is required".to_string()))?;
|
||||
let already_exists_username = sqlx::query_scalar!(
|
||||
@@ -1460,7 +1478,7 @@ async fn add_user_to_workspace<'c>(
|
||||
let already_exists_email = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE workspace_id = $1 AND email = $2)",
|
||||
&w_id,
|
||||
username,
|
||||
authed.email,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?
|
||||
@@ -1469,7 +1487,7 @@ async fn add_user_to_workspace<'c>(
|
||||
if already_exists_email {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"user with email {} already exists in workspace {}",
|
||||
email, w_id
|
||||
authed.email, w_id
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -1478,7 +1496,7 @@ async fn add_user_to_workspace<'c>(
|
||||
(workspace_id, email, username, is_admin, operator)
|
||||
VALUES ($1, $2, $3, $4, $5)",
|
||||
&w_id,
|
||||
email,
|
||||
authed.email,
|
||||
username,
|
||||
is_admin,
|
||||
operator
|
||||
@@ -1496,33 +1514,30 @@ async fn add_user_to_workspace<'c>(
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&AuditAuthor { username: username.clone(), ..authed.into() },
|
||||
"users.add_to_workspace",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
Some(email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok((tx, username))
|
||||
}
|
||||
|
||||
async fn leave_instance(
|
||||
Extension(db): Extension<DB>,
|
||||
ApiAuthed { email, username, .. }: ApiAuthed,
|
||||
) -> Result<String> {
|
||||
async fn leave_instance(Extension(db): Extension<DB>, authed: ApiAuthed) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
sqlx::query!("DELETE FROM password WHERE email = $1", &email)
|
||||
sqlx::query!("DELETE FROM password WHERE email = $1", &authed.email)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"workspaces.leave",
|
||||
ActionKind::Delete,
|
||||
"global",
|
||||
Some(&email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -1553,7 +1568,7 @@ async fn get_workspace_user(
|
||||
}
|
||||
|
||||
async fn update_workspace_user(
|
||||
ApiAuthed { username, email, is_admin, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Path((w_id, username_to_update)): Path<(String, String)>,
|
||||
@@ -1561,7 +1576,7 @@ async fn update_workspace_user(
|
||||
) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
require_admin(is_admin, &username)?;
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
if let Some(a) = eu.is_admin {
|
||||
sqlx::query_scalar!(
|
||||
@@ -1598,7 +1613,7 @@ async fn update_workspace_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"users.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -1618,8 +1633,8 @@ async fn update_workspace_user(
|
||||
tx.commit().await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&email,
|
||||
&username,
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
windmill_git_sync::DeployedObject::User { email: user_email.clone() },
|
||||
@@ -1633,12 +1648,12 @@ async fn update_workspace_user(
|
||||
}
|
||||
|
||||
async fn update_user(
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Path(email_to_update): Path<String>,
|
||||
Extension(db): Extension<DB>,
|
||||
Json(eu): Json<EditUser>,
|
||||
) -> Result<String> {
|
||||
require_super_admin(&db, &email).await?;
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
if let Some(sa) = eu.is_super_admin {
|
||||
@@ -1653,7 +1668,7 @@ async fn update_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email,
|
||||
&authed,
|
||||
"users.update",
|
||||
ActionKind::Update,
|
||||
"global",
|
||||
@@ -1666,11 +1681,11 @@ async fn update_user(
|
||||
}
|
||||
|
||||
async fn delete_user(
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Path(email_to_delete): Path<String>,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> Result<String> {
|
||||
require_super_admin(&db, &email).await?;
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
sqlx::query!("DELETE FROM password WHERE email = $1", &email_to_delete)
|
||||
@@ -1702,7 +1717,7 @@ async fn delete_user(
|
||||
}
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email,
|
||||
&authed,
|
||||
"users.delete",
|
||||
ActionKind::Delete,
|
||||
"global",
|
||||
@@ -1720,14 +1735,14 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
|
||||
async fn create_user(
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(argon2): Extension<Arc<Argon2<'_>>>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Json(mut nu): Json<NewUser>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
require_super_admin(&db, &email).await?;
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
nu.email = nu.email.to_lowercase();
|
||||
@@ -1787,7 +1802,7 @@ async fn create_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email,
|
||||
&authed,
|
||||
"users.add_global",
|
||||
ActionKind::Create,
|
||||
"global",
|
||||
@@ -1795,19 +1810,20 @@ async fn create_user(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx = add_to_demo_if_exists(tx, &email).await?;
|
||||
tx = add_to_demo_if_exists(tx, &nu.email).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
invite_user_to_all_auto_invite_worspaces(&db, &nu.email, rsmq).await?;
|
||||
invite_user_to_all_auto_invite_worspaces(&db, &nu.email, rsmq, &authed).await?;
|
||||
send_email_if_possible(
|
||||
"Invited to Windmill",
|
||||
&format!(
|
||||
"You have been granted access to Windmill by {email}.
|
||||
"You have been granted access to Windmill by {}.
|
||||
|
||||
Log in and change your password: {}/user/login?email={}&password={}&rd=%2F%23user-settings
|
||||
|
||||
You can then join or create a workspace. Happy building!",
|
||||
authed.email,
|
||||
BASE_URL.read().await.clone(),
|
||||
&nu.email,
|
||||
&nu.password
|
||||
@@ -1837,14 +1853,14 @@ pub async fn send_email_if_possible_intern(subject: &str, content: &str, to: Str
|
||||
}
|
||||
|
||||
async fn delete_workspace_user(
|
||||
ApiAuthed { username, email, is_admin, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Path((w_id, username_to_delete)): Path<(String, String)>,
|
||||
) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
require_admin(is_admin, &username)?;
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let email_to_delete_o = sqlx::query_scalar!(
|
||||
"SELECT email FROM usr where username = $1 AND workspace_id = $2",
|
||||
@@ -1856,17 +1872,17 @@ async fn delete_workspace_user(
|
||||
|
||||
let email_to_delete = not_found_if_none(email_to_delete_o, "User", &username_to_delete)?;
|
||||
|
||||
let username = sqlx::query_scalar!(
|
||||
"DELETE FROM usr WHERE email = $1 AND workspace_id = $2 RETURNING username",
|
||||
sqlx::query_scalar!(
|
||||
"DELETE FROM usr WHERE email = $1 AND workspace_id = $2",
|
||||
email_to_delete,
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM usr_to_group WHERE usr = $1 AND workspace_id = $2",
|
||||
&username,
|
||||
&username_to_delete,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
@@ -1874,7 +1890,7 @@ async fn delete_workspace_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"users.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -1885,8 +1901,8 @@ async fn delete_workspace_user(
|
||||
tx.commit().await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&email,
|
||||
&username,
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
windmill_git_sync::DeployedObject::User { email: email_to_delete.clone() },
|
||||
@@ -1905,14 +1921,14 @@ async fn delete_workspace_user(
|
||||
async fn set_password(
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(argon2): Extension<Arc<Argon2<'_>>>,
|
||||
ApiAuthed { username, email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Json(EditPassword { password }): Json<EditPassword>,
|
||||
) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let custom_type = sqlx::query_scalar!(
|
||||
"SELECT login_type::TEXT FROM password WHERE email = $1",
|
||||
&email
|
||||
&authed.email
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
@@ -1921,31 +1937,32 @@ async fn set_password(
|
||||
|
||||
if custom_type != "password".to_string() {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"login type for {email} is of type {custom_type}. Cannot set password."
|
||||
"login type for {} is of type {custom_type}. Cannot set password.",
|
||||
authed.email
|
||||
)));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE password SET password_hash = $1 WHERE email = $2",
|
||||
&hash_password(argon2, password)?,
|
||||
&email,
|
||||
&authed.email,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"users.setpassword",
|
||||
ActionKind::Update,
|
||||
"global",
|
||||
Some(&email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("password of {} updated", email))
|
||||
Ok(format!("password of {} updated", authed.email))
|
||||
}
|
||||
|
||||
pub fn hash_password(argon2: Arc<Argon2>, password: String) -> Result<String> {
|
||||
@@ -2141,7 +2158,11 @@ async fn login(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email,
|
||||
&AuditAuthor {
|
||||
username: email.clone(),
|
||||
email: email.clone(),
|
||||
username_override: None,
|
||||
},
|
||||
"users.login",
|
||||
ActionKind::Create,
|
||||
"global",
|
||||
@@ -2215,23 +2236,25 @@ pub async fn create_session_token<'c>(
|
||||
|
||||
async fn create_token(
|
||||
Extension(db): Extension<DB>,
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Json(new_token): Json<NewToken>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let token = rd_string(30);
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let is_super_admin =
|
||||
sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
let is_super_admin = sqlx::query_scalar!(
|
||||
"SELECT super_admin FROM password WHERE email = $1",
|
||||
authed.email
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
sqlx::query!(
|
||||
"INSERT INTO token
|
||||
(token, email, label, expiration, super_admin, scopes)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
token,
|
||||
email,
|
||||
authed.email,
|
||||
new_token.label,
|
||||
new_token.expiration,
|
||||
is_super_admin,
|
||||
@@ -2242,14 +2265,14 @@ async fn create_token(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email,
|
||||
&authed,
|
||||
"users.token.create",
|
||||
ActionKind::Create,
|
||||
&"global",
|
||||
Some(&token[0..10]),
|
||||
None,
|
||||
)
|
||||
.instrument(tracing::info_span!("token", email = &email))
|
||||
.instrument(tracing::info_span!("token", email = &authed.email))
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok((StatusCode::CREATED, token))
|
||||
@@ -2257,11 +2280,11 @@ async fn create_token(
|
||||
|
||||
async fn impersonate(
|
||||
Extension(db): Extension<DB>,
|
||||
ApiAuthed { email, username, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Json(new_token): Json<NewToken>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let token = rd_string(30);
|
||||
require_super_admin(&db, &email).await?;
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
if new_token.impersonate_email.is_none() {
|
||||
return Err(Error::BadRequest(
|
||||
@@ -2295,7 +2318,7 @@ async fn impersonate(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"users.impersonate",
|
||||
ActionKind::Delete,
|
||||
&"global",
|
||||
@@ -2344,7 +2367,7 @@ async fn list_tokens(
|
||||
|
||||
async fn delete_token(
|
||||
Extension(db): Extension<DB>,
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Path(token_prefix): Path<String>,
|
||||
) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
@@ -2355,14 +2378,14 @@ async fn delete_token(
|
||||
AND token LIKE concat($2::text, '%')
|
||||
RETURNING concat(substring(token for 10), '*****')",
|
||||
)
|
||||
.bind(&email)
|
||||
.bind(&authed.email)
|
||||
.bind(&token_prefix)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email,
|
||||
&authed,
|
||||
"users.token.delete",
|
||||
ActionKind::Delete,
|
||||
&"global",
|
||||
@@ -2383,20 +2406,20 @@ async fn delete_token(
|
||||
async fn leave_workspace(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { username, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
sqlx::query!(
|
||||
"DELETE FROM usr WHERE workspace_id = $1 AND username = $2",
|
||||
&w_id,
|
||||
username
|
||||
authed.username
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"users.leave_workspace",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -2584,18 +2607,35 @@ async fn get_instance_username_info(
|
||||
}))
|
||||
}
|
||||
|
||||
async fn email_from_username(
|
||||
Path((w_id, username)): Path<(String, String)>,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> Result<String> {
|
||||
let email = sqlx::query_scalar!(
|
||||
"SELECT email FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
&username,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
let email = not_found_if_none(email, "user", username)?;
|
||||
|
||||
Ok(email)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RenameUser {
|
||||
new_username: String,
|
||||
}
|
||||
|
||||
async fn rename_user(
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Path(user_email): Path<String>,
|
||||
Extension(db): Extension<DB>,
|
||||
Json(ru): Json<RenameUser>,
|
||||
) -> Result<String> {
|
||||
require_super_admin(&db, &email).await?;
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
@@ -2652,7 +2692,7 @@ async fn rename_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&email,
|
||||
&authed,
|
||||
"users.rename",
|
||||
ActionKind::Update,
|
||||
"global",
|
||||
|
||||
@@ -20,7 +20,7 @@ use axum::{
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use serde_json::Value;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthorable};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
@@ -151,7 +151,7 @@ async fn get_variable(
|
||||
if decrypt_secret {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"variables.decrypt_secret",
|
||||
ActionKind::Execute,
|
||||
&w_id,
|
||||
@@ -192,7 +192,7 @@ async fn get_value(
|
||||
) -> JsonResult<String> {
|
||||
let path = path.to_path();
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
return get_value_internal(tx, &db, &w_id, &path, &authed.username)
|
||||
return get_value_internal(tx, &db, &w_id, &path, &authed)
|
||||
.await
|
||||
.map(Json);
|
||||
}
|
||||
@@ -315,7 +315,7 @@ async fn create_variable(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"variables.create",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -391,7 +391,7 @@ async fn delete_variable(
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"variables.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -547,7 +547,7 @@ async fn update_variable(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"variables.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -603,7 +603,7 @@ pub async fn get_value_internal<'c>(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
path: &str,
|
||||
username: &str,
|
||||
audit_author: &impl AuditAuthorable,
|
||||
) -> Result<String> {
|
||||
let variable_o = sqlx::query!(
|
||||
"SELECT value, account, (now() > account.expires_at) as is_expired, is_secret, path from variable
|
||||
@@ -622,7 +622,7 @@ pub async fn get_value_internal<'c>(
|
||||
let r = if variable.is_secret {
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
username,
|
||||
audit_author,
|
||||
"variables.decrypt_secret",
|
||||
ActionKind::Execute,
|
||||
&w_id,
|
||||
|
||||
@@ -34,7 +34,7 @@ use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
|
||||
use uuid::Uuid;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor, AuditAuthorable};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_common::s3_helpers::LargeFileStorage;
|
||||
@@ -448,7 +448,7 @@ async fn edit_slack_command(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_command_script",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -533,7 +533,7 @@ async fn edit_deploy_to(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_deploy_to",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -571,7 +571,8 @@ async fn auto_add_user(
|
||||
w_id: &str,
|
||||
operator: &bool,
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
) -> Result<()> {
|
||||
authorable: &impl AuditAuthorable,
|
||||
) -> Result<String> {
|
||||
let automate_username_creation = sqlx::query_scalar!(
|
||||
"SELECT value FROM global_settings WHERE name = $1",
|
||||
AUTOMATE_USERNAME_CREATION_SETTING,
|
||||
@@ -645,9 +646,24 @@ async fn auto_add_user(
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
let audit_author = if authorable.username() == authorable.email() && authorable.email() == email
|
||||
{
|
||||
// if the user is auto adding themselves (e.g. by joining the instance), we use their newly created workspace username for audit logs
|
||||
AuditAuthor {
|
||||
username: username.clone(),
|
||||
email: email.to_string(),
|
||||
username_override: None,
|
||||
}
|
||||
} else {
|
||||
AuditAuthor {
|
||||
username: authorable.username().to_string(),
|
||||
email: authorable.email().to_string(),
|
||||
username_override: authorable.username_override().map(|x| x.to_string()),
|
||||
}
|
||||
};
|
||||
audit_log(
|
||||
&mut **tx,
|
||||
&username,
|
||||
&audit_author,
|
||||
"users.auto_invite_add",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -655,7 +671,7 @@ async fn auto_add_user(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
async fn edit_auto_invite(
|
||||
@@ -720,7 +736,7 @@ async fn edit_auto_invite(
|
||||
.fetch_all(&mut *tx).await?);
|
||||
|
||||
for user in users_to_auto_add.as_ref().unwrap() {
|
||||
auto_add_user(&user.email, &w_id, &operator, &mut tx).await?;
|
||||
auto_add_user(&user.email, &w_id, &operator, &mut tx, &authed).await?;
|
||||
send_email_if_possible(
|
||||
&format!("Added to Windmill's workspace: {w_id}"),
|
||||
&format!(
|
||||
@@ -757,7 +773,7 @@ async fn edit_auto_invite(
|
||||
}
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_auto_invite_domain",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -818,7 +834,7 @@ async fn edit_webhook(
|
||||
}
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_webhook",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -862,7 +878,7 @@ async fn edit_copilot_config(
|
||||
}
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_copilot_config",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -926,7 +942,7 @@ async fn edit_large_file_storage_config(
|
||||
let args_for_audit = format!("{:?}", new_config.large_file_storage);
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_large_file_storage_config",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -994,7 +1010,7 @@ async fn edit_git_sync_config(
|
||||
let args_for_audit = format!("{:?}", new_config.git_sync_settings);
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_git_sync_config",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -1058,7 +1074,7 @@ async fn edit_default_scripts(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_default_scripts",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -1128,7 +1144,7 @@ async fn edit_default_app(
|
||||
let args_for_audit = format!("{:?}", new_config.default_app_path);
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_default_app",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -1221,7 +1237,7 @@ async fn edit_error_handler(
|
||||
}
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.edit_error_handler",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -1263,7 +1279,7 @@ async fn set_environment_variable(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspace.set_environment_variable",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
@@ -1285,7 +1301,7 @@ async fn set_environment_variable(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspace.delete_environment_variable",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
@@ -1611,7 +1627,7 @@ async fn create_workspace(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.create",
|
||||
ActionKind::Create,
|
||||
&nw.id,
|
||||
@@ -1643,7 +1659,7 @@ async fn edit_workspace(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspaces.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -1659,9 +1675,9 @@ async fn edit_workspace(
|
||||
async fn archive_workspace(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { is_admin, username, email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let mut tx = db.begin().await?;
|
||||
sqlx::query!("UPDATE workspace SET deleted = true WHERE id = $1", &w_id)
|
||||
.execute(&mut *tx)
|
||||
@@ -1669,11 +1685,11 @@ async fn archive_workspace(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"workspaces.archive",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -1685,24 +1701,24 @@ async fn archive_workspace(
|
||||
async fn leave_workspace(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { email, username, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
sqlx::query!(
|
||||
"DELETE FROM usr WHERE workspace_id = $1 AND email = $2",
|
||||
&w_id,
|
||||
&email
|
||||
&authed.email
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"workspaces.leave",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
Some(&email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -1714,9 +1730,9 @@ async fn leave_workspace(
|
||||
async fn unarchive_workspace(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { is_admin, username, email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let mut tx = db.begin().await?;
|
||||
sqlx::query!("UPDATE workspace SET deleted = false WHERE id = $1", &w_id)
|
||||
.execute(&mut *tx)
|
||||
@@ -1724,11 +1740,11 @@ async fn unarchive_workspace(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"workspaces.unarchive",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -1740,7 +1756,7 @@ async fn unarchive_workspace(
|
||||
async fn delete_workspace(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { username, email, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
) -> Result<String> {
|
||||
let w_id = match w_id.as_str() {
|
||||
"starter" => Err(Error::BadRequest(
|
||||
@@ -1752,7 +1768,7 @@ async fn delete_workspace(
|
||||
_ => Ok(w_id),
|
||||
}?;
|
||||
let mut tx = db.begin().await?;
|
||||
require_super_admin(&db, &email).await?;
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
sqlx::query!("DELETE FROM dependency_map WHERE workspace_id = $1", &w_id)
|
||||
.execute(&mut *tx)
|
||||
@@ -1859,11 +1875,11 @@ async fn delete_workspace(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"workspaces.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
Some(&email),
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -1876,6 +1892,7 @@ pub async fn invite_user_to_all_auto_invite_worspaces(
|
||||
db: &DB,
|
||||
email: &str,
|
||||
rsmq: Option<rsmq_async::MultiplexedRsmq>,
|
||||
authorable: &impl AuditAuthorable,
|
||||
) -> Result<()> {
|
||||
let mut tx = db.begin().await?;
|
||||
let domain = email.split('@').last().unwrap();
|
||||
@@ -1890,14 +1907,8 @@ pub async fn invite_user_to_all_auto_invite_worspaces(
|
||||
for r in workspaces {
|
||||
if r.auto_add.is_some() && r.auto_add.unwrap() {
|
||||
let operator = r.auto_invite_operator.unwrap_or(false);
|
||||
auto_add_user(email, &r.workspace_id, &operator, &mut tx).await?;
|
||||
let username = sqlx::query_scalar!(
|
||||
"SELECT username FROM usr WHERE workspace_id = $1 AND email = $2",
|
||||
r.workspace_id,
|
||||
email
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
let username =
|
||||
auto_add_user(email, &r.workspace_id, &operator, &mut tx, authorable).await?;
|
||||
auto_added_workspace_usernames.push((r.workspace_id, username));
|
||||
} else {
|
||||
sqlx::query!(
|
||||
@@ -1999,14 +2010,14 @@ If you do not have an account on {}, login with SSO or ask an admin to create an
|
||||
}
|
||||
|
||||
async fn add_user(
|
||||
ApiAuthed { username, email, is_admin, .. }: ApiAuthed,
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(mut nu): Json<NewWorkspaceUser>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
require_admin(is_admin, &username)?;
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
nu.email = nu.email.to_lowercase();
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
@@ -2023,7 +2034,7 @@ async fn add_user(
|
||||
if already_exists_email {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"user with email {} already exists in workspace {}",
|
||||
email, w_id
|
||||
nu.email, w_id
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -2091,11 +2102,11 @@ async fn add_user(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&username,
|
||||
&authed,
|
||||
"users.add_to_workspace",
|
||||
ActionKind::Create,
|
||||
&w_id,
|
||||
Some(&email),
|
||||
Some(&nu.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -2103,8 +2114,8 @@ async fn add_user(
|
||||
tx.commit().await?;
|
||||
|
||||
handle_deployment_metadata(
|
||||
&email,
|
||||
&username,
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
windmill_git_sync::DeployedObject::User { email: nu.email.clone() },
|
||||
@@ -2117,9 +2128,10 @@ async fn add_user(
|
||||
send_email_if_possible(
|
||||
&format!("Added to Windmill's workspace: {w_id}"),
|
||||
&format!(
|
||||
"You have been granted access to Windmill's workspace {w_id} by {email}
|
||||
"You have been granted access to Windmill's workspace {w_id} by {}
|
||||
|
||||
If you do not have an account on {}, login with SSO or ask an admin to create an account for you.",
|
||||
authed.email,
|
||||
BASE_URL.read().await.clone()
|
||||
),
|
||||
&nu.email,
|
||||
@@ -2810,7 +2822,7 @@ async fn change_workspace_name(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspace.change_workspace_name",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
@@ -3135,7 +3147,7 @@ async fn change_workspace_id(
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
&authed,
|
||||
"workspace.change_workspace_id",
|
||||
ActionKind::Update,
|
||||
&rw.new_id,
|
||||
|
||||
@@ -15,10 +15,37 @@ use windmill_common::{
|
||||
use crate::{ActionKind, AuditLog, ListAuditLogQuery};
|
||||
use sqlx::{Postgres, Transaction};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuditAuthor {
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub username_override: Option<String>,
|
||||
}
|
||||
|
||||
impl AuditAuthorable for AuditAuthor {
|
||||
fn email(&self) -> &str {
|
||||
&self.email
|
||||
}
|
||||
|
||||
fn username(&self) -> &str {
|
||||
&self.username
|
||||
}
|
||||
|
||||
fn username_override(&self) -> Option<&str> {
|
||||
self.username_override.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AuditAuthorable {
|
||||
fn username(&self) -> &str;
|
||||
fn email(&self) -> &str;
|
||||
fn username_override(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>(
|
||||
_db: E,
|
||||
_username: &str,
|
||||
_authorable: &impl AuditAuthorable,
|
||||
mut _operation: &str,
|
||||
_action_kind: ActionKind,
|
||||
_w_id: &str,
|
||||
|
||||
@@ -40,7 +40,7 @@ use tokio::{sync::RwLock, time::sleep};
|
||||
use tracing::{instrument, Instrument};
|
||||
use ulid::Ulid;
|
||||
use uuid::Uuid;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
use windmill_common::worker::PriorityTags;
|
||||
@@ -3600,9 +3600,23 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
JobKind::DeploymentCallback => "jobs.run.deployment_callback",
|
||||
};
|
||||
|
||||
let audit_author = if format!("u/{user}") != permissioned_as && user != permissioned_as {
|
||||
AuditAuthor {
|
||||
email: email.to_string(),
|
||||
username: permissioned_as.trim_start_matches("u/").to_string(),
|
||||
username_override: Some(user.to_string()),
|
||||
}
|
||||
} else {
|
||||
AuditAuthor {
|
||||
email: email.to_string(),
|
||||
username: user.to_string(),
|
||||
username_override: None,
|
||||
}
|
||||
};
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&user,
|
||||
&audit_author,
|
||||
operation_name,
|
||||
ActionKind::Execute,
|
||||
workspace_id,
|
||||
|
||||
@@ -127,12 +127,20 @@ pub async fn create_token_for_owner_in_bg(
|
||||
let owner = job.permissioned_as.clone();
|
||||
let email = job.email.clone();
|
||||
let job_id = job.id.clone();
|
||||
|
||||
let label = if job.permissioned_as != format!("u/{}", job.created_by)
|
||||
&& job.permissioned_as != job.created_by
|
||||
{
|
||||
format!("ephemeral-script-end-user-{}", job.created_by)
|
||||
} else {
|
||||
"ephemeral-script".to_string()
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
let token = create_token_for_owner(
|
||||
&db.clone(),
|
||||
&w_id,
|
||||
&owner,
|
||||
"ephemeral-script",
|
||||
&label,
|
||||
*SCRIPT_TOKEN_EXPIRY,
|
||||
&email,
|
||||
&job_id,
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
{date}
|
||||
</Cell>
|
||||
</tr>
|
||||
{#each logsByDay as { id, timestamp, username, operation: op, action_kind, resource }}
|
||||
{#each logsByDay as { id, timestamp, username, operation: op, action_kind, resource, parameters }}
|
||||
<Row
|
||||
hoverable
|
||||
selected={id === selectedId}
|
||||
@@ -108,8 +108,11 @@
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<div class="whitespace-nowrap overflow-x-auto no-scrollbar w-20">
|
||||
<div class="whitespace-nowrap overflow-x-auto no-scrollbar w-48">
|
||||
{username}
|
||||
{#if parameters && 'end_user' in parameters}
|
||||
<span> (end user: {parameters.end_user})</span>
|
||||
{/if}
|
||||
</div>
|
||||
<Button
|
||||
color="light"
|
||||
|
||||
Reference in New Issue
Block a user