use std::sync::LazyLock;
use jsonschema::Validator;
use serde_json::{Map, Value};
use crate::csaf::types::csaf_vuln_metric::CsafVulnerabilityMetric;
use crate::{
csaf_traits::{ContentTrait, CsafTrait, MetricTrait, VulnerabilityTrait},
validation::ValidationError,
};
static CVSS20_VALIDATOR: LazyLock<Validator> =
LazyLock::new(|| create_validator(include_str!("../../assets/cvss-v2.0.json")));
static CVSS30_VALIDATOR: LazyLock<Validator> =
LazyLock::new(|| create_validator(include_str!("../../assets/cvss-v3.0.json")));
static CVSS31_VALIDATOR: LazyLock<Validator> =
LazyLock::new(|| create_validator(include_str!("../../assets/cvss-v3.1.json")));
static CVSS40_VALIDATOR: LazyLock<Validator> =
LazyLock::new(|| create_draft_validator(include_str!("../../assets/cvss-v4.0.2.json")));
pub fn test_6_1_08_invalid_cvss(doc: &impl CsafTrait) -> Result<(), Vec<ValidationError>> {
let mut errors: Vec<ValidationError> = Vec::new();
for (i_v, vulnerability) in doc.get_vulnerabilities().iter().enumerate() {
if let Some(metrics) = vulnerability.get_metrics() {
for (metric_index, metric) in metrics.iter().enumerate() {
let content = metric.get_content();
let instance_prefix = content.get_content_json_path(i_v, metric_index);
if let Some(cvss2) = content.get_cvss_v2() {
evaluate_cvss(
cvss2,
&CVSS20_VALIDATOR,
&instance_prefix,
CsafVulnerabilityMetric::CvssV2("2.0".to_string()),
&mut errors,
);
}
if let Some(cvss3) = content.get_cvss_v3() {
if let Some(version) = cvss3.get("version").and_then(|v| v.as_str()) {
let metric_type = CsafVulnerabilityMetric::CvssV3(version.to_string());
if version == "3.0" {
evaluate_cvss(cvss3, &CVSS30_VALIDATOR, &instance_prefix, metric_type, &mut errors);
} else if version == "3.1" {
evaluate_cvss(cvss3, &CVSS31_VALIDATOR, &instance_prefix, metric_type, &mut errors);
}
}
}
if let Some(cvss4) = content.get_cvss_v4() {
evaluate_cvss(
cvss4,
&CVSS40_VALIDATOR,
&instance_prefix,
CsafVulnerabilityMetric::CvssV4("4.0".to_string()),
&mut errors,
);
}
}
}
}
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
crate::test_validation::impl_validator!(ValidatorForTest6_1_8, test_6_1_08_invalid_cvss);
fn create_validator(schema_str: &str) -> Validator {
jsonschema::validator_for(&serde_json::from_str(schema_str).unwrap()).unwrap()
}
fn create_draft_validator(schema_str: &str) -> Validator {
jsonschema::draft202012::new(&serde_json::from_str(schema_str).unwrap()).unwrap()
}
fn evaluate_cvss(
cvss_value: &Map<String, Value>,
validator: &Validator,
base_path: &str,
metric: CsafVulnerabilityMetric,
errors: &mut Vec<ValidationError>,
) {
let value = serde_json::to_value(cvss_value).unwrap();
for error in validator.iter_errors(&value) {
errors.push(create_validation_error(error.to_string(), base_path, metric.clone()));
}
}
fn create_validation_error(message: String, base: &str, metric: CsafVulnerabilityMetric) -> ValidationError {
ValidationError {
message,
instance_path: format!("{}/{}", base, metric.get_metric_prop_name()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::csaf2_0::testcases::TESTS_2_0;
use crate::csaf2_1::testcases::TESTS_2_1;
#[test]
fn test_test_6_1_08() {
TESTS_2_0.test_6_1_8.expect(
Err(vec![create_validation_error(
"\"baseSeverity\" is a required property".to_string(),
"/vulnerabilities/0/scores/0",
CsafVulnerabilityMetric::CvssV3("3.1".to_string()),
)]),
Err(vec![create_validation_error(
"\"baseSeverity\" is a required property".to_string(),
"/vulnerabilities/0/scores/0",
CsafVulnerabilityMetric::CvssV3("3.0".to_string()),
)]),
Err(vec![create_validation_error(
"\"version\" is a required property".to_string(),
"/vulnerabilities/0/scores/0",
CsafVulnerabilityMetric::CvssV2("2.0".to_string()),
)]),
Ok(()), Ok(()), Ok(()), Ok(()), );
TESTS_2_1.test_6_1_8.expect(
Err(vec![create_validation_error(
"\"baseSeverity\" is a required property".to_string(),
"/vulnerabilities/0/metrics/0/content",
CsafVulnerabilityMetric::CvssV3("3.1".to_string()),
)]),
Err(vec![create_validation_error(
"\"baseSeverity\" is a required property".to_string(),
"/vulnerabilities/0/metrics/0/content",
CsafVulnerabilityMetric::CvssV3("3.0".to_string()),
)]),
Err(vec![create_validation_error(
"\"version\" is a required property".to_string(),
"/vulnerabilities/0/metrics/0/content",
CsafVulnerabilityMetric::CvssV2("2.0".to_string()),
)]),
Err(vec![create_validation_error(
"\"baseSeverity\" is a required property".to_string(),
"/vulnerabilities/0/metrics/0/content",
CsafVulnerabilityMetric::CvssV4("4.0".to_string()),
)]),
Err(vec![
create_validation_error(
"Unevaluated properties are not allowed ('threatScore', 'threatSeverity' were unexpected)".to_string(),
"/vulnerabilities/0/metrics/0/content",
CsafVulnerabilityMetric::CvssV4("4.0".to_string()),
),
]),
Err(vec![
create_validation_error(
"Unevaluated properties are not allowed ('threatScore', 'threatSeverity', 'environmentalScore', 'environmentalSeverity' were unexpected)".to_string(),
"/vulnerabilities/0/metrics/0/content",
CsafVulnerabilityMetric::CvssV4("4.0".to_string()),
),
]),
Ok(()), Ok(()), Ok(()), Ok(()), Ok(()), Ok(()), Ok(()), );
}
}