Skip to main content

authz_core/
policy_provider.rs

1//! PolicyProvider trait — abstracts authorization policy (TypeSystem) loading.
2//!
3//! `CoreResolver` accepts any `PolicyProvider` so that:
4//!   - `pgauthz` / `sqliteauthz` use `StaticPolicyProvider` (one global policy, loaded once)
5//!   - `authz-saas` implements a dynamic provider that resolves per-tenant policies
6
7use std::sync::Arc;
8
9use async_trait::async_trait;
10
11use crate::error::AuthzError;
12use crate::type_system::TypeSystem;
13
14/// Provides the active authorization policy (as a compiled `TypeSystem`) to the resolver.
15///
16/// Implementations decide how the policy is loaded, cached, and scoped.
17/// The `CoreResolver` never interprets *why* a particular `TypeSystem` is returned —
18/// tenant routing, model versioning, and caching are entirely the provider's concern.
19#[async_trait]
20pub trait PolicyProvider: Send + Sync {
21    async fn get_policy(&self) -> Result<Arc<TypeSystem>, AuthzError>;
22}
23
24/// A `PolicyProvider` backed by a single pre-loaded `TypeSystem`.
25///
26/// Used by single-tenant deployments (`pgauthz`, `sqliteauthz`) where the policy is
27/// loaded once (or cached externally) before the resolver is constructed.
28#[derive(Clone)]
29pub struct StaticPolicyProvider(Arc<TypeSystem>);
30
31impl StaticPolicyProvider {
32    /// Create from an owned `TypeSystem`.
33    pub fn new(type_system: TypeSystem) -> Self {
34        Self(Arc::new(type_system))
35    }
36
37    /// Create from an already-shared `Arc<TypeSystem>` (e.g. from a cache layer).
38    pub fn from_arc(type_system: Arc<TypeSystem>) -> Self {
39        Self(type_system)
40    }
41}
42
43#[async_trait]
44impl PolicyProvider for StaticPolicyProvider {
45    async fn get_policy(&self) -> Result<Arc<TypeSystem>, AuthzError> {
46        Ok(self.0.clone())
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::model_parser::parse_dsl;
54
55    #[tokio::test]
56    async fn static_provider_returns_type_system() {
57        let model = parse_dsl("type user {}").unwrap();
58        let ts = TypeSystem::new(model);
59        let provider = StaticPolicyProvider::new(ts);
60        let result = provider.get_policy().await.unwrap();
61        assert!(result.get_type("user").is_some());
62    }
63
64    #[tokio::test]
65    async fn from_arc_returns_same_type_system() {
66        let model = parse_dsl("type document { relations define viewer: [user] }").unwrap();
67        let ts = Arc::new(TypeSystem::new(model));
68        let provider = StaticPolicyProvider::from_arc(ts.clone());
69        let result = provider.get_policy().await.unwrap();
70        assert!(result.get_relation("document", "viewer").is_some());
71    }
72}