kotoba_security/
abac.rs

1//! Attribute-Based Access Control (ABAC) implementation
2//!
3//! This module provides comprehensive ABAC functionality including:
4//! - Attribute definitions for users, resources, and environment
5//! - Policy definitions with conditions and rules
6//! - Policy evaluation engine
7//! - Attribute collection and context building
8
9use crate::capabilities::{ResourceType, Action};
10use crate::error::{SecurityError, Result};
11use chrono::{Utc, Datelike, Timelike};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15/// Unique identifier for a policy
16pub type PolicyId = String;
17
18/// Unique identifier for a user/principal
19pub type PrincipalId = String;
20
21/// Unique identifier for a resource
22pub type ResourceId = String;
23
24/// Attribute value types
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
26pub enum AttributeValue {
27    String(String),
28    Integer(i64),
29    Float(f64),
30    Boolean(bool),
31    Array(Vec<AttributeValue>),
32    Object(HashMap<String, AttributeValue>),
33}
34
35impl AttributeValue {
36    /// Convert to string for comparison
37    pub fn as_string(&self) -> Option<&str> {
38        match self {
39            AttributeValue::String(s) => Some(s),
40            _ => None,
41        }
42    }
43
44    /// Convert to integer for comparison
45    pub fn as_integer(&self) -> Option<i64> {
46        match self {
47            AttributeValue::Integer(i) => Some(*i),
48            _ => None,
49        }
50    }
51
52    /// Convert to boolean for comparison
53    pub fn as_boolean(&self) -> Option<bool> {
54        match self {
55            AttributeValue::Boolean(b) => Some(*b),
56            _ => None,
57        }
58    }
59
60    /// Convert to float for comparison
61    pub fn as_float(&self) -> Option<f64> {
62        match self {
63            AttributeValue::Float(f) => Some(*f),
64            _ => None,
65        }
66    }
67}
68
69/// User/Principal attributes
70#[derive(Debug, Clone, Serialize, Deserialize, Default)]
71pub struct UserAttributes {
72    pub attributes: HashMap<String, AttributeValue>,
73}
74
75impl UserAttributes {
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    pub fn with_attribute(mut self, key: String, value: AttributeValue) -> Self {
81        self.attributes.insert(key, value);
82        self
83    }
84
85    pub fn get(&self, key: &str) -> Option<&AttributeValue> {
86        self.attributes.get(key)
87    }
88
89    pub fn set(&mut self, key: String, value: AttributeValue) {
90        self.attributes.insert(key, value);
91    }
92
93    pub fn remove(&mut self, key: &str) {
94        self.attributes.remove(key);
95    }
96}
97
98/// Resource attributes
99#[derive(Debug, Clone, Serialize, Deserialize, Default)]
100pub struct ResourceAttributes {
101    pub resource_type: ResourceType,
102    pub resource_id: Option<ResourceId>,
103    pub attributes: HashMap<String, AttributeValue>,
104}
105
106impl ResourceAttributes {
107    pub fn new(resource_type: ResourceType, resource_id: Option<ResourceId>) -> Self {
108        Self {
109            resource_type,
110            resource_id,
111            attributes: HashMap::new(),
112        }
113    }
114
115    pub fn with_attribute(mut self, key: String, value: AttributeValue) -> Self {
116        self.attributes.insert(key, value);
117        self
118    }
119
120    pub fn get(&self, key: &str) -> Option<&AttributeValue> {
121        self.attributes.get(key)
122    }
123
124    pub fn set(&mut self, key: String, value: AttributeValue) {
125        self.attributes.insert(key, value);
126    }
127
128    pub fn remove(&mut self, key: &str) {
129        self.attributes.remove(key);
130    }
131}
132
133/// Environment attributes (context)
134#[derive(Debug, Clone, Serialize, Deserialize, Default)]
135pub struct EnvironmentAttributes {
136    pub attributes: HashMap<String, AttributeValue>,
137}
138
139impl EnvironmentAttributes {
140    pub fn new() -> Self {
141        Self::default()
142    }
143
144    pub fn with_attribute(mut self, key: String, value: AttributeValue) -> Self {
145        self.attributes.insert(key, value);
146        self
147    }
148
149    pub fn get(&self, key: &str) -> Option<&AttributeValue> {
150        self.attributes.get(key)
151    }
152
153    pub fn set(&mut self, key: String, value: AttributeValue) {
154        self.attributes.insert(key, value);
155    }
156
157    pub fn remove(&mut self, key: &str) {
158        self.attributes.remove(key);
159    }
160
161    /// Create environment attributes with common context
162    pub fn with_common_context() -> Self {
163        let now = chrono::Utc::now();
164        Self::new()
165            .with_attribute("time.hour".to_string(), AttributeValue::Integer(now.hour() as i64))
166            .with_attribute("time.day".to_string(), AttributeValue::Integer(now.day() as i64))
167            .with_attribute("time.month".to_string(), AttributeValue::Integer(now.month() as i64))
168            .with_attribute("time.year".to_string(), AttributeValue::Integer(now.year() as i64))
169            .with_attribute("time.weekday".to_string(), AttributeValue::Integer(now.weekday().num_days_from_monday() as i64))
170            .with_attribute("timestamp".to_string(), AttributeValue::Integer(now.timestamp()))
171    }
172}
173
174/// Combined access context for policy evaluation
175#[derive(Debug, Clone)]
176pub struct AccessContext {
177    pub user: UserAttributes,
178    pub resource: ResourceAttributes,
179    pub environment: EnvironmentAttributes,
180    pub action: Action,
181}
182
183impl AccessContext {
184    pub fn new(
185        user: UserAttributes,
186        resource: ResourceAttributes,
187        environment: EnvironmentAttributes,
188        action: Action,
189    ) -> Self {
190        Self {
191            user,
192            resource,
193            environment,
194            action,
195        }
196    }
197
198    /// Get attribute value by path (e.g., "user.role", "resource.owner", "env.time.hour")
199    pub fn get_attribute(&self, path: &str) -> Option<&AttributeValue> {
200        let parts: Vec<&str> = path.split('.').collect();
201        if parts.is_empty() {
202            return None;
203        }
204
205        match parts[0] {
206            "user" => {
207                if parts.len() == 1 {
208                    return None;
209                }
210                self.user.get(&parts[1..].join("."))
211            }
212            "resource" => {
213                if parts.len() == 1 {
214                    return None;
215                }
216                self.resource.get(&parts[1..].join("."))
217            }
218            "env" | "environment" => {
219                if parts.len() == 1 {
220                    return None;
221                }
222                self.environment.get(&parts[1..].join("."))
223            }
224            _ => None,
225        }
226    }
227}
228
229/// Policy effect (allow or deny)
230#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
231pub enum PolicyEffect {
232    Allow,
233    Deny,
234}
235
236/// Policy target (what the policy applies to)
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct PolicyTarget {
239    pub resource_types: Vec<ResourceType>,
240    pub actions: Vec<Action>,
241    pub conditions: Vec<PolicyCondition>,
242}
243
244impl PolicyTarget {
245    pub fn new() -> Self {
246        Self {
247            resource_types: Vec::new(),
248            actions: Vec::new(),
249            conditions: Vec::new(),
250        }
251    }
252
253    pub fn with_resource_type(mut self, resource_type: ResourceType) -> Self {
254        self.resource_types.push(resource_type);
255        self
256    }
257
258    pub fn with_action(mut self, action: Action) -> Self {
259        self.actions.push(action);
260        self
261    }
262
263    pub fn with_condition(mut self, condition: PolicyCondition) -> Self {
264        self.conditions.push(condition);
265        self
266    }
267
268    /// Check if this target matches the given context
269    pub fn matches(&self, context: &AccessContext) -> Result<bool> {
270        // Check resource type
271        if !self.resource_types.is_empty() &&
272           !self.resource_types.contains(&context.resource.resource_type) {
273            return Ok(false);
274        }
275
276        // Check action
277        if !self.actions.is_empty() &&
278           !self.actions.contains(&context.action) {
279            return Ok(false);
280        }
281
282        // Check conditions
283        for condition in &self.conditions {
284            if !condition.evaluate(context)? {
285                return Ok(false);
286            }
287        }
288
289        Ok(true)
290    }
291}
292
293/// Policy condition operators
294#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
295pub enum ConditionOperator {
296    Equals,
297    NotEquals,
298    GreaterThan,
299    LessThan,
300    GreaterThanOrEqual,
301    LessThanOrEqual,
302    Contains,
303    NotContains,
304    In,
305    NotIn,
306    Regex,
307    Exists,
308    NotExists,
309}
310
311/// Policy condition for attribute-based rules
312#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct PolicyCondition {
314    pub attribute_path: String,
315    pub operator: ConditionOperator,
316    pub value: AttributeValue,
317}
318
319impl PolicyCondition {
320    pub fn new(attribute_path: String, operator: ConditionOperator, value: AttributeValue) -> Self {
321        Self {
322            attribute_path,
323            operator,
324            value,
325        }
326    }
327
328    /// Evaluate the condition against the access context
329    pub fn evaluate(&self, context: &AccessContext) -> Result<bool> {
330        let attribute_value = match context.get_attribute(&self.attribute_path) {
331            Some(value) => value,
332            None => {
333                // If attribute doesn't exist, only Exists/NotExists operators can match
334                return match self.operator {
335                    ConditionOperator::NotExists => Ok(true),
336                    ConditionOperator::Exists => Ok(false),
337                    _ => Ok(false),
338                };
339            }
340        };
341
342        match self.operator {
343            ConditionOperator::Exists => Ok(true),
344            ConditionOperator::NotExists => Ok(false),
345            ConditionOperator::Equals => Ok(attribute_value == &self.value),
346            ConditionOperator::NotEquals => Ok(attribute_value != &self.value),
347            ConditionOperator::GreaterThan => {
348                match (attribute_value.as_integer(), self.value.as_integer()) {
349                    (Some(a), Some(b)) => Ok(a > b),
350                    _ => Ok(false),
351                }
352            }
353            ConditionOperator::LessThan => {
354                match (attribute_value.as_integer(), self.value.as_integer()) {
355                    (Some(a), Some(b)) => Ok(a < b),
356                    _ => Ok(false),
357                }
358            }
359            ConditionOperator::GreaterThanOrEqual => {
360                match (attribute_value.as_integer(), self.value.as_integer()) {
361                    (Some(a), Some(b)) => Ok(a >= b),
362                    _ => Ok(false),
363                }
364            }
365            ConditionOperator::LessThanOrEqual => {
366                match (attribute_value.as_integer(), self.value.as_integer()) {
367                    (Some(a), Some(b)) => Ok(a <= b),
368                    _ => Ok(false),
369                }
370            }
371            ConditionOperator::Contains => {
372                match (attribute_value.as_string(), self.value.as_string()) {
373                    (Some(haystack), Some(needle)) => Ok(haystack.contains(needle)),
374                    _ => Ok(false),
375                }
376            }
377            ConditionOperator::NotContains => {
378                match (attribute_value.as_string(), self.value.as_string()) {
379                    (Some(haystack), Some(needle)) => Ok(!haystack.contains(needle)),
380                    _ => Ok(false),
381                }
382            }
383            ConditionOperator::In => {
384                // Check if attribute value is in the array specified by self.value
385                match &self.value {
386                    AttributeValue::Array(values) => Ok(values.contains(attribute_value)),
387                    _ => Ok(false),
388                }
389            }
390            ConditionOperator::NotIn => {
391                // Check if attribute value is NOT in the array specified by self.value
392                match &self.value {
393                    AttributeValue::Array(values) => Ok(!values.contains(attribute_value)),
394                    _ => Ok(false),
395                }
396            }
397            ConditionOperator::Regex => {
398                match (attribute_value.as_string(), self.value.as_string()) {
399                    (Some(text), Some(pattern)) => {
400                        match regex::Regex::new(pattern) {
401                            Ok(re) => Ok(re.is_match(text)),
402                            Err(_) => Ok(false),
403                        }
404                    }
405                    _ => Ok(false),
406                }
407            }
408        }
409    }
410}
411
412/// ABAC Policy definition
413#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct Policy {
415    pub id: PolicyId,
416    pub name: String,
417    pub description: Option<String>,
418    pub effect: PolicyEffect,
419    pub target: PolicyTarget,
420    pub priority: i32, // Higher priority policies are evaluated first
421    pub active: bool,
422    pub created_at: chrono::DateTime<chrono::Utc>,
423    pub updated_at: chrono::DateTime<chrono::Utc>,
424}
425
426impl Policy {
427    pub fn new(id: PolicyId, name: String, effect: PolicyEffect) -> Self {
428        let now = chrono::Utc::now();
429        Self {
430            id,
431            name,
432            description: None,
433            effect,
434            target: PolicyTarget::new(),
435            priority: 0,
436            active: true,
437            created_at: now,
438            updated_at: now,
439        }
440    }
441
442    pub fn with_description(mut self, description: String) -> Self {
443        self.description = Some(description);
444        self
445    }
446
447    pub fn with_target(mut self, target: PolicyTarget) -> Self {
448        self.target = target;
449        self.updated_at = chrono::Utc::now();
450        self
451    }
452
453    pub fn with_priority(mut self, priority: i32) -> Self {
454        self.priority = priority;
455        self.updated_at = chrono::Utc::now();
456        self
457    }
458
459    pub fn deactivate(mut self) -> Self {
460        self.active = false;
461        self.updated_at = chrono::Utc::now();
462        self
463    }
464
465    /// Evaluate this policy against the access context
466    pub fn evaluate(&self, context: &AccessContext) -> Result<Option<PolicyEffect>> {
467        if !self.active {
468            return Ok(None);
469        }
470
471        if self.target.matches(context)? {
472            Ok(Some(self.effect.clone()))
473        } else {
474            Ok(None)
475        }
476    }
477}
478
479/// Policy evaluation result
480#[derive(Debug, Clone, PartialEq)]
481pub enum PolicyDecision {
482    Allow,
483    Deny,
484    NotApplicable,
485}
486
487/// ABAC Policy Engine
488#[derive(Debug)]
489pub struct PolicyEngine {
490    policies: HashMap<PolicyId, Policy>,
491}
492
493impl PolicyEngine {
494    /// Create a new policy engine
495    pub fn new() -> Self {
496        Self {
497            policies: HashMap::new(),
498        }
499    }
500
501    /// Add a policy to the engine
502    pub fn add_policy(&mut self, policy: Policy) -> Result<()> {
503        if self.policies.contains_key(&policy.id) {
504            return Err(SecurityError::Configuration(
505                format!("Policy '{}' already exists", policy.id)
506            ));
507        }
508        self.policies.insert(policy.id.clone(), policy);
509        Ok(())
510    }
511
512    /// Update an existing policy
513    pub fn update_policy(&mut self, policy: Policy) -> Result<()> {
514        if !self.policies.contains_key(&policy.id) {
515            return Err(SecurityError::Configuration(
516                format!("Policy '{}' does not exist", policy.id)
517            ));
518        }
519        self.policies.insert(policy.id.clone(), policy);
520        Ok(())
521    }
522
523    /// Remove a policy
524    pub fn remove_policy(&mut self, policy_id: &PolicyId) -> Result<()> {
525        if self.policies.remove(policy_id).is_none() {
526            return Err(SecurityError::Configuration(
527                format!("Policy '{}' does not exist", policy_id)
528            ));
529        }
530        Ok(())
531    }
532
533    /// Get a policy by ID
534    pub fn get_policy(&self, policy_id: &PolicyId) -> Option<&Policy> {
535        self.policies.get(policy_id)
536    }
537
538    /// List all policies
539    pub fn list_policies(&self) -> Vec<&Policy> {
540        self.policies.values().collect()
541    }
542
543    /// Evaluate access request against all policies
544    pub fn evaluate_access(&self, context: &AccessContext) -> Result<PolicyDecision> {
545        let mut applicable_policies: Vec<&Policy> = self.policies.values()
546            .filter(|p| p.active)
547            .collect();
548
549        // Sort by priority (higher priority first)
550        applicable_policies.sort_by(|a, b| b.priority.cmp(&a.priority));
551
552        let mut has_allow = false;
553        let mut has_deny = false;
554
555        for policy in applicable_policies {
556            match policy.evaluate(context)? {
557                Some(PolicyEffect::Allow) => has_allow = true,
558                Some(PolicyEffect::Deny) => has_deny = true,
559                None => continue,
560            }
561        }
562
563        // Deny takes precedence over allow
564        if has_deny {
565            Ok(PolicyDecision::Deny)
566        } else if has_allow {
567            Ok(PolicyDecision::Allow)
568        } else {
569            Ok(PolicyDecision::NotApplicable)
570        }
571    }
572
573    /// Create common policies for quick setup
574    pub fn create_common_policies(&mut self) -> Result<()> {
575        // Policy 1: Administrators can do everything
576        let admin_policy = Policy::new(
577            "admin_full_access".to_string(),
578            "Administrator Full Access".to_string(),
579            PolicyEffect::Allow,
580        )
581        .with_description("Allows administrators full access to all resources".to_string())
582        .with_target(
583            PolicyTarget::new()
584                .with_condition(PolicyCondition::new(
585                    "user.role".to_string(),
586                    ConditionOperator::Equals,
587                    AttributeValue::String("admin".to_string()),
588                ))
589        )
590        .with_priority(1000);
591
592        // Policy 2: Users can read their own data
593        let user_own_data_policy = Policy::new(
594            "user_own_data".to_string(),
595            "User Own Data Access".to_string(),
596            PolicyEffect::Allow,
597        )
598        .with_description("Allows users to access their own data".to_string())
599        .with_target(
600            PolicyTarget::new()
601                .with_resource_type(ResourceType::User)
602                .with_condition(PolicyCondition::new(
603                    "user.id".to_string(),
604                    ConditionOperator::Equals,
605                    AttributeValue::String("resource.owner".to_string()), // This should be evaluated dynamically
606                ))
607        )
608        .with_priority(500);
609
610        // Policy 3: Content editors can modify during business hours
611        let business_hours_policy = Policy::new(
612            "business_hours_edit".to_string(),
613            "Business Hours Editing".to_string(),
614            PolicyEffect::Allow,
615        )
616        .with_description("Allows content editors to modify content during business hours".to_string())
617        .with_target(
618            PolicyTarget::new()
619                .with_resource_type(ResourceType::Graph)
620                .with_action(Action::Update)
621                .with_condition(PolicyCondition::new(
622                    "user.role".to_string(),
623                    ConditionOperator::Equals,
624                    AttributeValue::String("content_editor".to_string()),
625                ))
626                .with_condition(PolicyCondition::new(
627                    "env.time.hour".to_string(),
628                    ConditionOperator::GreaterThanOrEqual,
629                    AttributeValue::Integer(9),
630                ))
631                .with_condition(PolicyCondition::new(
632                    "env.time.hour".to_string(),
633                    ConditionOperator::LessThan,
634                    AttributeValue::Integer(17),
635                ))
636        )
637        .with_priority(200);
638
639        // Policy 4: Deny access to sensitive resources outside office network
640        let network_restriction_policy = Policy::new(
641            "network_restriction".to_string(),
642            "Network Access Restriction".to_string(),
643            PolicyEffect::Deny,
644        )
645        .with_description("Denies access to sensitive resources outside office network".to_string())
646        .with_target(
647            PolicyTarget::new()
648                .with_resource_type(ResourceType::Admin)
649                .with_condition(PolicyCondition::new(
650                    "env.network.type".to_string(),
651                    ConditionOperator::NotEquals,
652                    AttributeValue::String("office".to_string()),
653                ))
654        )
655        .with_priority(800);
656
657        self.add_policy(admin_policy)?;
658        self.add_policy(user_own_data_policy)?;
659        self.add_policy(business_hours_policy)?;
660        self.add_policy(network_restriction_policy)?;
661
662        Ok(())
663    }
664}
665
666impl Default for PolicyEngine {
667    fn default() -> Self {
668        Self::new()
669    }
670}
671
672/// ABAC Service combining attribute collection and policy evaluation
673pub struct ABACService {
674    policy_engine: PolicyEngine,
675    user_attribute_provider: Box<dyn UserAttributeProvider>,
676    resource_attribute_provider: Box<dyn ResourceAttributeProvider>,
677    environment_attribute_provider: Box<dyn EnvironmentAttributeProvider>,
678}
679
680impl ABACService {
681    /// Create a new ABAC service with providers
682    pub fn new(
683        user_provider: Box<dyn UserAttributeProvider>,
684        resource_provider: Box<dyn ResourceAttributeProvider>,
685        environment_provider: Box<dyn EnvironmentAttributeProvider>,
686    ) -> Self {
687        Self {
688            policy_engine: PolicyEngine::new(),
689            user_attribute_provider: user_provider,
690            resource_attribute_provider: resource_provider,
691            environment_attribute_provider: environment_provider,
692        }
693    }
694
695    /// Create ABAC service with existing policy engine
696    pub fn with_engine(
697        policy_engine: PolicyEngine,
698        user_provider: Box<dyn UserAttributeProvider>,
699        resource_provider: Box<dyn ResourceAttributeProvider>,
700        environment_provider: Box<dyn EnvironmentAttributeProvider>,
701    ) -> Self {
702        Self {
703            policy_engine,
704            user_attribute_provider: user_provider,
705            resource_attribute_provider: resource_provider,
706            environment_attribute_provider: environment_provider,
707        }
708    }
709
710    /// Add a policy
711    pub fn add_policy(&mut self, policy: Policy) -> Result<()> {
712        self.policy_engine.add_policy(policy)
713    }
714
715    /// Check access for a principal on a resource with specific action
716    pub async fn check_access(
717        &self,
718        principal_id: &PrincipalId,
719        resource_type: &ResourceType,
720        resource_id: Option<&ResourceId>,
721        action: &Action,
722    ) -> Result<PolicyDecision> {
723        // Collect attributes
724        let user_attrs = self.user_attribute_provider.get_attributes(principal_id).await?;
725        let resource_attrs = self.resource_attribute_provider.get_attributes(resource_type, resource_id).await?;
726        let env_attrs = self.environment_attribute_provider.get_attributes().await?;
727
728        // Build access context
729        let context = AccessContext::new(user_attrs, resource_attrs, env_attrs, action.clone());
730
731        // Evaluate policies
732        self.policy_engine.evaluate_access(&context)
733    }
734
735    /// Get all policies
736    pub fn get_policies(&self) -> Vec<&Policy> {
737        self.policy_engine.list_policies()
738    }
739
740    /// Create common policies and setup
741    pub fn setup_common_policies(&mut self) -> Result<()> {
742        self.policy_engine.create_common_policies()
743    }
744}
745
746/// Trait for providing user attributes
747#[async_trait::async_trait(?Send)]
748pub trait UserAttributeProvider: Send + Sync {
749    async fn get_attributes(&self, principal_id: &PrincipalId) -> Result<UserAttributes>;
750}
751
752/// Trait for providing resource attributes
753#[async_trait::async_trait(?Send)]
754pub trait ResourceAttributeProvider: Send + Sync {
755    async fn get_attributes(&self, resource_type: &ResourceType, resource_id: Option<&ResourceId>) -> Result<ResourceAttributes>;
756}
757
758/// Trait for providing environment attributes
759#[async_trait::async_trait(?Send)]
760pub trait EnvironmentAttributeProvider: Send + Sync {
761    async fn get_attributes(&self) -> Result<EnvironmentAttributes>;
762}
763
764/// Simple in-memory attribute providers for testing/demo
765pub struct SimpleUserAttributeProvider {
766    attributes: HashMap<PrincipalId, UserAttributes>,
767}
768
769impl SimpleUserAttributeProvider {
770    pub fn new() -> Self {
771        Self {
772            attributes: HashMap::new(),
773        }
774    }
775
776    pub fn add_user(mut self, principal_id: PrincipalId, attributes: UserAttributes) -> Self {
777        self.attributes.insert(principal_id, attributes);
778        self
779    }
780}
781
782#[async_trait::async_trait(?Send)]
783impl UserAttributeProvider for SimpleUserAttributeProvider {
784    async fn get_attributes(&self, principal_id: &PrincipalId) -> Result<UserAttributes> {
785        self.attributes.get(principal_id)
786            .cloned()
787            .ok_or_else(|| SecurityError::Configuration(
788                format!("User '{}' not found", principal_id)
789            ))
790    }
791}
792
793pub struct SimpleResourceAttributeProvider {
794    attributes: HashMap<String, ResourceAttributes>,
795}
796
797impl SimpleResourceAttributeProvider {
798    pub fn new() -> Self {
799        Self {
800            attributes: HashMap::new(),
801        }
802    }
803
804    pub fn add_resource(mut self, key: String, attributes: ResourceAttributes) -> Self {
805        self.attributes.insert(key, attributes);
806        self
807    }
808}
809
810#[async_trait::async_trait(?Send)]
811impl ResourceAttributeProvider for SimpleResourceAttributeProvider {
812    async fn get_attributes(&self, resource_type: &ResourceType, resource_id: Option<&ResourceId>) -> Result<ResourceAttributes> {
813        let key = match resource_id {
814            Some(id) => format!("{}:{}", resource_type.as_str(), id),
815            None => resource_type.as_str().to_string(),
816        };
817
818        self.attributes.get(&key)
819            .cloned()
820            .ok_or_else(|| SecurityError::Configuration(
821                format!("Resource '{}' not found", key)
822            ))
823    }
824}
825
826pub struct SimpleEnvironmentAttributeProvider;
827
828impl SimpleEnvironmentAttributeProvider {
829    pub fn new() -> Self {
830        Self
831    }
832}
833
834#[async_trait::async_trait(?Send)]
835impl EnvironmentAttributeProvider for SimpleEnvironmentAttributeProvider {
836    async fn get_attributes(&self) -> Result<EnvironmentAttributes> {
837        Ok(EnvironmentAttributes::with_common_context())
838    }
839}
840
841impl ResourceType {
842    fn as_str(&self) -> &str {
843        match self {
844            ResourceType::Graph => "graph",
845            ResourceType::FileSystem => "filesystem",
846            ResourceType::Network => "network",
847            ResourceType::Environment => "environment",
848            ResourceType::System => "system",
849            ResourceType::Plugin => "plugin",
850            ResourceType::Query => "query",
851            ResourceType::Admin => "admin",
852            ResourceType::User => "user",
853            ResourceType::Custom(name) => name,
854        }
855    }
856}
857
858#[cfg(test)]
859mod tests {
860    use super::*;
861
862    #[tokio::test]
863    async fn test_attribute_values() {
864        let string_val = AttributeValue::String("test".to_string());
865        assert_eq!(string_val.as_string(), Some("test"));
866
867        let int_val = AttributeValue::Integer(42);
868        assert_eq!(int_val.as_integer(), Some(42));
869
870        let bool_val = AttributeValue::Boolean(true);
871        assert_eq!(bool_val.as_boolean(), Some(true));
872    }
873
874    #[tokio::test]
875    async fn test_policy_condition_evaluation() {
876        let condition = PolicyCondition::new(
877            "user.age".to_string(),
878            ConditionOperator::GreaterThan,
879            AttributeValue::Integer(18),
880        );
881
882        let user_attrs = UserAttributes::new()
883            .with_attribute("age".to_string(), AttributeValue::Integer(25));
884
885        let resource_attrs = ResourceAttributes::new(ResourceType::Graph, None);
886        let env_attrs = EnvironmentAttributes::new();
887
888        let context = AccessContext::new(
889            user_attrs,
890            resource_attrs,
891            env_attrs,
892            Action::Read,
893        );
894
895        assert!(condition.evaluate(&context).unwrap());
896    }
897
898    #[tokio::test]
899    async fn test_policy_evaluation() {
900        let mut policy_engine = PolicyEngine::new();
901
902        let policy = Policy::new(
903            "test_policy".to_string(),
904            "Test Policy".to_string(),
905            PolicyEffect::Allow,
906        )
907        .with_target(
908            PolicyTarget::new()
909                .with_resource_type(ResourceType::Graph)
910                .with_action(Action::Read)
911                .with_condition(PolicyCondition::new(
912                    "user.role".to_string(),
913                    ConditionOperator::Equals,
914                    AttributeValue::String("admin".to_string()),
915                ))
916        );
917
918        policy_engine.add_policy(policy).unwrap();
919
920        let user_attrs = UserAttributes::new()
921            .with_attribute("role".to_string(), AttributeValue::String("admin".to_string()));
922        let resource_attrs = ResourceAttributes::new(ResourceType::Graph, None);
923        let env_attrs = EnvironmentAttributes::new();
924
925        let context = AccessContext::new(
926            user_attrs,
927            resource_attrs,
928            env_attrs,
929            Action::Read,
930        );
931
932        let result = policy_engine.evaluate_access(&context).unwrap();
933        assert_eq!(result, PolicyDecision::Allow);
934    }
935
936    #[tokio::test]
937    async fn test_abac_service() {
938        let user_provider = Box::new(
939            SimpleUserAttributeProvider::new()
940                .add_user(
941                    "user1".to_string(),
942                    UserAttributes::new()
943                        .with_attribute("role".to_string(), AttributeValue::String("admin".to_string())),
944                )
945        );
946
947        let resource_provider = Box::new(
948            SimpleResourceAttributeProvider::new()
949                .add_resource(
950                    "graph".to_string(),
951                    ResourceAttributes::new(ResourceType::Graph, None),
952                )
953        );
954
955        let env_provider = Box::new(SimpleEnvironmentAttributeProvider::new());
956
957        let mut abac = ABACService::new(user_provider, resource_provider, env_provider);
958        abac.setup_common_policies().unwrap();
959
960        let result = abac.check_access(
961            &"user1".to_string(),
962            &ResourceType::Graph,
963            None,
964            &Action::Read,
965        ).await.unwrap();
966
967        assert_eq!(result, PolicyDecision::Allow);
968    }
969}