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 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}