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}