diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-16 00:02:08 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-16 10:00:40 +0900 |
| commit | f7e1829b4884c611d1d95c5f7582a0560b88a889 (patch) | |
| tree | 9f48b55428b97e5047ad2861ab40ce777a409927 /crates/shirabe/src/advisory | |
| parent | a86e13b75e750fc7f44ef64694461ff65d066199 (diff) | |
| download | php-shirabe-f7e1829b4884c611d1d95c5f7582a0560b88a889.tar.gz php-shirabe-f7e1829b4884c611d1d95c5f7582a0560b88a889.tar.zst php-shirabe-f7e1829b4884c611d1d95c5f7582a0560b88a889.zip | |
feat(port): port AuditConfig.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/advisory')
| -rw-r--r-- | crates/shirabe/src/advisory/audit_config.rs | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/crates/shirabe/src/advisory/audit_config.rs b/crates/shirabe/src/advisory/audit_config.rs index 42f0fe7..da01a2c 100644 --- a/crates/shirabe/src/advisory/audit_config.rs +++ b/crates/shirabe/src/advisory/audit_config.rs @@ -1 +1,184 @@ //! ref: composer/src/Composer/Advisory/AuditConfig.php + +use indexmap::IndexMap; +use shirabe_php_shim::{InvalidArgumentException, PhpMixed}; + +use crate::advisory::auditor::Auditor; +use crate::config::Config; + +#[derive(Debug)] +pub struct AuditConfig { + pub audit: bool, + pub audit_format: String, + pub audit_abandoned: String, + pub block_insecure: bool, + pub block_abandoned: bool, + pub ignore_unreachable: bool, + pub ignore_list_for_audit: IndexMap<String, Option<String>>, + pub ignore_list_for_blocking: IndexMap<String, Option<String>>, + pub ignore_severity_for_audit: IndexMap<String, Option<String>>, + pub ignore_severity_for_blocking: IndexMap<String, Option<String>>, + pub ignore_abandoned_for_audit: IndexMap<String, Option<String>>, + pub ignore_abandoned_for_blocking: IndexMap<String, Option<String>>, +} + +impl AuditConfig { + pub fn new( + audit: bool, + audit_format: String, + audit_abandoned: String, + block_insecure: bool, + block_abandoned: bool, + ignore_unreachable: bool, + ignore_list_for_audit: IndexMap<String, Option<String>>, + ignore_list_for_blocking: IndexMap<String, Option<String>>, + ignore_severity_for_audit: IndexMap<String, Option<String>>, + ignore_severity_for_blocking: IndexMap<String, Option<String>>, + ignore_abandoned_for_audit: IndexMap<String, Option<String>>, + ignore_abandoned_for_blocking: IndexMap<String, Option<String>>, + ) -> Self { + Self { + audit, + audit_format, + audit_abandoned, + block_insecure, + block_abandoned, + ignore_unreachable, + ignore_list_for_audit, + ignore_list_for_blocking, + ignore_severity_for_audit, + ignore_severity_for_blocking, + ignore_abandoned_for_audit, + ignore_abandoned_for_blocking, + } + } + + /// Parse ignore configuration supporting both simple and detailed formats with apply scopes. + /// + /// Simple format: ['CVE-123', 'CVE-456'] or ['CVE-123' => 'reason'] + /// Detailed format: ['CVE-123' => ['apply' => 'audit|block|all', 'reason' => '...']] + fn parse_ignore_with_apply( + config: &PhpMixed, + ) -> anyhow::Result<(IndexMap<String, Option<String>>, IndexMap<String, Option<String>>)> { + let mut for_audit: IndexMap<String, Option<String>> = IndexMap::new(); + let mut for_block: IndexMap<String, Option<String>> = IndexMap::new(); + + let entries = match config { + PhpMixed::Array(arr) => arr, + PhpMixed::List(list) => { + for value in list { + if let Some(id) = value.as_string() { + for_audit.insert(id.to_string(), None); + for_block.insert(id.to_string(), None); + } + } + return Ok((for_audit, for_block)); + } + _ => return Ok((for_audit, for_block)), + }; + + for (key, value) in entries { + let (id, apply, reason) = match value.as_ref() { + PhpMixed::String(reason_str) => { + (key.clone(), "all".to_string(), Some(reason_str.clone())) + } + PhpMixed::Array(detail) => { + let apply = detail.get("apply") + .and_then(|v| v.as_string()) + .unwrap_or("all") + .to_string(); + let reason = detail.get("reason") + .and_then(|v| v.as_string()) + .map(|s| s.to_string()); + + if !["audit", "block", "all"].contains(&apply.as_str()) { + return Err(InvalidArgumentException { + message: format!( + "Invalid 'apply' value for '{}': {}. Expected 'audit', 'block', or 'all'.", + key, apply + ), + code: 0, + }.into()); + } + + (key.clone(), apply, reason) + } + PhpMixed::Null => (key.clone(), "all".to_string(), None), + _ => continue, + }; + + if apply == "audit" || apply == "all" { + for_audit.insert(id.clone(), reason.clone()); + } + if apply == "block" || apply == "all" { + for_block.insert(id, reason); + } + } + + Ok((for_audit, for_block)) + } + + pub fn from_config(config: &Config, audit: bool, audit_format: &str) -> anyhow::Result<Self> { + let audit_config_raw = config.get("audit"); + let audit_config = audit_config_raw.as_array(); + + let empty_array = PhpMixed::Array(IndexMap::new()); + + let ignore_raw = audit_config + .and_then(|m| m.get("ignore")) + .map(|v| *v.clone()) + .unwrap_or_else(|| empty_array.clone()); + let (ignore_list_for_audit, ignore_list_for_blocking) = + Self::parse_ignore_with_apply(&ignore_raw)?; + + let ignore_abandoned_raw = audit_config + .and_then(|m| m.get("ignore-abandoned")) + .map(|v| *v.clone()) + .unwrap_or_else(|| empty_array.clone()); + let (ignore_abandoned_for_audit, ignore_abandoned_for_blocking) = + Self::parse_ignore_with_apply(&ignore_abandoned_raw)?; + + let ignore_severity_raw = audit_config + .and_then(|m| m.get("ignore-severity")) + .map(|v| *v.clone()) + .unwrap_or_else(|| empty_array.clone()); + let (ignore_severity_for_audit, ignore_severity_for_blocking) = + Self::parse_ignore_with_apply(&ignore_severity_raw)?; + + let audit_abandoned = audit_config + .and_then(|m| m.get("abandoned")) + .and_then(|v| v.as_string()) + .unwrap_or(Auditor::ABANDONED_FAIL) + .to_string(); + + let block_insecure = audit_config + .and_then(|m| m.get("block-insecure")) + .and_then(|v| v.as_bool()) + .unwrap_or(true); + + let block_abandoned = audit_config + .and_then(|m| m.get("block-abandoned")) + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let ignore_unreachable = audit_config + .and_then(|m| m.get("ignore-unreachable")) + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + Ok(Self::new( + audit, + audit_format.to_string(), + audit_abandoned, + block_insecure, + block_abandoned, + ignore_unreachable, + ignore_list_for_audit, + ignore_list_for_blocking, + ignore_severity_for_audit, + ignore_severity_for_blocking, + ignore_abandoned_for_audit, + ignore_abandoned_for_blocking, + )) + } +} |
