Skip to main content

hirn_engine/
policy.rs

1//! Cedar-based authorization for hirn.
2//!
3//! The core [`PolicyEngine`], types, and error definitions live in the
4//! [`hirn_policy`] crate. This module re-exports them and provides the
5//! [`PolicyNamespaceResolver`] bridge between `hirn-policy` and
6//! `hirn-storage::NamespacePolicy`.
7
8// ── Re-exports from hirn-policy ─────────────────────────────────────────
9
10pub use hirn_policy::{
11    Action, AuthzDecision, AuthzRequest, DEFAULT_OPEN_POLICY, DEFAULT_SCHEMA, EntityKind,
12    PolicyEngine, PolicyError,
13};
14
15// ── NamespacePolicy adapter ─────────────────────────────────────────────
16
17use std::sync::Arc;
18
19/// Bridges [`PolicyEngine`] to [`hirn_storage::NamespacePolicy`] for
20/// storage-level scan filtering.
21///
22/// For each registered namespace, checks whether the principal is authorized to
23/// perform the configured action. Only namespaces that pass the Cedar check are
24/// returned as allowed.
25///
26/// When the policy engine is in open mode, returns `None` (permit all).
27pub struct PolicyNamespaceResolver {
28    engine: Arc<PolicyEngine>,
29    /// The Cedar action used to test namespace access (typically `Recall`).
30    action: Action,
31}
32
33impl PolicyNamespaceResolver {
34    /// Create a resolver that checks the given action for each namespace.
35    pub fn new(engine: Arc<PolicyEngine>, action: Action) -> Self {
36        Self { engine, action }
37    }
38
39    /// Create a resolver that checks `Recall` access for each namespace.
40    pub fn for_recall(engine: Arc<PolicyEngine>) -> Self {
41        Self::new(engine, Action::Recall)
42    }
43}
44
45#[async_trait::async_trait]
46impl hirn_storage::NamespacePolicy for PolicyNamespaceResolver {
47    async fn allowed_namespaces(&self, principal: &str) -> Option<Vec<String>> {
48        self.engine.allowed_namespaces_for(principal, self.action)
49    }
50}
51
52// ── Tests ───────────────────────────────────────────────────────────────
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use hirn_storage::NamespacePolicy;
58
59    #[tokio::test(flavor = "multi_thread")]
60    async fn policy_namespace_resolver_open_mode() {
61        let engine = Arc::new(PolicyEngine::open_mode());
62        let resolver = PolicyNamespaceResolver::for_recall(engine);
63        let result = resolver.allowed_namespaces("anyone").await;
64        // Open mode -> None (permit all).
65        assert!(result.is_none());
66    }
67
68    #[tokio::test(flavor = "multi_thread")]
69    async fn policy_namespace_resolver_filters_namespaces() {
70        let engine = Arc::new(
71            PolicyEngine::new(DEFAULT_SCHEMA, &[("default.cedar", DEFAULT_OPEN_POLICY)]).unwrap(),
72        );
73        engine.register_realm("production", "prod realm").unwrap();
74        engine
75            .register_namespace("ns_a", "public", "production")
76            .unwrap();
77        engine
78            .register_namespace("ns_b", "public", "production")
79            .unwrap();
80        engine
81            .register_agent("agent-1", 50, "2024-01-01", &[])
82            .unwrap();
83
84        let resolver = PolicyNamespaceResolver::for_recall(engine);
85        let result = resolver.allowed_namespaces("agent-1").await;
86
87        // With default open policy, all namespaces should be allowed.
88        assert!(result.is_some());
89        let mut allowed = result.unwrap();
90        allowed.sort();
91        assert_eq!(allowed, vec!["ns_a", "ns_b"]);
92    }
93}