helios_auth/policy/
mod.rs1use crate::error::{AuthError, FhirOperation};
2use crate::principal::Principal;
3use crate::scope::SmartPermissions;
4
5pub struct SmartScopePolicy;
7
8impl SmartScopePolicy {
9 pub fn check(
14 principal: &Principal,
15 resource_type: &str,
16 operation: FhirOperation,
17 ) -> Result<(), AuthError> {
18 let permission = Self::operation_to_permission(operation);
19
20 if principal.scopes.is_permitted(resource_type, permission) {
21 Ok(())
22 } else {
23 Err(AuthError::Forbidden {
24 resource_type: resource_type.to_string(),
25 operation: operation.to_string(),
26 })
27 }
28 }
29
30 fn operation_to_permission(operation: FhirOperation) -> SmartPermissions {
32 match operation {
33 FhirOperation::Read => SmartPermissions::READ,
34 FhirOperation::Search => SmartPermissions::SEARCH,
35 FhirOperation::Create => SmartPermissions::CREATE,
36 FhirOperation::Update => SmartPermissions::UPDATE,
37 FhirOperation::Delete => SmartPermissions::DELETE,
38 }
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::scope::ScopeSet;
46 use chrono::Utc;
47
48 fn make_principal(scope_str: &str) -> Principal {
49 Principal {
50 subject: "test-client".to_string(),
51 issuer: "https://idp.example.com".to_string(),
52 tenant_id: Some("tenant-1".to_string()),
53 scopes: ScopeSet::parse(scope_str),
54 jti: None,
55 expires_at: Utc::now() + chrono::Duration::hours(1),
56 custom_claims: serde_json::Map::new(),
57 }
58 }
59
60 #[test]
61 fn test_read_permitted() {
62 let principal = make_principal("system/Patient.rs");
63 assert!(SmartScopePolicy::check(&principal, "Patient", FhirOperation::Read).is_ok());
64 }
65
66 #[test]
67 fn test_search_permitted() {
68 let principal = make_principal("system/Patient.rs");
69 assert!(SmartScopePolicy::check(&principal, "Patient", FhirOperation::Search).is_ok());
70 }
71
72 #[test]
73 fn test_create_denied() {
74 let principal = make_principal("system/Patient.rs");
75 assert!(SmartScopePolicy::check(&principal, "Patient", FhirOperation::Create).is_err());
76 }
77
78 #[test]
79 fn test_wrong_resource_type() {
80 let principal = make_principal("system/Patient.rs");
81 assert!(SmartScopePolicy::check(&principal, "Observation", FhirOperation::Read).is_err());
82 }
83
84 #[test]
85 fn test_wildcard_full_access() {
86 let principal = make_principal("system/*.cruds");
87 assert!(SmartScopePolicy::check(&principal, "Patient", FhirOperation::Create).is_ok());
88 assert!(SmartScopePolicy::check(&principal, "Observation", FhirOperation::Delete).is_ok());
89 assert!(SmartScopePolicy::check(&principal, "Condition", FhirOperation::Search).is_ok());
90 }
91
92 #[test]
93 fn test_multiple_scopes() {
94 let principal = make_principal("system/Patient.rs system/Observation.crud");
95 assert!(SmartScopePolicy::check(&principal, "Patient", FhirOperation::Read).is_ok());
96 assert!(SmartScopePolicy::check(&principal, "Patient", FhirOperation::Create).is_err());
97 assert!(SmartScopePolicy::check(&principal, "Observation", FhirOperation::Create).is_ok());
98 assert!(SmartScopePolicy::check(&principal, "Observation", FhirOperation::Search).is_err());
99 }
100
101 #[test]
102 fn test_empty_scopes_deny_all() {
103 let principal = make_principal("");
104 assert!(SmartScopePolicy::check(&principal, "Patient", FhirOperation::Read).is_err());
105 }
106}