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