1use super::{
7 LegacyPermission, LegacyRole, LegacySystemAnalysis, LegacySystemType, LegacyUserAssignment,
8 MigrationConfig, MigrationError, MigrationStrategy,
9};
10use serde_json::Value;
11use std::collections::{HashMap, HashSet};
12use std::path::Path;
13use tokio::fs;
14
15pub async fn analyze_legacy_system<P: AsRef<Path>>(
17 config_path: P,
18 _config: &MigrationConfig,
19) -> Result<LegacySystemAnalysis, MigrationError> {
20 let config_content = fs::read_to_string(config_path).await?;
21
22 if let Ok(json_value) = serde_json::from_str::<Value>(&config_content) {
24 return analyze_json_config(&json_value).await;
25 }
26
27 if let Ok(toml_value) = toml::from_str::<Value>(&config_content) {
29 return analyze_toml_config(&toml_value).await;
30 }
31
32 if let Ok(yaml_value) = serde_yaml::from_str::<Value>(&config_content) {
34 return analyze_yaml_config(&yaml_value).await;
35 }
36
37 Err(MigrationError::AnalysisError(
38 "Unable to parse configuration file format".to_string(),
39 ))
40}
41
42async fn analyze_json_config(config: &Value) -> Result<LegacySystemAnalysis, MigrationError> {
44 let mut analysis = LegacySystemAnalysis {
45 system_type: LegacySystemType::Custom("JSON-based".to_string()),
46 role_count: 0,
47 permission_count: 0,
48 user_assignment_count: 0,
49 roles: Vec::new(),
50 permissions: Vec::new(),
51 user_assignments: Vec::new(),
52 hierarchy_depth: 0,
53 duplicates_found: false,
54 orphaned_permissions: Vec::new(),
55 circular_dependencies: Vec::new(),
56 custom_attributes: HashSet::new(),
57 complexity_score: 1,
58 recommended_strategy: MigrationStrategy::DirectMapping,
59 };
60
61 analysis.system_type = detect_json_system_type(config);
63
64 if let Some(roles_section) = config.get("roles") {
66 analysis.roles = extract_json_roles(roles_section)?;
67 analysis.role_count = analysis.roles.len();
68 }
69
70 if let Some(permissions_section) = config.get("permissions") {
72 analysis.permissions = extract_json_permissions(permissions_section)?;
73 analysis.permission_count = analysis.permissions.len();
74 }
75
76 if let Some(users_section) = config.get("users") {
78 analysis.user_assignments = extract_json_user_assignments(users_section)?;
79 analysis.user_assignment_count = analysis.user_assignments.len();
80 }
81
82 analyze_complexity_and_recommend_strategy(&mut analysis);
84
85 Ok(analysis)
86}
87
88fn detect_json_system_type(config: &Value) -> LegacySystemType {
90 let has_roles = config.get("roles").is_some();
91 let has_permissions = config.get("permissions").is_some();
92 let has_attributes = config.get("attributes").is_some();
93 let has_policies = config.get("policies").is_some();
94
95 match (has_roles, has_permissions, has_attributes, has_policies) {
96 (true, true, false, false) => LegacySystemType::BasicRbac,
97 (false, true, false, false) => LegacySystemType::PermissionBased,
98 (true, true, true, _) => LegacySystemType::Abac,
99 _ => LegacySystemType::Custom("JSON-based".to_string()),
100 }
101}
102
103fn extract_json_roles(roles_section: &Value) -> Result<Vec<LegacyRole>, MigrationError> {
105 let mut roles = Vec::new();
106
107 match roles_section {
108 Value::Object(roles_map) => {
109 for (role_id, role_data) in roles_map {
110 let role = parse_json_role(role_id, role_data)?;
111 roles.push(role);
112 }
113 }
114 Value::Array(roles_array) => {
115 for role_data in roles_array {
116 if let Some(role_id) = role_data.get("id").and_then(|v| v.as_str()) {
117 let role = parse_json_role(role_id, role_data)?;
118 roles.push(role);
119 }
120 }
121 }
122 _ => {
123 return Err(MigrationError::AnalysisError(
124 "Invalid roles format".to_string(),
125 ));
126 }
127 }
128
129 Ok(roles)
130}
131
132fn parse_json_role(role_id: &str, role_data: &Value) -> Result<LegacyRole, MigrationError> {
134 let name = role_data
135 .get("name")
136 .and_then(|v| v.as_str())
137 .unwrap_or(role_id)
138 .to_string();
139
140 let description = role_data
141 .get("description")
142 .and_then(|v| v.as_str())
143 .map(|s| s.to_string());
144
145 let permissions = role_data
146 .get("permissions")
147 .and_then(|v| v.as_array())
148 .map(|arr| {
149 arr.iter()
150 .filter_map(|v| v.as_str())
151 .map(|s| s.to_string())
152 .collect()
153 })
154 .unwrap_or_default();
155
156 let parent_roles = role_data
157 .get("parents")
158 .or_else(|| role_data.get("inherits"))
159 .and_then(|v| v.as_array())
160 .map(|arr| {
161 arr.iter()
162 .filter_map(|v| v.as_str())
163 .map(|s| s.to_string())
164 .collect()
165 })
166 .unwrap_or_default();
167
168 let mut metadata = HashMap::new();
169 if let Some(meta) = role_data.get("metadata").and_then(|v| v.as_object()) {
170 for (key, value) in meta {
171 if let Some(value_str) = value.as_str() {
172 metadata.insert(key.clone(), value_str.to_string());
173 }
174 }
175 }
176
177 Ok(LegacyRole {
178 id: role_id.to_string(),
179 name,
180 description,
181 permissions,
182 parent_roles,
183 metadata,
184 })
185}
186
187fn extract_json_permissions(
189 permissions_section: &Value,
190) -> Result<Vec<LegacyPermission>, MigrationError> {
191 let mut permissions = Vec::new();
192
193 match permissions_section {
194 Value::Object(perms_map) => {
195 for (perm_id, perm_data) in perms_map {
196 let permission = parse_json_permission(perm_id, perm_data)?;
197 permissions.push(permission);
198 }
199 }
200 Value::Array(perms_array) => {
201 for perm_data in perms_array {
202 if let Some(perm_id) = perm_data.get("id").and_then(|v| v.as_str()) {
203 let permission = parse_json_permission(perm_id, perm_data)?;
204 permissions.push(permission);
205 }
206 }
207 }
208 _ => {
209 return Err(MigrationError::AnalysisError(
210 "Invalid permissions format".to_string(),
211 ));
212 }
213 }
214
215 Ok(permissions)
216}
217
218fn parse_json_permission(
220 perm_id: &str,
221 perm_data: &Value,
222) -> Result<LegacyPermission, MigrationError> {
223 let action = perm_data
224 .get("action")
225 .and_then(|v| v.as_str())
226 .unwrap_or("unknown")
227 .to_string();
228
229 let resource = perm_data
230 .get("resource")
231 .and_then(|v| v.as_str())
232 .unwrap_or("unknown")
233 .to_string();
234
235 let mut conditions = HashMap::new();
236 if let Some(cond) = perm_data.get("conditions").and_then(|v| v.as_object()) {
237 for (key, value) in cond {
238 if let Some(value_str) = value.as_str() {
239 conditions.insert(key.clone(), value_str.to_string());
240 }
241 }
242 }
243
244 let mut metadata = HashMap::new();
245 if let Some(meta) = perm_data.get("metadata").and_then(|v| v.as_object()) {
246 for (key, value) in meta {
247 if let Some(value_str) = value.as_str() {
248 metadata.insert(key.clone(), value_str.to_string());
249 }
250 }
251 }
252
253 Ok(LegacyPermission {
254 id: perm_id.to_string(),
255 action,
256 resource,
257 conditions,
258 metadata,
259 })
260}
261
262fn extract_json_user_assignments(
264 users_section: &Value,
265) -> Result<Vec<LegacyUserAssignment>, MigrationError> {
266 let mut assignments = Vec::new();
267
268 match users_section {
269 Value::Object(users_map) => {
270 for (user_id, user_data) in users_map {
271 let assignment = parse_json_user_assignment(user_id, user_data)?;
272 assignments.push(assignment);
273 }
274 }
275 Value::Array(users_array) => {
276 for user_data in users_array {
277 if let Some(user_id) = user_data.get("id").and_then(|v| v.as_str()) {
278 let assignment = parse_json_user_assignment(user_id, user_data)?;
279 assignments.push(assignment);
280 }
281 }
282 }
283 _ => {
284 return Err(MigrationError::AnalysisError(
285 "Invalid users format".to_string(),
286 ));
287 }
288 }
289
290 Ok(assignments)
291}
292
293fn parse_json_user_assignment(
295 user_id: &str,
296 user_data: &Value,
297) -> Result<LegacyUserAssignment, MigrationError> {
298 let role_id = user_data
299 .get("role")
300 .and_then(|v| v.as_str())
301 .map(|s| s.to_string());
302
303 let permissions = user_data
304 .get("permissions")
305 .and_then(|v| v.as_array())
306 .map(|arr| {
307 arr.iter()
308 .filter_map(|v| v.as_str())
309 .map(|s| s.to_string())
310 .collect()
311 })
312 .unwrap_or_default();
313
314 let mut attributes = HashMap::new();
315 if let Some(attrs) = user_data.get("attributes").and_then(|v| v.as_object()) {
316 for (key, value) in attrs {
317 if let Some(value_str) = value.as_str() {
318 attributes.insert(key.clone(), value_str.to_string());
319 }
320 }
321 }
322
323 let expiration = user_data
324 .get("expires_at")
325 .and_then(|v| v.as_str())
326 .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
327 .map(|dt| dt.with_timezone(&chrono::Utc));
328
329 Ok(LegacyUserAssignment {
330 user_id: user_id.to_string(),
331 role_id,
332 permissions,
333 attributes,
334 expiration,
335 })
336}
337
338async fn analyze_toml_config(config: &Value) -> Result<LegacySystemAnalysis, MigrationError> {
340 analyze_json_config(config).await
343}
344
345async fn analyze_yaml_config(config: &Value) -> Result<LegacySystemAnalysis, MigrationError> {
347 analyze_json_config(config).await
350}
351
352fn analyze_complexity_and_recommend_strategy(analysis: &mut LegacySystemAnalysis) {
354 let mut complexity_score = 1u8;
355
356 complexity_score += match analysis.role_count {
358 0..=10 => 1,
359 11..=50 => 2,
360 51..=200 => 3,
361 _ => 4,
362 };
363
364 complexity_score += match analysis.permission_count {
366 0..=20 => 1,
367 21..=100 => 2,
368 101..=500 => 3,
369 _ => 4,
370 };
371
372 let max_depth = calculate_hierarchy_depth(&analysis.roles);
374 analysis.hierarchy_depth = max_depth;
375 complexity_score += match max_depth {
376 0..=2 => 1,
377 3..=5 => 2,
378 6..=10 => 3,
379 _ => 4,
380 };
381
382 analysis.duplicates_found = check_for_duplicates(&analysis.roles, &analysis.permissions);
384 if analysis.duplicates_found {
385 complexity_score += 1;
386 }
387
388 analysis.orphaned_permissions =
390 find_orphaned_permissions(&analysis.roles, &analysis.permissions);
391 if !analysis.orphaned_permissions.is_empty() {
392 complexity_score += 1;
393 }
394
395 analysis.circular_dependencies = find_circular_dependencies(&analysis.roles);
397 if !analysis.circular_dependencies.is_empty() {
398 complexity_score += 2;
399 }
400
401 for assignment in &analysis.user_assignments {
403 for key in assignment.attributes.keys() {
404 analysis.custom_attributes.insert(key.clone());
405 }
406 }
407
408 if !analysis.custom_attributes.is_empty() {
409 complexity_score += 1;
410 }
411
412 analysis.complexity_score = complexity_score.min(10);
414
415 analysis.recommended_strategy = match analysis.complexity_score {
417 1..=3 => MigrationStrategy::DirectMapping,
418 4..=6 => MigrationStrategy::GradualMigration,
419 7..=8 => MigrationStrategy::Rebuild,
420 _ => MigrationStrategy::Custom("High complexity requires custom approach".to_string()),
421 };
422}
423
424fn calculate_hierarchy_depth(roles: &[LegacyRole]) -> usize {
426 let mut max_depth = 0;
427 let mut role_map: HashMap<String, &LegacyRole> = HashMap::new();
428
429 for role in roles {
430 role_map.insert(role.id.clone(), role);
431 }
432
433 for role in roles {
434 let depth = calculate_role_depth(&role.id, &role_map, &mut HashSet::new());
435 max_depth = max_depth.max(depth);
436 }
437
438 max_depth
439}
440
441fn calculate_role_depth(
443 role_id: &str,
444 role_map: &HashMap<String, &LegacyRole>,
445 visited: &mut HashSet<String>,
446) -> usize {
447 if visited.contains(role_id) {
448 return 0; }
450
451 visited.insert(role_id.to_string());
452
453 if let Some(role) = role_map.get(role_id) {
454 if role.parent_roles.is_empty() {
455 visited.remove(role_id);
456 return 0;
457 }
458
459 let mut max_parent_depth = 0;
460 for parent_id in &role.parent_roles {
461 let parent_depth = calculate_role_depth(parent_id, role_map, visited);
462 max_parent_depth = max_parent_depth.max(parent_depth);
463 }
464
465 visited.remove(role_id);
466 return max_parent_depth + 1;
467 }
468
469 visited.remove(role_id);
470 0
471}
472
473fn check_for_duplicates(roles: &[LegacyRole], permissions: &[LegacyPermission]) -> bool {
475 let mut role_names = HashSet::new();
476 let mut permission_names = HashSet::new();
477
478 for role in roles {
479 if !role_names.insert(&role.name) {
480 return true; }
482 }
483
484 for permission in permissions {
485 let perm_key = format!("{}:{}", permission.action, permission.resource);
486 if !permission_names.insert(perm_key) {
487 return true; }
489 }
490
491 false
492}
493
494fn find_orphaned_permissions(
496 roles: &[LegacyRole],
497 permissions: &[LegacyPermission],
498) -> Vec<String> {
499 let mut assigned_permissions = HashSet::new();
500
501 for role in roles {
502 for permission in &role.permissions {
503 assigned_permissions.insert(permission.clone());
504 }
505 }
506
507 permissions
508 .iter()
509 .filter(|perm| !assigned_permissions.contains(&perm.id))
510 .map(|perm| perm.id.clone())
511 .collect()
512}
513
514fn find_circular_dependencies(roles: &[LegacyRole]) -> Vec<Vec<String>> {
516 let mut circular_deps = Vec::new();
517 let role_map: HashMap<String, &LegacyRole> =
518 roles.iter().map(|role| (role.id.clone(), role)).collect();
519
520 for role in roles {
521 if let Some(cycle) = detect_cycle(&role.id, &role_map, &mut Vec::new(), &mut HashSet::new())
522 && !circular_deps.contains(&cycle)
523 {
524 circular_deps.push(cycle);
525 }
526 }
527
528 circular_deps
529}
530
531fn detect_cycle(
533 role_id: &str,
534 role_map: &HashMap<String, &LegacyRole>,
535 path: &mut Vec<String>,
536 visited: &mut HashSet<String>,
537) -> Option<Vec<String>> {
538 if path.contains(&role_id.to_string()) {
539 if let Some(cycle_start) = path.iter().position(|id| id == role_id) {
541 return Some(path[cycle_start..].to_vec());
542 }
543 }
544
545 if visited.contains(role_id) {
546 return None;
547 }
548
549 visited.insert(role_id.to_string());
550 path.push(role_id.to_string());
551
552 if let Some(role) = role_map.get(role_id) {
553 for parent_id in &role.parent_roles {
554 if let Some(cycle) = detect_cycle(parent_id, role_map, path, visited) {
555 return Some(cycle);
556 }
557 }
558 }
559
560 path.pop();
561 None
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use serde_json::json;
568
569 #[tokio::test]
570 async fn test_analyze_json_config() {
571 let config = json!({
572 "roles": {
573 "admin": {
574 "name": "Administrator",
575 "permissions": ["read", "write", "delete"]
576 },
577 "user": {
578 "name": "User",
579 "permissions": ["read"]
580 }
581 },
582 "permissions": {
583 "read": {
584 "action": "read",
585 "resource": "data"
586 }
587 }
588 });
589
590 let analysis = analyze_json_config(&config).await.unwrap();
591 assert_eq!(analysis.role_count, 2);
592 assert_eq!(analysis.permission_count, 1);
593 assert_eq!(analysis.system_type, LegacySystemType::BasicRbac);
594 }
595
596 #[test]
597 fn test_detect_json_system_type() {
598 let basic_rbac = json!({
599 "roles": {},
600 "permissions": {}
601 });
602 assert_eq!(
603 detect_json_system_type(&basic_rbac),
604 LegacySystemType::BasicRbac
605 );
606
607 let permission_based = json!({
608 "permissions": {}
609 });
610 assert_eq!(
611 detect_json_system_type(&permission_based),
612 LegacySystemType::PermissionBased
613 );
614 }
615
616 #[test]
617 fn test_calculate_hierarchy_depth() {
618 let roles = vec![
619 LegacyRole {
620 id: "admin".to_string(),
621 name: "Admin".to_string(),
622 description: None,
623 permissions: vec![],
624 parent_roles: vec!["super_admin".to_string()],
625 metadata: HashMap::new(),
626 },
627 LegacyRole {
628 id: "super_admin".to_string(),
629 name: "Super Admin".to_string(),
630 description: None,
631 permissions: vec![],
632 parent_roles: vec![],
633 metadata: HashMap::new(),
634 },
635 ];
636
637 let depth = calculate_hierarchy_depth(&roles);
638 assert_eq!(depth, 1);
639 }
640}
641
642