pulseengine_mcp_auth/
storage.rs

1//! Storage backend for authentication data
2
3use crate::{
4    config::StorageConfig,
5    models::{ApiKey, SecureApiKey},
6};
7use async_trait::async_trait;
8use std::collections::HashMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use thiserror::Error;
12use tokio::fs;
13use tracing::{debug, error, info, warn};
14
15#[derive(Debug, Error)]
16pub enum StorageError {
17    #[error("Storage error: {0}")]
18    General(String),
19
20    #[error("File I/O error: {0}")]
21    Io(#[from] std::io::Error),
22
23    #[error("Serialization error: {0}")]
24    Serialization(#[from] serde_json::Error),
25
26    #[error("Permission error: {0}")]
27    Permission(String),
28
29    #[error("Encryption error: {0}")]
30    Encryption(#[from] crate::crypto::encryption::EncryptionError),
31}
32
33/// Storage backend trait
34#[async_trait]
35pub trait StorageBackend: Send + Sync {
36    async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError>;
37    async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError>;
38    async fn delete_key(&self, key_id: &str) -> Result<(), StorageError>;
39    async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError>;
40}
41
42/// Create a storage backend from configuration
43pub async fn create_storage_backend(
44    config: &StorageConfig,
45) -> Result<Arc<dyn StorageBackend>, StorageError> {
46    match config {
47        StorageConfig::File {
48            path,
49            file_permissions,
50            dir_permissions,
51            require_secure_filesystem,
52            enable_filesystem_monitoring,
53        } => {
54            let storage = FileStorage::new(
55                path.clone(),
56                *file_permissions,
57                *dir_permissions,
58                *require_secure_filesystem,
59                *enable_filesystem_monitoring,
60            )
61            .await?;
62            Ok(Arc::new(storage))
63        }
64        StorageConfig::Environment { prefix } => {
65            let storage = EnvironmentStorage::new(prefix.clone());
66            Ok(Arc::new(storage))
67        }
68        StorageConfig::Memory => {
69            let storage = MemoryStorage::new();
70            Ok(Arc::new(storage))
71        }
72    }
73}
74
75/// File-based storage backend with atomic operations and encryption
76pub struct FileStorage {
77    path: PathBuf,
78    encryption_key: [u8; 32],
79    file_permissions: u32,
80    #[allow(dead_code)]
81    dir_permissions: u32,
82    #[allow(dead_code)]
83    require_secure_filesystem: bool,
84    enable_filesystem_monitoring: bool,
85    write_mutex: tokio::sync::Mutex<()>,
86}
87
88impl FileStorage {
89    pub async fn new(
90        path: PathBuf,
91        file_permissions: u32,
92        dir_permissions: u32,
93        require_secure_filesystem: bool,
94        enable_filesystem_monitoring: bool,
95    ) -> Result<Self, StorageError> {
96        use crate::crypto::encryption::derive_encryption_key;
97        use crate::crypto::keys::generate_master_key;
98
99        // Validate filesystem security if required
100        if require_secure_filesystem {
101            Self::validate_filesystem_security(&path).await?;
102        }
103
104        // Ensure parent directory exists with secure permissions
105        if let Some(parent) = path.parent() {
106            fs::create_dir_all(parent).await?;
107
108            // Set secure permissions on Unix
109            #[cfg(unix)]
110            {
111                use std::os::unix::fs::PermissionsExt;
112                let mut perms = fs::metadata(parent).await?.permissions();
113                perms.set_mode(dir_permissions); // Use configured directory permissions
114                fs::set_permissions(parent, perms).await?;
115
116                // Verify no other users have access
117                Self::verify_directory_security(parent, dir_permissions).await?;
118            }
119        }
120
121        // Generate or load master key, then derive storage key
122        let master_key = generate_master_key().map_err(|e| StorageError::General(e.to_string()))?;
123        let encryption_key = derive_encryption_key(&master_key, "api-key-storage");
124
125        let storage = Self {
126            path,
127            encryption_key,
128            file_permissions,
129            dir_permissions,
130            require_secure_filesystem,
131            enable_filesystem_monitoring,
132            write_mutex: tokio::sync::Mutex::new(()),
133        };
134
135        // Initialize empty file if it doesn't exist
136        if !storage.path.exists() {
137            storage.save_all_keys(&HashMap::new()).await?;
138        } else {
139            // Verify existing file security
140            storage.ensure_secure_permissions().await?;
141        }
142
143        Ok(storage)
144    }
145
146    async fn ensure_secure_permissions(&self) -> Result<(), StorageError> {
147        #[cfg(unix)]
148        {
149            use std::os::unix::fs::PermissionsExt;
150
151            if self.path.exists() {
152                let metadata = fs::metadata(&self.path).await?;
153                let mode = metadata.permissions().mode() & 0o777;
154
155                // Check if permissions are more permissive than configured
156                if mode != self.file_permissions {
157                    warn!(
158                        "Incorrect permissions on key file: {:o}, fixing to {:o}",
159                        mode, self.file_permissions
160                    );
161                    let mut perms = metadata.permissions();
162                    perms.set_mode(self.file_permissions);
163                    fs::set_permissions(&self.path, perms).await?;
164                }
165
166                // Verify file ownership (only owner should have access)
167                Self::verify_file_ownership(&self.path).await?;
168            }
169        }
170        Ok(())
171    }
172
173    /// Validate that the filesystem is secure (not network/shared)
174    async fn validate_filesystem_security(path: &PathBuf) -> Result<(), StorageError> {
175        #[cfg(unix)]
176        {
177            use std::os::unix::fs::MetadataExt;
178
179            if let Some(parent) = path.parent() {
180                if parent.exists() {
181                    let metadata = fs::metadata(parent).await?;
182
183                    // Check if this is a network filesystem (basic check)
184                    let _dev = metadata.dev();
185
186                    // On many Unix systems, network filesystems have device IDs that indicate remote storage
187                    // This is a basic check - in production you might want more sophisticated detection
188                    if let Ok(mount_info) = fs::read_to_string("/proc/mounts").await {
189                        let path_str = parent.to_string_lossy();
190                        for line in mount_info.lines() {
191                            let parts: Vec<&str> = line.split_whitespace().collect();
192                            if parts.len() >= 3 {
193                                let mount_point = parts[1];
194                                let fs_type = parts[2];
195
196                                if path_str.starts_with(mount_point) {
197                                    // Check for network filesystem types
198                                    match fs_type {
199                                        "nfs" | "nfs4" | "cifs" | "smb" | "smbfs"
200                                        | "fuse.sshfs" => {
201                                            return Err(StorageError::Permission(format!(
202                                                "Storage path {} is on insecure network filesystem: {}",
203                                                path_str, fs_type
204                                            )));
205                                        }
206                                        _ => {}
207                                    }
208                                }
209                            }
210                        }
211                    }
212                }
213            }
214        }
215
216        Ok(())
217    }
218
219    /// Verify directory security and ownership
220    async fn verify_directory_security(
221        dir: &std::path::Path,
222        expected_perms: u32,
223    ) -> Result<(), StorageError> {
224        #[cfg(unix)]
225        {
226            use std::os::unix::fs::{MetadataExt, PermissionsExt};
227
228            let metadata = fs::metadata(dir).await?;
229            let mode = metadata.permissions().mode() & 0o777;
230
231            // Verify permissions are not more permissive than expected
232            if (mode & !expected_perms) != 0 {
233                return Err(StorageError::Permission(format!(
234                    "Directory {} has insecure permissions: {:o} (expected: {:o})",
235                    dir.display(),
236                    mode,
237                    expected_perms
238                )));
239            }
240
241            // Verify ownership (should be current user)
242            let current_uid = unsafe { libc::getuid() };
243            if metadata.uid() != current_uid {
244                return Err(StorageError::Permission(format!(
245                    "Directory {} is not owned by current user (uid: {} vs {})",
246                    dir.display(),
247                    metadata.uid(),
248                    current_uid
249                )));
250            }
251        }
252
253        Ok(())
254    }
255
256    /// Verify file ownership
257    async fn verify_file_ownership(file: &std::path::Path) -> Result<(), StorageError> {
258        #[cfg(unix)]
259        {
260            use std::os::unix::fs::MetadataExt;
261
262            let metadata = fs::metadata(file).await?;
263            let current_uid = unsafe { libc::getuid() };
264
265            if metadata.uid() != current_uid {
266                return Err(StorageError::Permission(format!(
267                    "File {} is not owned by current user (uid: {} vs {})",
268                    file.display(),
269                    metadata.uid(),
270                    current_uid
271                )));
272            }
273        }
274
275        Ok(())
276    }
277
278    /// Save secure keys with encryption
279    async fn save_secure_keys(
280        &self,
281        keys: &HashMap<String, SecureApiKey>,
282    ) -> Result<(), StorageError> {
283        use crate::crypto::encryption::encrypt_data;
284
285        let content = serde_json::to_string_pretty(keys)?;
286        let encrypted_data = encrypt_data(content.as_bytes(), &self.encryption_key)?;
287        let encrypted_content = serde_json::to_string_pretty(&encrypted_data)?;
288
289        // Atomic write using temp file
290        let temp_path = self.path.with_extension("tmp");
291        fs::write(&temp_path, encrypted_content).await?;
292
293        // Set secure permissions before moving
294        #[cfg(unix)]
295        {
296            use std::os::unix::fs::PermissionsExt;
297            let mut perms = fs::metadata(&temp_path).await?.permissions();
298            perms.set_mode(self.file_permissions); // Use configured file permissions
299            fs::set_permissions(&temp_path, perms).await?;
300        }
301
302        // Atomic move
303        fs::rename(&temp_path, &self.path).await?;
304
305        debug!("Saved {} keys to encrypted file storage", keys.len());
306        Ok(())
307    }
308
309    /// Create a secure backup of the storage file
310    pub async fn create_backup(&self) -> Result<PathBuf, StorageError> {
311        if !self.path.exists() {
312            return Err(StorageError::General(
313                "Storage file does not exist".to_string(),
314            ));
315        }
316
317        let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S_%3f");
318        let backup_path = self
319            .path
320            .with_extension(format!("backup_{}.enc", timestamp));
321
322        // Copy with secure permissions
323        fs::copy(&self.path, &backup_path).await?;
324
325        #[cfg(unix)]
326        {
327            use std::os::unix::fs::PermissionsExt;
328            let mut perms = fs::metadata(&backup_path).await?.permissions();
329            perms.set_mode(self.file_permissions);
330            fs::set_permissions(&backup_path, perms).await?;
331        }
332
333        debug!("Created secure backup: {}", backup_path.display());
334        Ok(backup_path)
335    }
336
337    /// Restore from a backup file
338    pub async fn restore_from_backup(&self, backup_path: &PathBuf) -> Result<(), StorageError> {
339        if !backup_path.exists() {
340            return Err(StorageError::General(
341                "Backup file does not exist".to_string(),
342            ));
343        }
344
345        // Verify backup file security
346        Self::verify_file_ownership(backup_path).await?;
347
348        // Create temp file for atomic restore
349        let temp_path = self.path.with_extension("restore_tmp");
350        fs::copy(backup_path, &temp_path).await?;
351
352        #[cfg(unix)]
353        {
354            use std::os::unix::fs::PermissionsExt;
355            let mut perms = fs::metadata(&temp_path).await?.permissions();
356            perms.set_mode(self.file_permissions);
357            fs::set_permissions(&temp_path, perms).await?;
358        }
359
360        // Atomic move
361        fs::rename(&temp_path, &self.path).await?;
362
363        info!("Restored from backup: {}", backup_path.display());
364        Ok(())
365    }
366
367    /// Clean up old backup files (keep only last N backups)
368    pub async fn cleanup_backups(&self, keep_count: usize) -> Result<(), StorageError> {
369        if let Some(parent) = self.path.parent() {
370            let filename_stem = self
371                .path
372                .file_stem()
373                .and_then(|s| s.to_str())
374                .unwrap_or("keys");
375
376            let mut backups = Vec::new();
377            let mut entries = fs::read_dir(parent).await?;
378
379            while let Some(entry) = entries.next_entry().await? {
380                let path = entry.path();
381                if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
382                    if filename.starts_with(&format!("{}.backup_", filename_stem)) {
383                        if let Ok(metadata) = entry.metadata().await {
384                            backups.push((
385                                path,
386                                metadata
387                                    .modified()
388                                    .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
389                            ));
390                        }
391                    }
392                }
393            }
394
395            // Sort by modification time (newest first)
396            backups.sort_by(|a, b| b.1.cmp(&a.1));
397
398            // Remove old backups
399            for (backup_path, _) in backups.iter().skip(keep_count) {
400                if let Err(e) = fs::remove_file(backup_path).await {
401                    warn!(
402                        "Failed to remove old backup {}: {}",
403                        backup_path.display(),
404                        e
405                    );
406                } else {
407                    debug!("Removed old backup: {}", backup_path.display());
408                }
409            }
410        }
411
412        Ok(())
413    }
414
415    /// Start filesystem monitoring for unauthorized changes (Linux only)
416    #[cfg(target_os = "linux")]
417    pub async fn start_filesystem_monitoring(&self) -> Result<(), StorageError> {
418        if !self.enable_filesystem_monitoring {
419            return Ok(());
420        }
421
422        use inotify::{Inotify, WatchMask};
423
424        let mut inotify = Inotify::init()
425            .map_err(|e| StorageError::General(format!("Failed to initialize inotify: {}", e)))?;
426
427        // Watch the directory for changes
428        if let Some(parent) = self.path.parent() {
429            inotify
430                .watches()
431                .add(
432                    parent,
433                    WatchMask::MODIFY | WatchMask::ATTRIB | WatchMask::MOVED_TO | WatchMask::DELETE,
434                )
435                .map_err(|e| {
436                    StorageError::General(format!("Failed to add inotify watch: {}", e))
437                })?;
438
439            info!("Started filesystem monitoring for: {}", parent.display());
440
441            // Spawn background task to monitor changes
442            let path = self.path.clone();
443            let file_permissions = self.file_permissions;
444
445            tokio::spawn(async move {
446                use tracing::{error, warn};
447                let mut buffer = [0; 1024];
448                loop {
449                    match inotify.read_events_blocking(&mut buffer) {
450                        Ok(events) => {
451                            for event in events {
452                                if let Some(name) = event.name {
453                                    if name.to_string_lossy().contains("keys") {
454                                        warn!(
455                                            "Detected unauthorized change to auth storage: {:?} (mask: {:?})",
456                                            name, event.mask
457                                        );
458
459                                        // Verify file permissions haven't been changed
460                                        if path.exists() {
461                                            #[cfg(unix)]
462                                            {
463                                                use std::os::unix::fs::PermissionsExt;
464                                                if let Ok(metadata) = std::fs::metadata(&path) {
465                                                    let mode =
466                                                        metadata.permissions().mode() & 0o777;
467                                                    if mode != file_permissions {
468                                                        error!(
469                                                            "Security violation: File permissions changed from {:o} to {:o}",
470                                                            file_permissions, mode
471                                                        );
472                                                    }
473                                                }
474                                            }
475                                        }
476                                    }
477                                }
478                            }
479                        }
480                        Err(e) => {
481                            error!("Error reading inotify events: {}", e);
482                            break;
483                        }
484                    }
485                }
486            });
487        }
488
489        Ok(())
490    }
491
492    /// Start filesystem monitoring (no-op on non-Linux systems)
493    #[cfg(not(target_os = "linux"))]
494    pub async fn start_filesystem_monitoring(&self) -> Result<(), StorageError> {
495        if self.enable_filesystem_monitoring {
496            warn!("Filesystem monitoring is only supported on Linux systems");
497        }
498        Ok(())
499    }
500}
501
502#[async_trait]
503impl StorageBackend for FileStorage {
504    async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
505        use crate::crypto::encryption::decrypt_data;
506
507        self.ensure_secure_permissions().await?;
508
509        if !self.path.exists() {
510            return Ok(HashMap::new());
511        }
512
513        let content = fs::read(&self.path).await?;
514        if content.is_empty() {
515            return Ok(HashMap::new());
516        }
517
518        // Try to decrypt the content (new format)
519        let decrypted_content = if let Ok(encrypted_data) = serde_json::from_slice(&content) {
520            // Encrypted format
521            let decrypted_bytes = decrypt_data(&encrypted_data, &self.encryption_key)?;
522            String::from_utf8(decrypted_bytes)
523                .map_err(|e| StorageError::General(format!("Invalid UTF-8: {}", e)))?
524        } else {
525            // Legacy plain text format - convert to secure format
526            let plain_text = String::from_utf8(content)
527                .map_err(|e| StorageError::General(format!("Invalid UTF-8: {}", e)))?;
528            warn!("Found legacy plain text keys, converting to secure format");
529
530            // Load legacy keys and convert them
531            let legacy_keys: HashMap<String, ApiKey> = serde_json::from_str(&plain_text)?;
532            let secure_keys: HashMap<String, SecureApiKey> = legacy_keys
533                .into_iter()
534                .map(|(id, key)| (id, key.to_secure_storage()))
535                .collect();
536
537            // Save in secure format
538            self.save_secure_keys(&secure_keys).await?;
539
540            // Return the decrypted content for this load
541            plain_text
542        };
543
544        // Parse secure keys from decrypted content
545        let secure_keys: HashMap<String, SecureApiKey> = serde_json::from_str(&decrypted_content)?;
546
547        // Convert secure keys back to API keys (without plain text)
548        let keys: HashMap<String, ApiKey> = secure_keys
549            .into_iter()
550            .map(|(id, secure_key)| (id, secure_key.to_api_key()))
551            .collect();
552
553        debug!("Loaded {} keys from encrypted file storage", keys.len());
554        Ok(keys)
555    }
556
557    async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
558        let _lock = self.write_mutex.lock().await;
559        let mut keys = self.load_keys().await?;
560        keys.insert(key.id.clone(), key.clone());
561        self.save_all_keys_internal(&keys).await
562    }
563
564    async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
565        let _lock = self.write_mutex.lock().await;
566        let mut keys = self.load_keys().await?;
567        keys.remove(key_id);
568        self.save_all_keys_internal(&keys).await
569    }
570
571    async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
572        let _lock = self.write_mutex.lock().await;
573        self.save_all_keys_internal(keys).await
574    }
575}
576
577impl FileStorage {
578    async fn save_all_keys_internal(
579        &self,
580        keys: &HashMap<String, ApiKey>,
581    ) -> Result<(), StorageError> {
582        // Convert to secure keys for storage
583        let secure_keys: HashMap<String, SecureApiKey> = keys
584            .iter()
585            .map(|(id, key)| (id.clone(), key.to_secure_storage()))
586            .collect();
587
588        self.save_secure_keys(&secure_keys).await
589    }
590}
591
592/// Environment variable storage backend
593pub struct EnvironmentStorage {
594    var_name: String,
595}
596
597impl EnvironmentStorage {
598    pub fn new(var_name: String) -> Self {
599        Self { var_name }
600    }
601}
602
603#[async_trait]
604impl StorageBackend for EnvironmentStorage {
605    async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
606        match std::env::var(&self.var_name) {
607            Ok(content) => {
608                if content.trim().is_empty() {
609                    return Ok(HashMap::new());
610                }
611                let keys: HashMap<String, ApiKey> = serde_json::from_str(&content)?;
612                debug!("Loaded {} keys from environment storage", keys.len());
613                Ok(keys)
614            }
615            Err(_) => {
616                debug!(
617                    "Environment variable {} not found, returning empty keys",
618                    self.var_name
619                );
620                Ok(HashMap::new())
621            }
622        }
623    }
624
625    async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
626        let mut keys = self.load_keys().await?;
627        keys.insert(key.id.clone(), key.clone());
628        self.save_all_keys(&keys).await
629    }
630
631    async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
632        let mut keys = self.load_keys().await?;
633        keys.remove(key_id);
634        self.save_all_keys(&keys).await
635    }
636
637    async fn save_all_keys(&self, keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
638        let content = serde_json::to_string(keys)?;
639        // SAFETY: Setting environment variable for storage purposes
640        unsafe {
641            std::env::set_var(&self.var_name, content);
642        }
643
644        debug!("Saved {} keys to environment storage", keys.len());
645        Ok(())
646    }
647}
648
649/// In-memory storage backend (for testing)
650pub struct MemoryStorage {
651    keys: tokio::sync::RwLock<HashMap<String, ApiKey>>,
652}
653
654impl MemoryStorage {
655    pub fn new() -> Self {
656        Self {
657            keys: tokio::sync::RwLock::new(HashMap::new()),
658        }
659    }
660}
661
662#[async_trait]
663impl StorageBackend for MemoryStorage {
664    async fn load_keys(&self) -> Result<HashMap<String, ApiKey>, StorageError> {
665        let keys = self.keys.read().await;
666        debug!("Loaded {} keys from memory storage", keys.len());
667        Ok(keys.clone())
668    }
669
670    async fn save_key(&self, key: &ApiKey) -> Result<(), StorageError> {
671        let mut keys = self.keys.write().await;
672        keys.insert(key.id.clone(), key.clone());
673        debug!("Saved key {} to memory storage", key.id);
674        Ok(())
675    }
676
677    async fn delete_key(&self, key_id: &str) -> Result<(), StorageError> {
678        let mut keys = self.keys.write().await;
679        keys.remove(key_id);
680        debug!("Deleted key {} from memory storage", key_id);
681        Ok(())
682    }
683
684    async fn save_all_keys(&self, new_keys: &HashMap<String, ApiKey>) -> Result<(), StorageError> {
685        let mut keys = self.keys.write().await;
686        *keys = new_keys.clone();
687        debug!(
688            "Replaced all keys in memory storage with {} keys",
689            new_keys.len()
690        );
691        Ok(())
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698    use crate::models::{ApiKey, Role};
699    use chrono::{Duration, Utc};
700    use std::collections::HashMap;
701    use tempfile::TempDir;
702    use tokio::fs;
703
704    // Helper function to create test API key
705    fn create_test_key(name: &str, role: Role) -> ApiKey {
706        ApiKey::new(
707            name.to_string(),
708            role,
709            Some(Utc::now() + Duration::days(30)),
710            vec!["127.0.0.1".to_string()],
711        )
712    }
713
714    // Helper function to create multiple test keys
715    fn create_test_keys() -> HashMap<String, ApiKey> {
716        let mut keys = HashMap::new();
717
718        let admin_key = create_test_key("admin-key", Role::Admin);
719        let operator_key = create_test_key("operator-key", Role::Operator);
720        let monitor_key = create_test_key("monitor-key", Role::Monitor);
721
722        keys.insert(admin_key.id.clone(), admin_key);
723        keys.insert(operator_key.id.clone(), operator_key);
724        keys.insert(monitor_key.id.clone(), monitor_key);
725
726        keys
727    }
728
729    #[test]
730    fn test_storage_error_display() {
731        let error = StorageError::General("test error".to_string());
732        assert_eq!(error.to_string(), "Storage error: test error");
733
734        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
735        let storage_error = StorageError::Io(io_error);
736        assert!(storage_error.to_string().contains("File I/O error"));
737
738        let perm_error = StorageError::Permission("access denied".to_string());
739        assert_eq!(perm_error.to_string(), "Permission error: access denied");
740    }
741
742    #[test]
743    fn test_storage_error_from_io_error() {
744        let io_error =
745            std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
746        let storage_error: StorageError = io_error.into();
747
748        match storage_error {
749            StorageError::Io(_) => (),
750            _ => panic!("Expected Io variant"),
751        }
752    }
753
754    #[test]
755    fn test_storage_error_from_serde_error() {
756        let serde_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
757        let storage_error: StorageError = serde_error.into();
758
759        match storage_error {
760            StorageError::Serialization(_) => (),
761            _ => panic!("Expected Serialization variant"),
762        }
763    }
764
765    mod memory_storage_tests {
766        use super::*;
767
768        #[tokio::test]
769        async fn test_memory_storage_new() {
770            let storage = MemoryStorage::new();
771            let keys = storage.load_keys().await.unwrap();
772            assert!(keys.is_empty());
773        }
774
775        #[tokio::test]
776        async fn test_memory_storage_save_and_load_key() {
777            let storage = MemoryStorage::new();
778            let test_key = create_test_key("test-key", Role::Operator);
779
780            storage.save_key(&test_key).await.unwrap();
781
782            let keys = storage.load_keys().await.unwrap();
783            assert_eq!(keys.len(), 1);
784            assert!(keys.contains_key(&test_key.id));
785
786            let loaded_key = &keys[&test_key.id];
787            assert_eq!(loaded_key.name, test_key.name);
788            assert_eq!(loaded_key.role, test_key.role);
789        }
790
791        #[tokio::test]
792        async fn test_memory_storage_save_multiple_keys() {
793            let storage = MemoryStorage::new();
794            let test_keys = create_test_keys();
795
796            for key in test_keys.values() {
797                storage.save_key(key).await.unwrap();
798            }
799
800            let loaded_keys = storage.load_keys().await.unwrap();
801            assert_eq!(loaded_keys.len(), test_keys.len());
802
803            for (id, key) in test_keys.iter() {
804                assert!(loaded_keys.contains_key(id));
805                assert_eq!(loaded_keys[id].name, key.name);
806            }
807        }
808
809        #[tokio::test]
810        async fn test_memory_storage_delete_key() {
811            let storage = MemoryStorage::new();
812            let test_key = create_test_key("test-key", Role::Monitor);
813
814            storage.save_key(&test_key).await.unwrap();
815            assert_eq!(storage.load_keys().await.unwrap().len(), 1);
816
817            storage.delete_key(&test_key.id).await.unwrap();
818            let keys = storage.load_keys().await.unwrap();
819            assert!(keys.is_empty());
820        }
821
822        #[tokio::test]
823        async fn test_memory_storage_delete_nonexistent_key() {
824            let storage = MemoryStorage::new();
825
826            // Should not error when deleting non-existent key
827            storage.delete_key("nonexistent").await.unwrap();
828            assert!(storage.load_keys().await.unwrap().is_empty());
829        }
830
831        #[tokio::test]
832        async fn test_memory_storage_save_all_keys() {
833            let storage = MemoryStorage::new();
834            let test_keys = create_test_keys();
835
836            storage.save_all_keys(&test_keys).await.unwrap();
837
838            let loaded_keys = storage.load_keys().await.unwrap();
839            assert_eq!(loaded_keys.len(), test_keys.len());
840
841            for (id, key) in test_keys.iter() {
842                assert!(loaded_keys.contains_key(id));
843                assert_eq!(loaded_keys[id].name, key.name);
844            }
845        }
846
847        #[tokio::test]
848        async fn test_memory_storage_save_all_keys_replaces_existing() {
849            let storage = MemoryStorage::new();
850
851            // Save initial keys
852            let initial_keys = create_test_keys();
853            storage.save_all_keys(&initial_keys).await.unwrap();
854            assert_eq!(storage.load_keys().await.unwrap().len(), initial_keys.len());
855
856            // Replace with new set
857            let mut new_keys = HashMap::new();
858            let new_key = create_test_key("new-key", Role::Admin);
859            new_keys.insert(new_key.id.clone(), new_key);
860
861            storage.save_all_keys(&new_keys).await.unwrap();
862
863            let loaded_keys = storage.load_keys().await.unwrap();
864            assert_eq!(loaded_keys.len(), 1);
865            assert!(loaded_keys.contains_key(new_keys.keys().next().unwrap()));
866        }
867
868        #[tokio::test]
869        async fn test_memory_storage_concurrent_access() {
870            let storage = std::sync::Arc::new(MemoryStorage::new());
871            let mut handles = vec![];
872
873            // Spawn multiple tasks that save keys concurrently
874            for i in 0..10 {
875                let storage_clone = storage.clone();
876                let handle = tokio::spawn(async move {
877                    let key = create_test_key(&format!("key-{}", i), Role::Operator);
878                    storage_clone.save_key(&key).await.unwrap();
879                    key.id
880                });
881                handles.push(handle);
882            }
883
884            let mut saved_ids = vec![];
885            for handle in handles {
886                saved_ids.push(handle.await.unwrap());
887            }
888
889            let keys = storage.load_keys().await.unwrap();
890            assert_eq!(keys.len(), 10);
891
892            for id in saved_ids {
893                assert!(keys.contains_key(&id));
894            }
895        }
896    }
897
898    mod environment_storage_tests {
899        use super::*;
900
901        #[tokio::test]
902        async fn test_environment_storage_new() {
903            let storage = EnvironmentStorage::new("TEST_MCP_KEYS".to_string());
904
905            // Clear any existing value
906            // SAFETY: Removing test environment variable
907            unsafe {
908                std::env::remove_var("TEST_MCP_KEYS");
909            }
910
911            let keys = storage.load_keys().await.unwrap();
912            assert!(keys.is_empty());
913        }
914
915        #[tokio::test]
916        async fn test_environment_storage_save_and_load_key() {
917            let var_name = "TEST_MCP_KEYS_SAVE_LOAD";
918            // SAFETY: Removing test environment variable
919            unsafe {
920                std::env::remove_var(var_name);
921            }
922
923            let storage = EnvironmentStorage::new(var_name.to_string());
924            let test_key = create_test_key("env-test-key", Role::Monitor);
925
926            storage.save_key(&test_key).await.unwrap();
927
928            let keys = storage.load_keys().await.unwrap();
929            assert_eq!(keys.len(), 1);
930            assert!(keys.contains_key(&test_key.id));
931
932            // Verify environment variable was set
933            assert!(std::env::var(var_name).is_ok());
934
935            // Cleanup
936            // SAFETY: Removing test environment variable
937            unsafe {
938                std::env::remove_var(var_name);
939            }
940        }
941
942        #[tokio::test]
943        async fn test_environment_storage_multiple_keys() {
944            let var_name = "TEST_MCP_KEYS_MULTIPLE";
945            // SAFETY: Removing test environment variable
946            unsafe {
947                std::env::remove_var(var_name);
948            }
949
950            let storage = EnvironmentStorage::new(var_name.to_string());
951            let test_keys = create_test_keys();
952
953            storage.save_all_keys(&test_keys).await.unwrap();
954
955            let loaded_keys = storage.load_keys().await.unwrap();
956            assert_eq!(loaded_keys.len(), test_keys.len());
957
958            for (id, key) in test_keys.iter() {
959                assert!(loaded_keys.contains_key(id));
960                assert_eq!(loaded_keys[id].name, key.name);
961            }
962
963            // Cleanup
964            // SAFETY: Removing test environment variable
965            unsafe {
966                std::env::remove_var(var_name);
967            }
968        }
969
970        #[tokio::test]
971        async fn test_environment_storage_delete_key() {
972            let var_name = "TEST_MCP_KEYS_DELETE";
973            // SAFETY: Removing test environment variable
974            unsafe {
975                std::env::remove_var(var_name);
976            }
977
978            let storage = EnvironmentStorage::new(var_name.to_string());
979            let test_keys = create_test_keys();
980            let key_to_delete = test_keys.values().next().unwrap().id.clone();
981
982            storage.save_all_keys(&test_keys).await.unwrap();
983            assert_eq!(storage.load_keys().await.unwrap().len(), test_keys.len());
984
985            storage.delete_key(&key_to_delete).await.unwrap();
986
987            let remaining_keys = storage.load_keys().await.unwrap();
988            assert_eq!(remaining_keys.len(), test_keys.len() - 1);
989            assert!(!remaining_keys.contains_key(&key_to_delete));
990
991            // Cleanup
992            // SAFETY: Removing test environment variable
993            unsafe {
994                std::env::remove_var(var_name);
995            }
996        }
997
998        #[tokio::test]
999        async fn test_environment_storage_empty_content() {
1000            let var_name = "TEST_MCP_KEYS_EMPTY";
1001            // SAFETY: Setting test environment variable
1002            unsafe {
1003                std::env::set_var(var_name, "");
1004            }
1005
1006            let storage = EnvironmentStorage::new(var_name.to_string());
1007            let keys = storage.load_keys().await.unwrap();
1008            assert!(keys.is_empty());
1009
1010            // Cleanup
1011            // SAFETY: Removing test environment variable
1012            unsafe {
1013                std::env::remove_var(var_name);
1014            }
1015        }
1016
1017        #[tokio::test]
1018        async fn test_environment_storage_invalid_json() {
1019            let var_name = "TEST_MCP_KEYS_INVALID";
1020            // SAFETY: Setting test environment variable
1021            unsafe {
1022                std::env::set_var(var_name, "invalid json content");
1023            }
1024
1025            let storage = EnvironmentStorage::new(var_name.to_string());
1026            let result = storage.load_keys().await;
1027
1028            assert!(result.is_err());
1029            match result.unwrap_err() {
1030                StorageError::Serialization(_) => (),
1031                _ => panic!("Expected serialization error"),
1032            }
1033
1034            // Cleanup
1035            // SAFETY: Removing test environment variable
1036            unsafe {
1037                std::env::remove_var(var_name);
1038            }
1039        }
1040
1041        #[tokio::test]
1042        async fn test_environment_storage_overwrite_existing() {
1043            let var_name = "TEST_MCP_KEYS_OVERWRITE";
1044            // SAFETY: Removing test environment variable
1045            unsafe {
1046                std::env::remove_var(var_name);
1047            }
1048
1049            let storage = EnvironmentStorage::new(var_name.to_string());
1050
1051            // Save initial keys
1052            let initial_keys = create_test_keys();
1053            storage.save_all_keys(&initial_keys).await.unwrap();
1054
1055            // Save new keys (should overwrite)
1056            let mut new_keys = HashMap::new();
1057            let new_key = create_test_key("overwrite-key", Role::Admin);
1058            new_keys.insert(new_key.id.clone(), new_key);
1059
1060            storage.save_all_keys(&new_keys).await.unwrap();
1061
1062            let loaded_keys = storage.load_keys().await.unwrap();
1063            assert_eq!(loaded_keys.len(), 1);
1064            assert!(loaded_keys.contains_key(new_keys.keys().next().unwrap()));
1065
1066            // Cleanup
1067            // SAFETY: Removing test environment variable
1068            unsafe {
1069                std::env::remove_var(var_name);
1070            }
1071        }
1072    }
1073
1074    mod file_storage_tests {
1075        use super::*;
1076
1077        async fn create_test_file_storage() -> (FileStorage, TempDir) {
1078            // Set a consistent master key for all file storage tests
1079            // SAFETY: Setting test environment variable
1080            unsafe {
1081                std::env::set_var(
1082                    "PULSEENGINE_MCP_MASTER_KEY",
1083                    "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1084                );
1085            }
1086            let temp_dir = TempDir::new().unwrap();
1087            let storage_path = temp_dir.path().join("test_keys.enc");
1088
1089            let storage = FileStorage::new(
1090                storage_path,
1091                0o600,
1092                0o700,
1093                false, // Don't require secure filesystem for tests
1094                false, // Don't enable filesystem monitoring for tests
1095            )
1096            .await
1097            .unwrap();
1098
1099            (storage, temp_dir)
1100        }
1101
1102        #[tokio::test]
1103        async fn test_file_storage_new() {
1104            let (storage, _temp_dir) = create_test_file_storage().await;
1105
1106            // Should create empty storage initially
1107            let keys = storage.load_keys().await.unwrap();
1108            assert!(keys.is_empty());
1109
1110            // Storage file should exist after creation
1111            assert!(storage.path.exists());
1112        }
1113
1114        #[tokio::test]
1115        async fn test_file_storage_save_and_load_key() {
1116            let (storage, _temp_dir) = create_test_file_storage().await;
1117            let test_key = create_test_key("file-test-key", Role::Operator);
1118
1119            storage.save_key(&test_key).await.unwrap();
1120
1121            let keys = storage.load_keys().await.unwrap();
1122            assert_eq!(keys.len(), 1);
1123            assert!(keys.contains_key(&test_key.id));
1124
1125            let loaded_key = &keys[&test_key.id];
1126            assert_eq!(loaded_key.name, test_key.name);
1127            assert_eq!(loaded_key.role, test_key.role);
1128            // Note: Plain text key should be redacted in loaded key
1129            assert_eq!(loaded_key.key, "***redacted***");
1130        }
1131
1132        #[tokio::test]
1133        async fn test_file_storage_multiple_keys() {
1134            let (storage, _temp_dir) = create_test_file_storage().await;
1135            let test_keys = create_test_keys();
1136
1137            storage.save_all_keys(&test_keys).await.unwrap();
1138
1139            let loaded_keys = storage.load_keys().await.unwrap();
1140            assert_eq!(loaded_keys.len(), test_keys.len());
1141
1142            for (id, key) in test_keys.iter() {
1143                assert!(loaded_keys.contains_key(id));
1144                assert_eq!(loaded_keys[id].name, key.name);
1145                assert_eq!(loaded_keys[id].role, key.role);
1146            }
1147        }
1148
1149        #[tokio::test]
1150        async fn test_file_storage_delete_key() {
1151            let (storage, _temp_dir) = create_test_file_storage().await;
1152            let test_keys = create_test_keys();
1153            let key_to_delete = test_keys.values().next().unwrap().id.clone();
1154
1155            storage.save_all_keys(&test_keys).await.unwrap();
1156            assert_eq!(storage.load_keys().await.unwrap().len(), test_keys.len());
1157
1158            storage.delete_key(&key_to_delete).await.unwrap();
1159
1160            let remaining_keys = storage.load_keys().await.unwrap();
1161            assert_eq!(remaining_keys.len(), test_keys.len() - 1);
1162            assert!(!remaining_keys.contains_key(&key_to_delete));
1163        }
1164
1165        #[tokio::test]
1166        async fn test_file_storage_encryption() {
1167            let (storage, _temp_dir) = create_test_file_storage().await;
1168            let test_key = create_test_key("encryption-test", Role::Admin);
1169
1170            storage.save_key(&test_key).await.unwrap();
1171
1172            // Read raw file content - should be encrypted
1173            let raw_content = fs::read(&storage.path).await.unwrap();
1174            let raw_text = String::from_utf8_lossy(&raw_content);
1175
1176            // Should not contain plain text key information
1177            assert!(!raw_text.contains(&test_key.name));
1178            assert!(!raw_text.contains(&test_key.key));
1179
1180            // But should be loadable through storage interface
1181            let loaded_keys = storage.load_keys().await.unwrap();
1182            assert_eq!(loaded_keys.len(), 1);
1183            assert!(loaded_keys.contains_key(&test_key.id));
1184        }
1185
1186        #[tokio::test]
1187        async fn test_file_storage_empty_file() {
1188            let temp_dir = TempDir::new().unwrap();
1189            let storage_path = temp_dir.path().join("empty_keys.enc");
1190
1191            // Create empty file
1192            fs::write(&storage_path, "").await.unwrap();
1193
1194            let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1195                .await
1196                .unwrap();
1197
1198            let keys = storage.load_keys().await.unwrap();
1199            assert!(keys.is_empty());
1200        }
1201
1202        #[tokio::test]
1203        async fn test_file_storage_nonexistent_file() {
1204            let temp_dir = TempDir::new().unwrap();
1205            let storage_path = temp_dir.path().join("nonexistent").join("keys.enc");
1206
1207            // Parent directory doesn't exist - should be created
1208            let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1209                .await
1210                .unwrap();
1211
1212            // Should create empty storage
1213            let keys = storage.load_keys().await.unwrap();
1214            assert!(keys.is_empty());
1215            assert!(storage_path.exists());
1216        }
1217
1218        #[tokio::test]
1219        #[allow(clippy::await_holding_lock)] // Required for thread-safe env var handling
1220        async fn test_file_storage_persistence() {
1221            // Set a consistent master key for persistence testing
1222            // Use async lock to ensure this test doesn't interfere with others
1223            static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1224            // Hold lock for entire test to ensure thread safety with env vars
1225            let _lock = TEST_LOCK.lock().await;
1226
1227            // Store and set master key in thread-safe manner
1228            let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1229            // SAFETY: Setting test environment variable
1230            unsafe {
1231                std::env::set_var(
1232                    "PULSEENGINE_MCP_MASTER_KEY",
1233                    "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1234                );
1235            }
1236
1237            // Small delay to ensure environment variable is set across threads
1238            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1239
1240            let temp_dir = TempDir::new().unwrap();
1241            let storage_path = temp_dir.path().join("persistent_keys.enc");
1242            let test_keys = create_test_keys();
1243
1244            // Create storage and save keys
1245            {
1246                let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1247                    .await
1248                    .unwrap();
1249
1250                storage.save_all_keys(&test_keys).await.unwrap();
1251            }
1252
1253            // Ensure the file was created and has content
1254            assert!(storage_path.exists());
1255            let file_metadata = std::fs::metadata(&storage_path).unwrap();
1256            assert!(file_metadata.len() > 0);
1257
1258            // Create new storage instance and verify keys persist
1259            {
1260                let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1261                    .await
1262                    .unwrap();
1263
1264                let loaded_keys = storage.load_keys().await.unwrap();
1265                assert_eq!(loaded_keys.len(), test_keys.len());
1266
1267                for (id, key) in test_keys.iter() {
1268                    assert!(loaded_keys.contains_key(id));
1269                    assert_eq!(loaded_keys[id].name, key.name);
1270                }
1271            }
1272
1273            // Restore original environment variable or remove if it didn't exist
1274            // SAFETY: Restoring test environment variable
1275            unsafe {
1276                match original_master_key {
1277                    Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1278                    None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1279                }
1280            }
1281        }
1282
1283        #[tokio::test]
1284        async fn test_file_storage_backup_and_restore() {
1285            let (storage, _temp_dir) = create_test_file_storage().await;
1286            let test_keys = create_test_keys();
1287
1288            // Save initial keys
1289            storage.save_all_keys(&test_keys).await.unwrap();
1290
1291            // Create backup
1292            let backup_path = storage.create_backup().await.unwrap();
1293            assert!(backup_path.exists());
1294            assert!(backup_path.to_string_lossy().contains("backup_"));
1295
1296            // Modify storage
1297            let mut modified_keys = HashMap::new();
1298            let new_key = create_test_key("backup-test", Role::Monitor);
1299            modified_keys.insert(new_key.id.clone(), new_key);
1300            storage.save_all_keys(&modified_keys).await.unwrap();
1301
1302            // Verify modification
1303            assert_eq!(storage.load_keys().await.unwrap().len(), 1);
1304
1305            // Restore from backup
1306            storage.restore_from_backup(&backup_path).await.unwrap();
1307
1308            // Verify restoration
1309            let restored_keys = storage.load_keys().await.unwrap();
1310            assert_eq!(restored_keys.len(), test_keys.len());
1311
1312            for id in test_keys.keys() {
1313                assert!(restored_keys.contains_key(id));
1314            }
1315        }
1316
1317        #[tokio::test]
1318        async fn test_file_storage_backup_nonexistent_storage() {
1319            let temp_dir = TempDir::new().unwrap();
1320            let storage_path = temp_dir.path().join("missing_keys.enc");
1321
1322            let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1323                .await
1324                .unwrap();
1325
1326            // Delete the storage file to simulate missing file
1327            fs::remove_file(&storage.path).await.unwrap();
1328
1329            let result = storage.create_backup().await;
1330            assert!(result.is_err());
1331            match result.unwrap_err() {
1332                StorageError::General(msg) => assert!(msg.contains("does not exist")),
1333                _ => panic!("Expected general error"),
1334            }
1335        }
1336
1337        #[tokio::test]
1338        async fn test_file_storage_restore_nonexistent_backup() {
1339            let (storage, temp_dir) = create_test_file_storage().await;
1340            let nonexistent_backup = temp_dir.path().join("nonexistent_backup.enc");
1341
1342            let result = storage.restore_from_backup(&nonexistent_backup).await;
1343            assert!(result.is_err());
1344            match result.unwrap_err() {
1345                StorageError::General(msg) => assert!(msg.contains("does not exist")),
1346                _ => panic!("Expected general error"),
1347            }
1348        }
1349
1350        #[tokio::test]
1351        #[allow(clippy::await_holding_lock)] // Required for thread-safe env var handling
1352        async fn test_file_storage_cleanup_backups() {
1353            // Use async lock to ensure this test doesn't interfere with others
1354            static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1355            let _lock = TEST_LOCK.lock().await;
1356            let original_master_key = {
1357                // Store original master key to restore later
1358                let original = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1359
1360                // Set a consistent master key for cleanup testing
1361                // SAFETY: Setting test environment variable
1362                unsafe {
1363                    std::env::set_var(
1364                        "PULSEENGINE_MCP_MASTER_KEY",
1365                        "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1366                    );
1367                }
1368                original
1369            };
1370
1371            // Small delay to ensure environment variable is set across threads
1372            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1373
1374            let (storage, _temp_dir) = create_test_file_storage().await;
1375            let test_key = create_test_key("cleanup-test", Role::Admin);
1376
1377            storage.save_key(&test_key).await.unwrap();
1378
1379            // Create multiple backups
1380            let mut backup_paths = vec![];
1381            for _i in 0..5 {
1382                let backup_path = storage.create_backup().await.unwrap();
1383                backup_paths.push(backup_path);
1384                // Small delay to ensure different timestamps
1385                tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1386            }
1387
1388            // Verify all backups exist
1389            for (i, path) in backup_paths.iter().enumerate() {
1390                assert!(path.exists(), "Backup {} does not exist: {:?}", i, path);
1391            }
1392
1393            // Cleanup keeping only 2 backups
1394            storage.cleanup_backups(2).await.unwrap();
1395
1396            // Count remaining backup files
1397            let parent = storage.path.parent().unwrap();
1398            let mut remaining_backups = 0;
1399            let mut entries = fs::read_dir(parent).await.unwrap();
1400
1401            while let Some(entry) = entries.next_entry().await.unwrap() {
1402                if entry.file_name().to_string_lossy().contains("backup_") {
1403                    remaining_backups += 1;
1404                }
1405            }
1406
1407            assert_eq!(remaining_backups, 2);
1408
1409            // Restore original environment variable or remove if it didn't exist
1410            // SAFETY: Restoring test environment variable
1411            unsafe {
1412                match original_master_key {
1413                    Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1414                    None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1415                }
1416            }
1417        }
1418
1419        #[cfg(unix)]
1420        #[tokio::test]
1421        async fn test_file_storage_permissions() {
1422            use std::os::unix::fs::PermissionsExt;
1423
1424            let (storage, _temp_dir) = create_test_file_storage().await;
1425            let test_key = create_test_key("perm-test", Role::Operator);
1426
1427            storage.save_key(&test_key).await.unwrap();
1428
1429            // Check file permissions
1430            let metadata = fs::metadata(&storage.path).await.unwrap();
1431            let mode = metadata.permissions().mode() & 0o777;
1432            assert_eq!(mode, 0o600);
1433
1434            // Check parent directory permissions
1435            if let Some(parent) = storage.path.parent() {
1436                let parent_metadata = fs::metadata(parent).await.unwrap();
1437                let parent_mode = parent_metadata.permissions().mode() & 0o777;
1438                assert_eq!(parent_mode, 0o700);
1439            }
1440        }
1441
1442        #[tokio::test]
1443        #[allow(clippy::await_holding_lock)] // Required for thread-safe env var handling
1444        async fn test_file_storage_atomic_operations() {
1445            // Use async mutex instead of sync mutex to prevent blocking threads
1446            static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1447            let _lock = TEST_LOCK.lock().await;
1448
1449            let original_master_key = {
1450                // Store original master key to restore later
1451                let original = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1452
1453                // Set a consistent master key for atomic operations testing
1454                // SAFETY: Setting test environment variable
1455                unsafe {
1456                    std::env::set_var(
1457                        "PULSEENGINE_MCP_MASTER_KEY",
1458                        "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1459                    );
1460                }
1461                original
1462            };
1463
1464            // Small delay to ensure environment variable is set across threads
1465            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1466
1467            let (storage, _temp_dir) = create_test_file_storage().await;
1468            let initial_keys = create_test_keys();
1469
1470            storage.save_all_keys(&initial_keys).await.unwrap();
1471
1472            // Simulate concurrent operations with reduced concurrency for platform compatibility
1473            let storage_clone = std::sync::Arc::new(storage);
1474            let mut handles = vec![];
1475
1476            // Reduce concurrent operations to prevent overwhelming slower platforms
1477            for i in 0..5 {
1478                let storage_ref = storage_clone.clone();
1479                let handle = tokio::spawn(async move {
1480                    let key = create_test_key(&format!("concurrent-{}", i), Role::Monitor);
1481                    // Add timeout to prevent infinite hangs on platform-specific file locking issues
1482                    tokio::time::timeout(
1483                        std::time::Duration::from_secs(10),
1484                        storage_ref.save_key(&key),
1485                    )
1486                    .await
1487                });
1488                handles.push(handle);
1489            }
1490
1491            // Wait for all operations to complete with timeout protection
1492            for handle in handles {
1493                // Use timeout to prevent test hanging indefinitely on slower platforms
1494                match tokio::time::timeout(std::time::Duration::from_secs(15), handle).await {
1495                    Ok(result) => {
1496                        // Handle the nested Result from timeout and save_key
1497                        match result {
1498                            Ok(save_result) => {
1499                                // Some operations may timeout, which is acceptable
1500                                let _ = save_result;
1501                            }
1502                            Err(_) => {
1503                                // Task panicked or was cancelled - acceptable in concurrent test
1504                            }
1505                        }
1506                    }
1507                    Err(_) => {
1508                        // Handle timeout - prevents indefinite hanging
1509                        eprintln!(
1510                            "Concurrent operation timed out - acceptable on slower platforms"
1511                        );
1512                    }
1513                }
1514            }
1515
1516            // Verify final state is consistent
1517            let final_keys = storage_clone.load_keys().await.unwrap();
1518            assert!(final_keys.len() >= initial_keys.len());
1519
1520            // Verify all initial keys are still present
1521            for id in initial_keys.keys() {
1522                assert!(final_keys.contains_key(id));
1523            }
1524
1525            // Restore original environment variable or remove if it didn't exist
1526            // SAFETY: Restoring test environment variable
1527            unsafe {
1528                match original_master_key {
1529                    Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1530                    None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1531                }
1532            }
1533        }
1534    }
1535
1536    mod storage_factory_tests {
1537        use super::*;
1538        use crate::config::StorageConfig;
1539
1540        #[tokio::test]
1541        async fn test_create_memory_storage_backend() {
1542            let config = StorageConfig::Memory;
1543            let backend = create_storage_backend(&config).await.unwrap();
1544
1545            // Test basic operations
1546            let test_key = create_test_key("memory-factory-test", Role::Admin);
1547            backend.save_key(&test_key).await.unwrap();
1548
1549            let keys = backend.load_keys().await.unwrap();
1550            assert_eq!(keys.len(), 1);
1551            assert!(keys.contains_key(&test_key.id));
1552        }
1553
1554        #[tokio::test]
1555        async fn test_create_environment_storage_backend() {
1556            let var_name = "TEST_FACTORY_ENV_STORAGE";
1557            // SAFETY: Removing test environment variable
1558            unsafe {
1559                std::env::remove_var(var_name);
1560            }
1561
1562            let config = StorageConfig::Environment {
1563                prefix: var_name.to_string(),
1564            };
1565            let backend = create_storage_backend(&config).await.unwrap();
1566
1567            // Test basic operations
1568            let test_key = create_test_key("env-factory-test", Role::Operator);
1569            backend.save_key(&test_key).await.unwrap();
1570
1571            let keys = backend.load_keys().await.unwrap();
1572            assert_eq!(keys.len(), 1);
1573            assert!(keys.contains_key(&test_key.id));
1574
1575            // Cleanup
1576            // SAFETY: Removing test environment variable
1577            unsafe {
1578                std::env::remove_var(var_name);
1579            }
1580        }
1581
1582        #[tokio::test]
1583        async fn test_create_file_storage_backend() {
1584            let temp_dir = TempDir::new().unwrap();
1585            let storage_path = temp_dir.path().join("factory_test_keys.enc");
1586
1587            let config = StorageConfig::File {
1588                path: storage_path.clone(),
1589                file_permissions: 0o600,
1590                dir_permissions: 0o700,
1591                require_secure_filesystem: false,
1592                enable_filesystem_monitoring: false,
1593            };
1594            let backend = create_storage_backend(&config).await.unwrap();
1595
1596            // Test basic operations
1597            let test_key = create_test_key("file-factory-test", Role::Monitor);
1598            backend.save_key(&test_key).await.unwrap();
1599
1600            let keys = backend.load_keys().await.unwrap();
1601            assert_eq!(keys.len(), 1);
1602            assert!(keys.contains_key(&test_key.id));
1603
1604            // Verify file was created
1605            assert!(storage_path.exists());
1606        }
1607
1608        #[tokio::test]
1609        async fn test_create_file_storage_backend_with_nested_path() {
1610            let temp_dir = TempDir::new().unwrap();
1611            let storage_path = temp_dir.path().join("nested").join("dirs").join("keys.enc");
1612
1613            let config = StorageConfig::File {
1614                path: storage_path.clone(),
1615                file_permissions: 0o600,
1616                dir_permissions: 0o700,
1617                require_secure_filesystem: false,
1618                enable_filesystem_monitoring: false,
1619            };
1620            let backend = create_storage_backend(&config).await.unwrap();
1621
1622            // Test that nested directories were created
1623            assert!(storage_path.parent().unwrap().exists());
1624
1625            // Test basic operations
1626            let test_key = create_test_key(
1627                "nested-factory-test",
1628                Role::Device {
1629                    allowed_devices: vec!["device1".to_string()],
1630                },
1631            );
1632            backend.save_key(&test_key).await.unwrap();
1633
1634            let keys = backend.load_keys().await.unwrap();
1635            assert_eq!(keys.len(), 1);
1636            assert!(keys.contains_key(&test_key.id));
1637        }
1638    }
1639
1640    #[tokio::test]
1641    async fn test_storage_backend_trait_object() {
1642        // Test that we can use storage backends through trait objects
1643        let memory_storage: Box<dyn StorageBackend> = Box::new(MemoryStorage::new());
1644        let env_storage: Box<dyn StorageBackend> =
1645            Box::new(EnvironmentStorage::new("TEST_TRAIT_OBJECT".to_string()));
1646
1647        let storages: Vec<Box<dyn StorageBackend>> = vec![memory_storage, env_storage];
1648
1649        for (i, storage) in storages.into_iter().enumerate() {
1650            let test_key = create_test_key(
1651                &format!("trait-test-{}", i),
1652                Role::Custom {
1653                    permissions: vec!["test:read".to_string()],
1654                },
1655            );
1656
1657            storage.save_key(&test_key).await.unwrap();
1658            let keys = storage.load_keys().await.unwrap();
1659            assert_eq!(keys.len(), 1);
1660            assert!(keys.contains_key(&test_key.id));
1661        }
1662
1663        // Cleanup
1664        // SAFETY: Removing test environment variable
1665        unsafe {
1666            std::env::remove_var("TEST_TRAIT_OBJECT");
1667        }
1668    }
1669
1670    #[tokio::test]
1671    async fn test_secure_api_key_conversion() {
1672        let original_key = create_test_key("conversion-test", Role::Admin);
1673        let secure_key = original_key.to_secure_storage();
1674        let restored_key = secure_key.to_api_key();
1675
1676        // Verify secure conversion
1677        assert_eq!(restored_key.id, original_key.id);
1678        assert_eq!(restored_key.name, original_key.name);
1679        assert_eq!(restored_key.role, original_key.role);
1680        assert_eq!(restored_key.created_at, original_key.created_at);
1681        assert_eq!(restored_key.expires_at, original_key.expires_at);
1682        assert_eq!(restored_key.ip_whitelist, original_key.ip_whitelist);
1683        assert_eq!(restored_key.active, original_key.active);
1684        assert_eq!(restored_key.usage_count, original_key.usage_count);
1685
1686        // Key should be redacted in restored version
1687        assert_eq!(restored_key.key, "***redacted***");
1688        assert_ne!(restored_key.key, original_key.key);
1689
1690        // Hash and salt should be preserved
1691        assert_eq!(restored_key.secret_hash, original_key.secret_hash);
1692        assert_eq!(restored_key.salt, original_key.salt);
1693    }
1694}