Skip to main content

verifyos_cli/
profiles.rs

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