kotoba_security/
policy.rs

1//! Unified Policy Engine combining RBAC and ABAC
2//!
3//! This module provides a comprehensive policy engine that integrates:
4//! - Role-Based Access Control (RBAC) for role-based permissions
5//! - Attribute-Based Access Control (ABAC) for fine-grained attribute-based policies
6//! - Unified policy evaluation with configurable precedence
7
8use crate::capabilities::{CapabilitySet, ResourceType, Action};
9use crate::error::{SecurityError, Result};
10use crate::rbac::{RBACService, RoleAssignment, PrincipalId};
11use crate::abac::{ABACService, PolicyDecision, UserAttributeProvider, ResourceAttributeProvider, EnvironmentAttributeProvider};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15/// Policy evaluation mode
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub enum PolicyMode {
18    /// RBAC only
19    RBACOnly,
20    /// ABAC only
21    ABACOnly,
22    /// RBAC first, then ABAC if RBAC doesn't apply
23    RBACFirst,
24    /// ABAC first, then RBAC if ABAC doesn't apply
25    ABACFirst,
26    /// Both RBAC and ABAC, deny takes precedence
27    Combined,
28}
29
30/// Unified policy decision
31#[derive(Debug, Clone, PartialEq)]
32pub enum UnifiedPolicyDecision {
33    Allow,
34    Deny,
35    NotApplicable,
36}
37
38/// Unified Policy Engine Configuration
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PolicyEngineConfig {
41    pub mode: PolicyMode,
42    pub rbac_enabled: bool,
43    pub abac_enabled: bool,
44    pub default_deny: bool, // If true, deny access when no policies apply
45}
46
47impl Default for PolicyEngineConfig {
48    fn default() -> Self {
49        Self {
50            mode: PolicyMode::Combined,
51            rbac_enabled: true,
52            abac_enabled: true,
53            default_deny: false,
54        }
55    }
56}
57
58/// Unified Policy Engine combining RBAC and ABAC
59pub struct UnifiedPolicyEngine {
60    config: PolicyEngineConfig,
61    rbac_service: Option<RBACService>,
62    abac_service: Option<ABACService>,
63}
64
65impl UnifiedPolicyEngine {
66    /// Create a new unified policy engine
67    pub fn new(config: PolicyEngineConfig) -> Self {
68        Self {
69            config,
70            rbac_service: None,
71            abac_service: None,
72        }
73    }
74
75    /// Create with RBAC service
76    pub fn with_rbac(mut self, rbac_service: RBACService) -> Self {
77        self.rbac_service = Some(rbac_service);
78        self
79    }
80
81    /// Create with ABAC service
82    pub fn with_abac(mut self, abac_service: ABACService) -> Self {
83        self.abac_service = Some(abac_service);
84        self
85    }
86
87    /// Set RBAC service
88    pub fn set_rbac_service(&mut self, rbac_service: RBACService) {
89        self.rbac_service = Some(rbac_service);
90    }
91
92    /// Set ABAC service
93    pub fn set_abac_service(&mut self, abac_service: ABACService) {
94        self.abac_service = Some(abac_service);
95    }
96
97    /// Evaluate access request using unified policy evaluation
98    pub async fn evaluate_access(
99        &self,
100        principal_id: &PrincipalId,
101        resource_type: &ResourceType,
102        resource_id: Option<&crate::abac::ResourceId>,
103        action: &Action,
104    ) -> Result<UnifiedPolicyDecision> {
105        match self.config.mode {
106            PolicyMode::RBACOnly => {
107                self.evaluate_rbac_only(principal_id, resource_type, action)
108            }
109            PolicyMode::ABACOnly => {
110                self.evaluate_abac_only(principal_id, resource_type, resource_id, action).await
111            }
112            PolicyMode::RBACFirst => {
113                self.evaluate_rbac_first(principal_id, resource_type, resource_id, action).await
114            }
115            PolicyMode::ABACFirst => {
116                self.evaluate_abac_first(principal_id, resource_type, resource_id, action).await
117            }
118            PolicyMode::Combined => {
119                self.evaluate_combined(principal_id, resource_type, resource_id, action).await
120            }
121        }
122    }
123
124    /// RBAC-only evaluation
125    fn evaluate_rbac_only(
126        &self,
127        principal_id: &PrincipalId,
128        resource_type: &ResourceType,
129        action: &Action,
130    ) -> Result<UnifiedPolicyDecision> {
131        if !self.config.rbac_enabled {
132            return Ok(UnifiedPolicyDecision::NotApplicable);
133        }
134
135        let rbac_service = self.rbac_service.as_ref()
136            .ok_or_else(|| SecurityError::Configuration("RBAC service not configured".to_string()))?;
137
138        match rbac_service.check_permission(principal_id, resource_type, action, None)? {
139            true => Ok(UnifiedPolicyDecision::Allow),
140            false => Ok(UnifiedPolicyDecision::Deny),
141        }
142    }
143
144    /// ABAC-only evaluation
145    async fn evaluate_abac_only(
146        &self,
147        principal_id: &PrincipalId,
148        resource_type: &ResourceType,
149        resource_id: Option<&crate::abac::ResourceId>,
150        action: &Action,
151    ) -> Result<UnifiedPolicyDecision> {
152        if !self.config.abac_enabled {
153            return Ok(UnifiedPolicyDecision::NotApplicable);
154        }
155
156        let abac_service = self.abac_service.as_ref()
157            .ok_or_else(|| SecurityError::Configuration("ABAC service not configured".to_string()))?;
158
159        match abac_service.check_access(principal_id, resource_type, resource_id, action).await? {
160            PolicyDecision::Allow => Ok(UnifiedPolicyDecision::Allow),
161            PolicyDecision::Deny => Ok(UnifiedPolicyDecision::Deny),
162            PolicyDecision::NotApplicable => Ok(UnifiedPolicyDecision::NotApplicable),
163        }
164    }
165
166    /// RBAC first, then ABAC if RBAC doesn't apply
167    async fn evaluate_rbac_first(
168        &self,
169        principal_id: &PrincipalId,
170        resource_type: &ResourceType,
171        resource_id: Option<&crate::abac::ResourceId>,
172        action: &Action,
173    ) -> Result<UnifiedPolicyDecision> {
174        // Try RBAC first
175        let rbac_result = self.evaluate_rbac_only(principal_id, resource_type, action)?;
176
177        match rbac_result {
178            UnifiedPolicyDecision::Allow | UnifiedPolicyDecision::Deny => {
179                // RBAC made a decision
180                Ok(rbac_result)
181            }
182            UnifiedPolicyDecision::NotApplicable => {
183                // RBAC doesn't apply, try ABAC
184                self.evaluate_abac_only(principal_id, resource_type, resource_id, action).await
185            }
186        }
187    }
188
189    /// ABAC first, then RBAC if ABAC doesn't apply
190    async fn evaluate_abac_first(
191        &self,
192        principal_id: &PrincipalId,
193        resource_type: &ResourceType,
194        resource_id: Option<&crate::abac::ResourceId>,
195        action: &Action,
196    ) -> Result<UnifiedPolicyDecision> {
197        // Try ABAC first
198        let abac_result = self.evaluate_abac_only(principal_id, resource_type, resource_id, action).await?;
199
200        match abac_result {
201            UnifiedPolicyDecision::Allow | UnifiedPolicyDecision::Deny => {
202                // ABAC made a decision
203                Ok(abac_result)
204            }
205            UnifiedPolicyDecision::NotApplicable => {
206                // ABAC doesn't apply, try RBAC
207                self.evaluate_rbac_only(principal_id, resource_type, action)
208            }
209        }
210    }
211
212    /// Combined evaluation: both RBAC and ABAC
213    async fn evaluate_combined(
214        &self,
215        principal_id: &PrincipalId,
216        resource_type: &ResourceType,
217        resource_id: Option<&crate::abac::ResourceId>,
218        action: &Action,
219    ) -> Result<UnifiedPolicyDecision> {
220        let rbac_result = if self.config.rbac_enabled {
221            Some(self.evaluate_rbac_only(principal_id, resource_type, action)?)
222        } else {
223            None
224        };
225
226        let abac_result = if self.config.abac_enabled {
227            Some(self.evaluate_abac_only(principal_id, resource_type, resource_id, action).await?)
228        } else {
229            None
230        };
231
232        // Combine results with deny taking precedence
233        let mut has_allow = false;
234        let mut has_deny = false;
235
236        if let Some(UnifiedPolicyDecision::Allow) = rbac_result {
237            has_allow = true;
238        }
239        if let Some(UnifiedPolicyDecision::Deny) = rbac_result {
240            has_deny = true;
241        }
242
243        if let Some(UnifiedPolicyDecision::Allow) = abac_result {
244            has_allow = true;
245        }
246        if let Some(UnifiedPolicyDecision::Deny) = abac_result {
247            has_deny = true;
248        }
249
250        // Deny takes precedence over allow
251        if has_deny {
252            Ok(UnifiedPolicyDecision::Deny)
253        } else if has_allow {
254            Ok(UnifiedPolicyDecision::Allow)
255        } else if rbac_result.is_some() || abac_result.is_some() {
256            // At least one system was enabled and both returned NotApplicable
257            if self.config.default_deny {
258                Ok(UnifiedPolicyDecision::Deny)
259            } else {
260                Ok(UnifiedPolicyDecision::NotApplicable)
261            }
262        } else {
263            // No systems enabled
264            Ok(UnifiedPolicyDecision::NotApplicable)
265        }
266    }
267
268    /// Get effective capabilities for a principal (RBAC only)
269    pub fn get_principal_capabilities(&self, principal_id: &PrincipalId) -> Result<CapabilitySet> {
270        let rbac_service = self.rbac_service.as_ref()
271            .ok_or_else(|| SecurityError::Configuration("RBAC service not configured".to_string()))?;
272
273        rbac_service.get_principal_capabilities(principal_id)
274    }
275
276    /// Add RBAC role
277    pub fn add_rbac_role(&mut self, role: crate::rbac::Role) -> Result<()> {
278        let rbac_service = self.rbac_service.as_mut()
279            .ok_or_else(|| SecurityError::Configuration("RBAC service not configured".to_string()))?;
280
281        rbac_service.add_role(role)
282    }
283
284    /// Assign RBAC role
285    pub fn assign_rbac_role(&mut self, assignment: RoleAssignment) -> Result<()> {
286        let rbac_service = self.rbac_service.as_mut()
287            .ok_or_else(|| SecurityError::Configuration("RBAC service not configured".to_string()))?;
288
289        rbac_service.assign_role(assignment)
290    }
291
292    /// Add ABAC policy
293    pub fn add_abac_policy(&mut self, policy: crate::abac::Policy) -> Result<()> {
294        let abac_service = self.abac_service.as_mut()
295            .ok_or_else(|| SecurityError::Configuration("ABAC service not configured".to_string()))?;
296
297        abac_service.add_policy(policy)
298    }
299
300    /// Setup common policies for both RBAC and ABAC
301    pub fn setup_common_policies(&mut self) -> Result<()> {
302        // Setup RBAC roles
303        if let Some(rbac_service) = &mut self.rbac_service {
304            rbac_service.create_common_roles()?;
305        }
306
307        // Setup ABAC policies
308        if let Some(abac_service) = &mut self.abac_service {
309            abac_service.setup_common_policies()?;
310        }
311
312        Ok(())
313    }
314
315    /// Get configuration
316    pub fn config(&self) -> &PolicyEngineConfig {
317        &self.config
318    }
319
320    /// Update configuration
321    pub fn set_config(&mut self, config: PolicyEngineConfig) {
322        self.config = config;
323    }
324}
325
326impl Default for UnifiedPolicyEngine {
327    fn default() -> Self {
328        Self::new(PolicyEngineConfig::default())
329    }
330}
331
332/// High-level Policy Service for application integration
333pub struct PolicyService {
334    engine: UnifiedPolicyEngine,
335}
336
337impl PolicyService {
338    /// Create a new policy service with default configuration
339    pub fn new() -> Self {
340        Self {
341            engine: UnifiedPolicyEngine::new(PolicyEngineConfig::default()),
342        }
343    }
344
345    /// Create with custom configuration
346    pub fn with_config(config: PolicyEngineConfig) -> Self {
347        Self {
348            engine: UnifiedPolicyEngine::new(config),
349        }
350    }
351
352    /// Initialize with RBAC and ABAC services
353    pub fn with_services(
354        config: PolicyEngineConfig,
355        rbac_service: Option<RBACService>,
356        abac_service: Option<ABACService>,
357    ) -> Self {
358        let mut engine = UnifiedPolicyEngine::new(config);
359
360        if let Some(rbac) = rbac_service {
361            engine.set_rbac_service(rbac);
362        }
363
364        if let Some(abac) = abac_service {
365            engine.set_abac_service(abac);
366        }
367
368        Self { engine }
369    }
370
371    /// Check access permission
372    pub async fn check_permission(
373        &self,
374        principal_id: &PrincipalId,
375        resource_type: &ResourceType,
376        resource_id: Option<&crate::abac::ResourceId>,
377        action: &Action,
378    ) -> Result<bool> {
379        match self.engine.evaluate_access(principal_id, resource_type, resource_id, action).await? {
380            UnifiedPolicyDecision::Allow => Ok(true),
381            UnifiedPolicyDecision::Deny => Ok(false),
382            UnifiedPolicyDecision::NotApplicable => {
383                // Default behavior when no policies apply
384                Ok(!self.engine.config().default_deny)
385            }
386        }
387    }
388
389    /// Authorize action with detailed result
390    pub async fn authorize(
391        &self,
392        principal_id: &PrincipalId,
393        resource_type: &ResourceType,
394        resource_id: Option<&crate::abac::ResourceId>,
395        action: &Action,
396    ) -> Result<UnifiedPolicyDecision> {
397        self.engine.evaluate_access(principal_id, resource_type, resource_id, action).await
398    }
399
400    /// Add RBAC role
401    pub fn add_role(&mut self, role: crate::rbac::Role) -> Result<()> {
402        self.engine.add_rbac_role(role)
403    }
404
405    /// Assign role to principal
406    pub fn assign_role(&mut self, assignment: RoleAssignment) -> Result<()> {
407        self.engine.assign_rbac_role(assignment)
408    }
409
410    /// Add ABAC policy
411    pub fn add_policy(&mut self, policy: crate::abac::Policy) -> Result<()> {
412        self.engine.add_abac_policy(policy)
413    }
414
415    /// Setup common roles and policies
416    pub fn setup_common_policies(&mut self) -> Result<()> {
417        self.engine.setup_common_policies()
418    }
419
420    /// Get policy engine for advanced operations
421    pub fn engine(&self) -> &UnifiedPolicyEngine {
422        &self.engine
423    }
424
425    /// Get mutable policy engine
426    pub fn engine_mut(&mut self) -> &mut UnifiedPolicyEngine {
427        &mut self.engine
428    }
429}
430
431impl Default for PolicyService {
432    fn default() -> Self {
433        Self::new()
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440    use crate::abac::{SimpleUserAttributeProvider, SimpleResourceAttributeProvider, SimpleEnvironmentAttributeProvider, UserAttributes, ResourceAttributes, AttributeValue};
441    use crate::rbac::Role;
442
443    #[test]
444    fn test_policy_engine_config() {
445        let config = PolicyEngineConfig::default();
446        assert_eq!(config.mode, PolicyMode::Combined);
447        assert!(config.rbac_enabled);
448        assert!(config.abac_enabled);
449    }
450
451    #[tokio::test]
452    async fn test_unified_policy_engine_rbac_only() {
453        let config = PolicyEngineConfig {
454            mode: PolicyMode::RBACOnly,
455            rbac_enabled: true,
456            abac_enabled: false,
457            default_deny: false,
458        };
459
460        let mut rbac_service = RBACService::new();
461        let role = Role::new("reader".to_string(), "Reader".to_string());
462        rbac_service.add_role(role).unwrap();
463
464        let mut engine = UnifiedPolicyEngine::new(config).with_rbac(rbac_service);
465
466        // Should return NotApplicable since no roles are assigned
467        let result = engine.evaluate_access(
468            &"user1".to_string(),
469            &ResourceType::Graph,
470            None,
471            &Action::Read,
472        ).await.unwrap();
473
474        assert_eq!(result, UnifiedPolicyDecision::Deny);
475    }
476
477    #[tokio::test]
478    async fn test_unified_policy_engine_abac_only() {
479        let config = PolicyEngineConfig {
480            mode: PolicyMode::ABACOnly,
481            rbac_enabled: false,
482            abac_enabled: true,
483            default_deny: false,
484        };
485
486        let user_provider = Box::new(
487            SimpleUserAttributeProvider::new()
488                .add_user(
489                    "user1".to_string(),
490                    UserAttributes::new()
491                        .with_attribute("role".to_string(), AttributeValue::String("admin".to_string())),
492                )
493        );
494
495        let resource_provider = Box::new(
496            SimpleResourceAttributeProvider::new()
497                .add_resource(
498                    "graph".to_string(),
499                    ResourceAttributes::new(ResourceType::Graph, None),
500                )
501        );
502
503        let env_provider = Box::new(SimpleEnvironmentAttributeProvider::new());
504
505        let abac_service = ABACService::new(user_provider, resource_provider, env_provider);
506        let mut engine = UnifiedPolicyEngine::new(config).with_abac(abac_service);
507
508        // Should return Allow due to admin policy
509        let result = engine.evaluate_access(
510            &"user1".to_string(),
511            &ResourceType::Graph,
512            None,
513            &Action::Read,
514        ).await.unwrap();
515
516        assert_eq!(result, UnifiedPolicyDecision::Allow);
517    }
518
519    #[tokio::test]
520    async fn test_policy_service() {
521        let config = PolicyEngineConfig {
522            mode: PolicyMode::Combined,
523            rbac_enabled: true,
524            abac_enabled: true,
525            default_deny: false,
526        };
527
528        let user_provider = Box::new(
529            SimpleUserAttributeProvider::new()
530                .add_user(
531                    "user1".to_string(),
532                    UserAttributes::new()
533                        .with_attribute("role".to_string(), AttributeValue::String("admin".to_string())),
534                )
535        );
536
537        let resource_provider = Box::new(
538            SimpleResourceAttributeProvider::new()
539                .add_resource(
540                    "graph".to_string(),
541                    ResourceAttributes::new(ResourceType::Graph, None),
542                )
543        );
544
545        let env_provider = Box::new(SimpleEnvironmentAttributeProvider::new());
546
547        let abac_service = ABACService::new(user_provider, resource_provider, env_provider);
548        let rbac_service = RBACService::new();
549
550        let mut policy_service = PolicyService::with_services(
551            config,
552            Some(rbac_service),
553            Some(abac_service),
554        );
555
556        policy_service.setup_common_policies().unwrap();
557
558        // Check permission
559        let allowed = policy_service.check_permission(
560            &"user1".to_string(),
561            &ResourceType::Graph,
562            None,
563            &Action::Read,
564        ).await.unwrap();
565
566        assert!(allowed);
567    }
568}