Enhance worker schedule access control with detailed debugging information: Updated the user_can_read_worker_schedules function to return a WorkerScheduleAccessDebug struct, providing detailed error messages and access information. Improved WebSocket server responses to include debug data when access is denied, facilitating better troubleshooting and monitoring of user permissions.
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 1m38s

This commit is contained in:
Torsten Schulz (local)
2026-05-08 10:45:32 +02:00
parent f7eb53ae90
commit d27230a0b5

View File

@@ -480,15 +480,24 @@ async fn handle_connection<S>(
let guard = user_id_for_incoming.lock().await; let guard = user_id_for_incoming.lock().await;
guard.clone() guard.clone()
}; };
let allowed = uid_opt let access = uid_opt
.as_deref() .as_deref()
.map(|uid| user_can_read_worker_schedules(&pool_for_incoming, uid)) .map(|uid| user_can_read_worker_schedules(&pool_for_incoming, uid))
.unwrap_or(false); .unwrap_or(WorkerScheduleAccessDebug {
if !allowed { requested_user_id: "".to_string(),
resolved_community_user_id: None,
matched_by: "missing_setUserId".to_string(),
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some("missing_setUserId".to_string()),
});
if !access.allowed {
let payload = serde_json::json!({ let payload = serde_json::json!({
"event": "getWorkerSchedulesResponse", "event": "getWorkerSchedulesResponse",
"ok": false, "ok": false,
"error": "forbidden" "error": "forbidden",
"debug": access
}) })
.to_string(); .to_string();
let _ = client_tx_incoming.send(payload).await; let _ = client_tx_incoming.send(payload).await;
@@ -514,15 +523,24 @@ async fn handle_connection<S>(
let guard = user_id_for_incoming.lock().await; let guard = user_id_for_incoming.lock().await;
guard.clone() guard.clone()
}; };
let allowed = uid_opt let access = uid_opt
.as_deref() .as_deref()
.map(|uid| user_can_read_worker_schedules(&pool_for_incoming, uid)) .map(|uid| user_can_read_worker_schedules(&pool_for_incoming, uid))
.unwrap_or(false); .unwrap_or(WorkerScheduleAccessDebug {
if !allowed { requested_user_id: "".to_string(),
resolved_community_user_id: None,
matched_by: "missing_setUserId".to_string(),
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some("missing_setUserId".to_string()),
});
if !access.allowed {
let payload = serde_json::json!({ let payload = serde_json::json!({
"event": "getWorkerSchedulesDetailedResponse", "event": "getWorkerSchedulesDetailedResponse",
"ok": false, "ok": false,
"error": "forbidden" "error": "forbidden",
"debug": access
}) })
.to_string(); .to_string();
let _ = client_tx_incoming.send(payload).await; let _ = client_tx_incoming.send(payload).await;
@@ -704,14 +722,47 @@ async fn handle_connection<S>(
println!("[WebSocketServer] Verbindung geschlossen: {}", peer_addr); println!("[WebSocketServer] Verbindung geschlossen: {}", peer_addr);
} }
fn user_can_read_worker_schedules(pool: &ConnectionPool, user_id_raw: &str) -> bool { #[derive(Debug, Clone, Serialize)]
struct WorkerScheduleAccessDebug {
requested_user_id: String,
resolved_community_user_id: Option<i32>,
matched_by: String,
allowed_direct: bool,
allowed_falukant: bool,
allowed: bool,
error: Option<String>,
}
fn user_can_read_worker_schedules(pool: &ConnectionPool, user_id_raw: &str) -> WorkerScheduleAccessDebug {
let mut conn = match pool.get() { let mut conn = match pool.get() {
Ok(c) => c, Ok(c) => c,
Err(_) => return false, Err(e) => {
return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: None,
matched_by: "none".to_string(),
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some(format!("db_connection_failed: {e}")),
};
}
}; };
let uid = if let Ok(v) = user_id_raw.parse::<i32>() { let (uid, matched_by) = if let Ok(v) = user_id_raw.parse::<i32>() {
if v > 0 { v } else { return false; } if v > 0 {
(v, "numeric".to_string())
} else {
return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: None,
matched_by: "numeric_invalid".to_string(),
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some("invalid_numeric_user_id".to_string()),
};
}
} else { } else {
// Fallback: setUserId kann auch username/hashed_id aus dem Community-User sein. // Fallback: setUserId kann auch username/hashed_id aus dem Community-User sein.
let resolve_sql = r#" let resolve_sql = r#"
@@ -721,23 +772,58 @@ SELECT id
OR LOWER(COALESCE(hashed_id, '')) = LOWER($1::text) OR LOWER(COALESCE(hashed_id, '')) = LOWER($1::text)
LIMIT 1; LIMIT 1;
"#; "#;
if conn.prepare("ws_resolve_community_user_id", resolve_sql).is_err() { if let Err(e) = conn.prepare("ws_resolve_community_user_id", resolve_sql) {
return false; return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: None,
matched_by: "lookup_prepare_failed".to_string(),
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some(format!("resolve_prepare_failed: {e}")),
};
} }
let rows = match conn.execute("ws_resolve_community_user_id", &[&user_id_raw]) { let rows = match conn.execute("ws_resolve_community_user_id", &[&user_id_raw]) {
Ok(r) => r, Ok(r) => r,
Err(_) => return false, Err(e) => {
return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: None,
matched_by: "lookup_execute_failed".to_string(),
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some(format!("resolve_execute_failed: {e}")),
};
}
}; };
let Some(row) = rows.first() else { let Some(row) = rows.first() else {
return false; return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: None,
matched_by: "username_or_hashed_id".to_string(),
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some("user_not_found".to_string()),
};
}; };
row.get("id") let resolved = row.get("id")
.and_then(|v| v.parse::<i32>().ok()) .and_then(|v| v.parse::<i32>().ok())
.filter(|v| *v > 0) .filter(|v| *v > 0)
.unwrap_or(-1) .unwrap_or(-1);
(resolved, "username_or_hashed_id".to_string())
}; };
if uid <= 0 { if uid <= 0 {
return false; return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: None,
matched_by,
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some("resolved_user_id_invalid".to_string()),
};
} }
let sql = r#" let sql = r#"
@@ -768,8 +854,16 @@ EXISTS (
) AS allowed_falukant; ) AS allowed_falukant;
"#; "#;
if conn.prepare("ws_can_read_worker_schedules", sql).is_err() { if let Err(e) = conn.prepare("ws_can_read_worker_schedules", sql) {
return false; return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: Some(uid),
matched_by,
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some(format!("rights_prepare_failed: {e}")),
};
} }
let admin = RIGHT_ADMIN.to_string(); let admin = RIGHT_ADMIN.to_string();
let allowed_right = RIGHT_WORKER_SCHEDULE.to_string(); let allowed_right = RIGHT_WORKER_SCHEDULE.to_string();
@@ -779,10 +873,28 @@ EXISTS (
&[&uid, &admin, &allowed_right, &mainadmin], &[&uid, &admin, &allowed_right, &mainadmin],
) { ) {
Ok(r) => r, Ok(r) => r,
Err(_) => return false, Err(e) => {
return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: Some(uid),
matched_by,
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some(format!("rights_execute_failed: {e}")),
};
}
}; };
let Some(row) = rows.first() else { let Some(row) = rows.first() else {
return false; return WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: Some(uid),
matched_by,
allowed_direct: false,
allowed_falukant: false,
allowed: false,
error: Some("rights_empty_result".to_string()),
};
}; };
let direct = row let direct = row
.get("allowed_direct") .get("allowed_direct")
@@ -792,7 +904,15 @@ EXISTS (
.get("allowed_falukant") .get("allowed_falukant")
.map(|v| v == "true" || v == "t" || v == "1") .map(|v| v == "true" || v == "t" || v == "1")
.unwrap_or(false); .unwrap_or(false);
direct || falukant WorkerScheduleAccessDebug {
requested_user_id: user_id_raw.to_string(),
resolved_community_user_id: Some(uid),
matched_by,
allowed_direct: direct,
allowed_falukant: falukant,
allowed: direct || falukant,
error: None,
}
} }
fn build_worker_schedule_overview(now_secs: u64) -> Vec<WorkerSchedule> { fn build_worker_schedule_overview(now_secs: u64) -> Vec<WorkerSchedule> {