Skip to main content

verifyos_cli/
profiles.rs

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}