core_policy/
authorizer.rs

1//! Authorization evaluation logic (SRP - Single Responsibility Principle)
2//!
3//! This module extracts the authorization logic from the `Policy` God Object,
4//! providing a focused, testable component for evaluating access control rules.
5//!
6//! ## Architecture
7//!
8//! - **SRP**: Only responsible for authorization evaluation
9//! - **Testable**: Can be tested independently without Policy overhead
10//! - **Reusable**: Can be used with different rule sources
11
12use crate::policy::{Action, PolicyRule, Resource};
13use alloc::collections::BTreeMap;
14use alloc::string::String;
15
16/// Evaluates authorization rules (SRP - extracted from Policy)
17///
18/// This struct is responsible **only** for evaluating whether a given
19/// peer/action/resource combination is allowed by a set of rules.
20///
21/// It does NOT handle:
22/// - Policy construction
23/// - Policy validation
24/// - Policy serialization
25/// - Rule management
26///
27/// ## Example
28///
29/// ```
30/// use core_policy::{PolicyRule, Action, Resource};
31/// use core_policy::authorizer::PolicyAuthorizer;
32///
33/// let rules = vec![
34///     PolicyRule::new("alice".to_string(), Action::Read, Resource::All),
35/// ];
36///
37/// let authorizer = PolicyAuthorizer::new(&rules);
38/// assert!(authorizer.is_allowed("alice", &Action::Read, &Resource::File("/docs/file.txt".into())));
39/// assert!(!authorizer.is_allowed("bob", &Action::Read, &Resource::File("/docs/file.txt".into())));
40/// ```
41#[derive(Debug)]
42pub struct PolicyAuthorizer<'a> {
43    rules: &'a [PolicyRule],
44}
45
46impl<'a> PolicyAuthorizer<'a> {
47    /// Create a new authorizer with the given rules
48    #[must_use]
49    pub const fn new(rules: &'a [PolicyRule]) -> Self {
50        Self { rules }
51    }
52
53    /// Check if a peer is allowed to perform an action on a resource (RBAC)
54    ///
55    /// This performs basic Role-Based Access Control checking without
56    /// time or context validation.
57    ///
58    /// # Arguments
59    ///
60    /// * `peer_id` - The peer attempting the action
61    /// * `action` - The action to perform
62    /// * `resource` - The resource to access
63    ///
64    /// # Returns
65    ///
66    /// `true` if at least one rule allows the access, `false` otherwise
67    #[must_use]
68    pub fn is_allowed(&self, peer_id: &str, action: &Action, resource: &Resource) -> bool {
69        self.rules
70            .iter()
71            .any(|rule| rule.allows(peer_id, action, resource))
72    }
73
74    /// Check if a peer is allowed with full ABAC validation
75    ///
76    /// This performs Attribute-Based Access Control checking including:
77    /// - Basic RBAC (peer/action/resource)
78    /// - Time-based validation (expiration)
79    /// - Context attributes validation
80    ///
81    /// # Arguments
82    ///
83    /// * `peer_id` - The peer attempting the action
84    /// * `action` - The action to perform
85    /// * `resource` - The resource to access
86    /// * `current_time` - Current Unix timestamp for expiration checks
87    /// * `context` - Context attributes for ABAC
88    ///
89    /// # Returns
90    ///
91    /// `true` if at least one rule allows the access with valid time and context, `false` otherwise
92    #[must_use]
93    pub fn is_allowed_with_context(
94        &self,
95        peer_id: &str,
96        action: &Action,
97        resource: &Resource,
98        current_time: u64,
99        context: &BTreeMap<String, String>,
100    ) -> bool {
101        self.rules
102            .iter()
103            .any(|rule| rule.allows_with_context(peer_id, action, resource, current_time, context))
104    }
105
106    /// Get all rules that allow a specific peer/action/resource combination
107    ///
108    /// Useful for auditing and debugging authorization decisions.
109    ///
110    /// # Returns
111    ///
112    /// An iterator over all matching rules
113    pub fn matching_rules(
114        &'a self,
115        peer_id: &'a str,
116        action: &'a Action,
117        resource: &'a Resource,
118    ) -> impl Iterator<Item = &'a PolicyRule> + 'a {
119        self.rules
120            .iter()
121            .filter(move |rule| rule.allows(peer_id, action, resource))
122    }
123
124    /// Get the number of rules being evaluated
125    #[must_use]
126    pub fn rule_count(&self) -> usize {
127        self.rules.len()
128    }
129}
130
131/// Trait for types that can provide authorization (DIP - Dependency Inversion)
132///
133/// This trait allows different authorization implementations to be used
134/// interchangeably. Clients depend on this abstraction, not concrete types.
135pub trait Authorizer {
136    /// Check if access is allowed
137    fn is_allowed(&self, peer_id: &str, action: &Action, resource: &Resource) -> bool;
138}
139
140impl<'a> Authorizer for PolicyAuthorizer<'a> {
141    fn is_allowed(&self, peer_id: &str, action: &Action, resource: &Resource) -> bool {
142        PolicyAuthorizer::is_allowed(self, peer_id, action, resource)
143    }
144}