Skip to main content

cfgd_core/composition/
permissions.rs

1use std::collections::HashMap;
2
3use crate::config::PolicyItems;
4
5use super::CompositionInput;
6
7/// Describes a permission-expanding change detected between old and new composition results.
8#[derive(Debug, Clone)]
9pub struct PermissionChange {
10    pub source: String,
11    pub description: String,
12}
13
14/// Compare old vs new composition results to detect permission-expanding changes.
15/// Returns a list of changes that require explicit user consent.
16pub fn detect_permission_changes(
17    old_sources: &[CompositionInput],
18    new_sources: &[CompositionInput],
19) -> Vec<PermissionChange> {
20    let mut changes = Vec::new();
21
22    let old_map: HashMap<&str, &CompositionInput> = old_sources
23        .iter()
24        .map(|s| (s.source_name.as_str(), s))
25        .collect();
26
27    for new_src in new_sources {
28        let name = &new_src.source_name;
29        match old_map.get(name.as_str()) {
30            None => {
31                changes.push(PermissionChange {
32                    source: name.clone(),
33                    description: "New source added".to_string(),
34                });
35            }
36            Some(old_src) => {
37                // Check if locked items increased
38                let old_locked = count_policy_tier_items(&old_src.policy.locked);
39                let new_locked = count_policy_tier_items(&new_src.policy.locked);
40                if new_locked > old_locked {
41                    changes.push(PermissionChange {
42                        source: name.clone(),
43                        description: format!(
44                            "Locked items increased from {} to {}",
45                            old_locked, new_locked
46                        ),
47                    });
48                }
49
50                // Check if required items increased
51                let old_required = count_policy_tier_items(&old_src.policy.required);
52                let new_required = count_policy_tier_items(&new_src.policy.required);
53                if new_required > old_required {
54                    changes.push(PermissionChange {
55                        source: name.clone(),
56                        description: format!(
57                            "Required items increased from {} to {}",
58                            old_required, new_required
59                        ),
60                    });
61                }
62
63                // Check if constraints relaxed (scripts enabled, paths expanded)
64                let old_c = &old_src.constraints;
65                let new_c = &new_src.constraints;
66                if old_c.no_scripts && !new_c.no_scripts {
67                    changes.push(PermissionChange {
68                        source: name.clone(),
69                        description: "Scripts have been enabled".to_string(),
70                    });
71                }
72                if new_c.allowed_target_paths.len() > old_c.allowed_target_paths.len() {
73                    changes.push(PermissionChange {
74                        source: name.clone(),
75                        description: "Allowed target paths expanded".to_string(),
76                    });
77                }
78            }
79        }
80    }
81
82    changes
83}
84
85pub(super) fn count_policy_tier_items(items: &PolicyItems) -> usize {
86    let mut count = 0;
87    if let Some(ref pkgs) = items.packages {
88        if let Some(ref brew) = pkgs.brew {
89            count += brew.formulae.len() + brew.casks.len() + brew.taps.len();
90        }
91        if let Some(ref apt) = pkgs.apt {
92            count += apt.packages.len();
93        }
94        if let Some(ref cargo) = pkgs.cargo {
95            count += cargo.packages.len();
96        }
97        count += pkgs.pipx.len() + pkgs.dnf.len();
98        if let Some(ref npm) = pkgs.npm {
99            count += npm.global.len();
100        }
101    }
102    count += items.files.len();
103    count += items.env.len();
104    count += items.aliases.len();
105    count += items.system.len();
106    count += items.modules.len();
107    count
108}