From 741aaf0e4099711236b0ea1bca6d9f971d239bd3 Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 5 May 2026 23:42:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(health):=20FHIR=20allowed=5Fpatient=5Fids?= =?UTF-8?q?=3DNone=20=E6=8B=92=E7=BB=9D=E6=89=80=E6=9C=89=E8=AE=BF?= =?UTF-8?q?=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/erp-health/src/fhir/handler.rs | 77 ++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/crates/erp-health/src/fhir/handler.rs b/crates/erp-health/src/fhir/handler.rs index aad91b5..6cbf507 100644 --- a/crates/erp-health/src/fhir/handler.rs +++ b/crates/erp-health/src/fhir/handler.rs @@ -70,14 +70,19 @@ pub struct FhirSearchParams { /// 检查单个 patient_id 是否在 OAuth 客户端的允许范围内 fn enforce_patient_scope(fhir_ctx: &FhirAuthContext, patient_id: Uuid) -> Result<(), AppError> { - if let Some(ref allowed) = fhir_ctx.allowed_patient_ids { - if !allowed.iter().any(|id| id == &patient_id.to_string()) { - tracing::warn!( - client_id = %fhir_ctx.client_id, - requested_patient = %patient_id, - "FHIR 客户端尝试访问授权范围外的患者" - ); - return Err(AppError::Forbidden("Access denied: patient not in allowed scope".into())); + match &fhir_ctx.allowed_patient_ids { + Some(allowed) if !allowed.is_empty() => { + if !allowed.iter().any(|id| id == &patient_id.to_string()) { + tracing::warn!( + client_id = %fhir_ctx.client_id, + requested_patient = %patient_id, + "FHIR 客户端尝试访问授权范围外的患者" + ); + return Err(AppError::Forbidden("Access denied: patient not in allowed scope".into())); + } + } + _ => { + return Err(AppError::Forbidden("OAuth client has no patient access configured".into())); } } Ok(()) @@ -751,3 +756,59 @@ pub async fn patient_everything( "entry": entries, }))) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::oauth::middleware::FhirAuthContext; + + fn default_fhir_ctx() -> FhirAuthContext { + FhirAuthContext { + client_id: Uuid::now_v7(), + tenant_id: Uuid::now_v7(), + scopes: vec!["patient/*.read".to_string()], + allowed_patient_ids: None, + } + } + + #[test] + fn test_enforce_patient_scope_none_is_denied() { + let fhir_ctx = FhirAuthContext { + allowed_patient_ids: None, + ..default_fhir_ctx() + }; + let result = enforce_patient_scope(&fhir_ctx, Uuid::now_v7()); + assert!(result.is_err(), "None should deny all access"); + } + + #[test] + fn test_enforce_patient_scope_empty_vec_is_denied() { + let fhir_ctx = FhirAuthContext { + allowed_patient_ids: Some(vec![]), + ..default_fhir_ctx() + }; + let result = enforce_patient_scope(&fhir_ctx, Uuid::now_v7()); + assert!(result.is_err(), "Empty list should deny all access"); + } + + #[test] + fn test_enforce_patient_scope_allowed_access() { + let patient_id = Uuid::now_v7(); + let fhir_ctx = FhirAuthContext { + allowed_patient_ids: Some(vec![patient_id.to_string()]), + ..default_fhir_ctx() + }; + let result = enforce_patient_scope(&fhir_ctx, patient_id); + assert!(result.is_ok(), "Patient in allowed list should succeed"); + } + + #[test] + fn test_enforce_patient_scope_wrong_patient_denied() { + let fhir_ctx = FhirAuthContext { + allowed_patient_ids: Some(vec![Uuid::now_v7().to_string()]), + ..default_fhir_ctx() + }; + let result = enforce_patient_scope(&fhir_ctx, Uuid::now_v7()); + assert!(result.is_err(), "Patient not in allowed list should be denied"); + } +}