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