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