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