kotoba_security/
lib.rs

1//! # Kotoba Security
2//!
3//! Comprehensive security components for Kotoba graph database system.
4//!
5//! This crate provides:
6//! - JWT token generation and validation
7//! - OAuth2/OpenID Connect integration
8//! - Multi-factor authentication (TOTP)
9//! - Secure password hashing
10//! - Session management
11
12pub 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/// User identity representation
41#[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/// Authentication result
54#[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/// Authorization check result
63#[derive(Debug, Clone)]
64pub struct AuthzResult {
65    pub allowed: bool,
66    pub reason: Option<String>,
67}
68
69/// Principal for authorization decisions
70#[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/// Resource for authorization checks
80#[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    /// Helper method to get resource type as string (for backward compatibility)
90    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    /// Helper method to get action as string (for backward compatibility)
106    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
120/// Main security service combining all components
121pub 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    /// Create a new security service with the given configuration
134    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; // Policy service needs to be set up separately
147
148        Ok(Self {
149            jwt,
150            oauth2,
151            mfa,
152            password,
153            session,
154            capabilities,
155            audit,
156            policy,
157        })
158    }
159
160    /// Authenticate a user with username/email and password
161    pub async fn authenticate_local(
162        &self,
163        identifier: &str,
164        password: &str,
165    ) -> Result<AuthResult> {
166        // Implementation will be added
167        todo!("Implement local authentication")
168    }
169
170    /// Start OAuth2 authentication flow
171    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    /// Complete OAuth2 authentication flow
180    pub async fn complete_oauth2_flow(
181        &self,
182        provider: OAuth2Provider,
183        code: &str,
184        state: &str,
185    ) -> Result<AuthResult> {
186        // Implementation will be added
187        todo!("Implement OAuth2 flow completion")
188    }
189
190    /// Generate MFA secret and QR code for user
191    pub fn setup_mfa(&self, user_id: &str) -> Result<(String, String)> {
192        self.mfa.generate_secret(user_id)
193    }
194
195    /// Verify MFA code
196    pub fn verify_mfa(&self, secret: &str, code: &str) -> Result<bool> {
197        self.mfa.verify_code(secret, code)
198    }
199
200    /// Validate JWT token
201    pub fn validate_token(&self, token: &str) -> Result<JwtClaims> {
202        self.jwt.validate_token(token)
203    }
204
205    /// Generate new token pair
206    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    /// Refresh access token
211    pub fn refresh_token(&self, refresh_token: &str) -> Result<TokenPair> {
212        self.jwt.refresh_access_token(refresh_token)
213    }
214
215    /// Check authorization for principal on resource using capabilities
216    pub fn check_authorization(
217        &self,
218        principal: &Principal,
219        resource: &Resource,
220    ) -> AuthzResult {
221        // First check capabilities (primary authorization mechanism)
222        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        // Fallback to legacy role-based permissions for backward compatibility
238        // This can be removed in future versions when all permissions are migrated to capabilities
239        self.check_legacy_authorization(principal, resource)
240    }
241
242    /// Legacy role-based authorization (for backward compatibility)
243    fn check_legacy_authorization(
244        &self,
245        principal: &Principal,
246        resource: &Resource,
247    ) -> AuthzResult {
248        // Simple role-based check for backward compatibility
249        // In practice, this should be migrated to capabilities
250
251        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        // Check admin roles
261        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    /// Hash password
275    pub fn hash_password(&self, password: &str) -> Result<PasswordHash> {
276        self.password.hash_password(password)
277    }
278
279    /// Verify password against hash
280    pub fn verify_password(&self, password: &str, hash: &PasswordHash) -> Result<bool> {
281        self.password.verify_password(password, hash)
282    }
283
284    /// Grant capabilities to a principal
285    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    /// Revoke capabilities from a principal
294    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    /// Create an attenuated capability set for safer operations
303    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    /// Create a principal with specific capabilities
312    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    /// Create a resource for authorization checks
330    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    /// Log an audit event
346    pub async fn log_audit_event(&self, event: AuditEvent) -> Result<()> {
347        self.audit.log_event(event).await
348    }
349
350    /// Log authentication event
351    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    /// Log authorization event
363    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    /// Log data access event
375    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    /// Get audit events
387    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    /// Get audit statistics
399    pub async fn get_audit_statistics(&self) -> Result<crate::audit::AuditStatistics> {
400        self.audit.get_statistics().await
401    }
402
403    /// Clean up old audit events
404    pub async fn cleanup_audit_events(&self) -> Result<usize> {
405        self.audit.cleanup_old_events().await
406    }
407
408    /// Set up policy service with RBAC and ABAC
409    pub fn setup_policy_service(&mut self, config: crate::policy::PolicyEngineConfig) -> Result<()> {
410        // Create RBAC service
411        let rbac_service = crate::rbac::RBACService::new();
412
413        // Create ABAC service with simple providers (can be customized later)
414        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        // For now, use the trait objects directly
427        // In a real implementation, you'd want to use Arc<Mutex<>> for thread safety
428        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    /// Set policy service directly
445    pub fn set_policy_service(&mut self, policy_service: PolicyService) {
446        self.policy = Some(policy_service);
447    }
448
449    /// Check access permission using unified RBAC/ABAC policy engine
450    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    /// Authorize action with detailed policy decision
467    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    /// Add RBAC role
484    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    /// Assign role to principal
492    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    /// Add ABAC policy
500    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    /// Setup common roles and policies
508    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    /// Get policy service for advanced operations
516    pub fn policy_service(&self) -> Option<&PolicyService> {
517        self.policy.as_ref()
518    }
519
520    /// Get mutable policy service
521    pub fn policy_service_mut(&mut self) -> Option<&mut PolicyService> {
522        self.policy.as_mut()
523    }
524}
525
526// Wrapper types for thread-safe attribute providers
527pub 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
557/// Convenience function to create a security service
558pub 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        // Test will be added once config is implemented
569        // let config = SecurityConfig::default();
570        // let service = SecurityService::new(config);
571        // assert!(service.is_ok());
572    }
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        // Test JSON serialization
711        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        // Test JSON serialization
743        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        // Test JSON serialization
765        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}