1pub mod jwt;
13pub mod oauth2;
14pub mod mfa;
15pub mod password;
16pub mod session;
17pub mod audit;
18pub mod rbac;
19pub mod abac;
20pub mod policy;
21pub mod error;
22pub mod config;
23pub mod capabilities;
24
25use policy::PolicyService;
26
27pub use jwt::{JwtService, JwtClaims, TokenPair};
28pub use oauth2::{OAuth2Service, OAuth2Provider, OAuth2Tokens};
29pub use crate::config::OAuth2Config;
30pub use mfa::{MfaService, MfaSecret, MfaCode};
31pub use password::{PasswordService, PasswordHash};
32pub use session::{SessionManager, SessionData};
33pub use audit::{AuditService, AuditEvent, AuditEventType, AuditSeverity, AuditResult};
34pub use error::{SecurityError, Result};
35pub use config::{SecurityConfig, AuthMethod};
36pub use capabilities::{Capability, CapabilitySet, CapabilityService, ResourceType, Action};
37
38use serde::{Deserialize, Serialize};
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct User {
43 pub id: String,
44 pub email: String,
45 pub username: Option<String>,
46 pub roles: Vec<String>,
47 pub mfa_enabled: bool,
48 pub email_verified: bool,
49 pub created_at: chrono::DateTime<chrono::Utc>,
50 pub updated_at: chrono::DateTime<chrono::Utc>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct AuthResult {
56 pub user: User,
57 pub token_pair: Option<TokenPair>,
58 pub mfa_required: bool,
59 pub redirect_url: Option<String>,
60}
61
62#[derive(Debug, Clone)]
64pub struct AuthzResult {
65 pub allowed: bool,
66 pub reason: Option<String>,
67}
68
69#[derive(Debug, Clone)]
71pub struct Principal {
72 pub user_id: String,
73 pub roles: Vec<String>,
74 pub permissions: Vec<String>,
75 pub capabilities: CapabilitySet,
76 pub attributes: std::collections::HashMap<String, serde_json::Value>,
77}
78
79#[derive(Debug, Clone)]
81pub struct Resource {
82 pub resource_type: ResourceType,
83 pub resource_id: Option<String>,
84 pub action: Action,
85 pub attributes: std::collections::HashMap<String, serde_json::Value>,
86}
87
88impl Resource {
89 pub fn resource_type_as_str(&self) -> &str {
91 match &self.resource_type {
92 ResourceType::Graph => "graph",
93 ResourceType::FileSystem => "filesystem",
94 ResourceType::Network => "network",
95 ResourceType::Environment => "environment",
96 ResourceType::System => "system",
97 ResourceType::Plugin => "plugin",
98 ResourceType::Query => "query",
99 ResourceType::Admin => "admin",
100 ResourceType::User => "user",
101 ResourceType::Custom(name) => name,
102 }
103 }
104
105 pub fn action_as_str(&self) -> &str {
107 match &self.action {
108 Action::Read => "read",
109 Action::Write => "write",
110 Action::Execute => "execute",
111 Action::Delete => "delete",
112 Action::Create => "create",
113 Action::Update => "update",
114 Action::Admin => "admin",
115 Action::Custom(name) => name,
116 }
117 }
118}
119
120pub struct SecurityService {
122 jwt: JwtService,
123 oauth2: Option<OAuth2Service>,
124 mfa: MfaService,
125 password: PasswordService,
126 session: SessionManager,
127 capabilities: CapabilityService,
128 audit: AuditService,
129 policy: Option<PolicyService>,
130}
131
132impl SecurityService {
133 pub async fn new(config: SecurityConfig) -> Result<Self> {
135 let jwt = JwtService::new(config.jwt_config)?;
136 let oauth2 = if let Some(oauth2_config) = config.oauth2_config {
137 Some(OAuth2Service::new(oauth2_config).await?)
138 } else {
139 None
140 };
141 let mfa = MfaService::new();
142 let password = PasswordService::new();
143 let session = SessionManager::new(config.session_config);
144 let capabilities = CapabilityService::with_config(config.capability_config);
145 let audit = AuditService::new(config.audit_config);
146 let policy = None; Ok(Self {
149 jwt,
150 oauth2,
151 mfa,
152 password,
153 session,
154 capabilities,
155 audit,
156 policy,
157 })
158 }
159
160 pub async fn authenticate_local(
162 &self,
163 identifier: &str,
164 password: &str,
165 ) -> Result<AuthResult> {
166 todo!("Implement local authentication")
168 }
169
170 pub async fn start_oauth2_flow(&self, provider: OAuth2Provider) -> Result<String> {
172 self.oauth2
173 .as_ref()
174 .ok_or_else(|| SecurityError::Configuration("OAuth2 not configured".to_string()))?
175 .get_authorization_url(provider)
176 .await
177 }
178
179 pub async fn complete_oauth2_flow(
181 &self,
182 provider: OAuth2Provider,
183 code: &str,
184 state: &str,
185 ) -> Result<AuthResult> {
186 todo!("Implement OAuth2 flow completion")
188 }
189
190 pub fn setup_mfa(&self, user_id: &str) -> Result<(String, String)> {
192 self.mfa.generate_secret(user_id)
193 }
194
195 pub fn verify_mfa(&self, secret: &str, code: &str) -> Result<bool> {
197 self.mfa.verify_code(secret, code)
198 }
199
200 pub fn validate_token(&self, token: &str) -> Result<JwtClaims> {
202 self.jwt.validate_token(token)
203 }
204
205 pub fn generate_tokens(&self, user_id: &str, roles: Vec<String>) -> Result<TokenPair> {
207 self.jwt.generate_token_pair(user_id, roles)
208 }
209
210 pub fn refresh_token(&self, refresh_token: &str) -> Result<TokenPair> {
212 self.jwt.refresh_access_token(refresh_token)
213 }
214
215 pub fn check_authorization(
217 &self,
218 principal: &Principal,
219 resource: &Resource,
220 ) -> AuthzResult {
221 let scope = resource.resource_id.as_deref();
223 let allowed = self.capabilities.check_capability(
224 &principal.capabilities,
225 &resource.resource_type,
226 &resource.action,
227 scope,
228 );
229
230 if allowed {
231 return AuthzResult {
232 allowed: true,
233 reason: None,
234 };
235 }
236
237 self.check_legacy_authorization(principal, resource)
240 }
241
242 fn check_legacy_authorization(
244 &self,
245 principal: &Principal,
246 resource: &Resource,
247 ) -> AuthzResult {
248 let required_permission = format!("{}:{}", resource.resource_type_as_str(), resource.action_as_str());
252
253 if principal.permissions.contains(&required_permission) {
254 return AuthzResult {
255 allowed: true,
256 reason: Some("Legacy permission check passed".to_string()),
257 };
258 }
259
260 if principal.roles.contains(&"admin".to_string()) {
262 return AuthzResult {
263 allowed: true,
264 reason: Some("Admin role override".to_string()),
265 };
266 }
267
268 AuthzResult {
269 allowed: false,
270 reason: Some(format!("Missing capability for {}:{}", required_permission, principal.user_id)),
271 }
272 }
273
274 pub fn hash_password(&self, password: &str) -> Result<PasswordHash> {
276 self.password.hash_password(password)
277 }
278
279 pub fn verify_password(&self, password: &str, hash: &PasswordHash) -> Result<bool> {
281 self.password.verify_password(password, hash)
282 }
283
284 pub fn grant_capabilities(
286 &self,
287 principal_caps: &CapabilitySet,
288 new_caps: Vec<Capability>,
289 ) -> CapabilitySet {
290 self.capabilities.grant_capabilities(principal_caps, new_caps)
291 }
292
293 pub fn revoke_capabilities(
295 &self,
296 principal_caps: &CapabilitySet,
297 caps_to_revoke: Vec<Capability>,
298 ) -> CapabilitySet {
299 self.capabilities.revoke_capabilities(principal_caps, caps_to_revoke)
300 }
301
302 pub fn attenuate_capabilities(
304 &self,
305 cap_set: &CapabilitySet,
306 restrictions: Vec<Capability>,
307 ) -> CapabilitySet {
308 self.capabilities.attenuate_capabilities(cap_set, restrictions)
309 }
310
311 pub fn create_principal_with_capabilities(
313 &self,
314 user_id: String,
315 capabilities: CapabilitySet,
316 roles: Vec<String>,
317 permissions: Vec<String>,
318 attributes: std::collections::HashMap<String, serde_json::Value>,
319 ) -> Principal {
320 Principal {
321 user_id,
322 roles,
323 permissions,
324 capabilities,
325 attributes,
326 }
327 }
328
329 pub fn create_resource(
331 &self,
332 resource_type: ResourceType,
333 action: Action,
334 resource_id: Option<String>,
335 attributes: std::collections::HashMap<String, serde_json::Value>,
336 ) -> Resource {
337 Resource {
338 resource_type,
339 resource_id,
340 action,
341 attributes,
342 }
343 }
344
345 pub async fn log_audit_event(&self, event: AuditEvent) -> Result<()> {
347 self.audit.log_event(event).await
348 }
349
350 pub async fn log_authentication(
352 &self,
353 user_id: Option<&str>,
354 ip_address: Option<&str>,
355 user_agent: Option<&str>,
356 result: AuditResult,
357 message: &str,
358 ) -> Result<()> {
359 self.audit.log_authentication(user_id, ip_address, user_agent, result, message).await
360 }
361
362 pub async fn log_authorization(
364 &self,
365 user_id: &str,
366 resource: &str,
367 action: &str,
368 result: AuditResult,
369 ip_address: Option<&str>,
370 ) -> Result<()> {
371 self.audit.log_authorization(user_id, resource, action, result, ip_address).await
372 }
373
374 pub async fn log_data_access(
376 &self,
377 user_id: &str,
378 resource: &str,
379 action: &str,
380 result: AuditResult,
381 metadata: std::collections::HashMap<String, serde_json::Value>,
382 ) -> Result<()> {
383 self.audit.log_data_access(user_id, resource, action, result, metadata).await
384 }
385
386 pub async fn get_audit_events(
388 &self,
389 start_time: Option<chrono::DateTime<chrono::Utc>>,
390 end_time: Option<chrono::DateTime<chrono::Utc>>,
391 event_type: Option<&AuditEventType>,
392 user_id: Option<&str>,
393 limit: Option<usize>,
394 ) -> Result<Vec<AuditEvent>> {
395 self.audit.get_events(start_time, end_time, event_type, user_id, limit).await
396 }
397
398 pub async fn get_audit_statistics(&self) -> Result<crate::audit::AuditStatistics> {
400 self.audit.get_statistics().await
401 }
402
403 pub async fn cleanup_audit_events(&self) -> Result<usize> {
405 self.audit.cleanup_old_events().await
406 }
407
408 pub fn setup_policy_service(&mut self, config: crate::policy::PolicyEngineConfig) -> Result<()> {
410 let rbac_service = crate::rbac::RBACService::new();
412
413 let user_provider = std::sync::Arc::new(std::sync::Mutex::new(
415 crate::abac::SimpleUserAttributeProvider::new()
416 ));
417
418 let resource_provider = std::sync::Arc::new(std::sync::Mutex::new(
419 crate::abac::SimpleResourceAttributeProvider::new()
420 ));
421
422 let env_provider = std::sync::Arc::new(std::sync::Mutex::new(
423 crate::abac::SimpleEnvironmentAttributeProvider::new()
424 ));
425
426 let abac_service = crate::abac::ABACService::new(
429 Box::new(SimpleUserProviderWrapper(user_provider)),
430 Box::new(SimpleResourceProviderWrapper(resource_provider)),
431 Box::new(SimpleEnvProviderWrapper(env_provider)),
432 );
433
434 let policy_service = crate::policy::PolicyService::with_services(
435 config,
436 Some(rbac_service),
437 Some(abac_service),
438 );
439
440 self.policy = Some(policy_service);
441 Ok(())
442 }
443
444 pub fn set_policy_service(&mut self, policy_service: PolicyService) {
446 self.policy = Some(policy_service);
447 }
448
449 pub async fn check_access_policy(
451 &self,
452 principal_id: &str,
453 resource_type: &ResourceType,
454 resource_id: Option<&str>,
455 action: &Action,
456 ) -> Result<bool> {
457 let policy_service = self.policy.as_ref()
458 .ok_or_else(|| SecurityError::Configuration("Policy service not configured".to_string()))?;
459
460 let principal_id_string = principal_id.to_string();
461 let resource_id_string = resource_id.map(|s| s.to_string());
462
463 policy_service.check_permission(&principal_id_string, resource_type, resource_id_string.as_ref(), action).await
464 }
465
466 pub async fn authorize_action(
468 &self,
469 principal_id: &str,
470 resource_type: &ResourceType,
471 resource_id: Option<&str>,
472 action: &Action,
473 ) -> Result<crate::policy::UnifiedPolicyDecision> {
474 let policy_service = self.policy.as_ref()
475 .ok_or_else(|| SecurityError::Configuration("Policy service not configured".to_string()))?;
476
477 let principal_id_string = principal_id.to_string();
478 let resource_id_string = resource_id.map(|s| s.to_string());
479
480 policy_service.authorize(&principal_id_string, resource_type, resource_id_string.as_ref(), action).await
481 }
482
483 pub fn add_role(&mut self, role: crate::rbac::Role) -> Result<()> {
485 let policy_service = self.policy.as_mut()
486 .ok_or_else(|| SecurityError::Configuration("Policy service not configured".to_string()))?;
487
488 policy_service.add_role(role)
489 }
490
491 pub fn assign_role(&mut self, assignment: crate::rbac::RoleAssignment) -> Result<()> {
493 let policy_service = self.policy.as_mut()
494 .ok_or_else(|| SecurityError::Configuration("Policy service not configured".to_string()))?;
495
496 policy_service.assign_role(assignment)
497 }
498
499 pub fn add_policy(&mut self, policy: crate::abac::Policy) -> Result<()> {
501 let policy_service = self.policy.as_mut()
502 .ok_or_else(|| SecurityError::Configuration("Policy service not configured".to_string()))?;
503
504 policy_service.add_policy(policy)
505 }
506
507 pub fn setup_common_policies(&mut self) -> Result<()> {
509 let policy_service = self.policy.as_mut()
510 .ok_or_else(|| SecurityError::Configuration("Policy service not configured".to_string()))?;
511
512 policy_service.setup_common_policies()
513 }
514
515 pub fn policy_service(&self) -> Option<&PolicyService> {
517 self.policy.as_ref()
518 }
519
520 pub fn policy_service_mut(&mut self) -> Option<&mut PolicyService> {
522 self.policy.as_mut()
523 }
524}
525
526pub struct SimpleUserProviderWrapper(std::sync::Arc<std::sync::Mutex<crate::abac::SimpleUserAttributeProvider>>);
528
529#[async_trait::async_trait(?Send)]
530impl crate::abac::UserAttributeProvider for SimpleUserProviderWrapper {
531 async fn get_attributes(&self, principal_id: &crate::abac::PrincipalId) -> Result<crate::abac::UserAttributes> {
532 let provider = self.0.lock().unwrap();
533 provider.get_attributes(principal_id).await
534 }
535}
536
537pub struct SimpleResourceProviderWrapper(std::sync::Arc<std::sync::Mutex<crate::abac::SimpleResourceAttributeProvider>>);
538
539#[async_trait::async_trait(?Send)]
540impl crate::abac::ResourceAttributeProvider for SimpleResourceProviderWrapper {
541 async fn get_attributes(&self, resource_type: &ResourceType, resource_id: Option<&crate::abac::ResourceId>) -> Result<crate::abac::ResourceAttributes> {
542 let provider = self.0.lock().unwrap();
543 provider.get_attributes(resource_type, resource_id).await
544 }
545}
546
547pub struct SimpleEnvProviderWrapper(std::sync::Arc<std::sync::Mutex<crate::abac::SimpleEnvironmentAttributeProvider>>);
548
549#[async_trait::async_trait(?Send)]
550impl crate::abac::EnvironmentAttributeProvider for SimpleEnvProviderWrapper {
551 async fn get_attributes(&self) -> Result<crate::abac::EnvironmentAttributes> {
552 let provider = self.0.lock().unwrap();
553 provider.get_attributes().await
554 }
555}
556
557pub async fn init_security(config: SecurityConfig) -> Result<SecurityService> {
559 SecurityService::new(config).await
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 #[test]
567 fn test_security_service_creation() {
568 }
573
574 #[test]
575 fn test_user_creation() {
576 let created_at = chrono::Utc::now();
577 let updated_at = chrono::Utc::now();
578
579 let user = User {
580 id: "user123".to_string(),
581 email: "test@example.com".to_string(),
582 username: Some("testuser".to_string()),
583 roles: vec!["user".to_string(), "editor".to_string()],
584 mfa_enabled: true,
585 email_verified: false,
586 created_at,
587 updated_at,
588 };
589
590 assert_eq!(user.id, "user123");
591 assert_eq!(user.email, "test@example.com");
592 assert_eq!(user.username, Some("testuser".to_string()));
593 assert_eq!(user.roles.len(), 2);
594 assert!(user.mfa_enabled);
595 assert!(!user.email_verified);
596 }
597
598 #[test]
599 fn test_auth_result_creation() {
600 let user = User {
601 id: "user123".to_string(),
602 email: "test@example.com".to_string(),
603 username: None,
604 roles: vec![],
605 mfa_enabled: false,
606 email_verified: false,
607 created_at: chrono::Utc::now(),
608 updated_at: chrono::Utc::now(),
609 };
610
611 let auth_result = AuthResult {
612 user: user.clone(),
613 token_pair: None,
614 mfa_required: false,
615 redirect_url: None,
616 };
617
618 assert_eq!(auth_result.user.id, "user123");
619 assert!(auth_result.token_pair.is_none());
620 assert!(!auth_result.mfa_required);
621 assert!(auth_result.redirect_url.is_none());
622 }
623
624 #[test]
625 fn test_authz_result_creation() {
626 let allowed_result = AuthzResult {
627 allowed: true,
628 reason: None,
629 };
630
631 let denied_result = AuthzResult {
632 allowed: false,
633 reason: Some("Insufficient permissions".to_string()),
634 };
635
636 assert!(allowed_result.allowed);
637 assert!(allowed_result.reason.is_none());
638
639 assert!(!denied_result.allowed);
640 assert_eq!(denied_result.reason, Some("Insufficient permissions".to_string()));
641 }
642
643 #[test]
644 fn test_principal_creation() {
645 use std::collections::HashMap;
646
647 let mut attributes = HashMap::new();
648 attributes.insert("department".to_string(), serde_json::json!("engineering"));
649
650 let principal = Principal {
651 user_id: "user123".to_string(),
652 roles: vec!["user".to_string()],
653 permissions: vec!["read".to_string()],
654 capabilities: CapabilitySet::new(vec![]),
655 attributes,
656 };
657
658 assert_eq!(principal.user_id, "user123");
659 assert_eq!(principal.roles.len(), 1);
660 assert_eq!(principal.permissions.len(), 1);
661 assert_eq!(principal.attributes.get("department"), Some(&serde_json::json!("engineering")));
662 }
663
664 #[test]
665 fn test_resource_creation() {
666 use std::collections::HashMap;
667
668 let mut attributes = HashMap::new();
669 attributes.insert("owner".to_string(), serde_json::json!("user123"));
670
671 let resource = Resource {
672 resource_type: ResourceType::Graph,
673 resource_id: Some("graph123".to_string()),
674 action: Action::Read,
675 attributes,
676 };
677
678 assert!(matches!(resource.resource_type, ResourceType::Graph));
679 assert_eq!(resource.resource_id, Some("graph123".to_string()));
680 assert!(matches!(resource.action, Action::Read));
681 assert_eq!(resource.attributes.get("owner"), Some(&serde_json::json!("user123")));
682 }
683
684 #[test]
685 fn test_resource_helper_methods() {
686 let resource = Resource {
687 resource_type: ResourceType::Graph,
688 resource_id: None,
689 action: Action::Read,
690 attributes: std::collections::HashMap::new(),
691 };
692
693 assert_eq!(resource.resource_type_as_str(), "graph");
694 assert_eq!(resource.action_as_str(), "read");
695 }
696
697 #[test]
698 fn test_user_serialization() {
699 let user = User {
700 id: "user123".to_string(),
701 email: "test@example.com".to_string(),
702 username: Some("testuser".to_string()),
703 roles: vec!["user".to_string()],
704 mfa_enabled: true,
705 email_verified: true,
706 created_at: chrono::Utc::now(),
707 updated_at: chrono::Utc::now(),
708 };
709
710 let json_result = serde_json::to_string(&user);
712 assert!(json_result.is_ok());
713
714 let json_str = json_result.unwrap();
715 assert!(json_str.contains("user123"));
716 assert!(json_str.contains("test@example.com"));
717 assert!(json_str.contains("testuser"));
718 assert!(json_str.contains("user"));
719 assert!(json_str.contains("true"));
720 }
721
722 #[test]
723 fn test_auth_result_serialization() {
724 let user = User {
725 id: "user123".to_string(),
726 email: "test@example.com".to_string(),
727 username: None,
728 roles: vec![],
729 mfa_enabled: false,
730 email_verified: false,
731 created_at: chrono::Utc::now(),
732 updated_at: chrono::Utc::now(),
733 };
734
735 let auth_result = AuthResult {
736 user: user.clone(),
737 token_pair: None,
738 mfa_required: true,
739 redirect_url: None,
740 };
741
742 let json_result = serde_json::to_string(&auth_result);
744 assert!(json_result.is_ok());
745
746 let json_str = json_result.unwrap();
747 assert!(json_str.contains("user123"));
748 assert!(json_str.contains("true"));
749 assert!(json_str.contains("null"));
750 }
751
752 #[test]
753 fn test_principal_serialization() {
754 use std::collections::HashMap;
755
756 let principal = Principal {
757 user_id: "user123".to_string(),
758 roles: vec!["user".to_string()],
759 permissions: vec!["read".to_string()],
760 capabilities: CapabilitySet::new(vec![]),
761 attributes: HashMap::new(),
762 };
763
764 let json_result = serde_json::to_string(&principal);
766 assert!(json_result.is_ok());
767
768 let json_str = json_result.unwrap();
769 assert!(json_str.contains("user123"));
770 assert!(json_str.contains("user"));
771 assert!(json_str.contains("read"));
772 }
773
774 #[test]
775 fn test_user_clone() {
776 let user = User {
777 id: "user123".to_string(),
778 email: "test@example.com".to_string(),
779 username: None,
780 roles: vec![],
781 mfa_enabled: false,
782 email_verified: false,
783 created_at: chrono::Utc::now(),
784 updated_at: chrono::Utc::now(),
785 };
786
787 let cloned = user.clone();
788
789 assert_eq!(user.id, cloned.id);
790 assert_eq!(user.email, cloned.email);
791 assert_eq!(user.username, cloned.username);
792 assert_eq!(user.roles, cloned.roles);
793 assert_eq!(user.mfa_enabled, cloned.mfa_enabled);
794 assert_eq!(user.email_verified, cloned.email_verified);
795 }
796
797 #[test]
798 fn test_resource_type_variants() {
799 let resource = Resource {
800 resource_type: ResourceType::Graph,
801 resource_id: None,
802 action: Action::Read,
803 attributes: std::collections::HashMap::new(),
804 };
805 assert_eq!(resource.resource_type_as_str(), "graph");
806
807 let resource = Resource {
808 resource_type: ResourceType::FileSystem,
809 resource_id: None,
810 action: Action::Write,
811 attributes: std::collections::HashMap::new(),
812 };
813 assert_eq!(resource.resource_type_as_str(), "filesystem");
814
815 let resource = Resource {
816 resource_type: ResourceType::Custom("custom_type".to_string()),
817 resource_id: None,
818 action: Action::Read,
819 attributes: std::collections::HashMap::new(),
820 };
821 assert_eq!(resource.resource_type_as_str(), "custom_type");
822 }
823
824 #[test]
825 fn test_action_variants() {
826 let actions = vec![
827 (Action::Read, "read"),
828 (Action::Write, "write"),
829 (Action::Execute, "execute"),
830 (Action::Delete, "delete"),
831 (Action::Create, "create"),
832 (Action::Update, "update"),
833 (Action::Admin, "admin"),
834 (Action::Custom("custom_action".to_string()), "custom_action"),
835 ];
836
837 for (action, expected_str) in actions {
838 let resource = Resource {
839 resource_type: ResourceType::Graph,
840 resource_id: None,
841 action,
842 attributes: std::collections::HashMap::new(),
843 };
844 assert_eq!(resource.action_as_str(), expected_str);
845 }
846 }
847
848 #[test]
849 fn test_capability_creation() {
850 let capability = Capability {
851 resource_type: ResourceType::Graph,
852 action: Action::Read,
853 scope: Some("graph123".to_string()),
854 };
855
856 assert!(matches!(capability.resource_type, ResourceType::Graph));
857 assert!(matches!(capability.action, Action::Read));
858 assert_eq!(capability.scope, Some("graph123".to_string()));
859 }
860
861 #[test]
862 fn test_capability_set_creation() {
863 let capabilities = vec![
864 Capability {
865 resource_type: ResourceType::Graph,
866 action: Action::Read,
867 scope: None,
868 },
869 Capability {
870 resource_type: ResourceType::FileSystem,
871 action: Action::Write,
872 scope: Some("*.txt".to_string()),
873 },
874 ];
875
876 let capability_set = CapabilitySet::new(capabilities);
877
878 assert_eq!(capability_set.capabilities.len(), 2);
879 assert!(matches!(capability_set.capabilities[0].resource_type, ResourceType::Graph));
880 assert!(matches!(capability_set.capabilities[1].action, Action::Write));
881 }
882}