Skip to main content

auth_framework/migration/
mod.rs

1//! Migration utilities for transitioning to role-system v1.0
2//!
3//! This module provides comprehensive migration tools to help users
4//! transition from legacy authorization systems to the unified
5//! role-system v1.0 approach with minimal disruption.
6
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::path::PathBuf;
10use thiserror::Error;
11use tokio::fs;
12
13pub mod analyzers;
14pub mod converters;
15pub mod executors;
16pub mod planners;
17pub mod validators;
18
19/// Migration-related errors
20#[derive(Error, Debug)]
21pub enum MigrationError {
22    #[error("Legacy system analysis failed: {0}")]
23    AnalysisError(String),
24
25    #[error("Migration plan generation failed: {0}")]
26    PlanningError(String),
27
28    #[error("Migration execution failed: {0}")]
29    ExecutionError(String),
30
31    #[error("Migration validation failed: {0}")]
32    ValidationError(String),
33
34    #[error("Rollback operation failed: {0}")]
35    RollbackError(String),
36
37    #[error("I/O error: {0}")]
38    IoError(#[from] std::io::Error),
39
40    #[error("Serialization error: {0}")]
41    SerializationError(#[from] serde_json::Error),
42}
43
44/// Type of legacy authorization system
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46pub enum LegacySystemType {
47    /// Simple permission lists
48    PermissionBased,
49    /// Basic role-based access control
50    BasicRbac,
51    /// Attribute-based access control
52    Abac,
53    /// Custom authorization implementation
54    Custom(String),
55    /// Multiple mixed systems
56    Hybrid(Vec<LegacySystemType>),
57}
58
59/// Legacy role definition
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct LegacyRole {
62    pub id: String,
63    pub name: String,
64    pub description: Option<String>,
65    pub permissions: Vec<String>,
66    pub parent_roles: Vec<String>,
67    pub metadata: HashMap<String, String>,
68}
69
70/// Legacy user assignment
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct LegacyUserAssignment {
73    pub user_id: String,
74    pub role_id: Option<String>,
75    pub permissions: Vec<String>,
76    pub attributes: HashMap<String, String>,
77    pub expiration: Option<chrono::DateTime<chrono::Utc>>,
78}
79
80/// Legacy permission definition
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct LegacyPermission {
83    pub id: String,
84    pub action: String,
85    pub resource: String,
86    pub conditions: HashMap<String, String>,
87    pub metadata: HashMap<String, String>,
88}
89
90/// Analysis result of legacy authorization system
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct LegacySystemAnalysis {
93    /// Type of legacy system detected
94    pub system_type: LegacySystemType,
95
96    /// Total number of roles found
97    pub role_count: usize,
98
99    /// Total number of permissions found
100    pub permission_count: usize,
101
102    /// Total number of user assignments
103    pub user_assignment_count: usize,
104
105    /// Discovered roles
106    pub roles: Vec<LegacyRole>,
107
108    /// Discovered permissions
109    pub permissions: Vec<LegacyPermission>,
110
111    /// User assignments
112    pub user_assignments: Vec<LegacyUserAssignment>,
113
114    /// Role hierarchy complexity (depth levels)
115    pub hierarchy_depth: usize,
116
117    /// Duplicate roles/permissions detected
118    pub duplicates_found: bool,
119
120    /// Orphaned permissions (not assigned to any role)
121    pub orphaned_permissions: Vec<String>,
122
123    /// Circular dependencies in role hierarchy
124    pub circular_dependencies: Vec<Vec<String>>,
125
126    /// Custom attributes that need special handling
127    pub custom_attributes: HashSet<String>,
128
129    /// Estimated migration complexity (1-10 scale)
130    pub complexity_score: u8,
131
132    /// Recommended migration strategy
133    pub recommended_strategy: MigrationStrategy,
134}
135
136/// Migration strategy options
137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
138pub enum MigrationStrategy {
139    /// Direct mapping with minimal changes
140    DirectMapping,
141    /// Gradual migration with coexistence period
142    GradualMigration,
143    /// Complete rebuild with role consolidation
144    Rebuild,
145    /// Custom strategy for complex scenarios
146    Custom(String),
147}
148
149/// Migration plan for transitioning to role-system v1.0
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct MigrationPlan {
152    /// Unique plan identifier
153    pub id: String,
154
155    /// Source system analysis
156    pub source_analysis: LegacySystemAnalysis,
157
158    /// Selected migration strategy
159    pub strategy: MigrationStrategy,
160
161    /// Planned migration phases
162    pub phases: Vec<MigrationPhase>,
163
164    /// Role mapping from legacy to new system
165    pub role_mappings: HashMap<String, String>,
166
167    /// Permission mapping from legacy to new system
168    pub permission_mappings: HashMap<String, String>,
169
170    /// User assignment migrations
171    pub user_migrations: Vec<UserMigration>,
172
173    /// Pre-migration validation steps
174    pub pre_validation_steps: Vec<ValidationStep>,
175
176    /// Post-migration validation steps
177    pub post_validation_steps: Vec<ValidationStep>,
178
179    /// Rollback plan
180    pub rollback_plan: RollbackPlan,
181
182    /// Estimated migration time
183    pub estimated_duration: chrono::Duration,
184
185    /// Risk assessment
186    pub risk_level: RiskLevel,
187
188    /// Required downtime (if any)
189    pub downtime_required: Option<chrono::Duration>,
190}
191
192/// Individual migration phase
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct MigrationPhase {
195    pub id: String,
196    pub name: String,
197    pub description: String,
198    pub order: u32,
199    pub operations: Vec<MigrationOperation>,
200    pub dependencies: Vec<String>,
201    pub estimated_duration: chrono::Duration,
202    pub rollback_operations: Vec<MigrationOperation>,
203}
204
205/// Migration operation types
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub enum MigrationOperation {
208    CreateRole {
209        role_id: String,
210        name: String,
211        description: Option<String>,
212        permissions: Vec<String>,
213        parent_role: Option<String>,
214    },
215    AssignUserRole {
216        user_id: String,
217        role_id: String,
218        expiration: Option<chrono::DateTime<chrono::Utc>>,
219    },
220    CreatePermission {
221        permission_id: String,
222        action: String,
223        resource: String,
224        conditions: HashMap<String, String>,
225    },
226    MigrateCustomAttribute {
227        attribute_name: String,
228        conversion_logic: String,
229    },
230    ValidateIntegrity {
231        validation_type: String,
232        parameters: HashMap<String, String>,
233    },
234    Backup {
235        backup_location: PathBuf,
236        backup_type: BackupType,
237    },
238}
239
240/// User migration details
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct UserMigration {
243    pub user_id: String,
244    pub legacy_roles: Vec<String>,
245    pub legacy_permissions: Vec<String>,
246    pub new_roles: Vec<String>,
247    pub migration_notes: Option<String>,
248}
249
250/// Validation step definition
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct ValidationStep {
253    pub id: String,
254    pub name: String,
255    pub description: String,
256    pub validation_type: ValidationType,
257    pub parameters: HashMap<String, String>,
258    pub required: bool,
259}
260
261/// Types of validation
262#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
263pub enum ValidationType {
264    /// Check role hierarchy integrity
265    HierarchyIntegrity,
266    /// Validate permission consistency
267    PermissionConsistency,
268    /// Check user assignment validity
269    UserAssignmentValidity,
270    /// Verify no privilege escalation
271    PrivilegeEscalationCheck,
272    /// Custom validation script
273    Custom(String),
274}
275
276/// Rollback plan for migration
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct RollbackPlan {
279    pub phases: Vec<RollbackPhase>,
280    pub backup_locations: Vec<PathBuf>,
281    pub recovery_time_objective: chrono::Duration,
282    pub manual_steps: Vec<String>,
283}
284
285/// Rollback phase
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct RollbackPhase {
288    pub id: String,
289    pub name: String,
290    pub operations: Vec<MigrationOperation>,
291    pub order: u32,
292}
293
294/// Risk assessment levels
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
296pub enum RiskLevel {
297    Low,
298    Medium,
299    High,
300    Critical,
301}
302
303/// Backup types
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub enum BackupType {
306    /// Full system backup
307    Full,
308    /// Incremental backup
309    Incremental,
310    /// Configuration only
311    ConfigOnly,
312    /// Data only
313    DataOnly,
314}
315
316/// Migration execution result
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct MigrationResult {
319    pub plan_id: String,
320    pub status: MigrationStatus,
321    pub started_at: chrono::DateTime<chrono::Utc>,
322    pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
323    pub phases_completed: Vec<String>,
324    pub current_phase: Option<String>,
325    pub errors: Vec<String>,
326    pub warnings: Vec<String>,
327    pub metrics: MigrationMetrics,
328}
329
330/// Migration execution status
331#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
332pub enum MigrationStatus {
333    Planned,
334    InProgress,
335    Completed,
336    Failed,
337    RolledBack,
338    Paused,
339}
340
341/// Migration execution metrics
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct MigrationMetrics {
344    pub roles_migrated: usize,
345    pub permissions_migrated: usize,
346    pub users_migrated: usize,
347    pub errors_encountered: usize,
348    pub warnings_generated: usize,
349    pub validation_failures: usize,
350    pub rollback_count: usize,
351}
352
353/// Main migration manager
354pub struct MigrationManager {
355    /// Configuration for migration operations
356    config: MigrationConfig,
357}
358
359/// Migration manager configuration
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct MigrationConfig {
362    /// Working directory for migration files
363    pub working_directory: PathBuf,
364
365    /// Backup directory
366    pub backup_directory: PathBuf,
367
368    /// Maximum concurrent operations
369    pub max_concurrent_operations: usize,
370
371    /// Operation timeout
372    pub operation_timeout: chrono::Duration,
373
374    /// Enable dry-run mode
375    pub dry_run: bool,
376
377    /// Verbose logging
378    pub verbose: bool,
379}
380
381impl Default for MigrationConfig {
382    fn default() -> Self {
383        Self {
384            working_directory: PathBuf::from("./migration"),
385            backup_directory: PathBuf::from("./migration/backups"),
386            max_concurrent_operations: 4,
387            operation_timeout: chrono::Duration::minutes(30),
388            dry_run: false,
389            verbose: false,
390        }
391    }
392}
393
394impl MigrationConfig {
395    /// Dry-run mode: analyse the migration plan without applying changes.
396    ///
397    /// Verbose logging is enabled automatically.
398    pub fn dry_run() -> Self {
399        Self {
400            dry_run: true,
401            verbose: true,
402            ..Default::default()
403        }
404    }
405
406    /// Conservative mode: sequential execution with long timeouts.
407    ///
408    /// Best for production environments where safety is paramount.
409    pub fn conservative() -> Self {
410        Self {
411            max_concurrent_operations: 1,
412            operation_timeout: chrono::Duration::minutes(120),
413            verbose: true,
414            ..Default::default()
415        }
416    }
417
418    /// Aggressive mode: maximum parallelism for large-scale migrations.
419    pub fn aggressive() -> Self {
420        Self {
421            max_concurrent_operations: 16,
422            operation_timeout: chrono::Duration::minutes(60),
423            ..Default::default()
424        }
425    }
426
427    /// Create a builder starting from the default configuration.
428    pub fn builder() -> MigrationConfigBuilder {
429        MigrationConfigBuilder {
430            config: MigrationConfig::default(),
431        }
432    }
433}
434
435/// Fluent builder for [`MigrationConfig`].
436///
437/// # Example
438///
439/// ```rust,no_run
440/// use auth_framework::migration::MigrationConfig;
441///
442/// let config = MigrationConfig::builder()
443///     .working_directory("./my_migration")
444///     .dry_run(true)
445///     .max_concurrent(8)
446///     .build();
447/// ```
448#[derive(Debug, Clone)]
449pub struct MigrationConfigBuilder {
450    config: MigrationConfig,
451}
452
453impl MigrationConfigBuilder {
454    /// Set the working directory.
455    pub fn working_directory(mut self, dir: impl Into<PathBuf>) -> Self {
456        self.config.working_directory = dir.into();
457        self
458    }
459
460    /// Set the backup directory.
461    pub fn backup_directory(mut self, dir: impl Into<PathBuf>) -> Self {
462        self.config.backup_directory = dir.into();
463        self
464    }
465
466    /// Set maximum concurrent operations.
467    pub fn max_concurrent(mut self, count: usize) -> Self {
468        self.config.max_concurrent_operations = count;
469        self
470    }
471
472    /// Set operation timeout.
473    pub fn timeout(mut self, timeout: chrono::Duration) -> Self {
474        self.config.operation_timeout = timeout;
475        self
476    }
477
478    /// Enable or disable dry-run mode.
479    pub fn dry_run(mut self, enabled: bool) -> Self {
480        self.config.dry_run = enabled;
481        self
482    }
483
484    /// Enable or disable verbose logging.
485    pub fn verbose(mut self, enabled: bool) -> Self {
486        self.config.verbose = enabled;
487        self
488    }
489
490    /// Consume the builder and produce the config.
491    pub fn build(self) -> MigrationConfig {
492        self.config
493    }
494}
495
496impl MigrationManager {
497    /// Create new migration manager
498    pub fn new(config: MigrationConfig) -> Result<Self, MigrationError> {
499        // Ensure directories exist
500        std::fs::create_dir_all(&config.working_directory)?;
501        std::fs::create_dir_all(&config.backup_directory)?;
502
503        Ok(Self { config })
504    }
505
506    /// Analyze legacy authorization system
507    pub async fn analyze_legacy_system<P: AsRef<std::path::Path>>(
508        &self,
509        config_path: P,
510    ) -> Result<LegacySystemAnalysis, MigrationError> {
511        analyzers::analyze_legacy_system(config_path, &self.config).await
512    }
513
514    /// Generate migration plan
515    pub async fn generate_migration_plan(
516        &self,
517        analysis: &LegacySystemAnalysis,
518        strategy: Option<MigrationStrategy>,
519    ) -> Result<MigrationPlan, MigrationError> {
520        planners::generate_migration_plan(analysis, strategy, &self.config).await
521    }
522
523    /// Validate migration plan
524    pub async fn validate_migration_plan(
525        &self,
526        plan: &MigrationPlan,
527    ) -> Result<Vec<String>, MigrationError> {
528        validators::validate_migration_plan(plan, &self.config).await
529    }
530
531    /// Execute migration plan
532    pub async fn execute_migration(
533        &self,
534        plan: &MigrationPlan,
535    ) -> Result<MigrationResult, MigrationError> {
536        executors::execute_migration_plan(plan, &self.config).await
537    }
538
539    /// Get migration status
540    pub async fn get_migration_status(
541        &self,
542        plan_id: &str,
543    ) -> Result<Option<MigrationResult>, MigrationError> {
544        let status_file = self
545            .config
546            .working_directory
547            .join(format!("{}_status.json", plan_id));
548
549        if !status_file.exists() {
550            return Ok(None);
551        }
552
553        let content = fs::read_to_string(status_file).await?;
554        let result: MigrationResult = serde_json::from_str(&content)?;
555        Ok(Some(result))
556    }
557
558    /// Rollback migration
559    pub async fn rollback_migration(
560        &self,
561        plan: &MigrationPlan,
562    ) -> Result<MigrationResult, MigrationError> {
563        executors::rollback_migration(plan, &self.config).await
564    }
565
566    /// List available migration plans
567    pub async fn list_migration_plans(&self) -> Result<Vec<String>, MigrationError> {
568        let mut plans = Vec::new();
569        let mut entries = fs::read_dir(&self.config.working_directory).await?;
570
571        while let Some(entry) = entries.next_entry().await? {
572            let path = entry.path();
573            if path.extension().is_some_and(|ext| ext == "json")
574                && let Some(file_name) = path.file_stem()
575                && let Some(name) = file_name.to_str()
576                && name.ends_with("_plan")
577            {
578                plans.push(name.trim_end_matches("_plan").to_string());
579            }
580        }
581
582        Ok(plans)
583    }
584
585    /// Save migration plan to disk
586    pub async fn save_migration_plan(
587        &self,
588        plan: &MigrationPlan,
589    ) -> Result<PathBuf, MigrationError> {
590        let plan_file = self
591            .config
592            .working_directory
593            .join(format!("{}_plan.json", plan.id));
594        let content = serde_json::to_string_pretty(plan)?;
595        fs::write(&plan_file, content).await?;
596        Ok(plan_file)
597    }
598
599    /// Load migration plan from disk
600    pub async fn load_migration_plan(
601        &self,
602        plan_id: &str,
603    ) -> Result<MigrationPlan, MigrationError> {
604        let plan_file = self
605            .config
606            .working_directory
607            .join(format!("{}_plan.json", plan_id));
608        let content = fs::read_to_string(plan_file).await?;
609        let plan: MigrationPlan = serde_json::from_str(&content)?;
610        Ok(plan)
611    }
612
613    /// Generate migration report
614    pub async fn generate_migration_report(
615        &self,
616        result: &MigrationResult,
617    ) -> Result<String, MigrationError> {
618        let mut report = String::new();
619
620        report.push_str("# Migration Report\n\n");
621        report.push_str(&format!("**Plan ID**: {}\n", result.plan_id));
622        report.push_str(&format!("**Status**: {:?}\n", result.status));
623        report.push_str(&format!("**Started**: {}\n", result.started_at));
624
625        if let Some(completed) = result.completed_at {
626            report.push_str(&format!("**Completed**: {}\n", completed));
627            let duration = completed - result.started_at;
628            report.push_str(&format!(
629                "**Duration**: {} minutes\n",
630                duration.num_minutes()
631            ));
632        }
633
634        report.push_str("\n## Metrics\n\n");
635        report.push_str(&format!(
636            "- Roles migrated: {}\n",
637            result.metrics.roles_migrated
638        ));
639        report.push_str(&format!(
640            "- Permissions migrated: {}\n",
641            result.metrics.permissions_migrated
642        ));
643        report.push_str(&format!(
644            "- Users migrated: {}\n",
645            result.metrics.users_migrated
646        ));
647        report.push_str(&format!(
648            "- Errors: {}\n",
649            result.metrics.errors_encountered
650        ));
651        report.push_str(&format!(
652            "- Warnings: {}\n",
653            result.metrics.warnings_generated
654        ));
655
656        if !result.errors.is_empty() {
657            report.push_str("\n## Errors\n\n");
658            for error in &result.errors {
659                report.push_str(&format!("- {}\n", error));
660            }
661        }
662
663        if !result.warnings.is_empty() {
664            report.push_str("\n## Warnings\n\n");
665            for warning in &result.warnings {
666                report.push_str(&format!("- {}\n", warning));
667            }
668        }
669
670        Ok(report)
671    }
672}
673
674#[cfg(test)]
675mod tests {
676    use super::*;
677
678    #[tokio::test]
679    async fn test_migration_manager_creation() {
680        let config = MigrationConfig::default();
681        let manager = MigrationManager::new(config);
682        assert!(manager.is_ok());
683    }
684
685    #[test]
686    fn test_legacy_system_type_serialization() {
687        let system_type = LegacySystemType::BasicRbac;
688        let serialized = serde_json::to_string(&system_type).unwrap();
689        let deserialized: LegacySystemType = serde_json::from_str(&serialized).unwrap();
690        assert_eq!(system_type, deserialized);
691    }
692
693    #[test]
694    fn test_migration_strategy_serialization() {
695        let strategy = MigrationStrategy::GradualMigration;
696        let serialized = serde_json::to_string(&strategy).unwrap();
697        let deserialized: MigrationStrategy = serde_json::from_str(&serialized).unwrap();
698        assert_eq!(strategy, deserialized);
699    }
700
701    #[test]
702    fn test_risk_level_ordering() {
703        assert!(RiskLevel::Low < RiskLevel::Medium);
704        assert!(RiskLevel::Medium < RiskLevel::High);
705        assert!(RiskLevel::High < RiskLevel::Critical);
706    }
707
708    #[test]
709    fn test_migration_config_dry_run_preset() {
710        let config = MigrationConfig::dry_run();
711        assert!(config.dry_run);
712        assert!(config.verbose);
713    }
714
715    #[test]
716    fn test_migration_config_conservative_preset() {
717        let config = MigrationConfig::conservative();
718        assert_eq!(config.max_concurrent_operations, 1);
719        assert!(config.verbose);
720        assert!(!config.dry_run);
721    }
722
723    #[test]
724    fn test_migration_config_aggressive_preset() {
725        let config = MigrationConfig::aggressive();
726        assert_eq!(config.max_concurrent_operations, 16);
727    }
728
729    #[test]
730    fn test_migration_config_builder() {
731        let config = MigrationConfig::builder()
732            .working_directory("/tmp/migration")
733            .dry_run(true)
734            .max_concurrent(8)
735            .verbose(true)
736            .build();
737        assert_eq!(config.working_directory, PathBuf::from("/tmp/migration"));
738        assert!(config.dry_run);
739        assert_eq!(config.max_concurrent_operations, 8);
740        assert!(config.verbose);
741    }
742
743    #[tokio::test]
744    async fn test_migration_manager_with_dry_run_preset() {
745        let config = MigrationConfig::dry_run();
746        let manager = MigrationManager::new(config);
747        assert!(manager.is_ok());
748    }
749}