Skip to main content

haste_access_control/
lib.rs

1use crate::context::PermissionLevel;
2use haste_fhir_client::FHIRClient;
3use haste_fhir_model::r4::generated::{
4    resources::AccessPolicyV2,
5    terminology::{AccessPolicyv2Engine, IssueType},
6};
7use haste_fhir_operation_error::OperationOutcomeError;
8use std::sync::Arc;
9
10pub mod context;
11mod engine;
12mod request_reflection;
13mod utilities;
14
15/// Evaluates an access policy using the configured policy engine.
16///
17/// # Errors
18///
19/// Returns an [`OperationOutcomeError`] when:
20/// - the policy engine denies access,
21/// - the rule engine fails while evaluating rules,
22/// - the policy contains an invalid or unsupported configuration.
23pub async fn evaluate_policy<
24    CTX: Send + Sync + Clone + 'static,
25    Client: FHIRClient<CTX, OperationOutcomeError> + Send + Sync + 'static,
26>(
27    context: Arc<context::PolicyContext<CTX, Client>>,
28    policy: Arc<AccessPolicyV2>,
29) -> Result<PermissionLevel, OperationOutcomeError> {
30    match &policy.engine {
31        policy_engine if policy_engine == &AccessPolicyv2Engine::full_access() => {
32            Ok(engine::full_access::evaluate(policy.as_ref()))
33        }
34        policy_engine if policy_engine == &AccessPolicyv2Engine::rule_engine() => {
35            Ok(engine::rule_engine::pdp::evaluate(context, policy).await?)
36        }
37        policy_engine if policy_engine == &AccessPolicyv2Engine::null() => {
38            Err(OperationOutcomeError::fatal(
39                haste_fhir_model::r4::generated::terminology::IssueType::forbidden(),
40                "Access policy denies access.".to_string(),
41            ))
42        }
43        _ => Err(OperationOutcomeError::fatal(
44            haste_fhir_model::r4::generated::terminology::IssueType::invalid(),
45            "Unsupported policy engine.".to_string(),
46        )),
47    }
48}
49
50/// Evaluates a list of access policies and returns the updated policy context
51/// when access is granted.
52///
53/// Policies are evaluated in order. The first policy returning
54/// [`PermissionLevel::Allow`] grants access.
55///
56/// # Errors
57///
58/// Returns [`OperationOutcomeError`] when:
59/// - no policy grants access,
60/// - the evaluated policy returns an evaluation error,
61/// - the policy context cannot be recovered after granting access.
62pub async fn evaluate_policies<
63    CTX: Send + Sync + Clone + 'static,
64    Client: FHIRClient<CTX, OperationOutcomeError> + Send + Sync + 'static,
65>(
66    context: context::PolicyContext<CTX, Client>,
67    policies: &Vec<Arc<AccessPolicyV2>>,
68) -> Result<context::PolicyContext<CTX, Client>, OperationOutcomeError> {
69    let mut outcomes = vec![];
70    let context = Arc::new(context);
71
72    for policy in policies {
73        let result = evaluate_policy(context.clone(), policy.clone()).await;
74        if let Ok(permission) = result {
75            if permission == PermissionLevel::Allow {
76                return Arc::into_inner(context).ok_or_else(|| {
77                    OperationOutcomeError::error(
78                        IssueType::forbidden(),
79                        "Failed to retrieve policy context.".to_string(),
80                    )
81                });
82            }
83        } else if let Err(e) = result {
84            outcomes.push(e);
85        }
86    }
87
88    Err(OperationOutcomeError::error(
89        IssueType::forbidden(),
90        "No policy has granted access to your request.".to_string(),
91    ))
92}