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
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 1m38s
This commit is contained in:
@@ -480,15 +480,24 @@ async fn handle_connection<S>(
|
||||
let guard = user_id_for_incoming.lock().await;
|
||||
guard.clone()
|
||||
};
|
||||
let allowed = uid_opt
|
||||
let access = uid_opt
|
||||
.as_deref()
|
||||
.map(|uid| user_can_read_worker_schedules(&pool_for_incoming, uid))
|
||||
.unwrap_or(false);
|
||||
if !allowed {
|
||||
.unwrap_or(WorkerScheduleAccessDebug {
|
||||
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!({
|
||||
"event": "getWorkerSchedulesResponse",
|
||||
"ok": false,
|
||||
"error": "forbidden"
|
||||
"error": "forbidden",
|
||||
"debug": access
|
||||
})
|
||||
.to_string();
|
||||
let _ = client_tx_incoming.send(payload).await;
|
||||
@@ -514,15 +523,24 @@ async fn handle_connection<S>(
|
||||
let guard = user_id_for_incoming.lock().await;
|
||||
guard.clone()
|
||||
};
|
||||
let allowed = uid_opt
|
||||
let access = uid_opt
|
||||
.as_deref()
|
||||
.map(|uid| user_can_read_worker_schedules(&pool_for_incoming, uid))
|
||||
.unwrap_or(false);
|
||||
if !allowed {
|
||||
.unwrap_or(WorkerScheduleAccessDebug {
|
||||
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!({
|
||||
"event": "getWorkerSchedulesDetailedResponse",
|
||||
"ok": false,
|
||||
"error": "forbidden"
|
||||
"error": "forbidden",
|
||||
"debug": access
|
||||
})
|
||||
.to_string();
|
||||
let _ = client_tx_incoming.send(payload).await;
|
||||
@@ -704,14 +722,47 @@ async fn handle_connection<S>(
|
||||
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() {
|
||||
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>() {
|
||||
if v > 0 { v } else { return false; }
|
||||
let (uid, matched_by) = if let Ok(v) = user_id_raw.parse::<i32>() {
|
||||
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 {
|
||||
// Fallback: setUserId kann auch username/hashed_id aus dem Community-User sein.
|
||||
let resolve_sql = r#"
|
||||
@@ -721,23 +772,58 @@ SELECT id
|
||||
OR LOWER(COALESCE(hashed_id, '')) = LOWER($1::text)
|
||||
LIMIT 1;
|
||||
"#;
|
||||
if conn.prepare("ws_resolve_community_user_id", resolve_sql).is_err() {
|
||||
return false;
|
||||
if let Err(e) = conn.prepare("ws_resolve_community_user_id", resolve_sql) {
|
||||
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]) {
|
||||
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 {
|
||||
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())
|
||||
.filter(|v| *v > 0)
|
||||
.unwrap_or(-1)
|
||||
.unwrap_or(-1);
|
||||
(resolved, "username_or_hashed_id".to_string())
|
||||
};
|
||||
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#"
|
||||
@@ -768,8 +854,16 @@ EXISTS (
|
||||
) AS allowed_falukant;
|
||||
"#;
|
||||
|
||||
if conn.prepare("ws_can_read_worker_schedules", sql).is_err() {
|
||||
return false;
|
||||
if let Err(e) = conn.prepare("ws_can_read_worker_schedules", sql) {
|
||||
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 allowed_right = RIGHT_WORKER_SCHEDULE.to_string();
|
||||
@@ -779,10 +873,28 @@ EXISTS (
|
||||
&[&uid, &admin, &allowed_right, &mainadmin],
|
||||
) {
|
||||
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 {
|
||||
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
|
||||
.get("allowed_direct")
|
||||
@@ -792,7 +904,15 @@ EXISTS (
|
||||
.get("allowed_falukant")
|
||||
.map(|v| v == "true" || v == "t" || v == "1")
|
||||
.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> {
|
||||
|
||||
Reference in New Issue
Block a user