1use crate::core::engine::Engine;
2use crate::rules::ats::{AtsAuditRule, AtsExceptionsGranularityRule};
3use crate::rules::bundle_leakage::BundleResourceLeakageRule;
4use crate::rules::bundle_metadata::BundleMetadataConsistencyRule;
5use crate::rules::core::AppStoreRule;
6use crate::rules::core::{RuleCategory, Severity};
7use crate::rules::entitlements::{EntitlementsMismatchRule, EntitlementsProvisioningMismatchRule};
8use crate::rules::export_compliance::ExportComplianceRule;
9use crate::rules::extensions::ExtensionEntitlementsCompatibilityRule;
10use crate::rules::info_plist::{
11 InfoPlistCapabilitiesRule, InfoPlistRequiredKeysRule, InfoPlistVersionConsistencyRule,
12 LSApplicationQueriesSchemesAuditRule, UIRequiredDeviceCapabilitiesAuditRule,
13 UsageDescriptionsRule, UsageDescriptionsValueRule,
14};
15use crate::rules::permissions::CameraUsageDescriptionRule;
16use crate::rules::privacy::MissingPrivacyManifestRule;
17use crate::rules::privacy_manifest::PrivacyManifestCompletenessRule;
18use crate::rules::privacy_sdk::PrivacyManifestSdkCrossCheckRule;
19use crate::rules::private_api::PrivateApiRule;
20use crate::rules::signing::EmbeddedCodeSignatureTeamRule;
21use serde::Serialize;
22use std::collections::BTreeMap;
23use std::collections::HashSet;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ScanProfile {
27 Basic,
28 Full,
29}
30
31#[derive(Debug, Clone, Default)]
32pub struct RuleSelection {
33 pub include: HashSet<String>,
34 pub exclude: HashSet<String>,
35}
36
37#[derive(Debug, Clone, Serialize)]
38pub struct RuleInventoryItem {
39 pub rule_id: String,
40 pub name: String,
41 pub severity: Severity,
42 pub category: RuleCategory,
43 pub default_profiles: Vec<String>,
44}
45
46#[derive(Debug, Clone, Serialize)]
47pub struct RuleDetailItem {
48 pub rule_id: String,
49 pub name: String,
50 pub severity: Severity,
51 pub category: RuleCategory,
52 pub recommendation: String,
53 pub default_profiles: Vec<String>,
54}
55
56impl RuleSelection {
57 pub fn allows(&self, rule_id: &str) -> bool {
58 let normalized = normalize_rule_id(rule_id);
59 let included = self.include.is_empty() || self.include.contains(&normalized);
60 let excluded = self.exclude.contains(&normalized);
61 included && !excluded
62 }
63}
64
65pub fn register_rules(engine: &mut Engine, profile: ScanProfile, selection: &RuleSelection) {
66 for rule in profile_rules(profile) {
67 if selection.allows(rule.id()) {
68 engine.register_rule(rule);
69 }
70 }
71}
72
73pub fn available_rule_ids(profile: ScanProfile) -> Vec<String> {
74 let mut ids: Vec<String> = profile_rules(profile)
75 .into_iter()
76 .map(|rule| normalize_rule_id(rule.id()))
77 .collect();
78 ids.sort();
79 ids.dedup();
80 ids
81}
82
83pub fn normalize_rule_id(rule_id: &str) -> String {
84 rule_id.trim().to_ascii_uppercase()
85}
86
87pub fn rule_inventory() -> Vec<RuleInventoryItem> {
88 let mut items: BTreeMap<String, RuleInventoryItem> = BTreeMap::new();
89
90 for (profile_name, profile) in [("basic", ScanProfile::Basic), ("full", ScanProfile::Full)] {
91 for rule in profile_rules(profile) {
92 let rule_id = normalize_rule_id(rule.id());
93 let entry = items
94 .entry(rule_id.clone())
95 .or_insert_with(|| RuleInventoryItem {
96 rule_id,
97 name: rule.name().to_string(),
98 severity: rule.severity(),
99 category: rule.category(),
100 default_profiles: Vec::new(),
101 });
102
103 if !entry
104 .default_profiles
105 .iter()
106 .any(|name| name == profile_name)
107 {
108 entry.default_profiles.push(profile_name.to_string());
109 }
110 }
111 }
112
113 items.into_values().collect()
114}
115
116pub fn rule_detail(rule_id: &str) -> Option<RuleDetailItem> {
117 let normalized = normalize_rule_id(rule_id);
118 let mut detail: Option<RuleDetailItem> = None;
119
120 for (profile_name, profile) in [("basic", ScanProfile::Basic), ("full", ScanProfile::Full)] {
121 for rule in profile_rules(profile) {
122 if normalize_rule_id(rule.id()) != normalized {
123 continue;
124 }
125
126 let entry = detail.get_or_insert_with(|| RuleDetailItem {
127 rule_id: normalize_rule_id(rule.id()),
128 name: rule.name().to_string(),
129 severity: rule.severity(),
130 category: rule.category(),
131 recommendation: rule.recommendation().to_string(),
132 default_profiles: Vec::new(),
133 });
134
135 if !entry
136 .default_profiles
137 .iter()
138 .any(|name| name == profile_name)
139 {
140 entry.default_profiles.push(profile_name.to_string());
141 }
142 }
143 }
144
145 detail
146}
147
148fn profile_rules(profile: ScanProfile) -> Vec<Box<dyn AppStoreRule>> {
149 match profile {
150 ScanProfile::Basic => basic_rules(),
151 ScanProfile::Full => full_rules(),
152 }
153}
154
155fn basic_rules() -> Vec<Box<dyn AppStoreRule>> {
156 vec![
157 Box::new(MissingPrivacyManifestRule),
158 Box::new(UsageDescriptionsRule),
159 Box::new(UsageDescriptionsValueRule),
160 Box::new(CameraUsageDescriptionRule),
161 Box::new(AtsAuditRule),
162 Box::new(AtsExceptionsGranularityRule),
163 Box::new(EntitlementsMismatchRule),
164 Box::new(EntitlementsProvisioningMismatchRule),
165 Box::new(EmbeddedCodeSignatureTeamRule),
166 ]
167}
168
169fn full_rules() -> Vec<Box<dyn AppStoreRule>> {
170 vec![
171 Box::new(MissingPrivacyManifestRule),
172 Box::new(PrivacyManifestCompletenessRule),
173 Box::new(PrivacyManifestSdkCrossCheckRule),
174 Box::new(CameraUsageDescriptionRule),
175 Box::new(UsageDescriptionsRule),
176 Box::new(UsageDescriptionsValueRule),
177 Box::new(InfoPlistRequiredKeysRule),
178 Box::new(InfoPlistCapabilitiesRule),
179 Box::new(LSApplicationQueriesSchemesAuditRule),
180 Box::new(UIRequiredDeviceCapabilitiesAuditRule),
181 Box::new(InfoPlistVersionConsistencyRule),
182 Box::new(ExportComplianceRule),
183 Box::new(AtsAuditRule),
184 Box::new(AtsExceptionsGranularityRule),
185 Box::new(EntitlementsMismatchRule),
186 Box::new(EntitlementsProvisioningMismatchRule),
187 Box::new(BundleMetadataConsistencyRule),
188 Box::new(BundleResourceLeakageRule),
189 Box::new(ExtensionEntitlementsCompatibilityRule),
190 Box::new(PrivateApiRule),
191 Box::new(EmbeddedCodeSignatureTeamRule),
192 ]
193}