1use crate::capabilities::{ResourceType, Action};
10use crate::error::{SecurityError, Result};
11use chrono::{Utc, Datelike, Timelike};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15pub type PolicyId = String;
17
18pub type PrincipalId = String;
20
21pub type ResourceId = String;
23
24#[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 pub fn as_string(&self) -> Option<&str> {
38 match self {
39 AttributeValue::String(s) => Some(s),
40 _ => None,
41 }
42 }
43
44 pub fn as_integer(&self) -> Option<i64> {
46 match self {
47 AttributeValue::Integer(i) => Some(*i),
48 _ => None,
49 }
50 }
51
52 pub fn as_boolean(&self) -> Option<bool> {
54 match self {
55 AttributeValue::Boolean(b) => Some(*b),
56 _ => None,
57 }
58 }
59
60 pub fn as_float(&self) -> Option<f64> {
62 match self {
63 AttributeValue::Float(f) => Some(*f),
64 _ => None,
65 }
66 }
67}
68
69#[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#[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#[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 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#[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 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
231pub enum PolicyEffect {
232 Allow,
233 Deny,
234}
235
236#[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 pub fn matches(&self, context: &AccessContext) -> Result<bool> {
270 if !self.resource_types.is_empty() &&
272 !self.resource_types.contains(&context.resource.resource_type) {
273 return Ok(false);
274 }
275
276 if !self.actions.is_empty() &&
278 !self.actions.contains(&context.action) {
279 return Ok(false);
280 }
281
282 for condition in &self.conditions {
284 if !condition.evaluate(context)? {
285 return Ok(false);
286 }
287 }
288
289 Ok(true)
290 }
291}
292
293#[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#[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 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 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 match &self.value {
386 AttributeValue::Array(values) => Ok(values.contains(attribute_value)),
387 _ => Ok(false),
388 }
389 }
390 ConditionOperator::NotIn => {
391 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#[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, 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 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#[derive(Debug, Clone, PartialEq)]
481pub enum PolicyDecision {
482 Allow,
483 Deny,
484 NotApplicable,
485}
486
487#[derive(Debug)]
489pub struct PolicyEngine {
490 policies: HashMap<PolicyId, Policy>,
491}
492
493impl PolicyEngine {
494 pub fn new() -> Self {
496 Self {
497 policies: HashMap::new(),
498 }
499 }
500
501 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 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 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 pub fn get_policy(&self, policy_id: &PolicyId) -> Option<&Policy> {
535 self.policies.get(policy_id)
536 }
537
538 pub fn list_policies(&self) -> Vec<&Policy> {
540 self.policies.values().collect()
541 }
542
543 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 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 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 pub fn create_common_policies(&mut self) -> Result<()> {
575 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 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()), ))
607 )
608 .with_priority(500);
609
610 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 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
672pub 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 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 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 pub fn add_policy(&mut self, policy: Policy) -> Result<()> {
712 self.policy_engine.add_policy(policy)
713 }
714
715 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 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 let context = AccessContext::new(user_attrs, resource_attrs, env_attrs, action.clone());
730
731 self.policy_engine.evaluate_access(&context)
733 }
734
735 pub fn get_policies(&self) -> Vec<&Policy> {
737 self.policy_engine.list_policies()
738 }
739
740 pub fn setup_common_policies(&mut self) -> Result<()> {
742 self.policy_engine.create_common_policies()
743 }
744}
745
746#[async_trait::async_trait(?Send)]
748pub trait UserAttributeProvider: Send + Sync {
749 async fn get_attributes(&self, principal_id: &PrincipalId) -> Result<UserAttributes>;
750}
751
752#[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#[async_trait::async_trait(?Send)]
760pub trait EnvironmentAttributeProvider: Send + Sync {
761 async fn get_attributes(&self) -> Result<EnvironmentAttributes>;
762}
763
764pub 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}