1use 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#[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
42pub 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
75pub 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 if require_secure_filesystem {
102 Self::validate_filesystem_security(&path).await?;
103 }
104
105 if let Some(parent) = path.parent() {
107 fs::create_dir_all(parent).await?;
108
109 #[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); fs::set_permissions(parent, perms).await?;
116
117 Self::verify_directory_security(parent, dir_permissions).await?;
119 }
120 }
121
122 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 if !storage.path.exists() {
138 storage.save_all_keys(&HashMap::new()).await?;
139 } else {
140 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 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 Self::verify_file_ownership(&self.path).await?;
169 }
170 }
171 Ok(())
172 }
173
174 #[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 let _dev = metadata.dev();
187
188 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 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 #[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 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 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 #[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 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 let temp_path = self.path.with_extension("tmp");
295 fs::write(&temp_path, encrypted_content).await?;
296
297 #[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); fs::set_permissions(&temp_path, perms).await?;
304 }
305
306 fs::rename(&temp_path, &self.path).await?;
308
309 debug!("Saved {} keys to encrypted file storage", keys.len());
310 Ok(())
311 }
312
313 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 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 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 Self::verify_file_ownership(backup_path).await?;
351
352 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 fs::rename(&temp_path, &self.path).await?;
366
367 info!("Restored from backup: {}", backup_path.display());
368 Ok(())
369 }
370
371 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 backups.sort_by(|a, b| b.1.cmp(&a.1));
401
402 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 #[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 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 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 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 #[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 let decrypted_content = if let Ok(encrypted_data) = serde_json::from_slice(&content) {
524 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 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 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 self.save_secure_keys(&secure_keys).await?;
543
544 plain_text
546 };
547
548 let secure_keys: HashMap<String, SecureApiKey> = serde_json::from_str(&decrypted_content)?;
550
551 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 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
596pub 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 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
653pub 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 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 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 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 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 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 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 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 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 assert!(std::env::var(var_name).is_ok());
938
939 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 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 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 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 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 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 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 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 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 unsafe {
1050 std::env::remove_var(var_name);
1051 }
1052
1053 let storage = EnvironmentStorage::new(var_name.to_string());
1054
1055 let initial_keys = create_test_keys();
1057 storage.save_all_keys(&initial_keys).await.unwrap();
1058
1059 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 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 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, false, )
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 let keys = storage.load_keys().await.unwrap();
1112 assert!(keys.is_empty());
1113
1114 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 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 let raw_content = fs::read(&storage.path).await.unwrap();
1178 let raw_text = String::from_utf8_lossy(&raw_content);
1179
1180 assert!(!raw_text.contains(&test_key.name));
1182 assert!(!raw_text.contains(&test_key.key));
1183
1184 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 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 let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1213 .await
1214 .unwrap();
1215
1216 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)] async fn test_file_storage_persistence() {
1225 static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1228 let _lock = TEST_LOCK.lock().await;
1230
1231 let original_master_key = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1233 unsafe {
1235 std::env::set_var(
1236 "PULSEENGINE_MCP_MASTER_KEY",
1237 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1238 );
1239 }
1240
1241 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1243
1244 let temp_dir = TempDir::new().unwrap();
1245 let storage_path = temp_dir.path().join("persistent_keys.enc");
1246 let test_keys = create_test_keys();
1247
1248 {
1250 let storage = FileStorage::new(storage_path.clone(), 0o600, 0o700, false, false)
1251 .await
1252 .unwrap();
1253
1254 storage.save_all_keys(&test_keys).await.unwrap();
1255 }
1256
1257 assert!(storage_path.exists());
1259 let file_metadata = std::fs::metadata(&storage_path).unwrap();
1260 assert!(file_metadata.len() > 0);
1261
1262 {
1264 let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1265 .await
1266 .unwrap();
1267
1268 let loaded_keys = storage.load_keys().await.unwrap();
1269 assert_eq!(loaded_keys.len(), test_keys.len());
1270
1271 for (id, key) in test_keys.iter() {
1272 assert!(loaded_keys.contains_key(id));
1273 assert_eq!(loaded_keys[id].name, key.name);
1274 }
1275 }
1276
1277 unsafe {
1280 match original_master_key {
1281 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1282 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1283 }
1284 }
1285 }
1286
1287 #[tokio::test]
1288 async fn test_file_storage_backup_and_restore() {
1289 let (storage, _temp_dir) = create_test_file_storage().await;
1290 let test_keys = create_test_keys();
1291
1292 storage.save_all_keys(&test_keys).await.unwrap();
1294
1295 let backup_path = storage.create_backup().await.unwrap();
1297 assert!(backup_path.exists());
1298 assert!(backup_path.to_string_lossy().contains("backup_"));
1299
1300 let mut modified_keys = HashMap::new();
1302 let new_key = create_test_key("backup-test", Role::Monitor);
1303 modified_keys.insert(new_key.id.clone(), new_key);
1304 storage.save_all_keys(&modified_keys).await.unwrap();
1305
1306 assert_eq!(storage.load_keys().await.unwrap().len(), 1);
1308
1309 storage.restore_from_backup(&backup_path).await.unwrap();
1311
1312 let restored_keys = storage.load_keys().await.unwrap();
1314 assert_eq!(restored_keys.len(), test_keys.len());
1315
1316 for id in test_keys.keys() {
1317 assert!(restored_keys.contains_key(id));
1318 }
1319 }
1320
1321 #[tokio::test]
1322 async fn test_file_storage_backup_nonexistent_storage() {
1323 let temp_dir = TempDir::new().unwrap();
1324 let storage_path = temp_dir.path().join("missing_keys.enc");
1325
1326 let storage = FileStorage::new(storage_path, 0o600, 0o700, false, false)
1327 .await
1328 .unwrap();
1329
1330 fs::remove_file(&storage.path).await.unwrap();
1332
1333 let result = storage.create_backup().await;
1334 assert!(result.is_err());
1335 match result.unwrap_err() {
1336 StorageError::General(msg) => assert!(msg.contains("does not exist")),
1337 _ => panic!("Expected general error"),
1338 }
1339 }
1340
1341 #[tokio::test]
1342 async fn test_file_storage_restore_nonexistent_backup() {
1343 let (storage, temp_dir) = create_test_file_storage().await;
1344 let nonexistent_backup = temp_dir.path().join("nonexistent_backup.enc");
1345
1346 let result = storage.restore_from_backup(&nonexistent_backup).await;
1347 assert!(result.is_err());
1348 match result.unwrap_err() {
1349 StorageError::General(msg) => assert!(msg.contains("does not exist")),
1350 _ => panic!("Expected general error"),
1351 }
1352 }
1353
1354 #[tokio::test]
1355 #[allow(clippy::await_holding_lock)] async fn test_file_storage_cleanup_backups() {
1357 static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1359 let _lock = TEST_LOCK.lock().await;
1360 let original_master_key = {
1361 let original = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1363
1364 unsafe {
1367 std::env::set_var(
1368 "PULSEENGINE_MCP_MASTER_KEY",
1369 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1370 );
1371 }
1372 original
1373 };
1374
1375 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1377
1378 let (storage, _temp_dir) = create_test_file_storage().await;
1379 let test_key = create_test_key("cleanup-test", Role::Admin);
1380
1381 storage.save_key(&test_key).await.unwrap();
1382
1383 let mut backup_paths = vec![];
1385 for _i in 0..5 {
1386 let backup_path = storage.create_backup().await.unwrap();
1387 backup_paths.push(backup_path);
1388 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1390 }
1391
1392 for (i, path) in backup_paths.iter().enumerate() {
1394 assert!(path.exists(), "Backup {} does not exist: {:?}", i, path);
1395 }
1396
1397 storage.cleanup_backups(2).await.unwrap();
1399
1400 let parent = storage.path.parent().unwrap();
1402 let mut remaining_backups = 0;
1403 let mut entries = fs::read_dir(parent).await.unwrap();
1404
1405 while let Some(entry) = entries.next_entry().await.unwrap() {
1406 if entry.file_name().to_string_lossy().contains("backup_") {
1407 remaining_backups += 1;
1408 }
1409 }
1410
1411 assert_eq!(remaining_backups, 2);
1412
1413 unsafe {
1416 match original_master_key {
1417 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1418 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1419 }
1420 }
1421 }
1422
1423 #[cfg(unix)]
1424 #[tokio::test]
1425 async fn test_file_storage_permissions() {
1426 use std::os::unix::fs::PermissionsExt;
1427
1428 let (storage, _temp_dir) = create_test_file_storage().await;
1429 let test_key = create_test_key("perm-test", Role::Operator);
1430
1431 storage.save_key(&test_key).await.unwrap();
1432
1433 let metadata = fs::metadata(&storage.path).await.unwrap();
1435 let mode = metadata.permissions().mode() & 0o777;
1436 assert_eq!(mode, 0o600);
1437
1438 if let Some(parent) = storage.path.parent() {
1440 let parent_metadata = fs::metadata(parent).await.unwrap();
1441 let parent_mode = parent_metadata.permissions().mode() & 0o777;
1442 assert_eq!(parent_mode, 0o700);
1443 }
1444 }
1445
1446 #[tokio::test]
1447 #[allow(clippy::await_holding_lock)] async fn test_file_storage_atomic_operations() {
1449 static TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1451 let _lock = TEST_LOCK.lock().await;
1452
1453 let original_master_key = {
1454 let original = std::env::var("PULSEENGINE_MCP_MASTER_KEY").ok();
1456
1457 unsafe {
1460 std::env::set_var(
1461 "PULSEENGINE_MCP_MASTER_KEY",
1462 "l9EYbalIRp2CF35M4mKcWDqRvx3TFc7U4nX5zvQF56Q",
1463 );
1464 }
1465 original
1466 };
1467
1468 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1470
1471 let (storage, _temp_dir) = create_test_file_storage().await;
1472 let initial_keys = create_test_keys();
1473
1474 storage.save_all_keys(&initial_keys).await.unwrap();
1475
1476 let storage_clone = std::sync::Arc::new(storage);
1478 let mut handles = vec![];
1479
1480 for i in 0..5 {
1482 let storage_ref = storage_clone.clone();
1483 let handle = tokio::spawn(async move {
1484 let key = create_test_key(&format!("concurrent-{}", i), Role::Monitor);
1485 tokio::time::timeout(
1487 std::time::Duration::from_secs(10),
1488 storage_ref.save_key(&key),
1489 )
1490 .await
1491 });
1492 handles.push(handle);
1493 }
1494
1495 for handle in handles {
1497 match tokio::time::timeout(std::time::Duration::from_secs(15), handle).await {
1499 Ok(result) => {
1500 match result {
1502 Ok(save_result) => {
1503 let _ = save_result;
1505 }
1506 Err(_) => {
1507 }
1509 }
1510 }
1511 Err(_) => {
1512 eprintln!(
1514 "Concurrent operation timed out - acceptable on slower platforms"
1515 );
1516 }
1517 }
1518 }
1519
1520 let final_keys = storage_clone.load_keys().await.unwrap();
1522 assert!(final_keys.len() >= initial_keys.len());
1523
1524 for id in initial_keys.keys() {
1526 assert!(final_keys.contains_key(id));
1527 }
1528
1529 unsafe {
1532 match original_master_key {
1533 Some(key) => std::env::set_var("PULSEENGINE_MCP_MASTER_KEY", key),
1534 None => std::env::remove_var("PULSEENGINE_MCP_MASTER_KEY"),
1535 }
1536 }
1537 }
1538 }
1539
1540 mod storage_factory_tests {
1541 use super::*;
1542 use crate::config::StorageConfig;
1543
1544 #[tokio::test]
1545 async fn test_create_memory_storage_backend() {
1546 let config = StorageConfig::Memory;
1547 let backend = create_storage_backend(&config).await.unwrap();
1548
1549 let test_key = create_test_key("memory-factory-test", Role::Admin);
1551 backend.save_key(&test_key).await.unwrap();
1552
1553 let keys = backend.load_keys().await.unwrap();
1554 assert_eq!(keys.len(), 1);
1555 assert!(keys.contains_key(&test_key.id));
1556 }
1557
1558 #[tokio::test]
1559 async fn test_create_environment_storage_backend() {
1560 let var_name = "TEST_FACTORY_ENV_STORAGE";
1561 unsafe {
1563 std::env::remove_var(var_name);
1564 }
1565
1566 let config = StorageConfig::Environment {
1567 prefix: var_name.to_string(),
1568 };
1569 let backend = create_storage_backend(&config).await.unwrap();
1570
1571 let test_key = create_test_key("env-factory-test", Role::Operator);
1573 backend.save_key(&test_key).await.unwrap();
1574
1575 let keys = backend.load_keys().await.unwrap();
1576 assert_eq!(keys.len(), 1);
1577 assert!(keys.contains_key(&test_key.id));
1578
1579 unsafe {
1582 std::env::remove_var(var_name);
1583 }
1584 }
1585
1586 #[tokio::test]
1587 async fn test_create_file_storage_backend() {
1588 let temp_dir = TempDir::new().unwrap();
1589 let storage_path = temp_dir.path().join("factory_test_keys.enc");
1590
1591 let config = StorageConfig::File {
1592 path: storage_path.clone(),
1593 file_permissions: 0o600,
1594 dir_permissions: 0o700,
1595 require_secure_filesystem: false,
1596 enable_filesystem_monitoring: false,
1597 };
1598 let backend = create_storage_backend(&config).await.unwrap();
1599
1600 let test_key = create_test_key("file-factory-test", Role::Monitor);
1602 backend.save_key(&test_key).await.unwrap();
1603
1604 let keys = backend.load_keys().await.unwrap();
1605 assert_eq!(keys.len(), 1);
1606 assert!(keys.contains_key(&test_key.id));
1607
1608 assert!(storage_path.exists());
1610 }
1611
1612 #[tokio::test]
1613 async fn test_create_file_storage_backend_with_nested_path() {
1614 let temp_dir = TempDir::new().unwrap();
1615 let storage_path = temp_dir.path().join("nested").join("dirs").join("keys.enc");
1616
1617 let config = StorageConfig::File {
1618 path: storage_path.clone(),
1619 file_permissions: 0o600,
1620 dir_permissions: 0o700,
1621 require_secure_filesystem: false,
1622 enable_filesystem_monitoring: false,
1623 };
1624 let backend = create_storage_backend(&config).await.unwrap();
1625
1626 assert!(storage_path.parent().unwrap().exists());
1628
1629 let test_key = create_test_key(
1631 "nested-factory-test",
1632 Role::Device {
1633 allowed_devices: vec!["device1".to_string()],
1634 },
1635 );
1636 backend.save_key(&test_key).await.unwrap();
1637
1638 let keys = backend.load_keys().await.unwrap();
1639 assert_eq!(keys.len(), 1);
1640 assert!(keys.contains_key(&test_key.id));
1641 }
1642 }
1643
1644 #[tokio::test]
1645 async fn test_storage_backend_trait_object() {
1646 let memory_storage: Box<dyn StorageBackend> = Box::new(MemoryStorage::new());
1648 let env_storage: Box<dyn StorageBackend> =
1649 Box::new(EnvironmentStorage::new("TEST_TRAIT_OBJECT".to_string()));
1650
1651 let storages: Vec<Box<dyn StorageBackend>> = vec![memory_storage, env_storage];
1652
1653 for (i, storage) in storages.into_iter().enumerate() {
1654 let test_key = create_test_key(
1655 &format!("trait-test-{}", i),
1656 Role::Custom {
1657 permissions: vec!["test:read".to_string()],
1658 },
1659 );
1660
1661 storage.save_key(&test_key).await.unwrap();
1662 let keys = storage.load_keys().await.unwrap();
1663 assert_eq!(keys.len(), 1);
1664 assert!(keys.contains_key(&test_key.id));
1665 }
1666
1667 unsafe {
1670 std::env::remove_var("TEST_TRAIT_OBJECT");
1671 }
1672 }
1673
1674 #[tokio::test]
1675 async fn test_secure_api_key_conversion() {
1676 let original_key = create_test_key("conversion-test", Role::Admin);
1677 let secure_key = original_key.to_secure_storage();
1678 let restored_key = secure_key.to_api_key();
1679
1680 assert_eq!(restored_key.id, original_key.id);
1682 assert_eq!(restored_key.name, original_key.name);
1683 assert_eq!(restored_key.role, original_key.role);
1684 assert_eq!(restored_key.created_at, original_key.created_at);
1685 assert_eq!(restored_key.expires_at, original_key.expires_at);
1686 assert_eq!(restored_key.ip_whitelist, original_key.ip_whitelist);
1687 assert_eq!(restored_key.active, original_key.active);
1688 assert_eq!(restored_key.usage_count, original_key.usage_count);
1689
1690 assert_eq!(restored_key.key, "***redacted***");
1692 assert_ne!(restored_key.key, original_key.key);
1693
1694 assert_eq!(restored_key.secret_hash, original_key.secret_hash);
1696 assert_eq!(restored_key.salt, original_key.salt);
1697 }
1698}