diff --git a/Cargo.lock b/Cargo.lock index b59c56b..d31eccd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ahash" @@ -14,6 +14,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.18" @@ -190,6 +199,7 @@ version = "0.1.0" dependencies = [ "chrono", "prettyplease", + "regex", "regress", "schemars", "serde", @@ -335,6 +345,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "regress" version = "0.10.1" diff --git a/csaf-lib/Cargo.toml b/csaf-lib/Cargo.toml index 3f3d53a..c22a413 100644 --- a/csaf-lib/Cargo.toml +++ b/csaf-lib/Cargo.toml @@ -8,6 +8,7 @@ regress = "0.10.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" chrono = { version = "0.4.38", features = ["serde"] } +regex = "1.11.1" [build-dependencies] schemars = "0.8.21" diff --git a/csaf-lib/build.rs b/csaf-lib/build.rs index 815a8a4..6538f4d 100644 --- a/csaf-lib/build.rs +++ b/csaf-lib/build.rs @@ -32,7 +32,10 @@ fn main() -> Result<(), BuildError> { fn build(input: &str, output: &str) -> Result<(), BuildError> { let content = fs::read_to_string(input)?; - let schema = serde_json::from_str::(&content)?; + let mut schema_value = serde_json::from_str(&content)?; + // Recursively search for "format": "date-time" and replace with something else + remove_datetime_formats(&mut schema_value); + let schema = serde_json::from_value::(schema_value)?; let mut type_space = TypeSpace::new(TypeSpaceSettings::default().with_struct_builder(true)); type_space.add_root_schema(schema)?; @@ -43,3 +46,23 @@ fn build(input: &str, output: &str) -> Result<(), BuildError> { out_file.push(output); Ok(fs::write(out_file, content)?) } + +fn remove_datetime_formats(value: &mut serde_json::Value) { + if let serde_json::Value::Object(map) = value { + if let Some(format) = map.get("format") { + if format.as_str() == Some("date-time") { + // Remove the format property entirely + map.remove("format"); + } + } + + // Recursively process all values in the object + for (_, v) in map.iter_mut() { + remove_datetime_formats(v); + } + } else if let serde_json::Value::Array(arr) = value { + for item in arr.iter_mut() { + remove_datetime_formats(item); + } + } +} diff --git a/csaf-lib/src/csaf/csaf2_0/getter_implementations.rs b/csaf-lib/src/csaf/csaf2_0/getter_implementations.rs index efa8d34..72e46e8 100644 --- a/csaf-lib/src/csaf/csaf2_0/getter_implementations.rs +++ b/csaf-lib/src/csaf/csaf2_0/getter_implementations.rs @@ -1,10 +1,9 @@ -use crate::csaf::csaf2_0::schema::{Branch, CategoryOfTheRemediation, CommonSecurityAdvisoryFramework, FullProductNameT, ProductGroup, ProductStatus, ProductTree, Relationship, Remediation, Threat, Vulnerability}; -use crate::csaf::csaf2_1::schema::CategoryOfTheRemediation as Remediation21; -use crate::csaf::getter_traits::{BranchTrait, CsafTrait, FullProductNameTrait, MetricTrait, ProductGroupTrait, ProductStatusTrait, ProductTreeTrait, RelationshipTrait, RemediationTrait, ThreatTrait, VulnerabilityTrait}; +use crate::csaf::csaf2_0::schema::{Branch, CategoryOfTheRemediation, CommonSecurityAdvisoryFramework, DocumentGenerator, DocumentLevelMetaData, Flag, FullProductNameT, Involvement, ProductGroup, ProductStatus, ProductTree, Relationship, Remediation, Revision, Threat, Tracking, Vulnerability}; +use crate::csaf::csaf2_1::schema::{CategoryOfTheRemediation as Remediation21}; +use crate::csaf::getter_traits::{BranchTrait, CsafTrait, DocumentTrait, FlagTrait, FullProductNameTrait, GeneratorTrait, InvolvementTrait, MetricTrait, ProductGroupTrait, ProductStatusTrait, ProductTreeTrait, RelationshipTrait, RemediationTrait, RevisionTrait, ThreatTrait, TrackingTrait, VulnerabilityTrait}; use std::ops::Deref; impl RemediationTrait for Remediation { - /// Normalizes the remediation categories from CSAF 2.0 to those of CSAF 2.1. /// /// # Explanation @@ -25,58 +24,72 @@ impl RemediationTrait for Remediation { } } - fn get_product_ids(&self) -> Option> { - self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_product_ids(&self) -> Option + '_> { + self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + } + + fn get_group_ids(&self) -> Option + '_> { + self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref())) } - fn get_group_ids(&self) -> Option> { - self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref()).collect()) + fn get_date(&self) -> &Option { + &self.date } } impl ProductStatusTrait for ProductStatus { - fn get_first_affected(&self) -> Option> { - self.first_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_first_affected(&self) -> Option + '_> { + self.first_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_first_fixed(&self) -> Option> { - self.first_fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_first_fixed(&self) -> Option + '_> { + self.first_fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_fixed(&self) -> Option> { - self.fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_fixed(&self) -> Option + '_> { + self.fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_known_affected(&self) -> Option> { - self.known_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_known_affected(&self) -> Option + '_> { + self.known_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_known_not_affected(&self) -> Option> { - self.known_not_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_known_not_affected(&self) -> Option + '_> { + self.known_not_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_last_affected(&self) -> Option> { - self.last_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_last_affected(&self) -> Option + '_> { + self.last_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_recommended(&self) -> Option> { - self.recommended.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_recommended(&self) -> Option + '_> { + self.recommended.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_under_investigation(&self) -> Option> { - self.under_investigation.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_under_investigation(&self) -> Option + '_> { + self.under_investigation.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } } impl MetricTrait for () { - fn get_products(&self) -> Vec<&String> { - panic!("Metrics are not implemented in CSAF 2.0") + //noinspection RsConstantConditionIf + fn get_products(&self) -> impl Iterator + '_ { + // This construction is required to satisfy compiler checks + // and still panic if this is ever called (as this would be a clear error!). + if true { + panic!("Metrics are not implemented in CSAF 2.0"); + } + std::iter::empty() } } impl ThreatTrait for Threat { - fn get_product_ids(&self) -> Option> { - self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_product_ids(&self) -> Option + '_> { + self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + } + + fn get_date(&self) -> &Option { + &self.date } } @@ -86,35 +99,117 @@ impl VulnerabilityTrait for Vulnerability { // Metrics are not implemented in CSAF 2.0 type MetricType = (); type ThreatType = Threat; + type FlagType = Flag; + type InvolvementType = Involvement; - fn get_remediations(&self) -> Vec { - self.remediations.clone() + fn get_remediations(&self) -> &Vec { + &self.remediations } - fn get_product_status(&self) -> Option { - self.product_status.clone() + fn get_product_status(&self) -> &Option { + &self.product_status } - fn get_metrics(&self) -> Option> { + fn get_metrics(&self) -> &Option> { // Metrics are not implemented in CSAF 2.0 - None + &None + } + + fn get_threats(&self) -> &Vec { + &self.threats + } + + fn get_release_date(&self) -> &Option { + &self.release_date + } + + fn get_discovery_date(&self) -> &Option { + &self.discovery_date + } + + fn get_flags(&self) -> &Option> { + &self.flags } - fn get_threats(&self) -> Vec { - self.threats.clone() + fn get_involvements(&self) -> &Option> { + &self.involvements + } +} + +impl FlagTrait for Flag { + fn get_date(&self) -> &Option { + &self.date + } +} + +impl InvolvementTrait for Involvement { + fn get_date(&self) -> &Option { + &self.date } } impl CsafTrait for CommonSecurityAdvisoryFramework { type VulnerabilityType = Vulnerability; type ProductTreeType = ProductTree; + type DocumentType = DocumentLevelMetaData; + + fn get_product_tree(&self) -> &Option { + &self.product_tree + } - fn get_product_tree(&self) -> Option { - self.product_tree.clone() + fn get_vulnerabilities(&self) -> &Vec { + &self.vulnerabilities } - fn get_vulnerabilities(&self) -> Vec { - self.vulnerabilities.clone() + fn get_document(&self) -> &Self::DocumentType { + &self.document + } +} + +impl DocumentTrait for DocumentLevelMetaData { + type TrackingType = Tracking; + + fn get_tracking(&self) -> &Self::TrackingType { + &self.tracking + } +} + +impl TrackingTrait for Tracking { + type GeneratorType = DocumentGenerator; + type RevisionType = Revision; + + fn get_current_release_date(&self) -> &String { + &self.current_release_date + } + + fn get_initial_release_date(&self) -> &String { + &self.initial_release_date + } + + fn get_generator(&self) -> &Option { + &self.generator + } + + fn get_revision_history(&self) -> &Vec { + &self.revision_history + } +} + +impl GeneratorTrait for DocumentGenerator { + fn get_date(&self) -> &Option { + &self.date + } +} + +impl RevisionTrait for Revision { + fn get_date(&self) -> &String { + &self.date + } + fn get_number(&self) -> &String { + &self.number + } + fn get_summary(&self) -> &String { + &self.summary } } @@ -149,8 +244,8 @@ impl BranchTrait for Branch { self.branches.as_ref().map(|branches| branches.deref()) } - fn get_product(&self) -> Option<&Self::FullProductNameType> { - self.product.as_ref() + fn get_product(&self) -> &Option { + &self.product } } @@ -159,8 +254,8 @@ impl ProductGroupTrait for ProductGroup { self.group_id.deref() } - fn get_product_ids(&self) -> Vec<&String> { - self.product_ids.iter().map(|x| x.deref()).collect() + fn get_product_ids(&self) -> impl Iterator + '_ { + self.product_ids.iter().map(|x| x.deref()) } } diff --git a/csaf-lib/src/csaf/csaf2_0/loader.rs b/csaf-lib/src/csaf/csaf2_0/loader.rs index 000868e..cef5102 100644 --- a/csaf-lib/src/csaf/csaf2_0/loader.rs +++ b/csaf-lib/src/csaf/csaf2_0/loader.rs @@ -23,6 +23,7 @@ mod tests { }; fn mock_document() -> CommonSecurityAdvisoryFramework { + let now = chrono::Utc::now().to_string(); let metadata: DocumentLevelMetaData = DocumentLevelMetaData::builder() .title("Test") .category("csaf_base") @@ -36,13 +37,13 @@ mod tests { .tracking( Tracking::builder() .id("test") - .current_release_date(chrono::Utc::now()) - .initial_release_date(chrono::Utc::now()) + .current_release_date(now.clone()) + .initial_release_date(now.clone()) .status("final") .version("1") .revision_history(vec![Revision::builder() .number("1") - .date(chrono::Utc::now()) + .date(now.clone()) .summary("test") .try_into() .unwrap()]), diff --git a/csaf-lib/src/csaf/csaf2_0/schema.rs b/csaf-lib/src/csaf/csaf2_0/schema.rs index 64158f8..2d49d10 100644 --- a/csaf-lib/src/csaf/csaf2_0/schema.rs +++ b/csaf-lib/src/csaf/csaf2_0/schema.rs @@ -1654,8 +1654,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "current_release_date": { /// "title": "Current release date", /// "description": "The date when the current revision of this document was released", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "generator": { /// "title": "Document generator", @@ -1668,8 +1667,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of document generation", /// "description": "This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "engine": { /// "title": "Engine of document generation", @@ -1720,8 +1718,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "initial_release_date": { /// "title": "Initial release date", /// "description": "The date when this document was first published.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "revision_history": { /// "title": "Revision history", @@ -1740,8 +1737,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of the revision", /// "description": "The date of the revision entry", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "legacy_version": { /// "title": "Legacy version of the revision", @@ -1943,8 +1939,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "discovery_date": { /// "title": "Discovery date", /// "description": "Holds the date and time the vulnerability was originally discovered.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "flags": { /// "title": "List of flags", @@ -1961,8 +1956,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of the flag", /// "description": "Contains the date when assessment was done or the flag was assigned.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "group_ids": { /// "$ref": "#/$defs/product_groups_t" @@ -2041,8 +2035,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of involvement", /// "description": "Holds the date and time of the involvement entry.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "party": { /// "title": "Party category", @@ -2141,8 +2134,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "release_date": { /// "title": "Release date", /// "description": "Holds the date and time the vulnerability was originally released into the wild.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "remediations": { /// "title": "List of remediations", @@ -2172,8 +2164,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of the remediation", /// "description": "Contains the date from which the remediation is available.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the remediation", @@ -2293,8 +2284,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of the threat", /// "description": "Contains the date when the assessment was done or the threat appeared.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the threat", @@ -3022,8 +3012,7 @@ impl<'de> ::serde::Deserialize<'de> for DocumentCategory { /// "date": { /// "title": "Date of document generation", /// "description": "This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "engine": { /// "title": "Engine of document generation", @@ -3065,7 +3054,7 @@ impl<'de> ::serde::Deserialize<'de> for DocumentCategory { pub struct DocumentGenerator { ///This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, pub engine: EngineOfDocumentGeneration, } impl From<&DocumentGenerator> for DocumentGenerator { @@ -3322,8 +3311,7 @@ impl DocumentGenerator { /// "current_release_date": { /// "title": "Current release date", /// "description": "The date when the current revision of this document was released", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "generator": { /// "title": "Document generator", @@ -3336,8 +3324,7 @@ impl DocumentGenerator { /// "date": { /// "title": "Date of document generation", /// "description": "This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "engine": { /// "title": "Engine of document generation", @@ -3388,8 +3375,7 @@ impl DocumentGenerator { /// "initial_release_date": { /// "title": "Initial release date", /// "description": "The date when this document was first published.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "revision_history": { /// "title": "Revision history", @@ -3408,8 +3394,7 @@ impl DocumentGenerator { /// "date": { /// "title": "Date of the revision", /// "description": "The date of the revision entry", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "legacy_version": { /// "title": "Legacy version of the revision", @@ -4001,8 +3986,7 @@ impl<'de> ::serde::Deserialize<'de> for Filename { /// "date": { /// "title": "Date of the flag", /// "description": "Contains the date when assessment was done or the flag was assigned.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "group_ids": { /// "$ref": "#/$defs/product_groups_t" @@ -4030,7 +4014,7 @@ impl<'de> ::serde::Deserialize<'de> for Filename { pub struct Flag { ///Contains the date when assessment was done or the flag was assigned. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub group_ids: Option, ///Specifies the machine readable label. @@ -4606,8 +4590,7 @@ impl Id { /// "date": { /// "title": "Date of involvement", /// "description": "Holds the date and time of the involvement entry.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "party": { /// "title": "Party category", @@ -4648,7 +4631,7 @@ impl Id { pub struct Involvement { ///Holds the date and time of the involvement entry. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, ///Defines the category of the involved party. pub party: PartyCategory, ///Defines contact status of the involved party. @@ -6830,8 +6813,7 @@ impl std::convert::TryFrom for RelationshipCategory { /// "date": { /// "title": "Date of the remediation", /// "description": "Contains the date from which the remediation is available.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the remediation", @@ -6905,7 +6887,7 @@ pub struct Remediation { pub category: CategoryOfTheRemediation, ///Contains the date from which the remediation is available. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, ///Contains a thorough human-readable discussion of the remediation. pub details: DetailsOfTheRemediation, ///Contains a list of entitlements. @@ -7006,8 +6988,7 @@ impl RestartRequiredByRemediation { /// "date": { /// "title": "Date of the revision", /// "description": "The date of the revision entry", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "legacy_version": { /// "title": "Legacy version of the revision", @@ -7034,7 +7015,7 @@ impl RestartRequiredByRemediation { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct Revision { ///The date of the revision entry - pub date: chrono::DateTime, + pub date: String, ///Contains the version string used in an existing document with the same content. #[serde(default, skip_serializing_if = "Option::is_none")] pub legacy_version: Option, @@ -8072,8 +8053,7 @@ impl<'de> ::serde::Deserialize<'de> for TextualDescriptionOfTheProduct { /// "date": { /// "title": "Date of the threat", /// "description": "Contains the date when the assessment was done or the threat appeared.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the threat", @@ -8097,7 +8077,7 @@ pub struct Threat { pub category: CategoryOfTheThreat, ///Contains the date when the assessment was done or the threat appeared. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, ///Represents a thorough human-readable discussion of the threat. pub details: DetailsOfTheThreat, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -8372,8 +8352,7 @@ impl<'de> ::serde::Deserialize<'de> for TitleOfThisDocument { /// "current_release_date": { /// "title": "Current release date", /// "description": "The date when the current revision of this document was released", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "generator": { /// "title": "Document generator", @@ -8386,8 +8365,7 @@ impl<'de> ::serde::Deserialize<'de> for TitleOfThisDocument { /// "date": { /// "title": "Date of document generation", /// "description": "This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "engine": { /// "title": "Engine of document generation", @@ -8438,8 +8416,7 @@ impl<'de> ::serde::Deserialize<'de> for TitleOfThisDocument { /// "initial_release_date": { /// "title": "Initial release date", /// "description": "The date when this document was first published.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "revision_history": { /// "title": "Revision history", @@ -8458,8 +8435,7 @@ impl<'de> ::serde::Deserialize<'de> for TitleOfThisDocument { /// "date": { /// "title": "Date of the revision", /// "description": "The date of the revision entry", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "legacy_version": { /// "title": "Legacy version of the revision", @@ -8506,13 +8482,13 @@ pub struct Tracking { #[serde(default, skip_serializing_if = "Option::is_none")] pub aliases: Option>, ///The date when the current revision of this document was released - pub current_release_date: chrono::DateTime, + pub current_release_date: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub generator: Option, ///The ID is a simple label that provides for a wide range of numbering values, types, and schemes. Its value SHOULD be assigned and maintained by the original document issuing authority. pub id: UniqueIdentifierForTheDocument, ///The date when this document was first published. - pub initial_release_date: chrono::DateTime, + pub initial_release_date: String, ///Holds one revision item for each version of the CSAF document, including the initial one. pub revision_history: Vec, ///Defines the draft status of the document. @@ -8888,8 +8864,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "discovery_date": { /// "title": "Discovery date", /// "description": "Holds the date and time the vulnerability was originally discovered.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "flags": { /// "title": "List of flags", @@ -8906,8 +8881,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "date": { /// "title": "Date of the flag", /// "description": "Contains the date when assessment was done or the flag was assigned.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "group_ids": { /// "$ref": "#/$defs/product_groups_t" @@ -8986,8 +8960,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "date": { /// "title": "Date of involvement", /// "description": "Holds the date and time of the involvement entry.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "party": { /// "title": "Party category", @@ -9086,8 +9059,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "release_date": { /// "title": "Release date", /// "description": "Holds the date and time the vulnerability was originally released into the wild.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "remediations": { /// "title": "List of remediations", @@ -9117,8 +9089,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "date": { /// "title": "Date of the remediation", /// "description": "Contains the date from which the remediation is available.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the remediation", @@ -9238,8 +9209,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "date": { /// "title": "Date of the threat", /// "description": "Contains the date when the assessment was done or the threat appeared.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the threat", @@ -9279,7 +9249,7 @@ pub struct Vulnerability { pub cwe: Option, ///Holds the date and time the vulnerability was originally discovered. #[serde(default, skip_serializing_if = "Option::is_none")] - pub discovery_date: Option>, + pub discovery_date: Option, ///Contains a list of machine readable flags. #[serde(default, skip_serializing_if = "Option::is_none")] pub flags: Option>, @@ -9299,7 +9269,7 @@ pub struct Vulnerability { pub references: Option, ///Holds the date and time the vulnerability was originally released into the wild. #[serde(default, skip_serializing_if = "Option::is_none")] - pub release_date: Option>, + pub release_date: Option, ///Contains a list of remediations. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub remediations: Vec, @@ -9893,7 +9863,7 @@ pub mod builder { } #[derive(Clone, Debug)] pub struct DocumentGenerator { - date: Result>, String>, + date: Result, String>, engine: Result, } impl Default for DocumentGenerator { @@ -9907,7 +9877,7 @@ pub mod builder { impl DocumentGenerator { pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -10280,7 +10250,7 @@ pub mod builder { } #[derive(Clone, Debug)] pub struct Flag { - date: Result>, String>, + date: Result, String>, group_ids: Result, String>, label: Result, product_ids: Result, String>, @@ -10298,7 +10268,7 @@ pub mod builder { impl Flag { pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -10703,7 +10673,7 @@ pub mod builder { } #[derive(Clone, Debug)] pub struct Involvement { - date: Result>, String>, + date: Result, String>, party: Result, status: Result, summary: Result, String>, @@ -10721,7 +10691,7 @@ pub mod builder { impl Involvement { pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -11471,7 +11441,7 @@ pub mod builder { #[derive(Clone, Debug)] pub struct Remediation { category: Result, - date: Result>, String>, + date: Result, String>, details: Result, entitlements: Result, String>, group_ids: Result, String>, @@ -11508,7 +11478,7 @@ pub mod builder { } pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -11679,7 +11649,7 @@ pub mod builder { } #[derive(Clone, Debug)] pub struct Revision { - date: Result, String>, + date: Result, legacy_version: Result, String>, number: Result, summary: Result, @@ -11697,7 +11667,7 @@ pub mod builder { impl Revision { pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.date = value @@ -11893,7 +11863,7 @@ pub mod builder { #[derive(Clone, Debug)] pub struct Threat { category: Result, - date: Result>, String>, + date: Result, String>, details: Result, group_ids: Result, String>, product_ids: Result, String>, @@ -11924,7 +11894,7 @@ pub mod builder { } pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -11995,10 +11965,10 @@ pub mod builder { #[derive(Clone, Debug)] pub struct Tracking { aliases: Result>, String>, - current_release_date: Result, String>, + current_release_date: Result, generator: Result, String>, id: Result, - initial_release_date: Result, String>, + initial_release_date: Result, revision_history: Result, String>, status: Result, version: Result, @@ -12038,7 +12008,7 @@ pub mod builder { } pub fn current_release_date(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.current_release_date = value @@ -12074,7 +12044,7 @@ pub mod builder { } pub fn initial_release_date(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.initial_release_date = value @@ -12216,14 +12186,14 @@ pub mod builder { acknowledgments: Result, String>, cve: Result, String>, cwe: Result, String>, - discovery_date: Result>, String>, + discovery_date: Result, String>, flags: Result>, String>, ids: Result>, String>, involvements: Result>, String>, notes: Result, String>, product_status: Result, String>, references: Result, String>, - release_date: Result>, String>, + release_date: Result, String>, remediations: Result, String>, scores: Result, String>, threats: Result, String>, @@ -12285,7 +12255,7 @@ pub mod builder { } pub fn discovery_date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.discovery_date = value @@ -12367,7 +12337,7 @@ pub mod builder { } pub fn release_date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.release_date = value diff --git a/csaf-lib/src/csaf/csaf2_1/getter_implementations.rs b/csaf-lib/src/csaf/csaf2_1/getter_implementations.rs index 39abc6e..8aa0fd0 100644 --- a/csaf-lib/src/csaf/csaf2_1/getter_implementations.rs +++ b/csaf-lib/src/csaf/csaf2_1/getter_implementations.rs @@ -1,5 +1,5 @@ -use crate::csaf::csaf2_1::schema::{Branch, CategoryOfTheRemediation, CommonSecurityAdvisoryFramework, FullProductNameT, Metric, ProductGroup, ProductStatus, ProductTree, Relationship, Remediation, Threat, Vulnerability}; -use crate::csaf::getter_traits::{BranchTrait, CsafTrait, FullProductNameTrait, MetricTrait, ProductGroupTrait, ProductStatusTrait, ProductTreeTrait, RelationshipTrait, RemediationTrait, ThreatTrait, VulnerabilityTrait}; +use crate::csaf::csaf2_1::schema::{Branch, CategoryOfTheRemediation, CommonSecurityAdvisoryFramework, DocumentGenerator, DocumentLevelMetaData, Flag, FullProductNameT, Involvement, Metric, ProductGroup, ProductStatus, ProductTree, Relationship, Remediation, Revision, Threat, Tracking, Vulnerability}; +use crate::csaf::getter_traits::{BranchTrait, CsafTrait, DocumentTrait, FlagTrait, FullProductNameTrait, GeneratorTrait, InvolvementTrait, MetricTrait, ProductGroupTrait, ProductStatusTrait, ProductTreeTrait, RelationshipTrait, RemediationTrait, RevisionTrait, ThreatTrait, TrackingTrait, VulnerabilityTrait}; use std::ops::Deref; impl RemediationTrait for Remediation { @@ -7,58 +7,66 @@ impl RemediationTrait for Remediation { self.category.clone() } - fn get_product_ids(&self) -> Option> { - self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_product_ids(&self) -> Option + '_> { + self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_group_ids(&self) -> Option> { - self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref()).collect()) + fn get_group_ids(&self) -> Option + '_> { + self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref())) + } + + fn get_date(&self) -> &Option { + &self.date } } impl ProductStatusTrait for ProductStatus { - fn get_first_affected(&self) -> Option> { - self.first_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_first_affected(&self) -> Option + '_> { + self.first_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_first_fixed(&self) -> Option> { - self.first_fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_first_fixed(&self) -> Option + '_> { + self.first_fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_fixed(&self) -> Option> { - self.fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_fixed(&self) -> Option + '_> { + self.fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_known_affected(&self) -> Option> { - self.known_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_known_affected(&self) -> Option + '_> { + self.known_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_known_not_affected(&self) -> Option> { - self.known_not_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_known_not_affected(&self) -> Option + '_> { + self.known_not_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_last_affected(&self) -> Option> { - self.last_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_last_affected(&self) -> Option + '_> { + self.last_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_recommended(&self) -> Option> { - self.recommended.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_recommended(&self) -> Option + '_> { + self.recommended.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } - fn get_under_investigation(&self) -> Option> { - self.under_investigation.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_under_investigation(&self) -> Option + '_> { + self.under_investigation.as_ref().map(|p| (*p).iter().map(|x| x.deref())) } } impl MetricTrait for Metric { - fn get_products(&self) -> Vec<&String> { - self.products.deref().iter().map(|p| p.deref()).collect() + fn get_products(&self) -> impl Iterator + '_ { + self.products.deref().iter().map(|p| p.deref()) } } impl ThreatTrait for Threat { - fn get_product_ids(&self) -> Option> { - self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref()).collect()) + fn get_product_ids(&self) -> Option + '_> { + self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + } + + fn get_date(&self) -> &Option { + &self.date } } @@ -67,34 +75,116 @@ impl VulnerabilityTrait for Vulnerability { type ProductStatusType = ProductStatus; type MetricType = Metric; type ThreatType = Threat; + type FlagType = Flag; + type InvolvementType = Involvement; + + fn get_remediations(&self) -> &Vec { + &self.remediations + } - fn get_remediations(&self) -> Vec { - self.remediations.clone() + fn get_product_status(&self) -> &Option { + &self.product_status } - fn get_product_status(&self) -> Option { - self.product_status.clone() + fn get_metrics(&self) -> &Option> { + &self.metrics } - fn get_metrics(&self) -> Option> { - self.metrics.clone() + fn get_threats(&self) -> &Vec { + &self.threats } - fn get_threats(&self) -> Vec { - self.threats.clone() + fn get_release_date(&self) -> &Option { + &self.release_date + } + + fn get_discovery_date(&self) -> &Option { + &self.discovery_date + } + + fn get_flags(&self) -> &Option> { + &self.flags + } + + fn get_involvements(&self) -> &Option> { + &self.involvements + } +} + +impl FlagTrait for Flag { + fn get_date(&self) -> &Option { + &self.date + } +} + +impl InvolvementTrait for Involvement { + fn get_date(&self) -> &Option { + &self.date } } impl CsafTrait for CommonSecurityAdvisoryFramework { type VulnerabilityType = Vulnerability; type ProductTreeType = ProductTree; + type DocumentType = DocumentLevelMetaData; + + fn get_product_tree(&self) -> &Option { + &self.product_tree + } + + fn get_vulnerabilities(&self) -> &Vec { + &self.vulnerabilities + } + + fn get_document(&self) -> &Self::DocumentType { + &self.document + } +} + +impl DocumentTrait for DocumentLevelMetaData { + type TrackingType = Tracking; + + fn get_tracking(&self) -> &Self::TrackingType { + &self.tracking + } +} + +impl TrackingTrait for Tracking { + type GeneratorType = DocumentGenerator; + type RevisionType = Revision; + + fn get_current_release_date(&self) -> &String { + &self.current_release_date + } - fn get_product_tree(&self) -> Option { - self.product_tree.clone() + fn get_initial_release_date(&self) -> &String { + &self.initial_release_date } - fn get_vulnerabilities(&self) -> Vec { - self.vulnerabilities.clone() + fn get_generator(&self) -> &Option { + &self.generator + } + + fn get_revision_history(&self) -> &Vec { + &self.revision_history + } +} + +impl GeneratorTrait for DocumentGenerator { + fn get_date(&self) -> &Option { + &self.date + } +} + +impl RevisionTrait for Revision { + fn get_date(&self) -> &String { + &self.date + } + fn get_number(&self) -> &String { + &self.number + } + fn get_summary(&self) -> &String { + &self.summary } } @@ -129,8 +219,8 @@ impl BranchTrait for Branch { self.branches.as_ref().map(|branches| branches.deref()) } - fn get_product(&self) -> Option<&Self::FullProductNameType> { - self.product.as_ref() + fn get_product(&self) -> &Option { + &self.product } } @@ -139,8 +229,8 @@ impl ProductGroupTrait for ProductGroup { self.group_id.deref() } - fn get_product_ids(&self) -> Vec<&String> { - self.product_ids.iter().map(|x| x.deref()).collect() + fn get_product_ids(&self) -> impl Iterator + '_ { + self.product_ids.iter().map(|x| x.deref()) } } diff --git a/csaf-lib/src/csaf/csaf2_1/loader.rs b/csaf-lib/src/csaf/csaf2_1/loader.rs index 9071d15..b8fa11a 100644 --- a/csaf-lib/src/csaf/csaf2_1/loader.rs +++ b/csaf-lib/src/csaf/csaf2_1/loader.rs @@ -20,6 +20,7 @@ mod tests { use crate::csaf::csaf2_1::schema::{CategoryOfPublisher, CommonSecurityAdvisoryFramework, DocumentLevelMetaData, JsonSchema, LabelOfTlp, Publisher, Revision, RulesForSharingDocument, Tracking, TrafficLightProtocolTlp}; fn mock_document() -> CommonSecurityAdvisoryFramework { + let now = chrono::Utc::now().to_string(); let metadata: DocumentLevelMetaData = DocumentLevelMetaData::builder() .title("Test") .category("csaf_base") @@ -41,13 +42,13 @@ mod tests { .tracking( Tracking::builder() .id("test") - .current_release_date(chrono::Utc::now()) - .initial_release_date(chrono::Utc::now()) + .current_release_date(now.clone()) + .initial_release_date(now.clone()) .status("final") .version("1") .revision_history(vec![Revision::builder() .number("1") - .date(chrono::Utc::now()) + .date(now.clone()) .summary("test") .try_into() .unwrap()]), diff --git a/csaf-lib/src/csaf/csaf2_1/schema.rs b/csaf-lib/src/csaf/csaf2_1/schema.rs index 10f1422..6525fab 100644 --- a/csaf-lib/src/csaf/csaf2_1/schema.rs +++ b/csaf-lib/src/csaf/csaf2_1/schema.rs @@ -1722,8 +1722,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "current_release_date": { /// "title": "Current release date", /// "description": "The date when the current revision of this document was released", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "generator": { /// "title": "Document generator", @@ -1736,8 +1735,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of document generation", /// "description": "This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "engine": { /// "title": "Engine of document generation", @@ -1788,8 +1786,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "initial_release_date": { /// "title": "Initial release date", /// "description": "The date when this document was first published.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "revision_history": { /// "title": "Revision history", @@ -1808,8 +1805,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of the revision", /// "description": "The date of the revision entry", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "legacy_version": { /// "title": "Legacy version of the revision", @@ -2033,8 +2029,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "discovery_date": { /// "title": "Discovery date", /// "description": "Holds the date and time the vulnerability was originally discovered.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "flags": { /// "title": "List of flags", @@ -2051,8 +2046,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of the flag", /// "description": "Contains the date when assessment was done or the flag was assigned.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "group_ids": { /// "$ref": "#/$defs/product_groups_t" @@ -2131,8 +2125,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of involvement", /// "description": "Holds the date and time of the involvement entry.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "party": { /// "title": "Party category", @@ -2275,8 +2268,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "release_date": { /// "title": "Release date", /// "description": "Holds the date and time the vulnerability was originally released into the wild.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "remediations": { /// "title": "List of remediations", @@ -2308,8 +2300,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of the remediation", /// "description": "Contains the date from which the remediation is available.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the remediation", @@ -2403,8 +2394,7 @@ impl<'de> ::serde::Deserialize<'de> for CommonPlatformEnumerationRepresentation /// "date": { /// "title": "Date of the threat", /// "description": "Contains the date when the assessment was done or the threat appeared.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the threat", @@ -3283,8 +3273,7 @@ impl<'de> ::serde::Deserialize<'de> for DocumentCategory { /// "date": { /// "title": "Date of document generation", /// "description": "This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "engine": { /// "title": "Engine of document generation", @@ -3326,7 +3315,7 @@ impl<'de> ::serde::Deserialize<'de> for DocumentCategory { pub struct DocumentGenerator { ///This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, pub engine: EngineOfDocumentGeneration, } impl From<&DocumentGenerator> for DocumentGenerator { @@ -3619,8 +3608,7 @@ impl DocumentGenerator { /// "current_release_date": { /// "title": "Current release date", /// "description": "The date when the current revision of this document was released", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "generator": { /// "title": "Document generator", @@ -3633,8 +3621,7 @@ impl DocumentGenerator { /// "date": { /// "title": "Date of document generation", /// "description": "This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "engine": { /// "title": "Engine of document generation", @@ -3685,8 +3672,7 @@ impl DocumentGenerator { /// "initial_release_date": { /// "title": "Initial release date", /// "description": "The date when this document was first published.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "revision_history": { /// "title": "Revision history", @@ -3705,8 +3691,7 @@ impl DocumentGenerator { /// "date": { /// "title": "Date of the revision", /// "description": "The date of the revision entry", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "legacy_version": { /// "title": "Legacy version of the revision", @@ -4297,8 +4282,7 @@ impl<'de> ::serde::Deserialize<'de> for Filename { /// "date": { /// "title": "Date of the flag", /// "description": "Contains the date when assessment was done or the flag was assigned.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "group_ids": { /// "$ref": "#/$defs/product_groups_t" @@ -4326,7 +4310,7 @@ impl<'de> ::serde::Deserialize<'de> for Filename { pub struct Flag { ///Contains the date when assessment was done or the flag was assigned. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub group_ids: Option, ///Specifies the machine readable label. @@ -4916,8 +4900,7 @@ impl Id { /// "date": { /// "title": "Date of involvement", /// "description": "Holds the date and time of the involvement entry.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "party": { /// "title": "Party category", @@ -4958,7 +4941,7 @@ impl Id { pub struct Involvement { ///Holds the date and time of the involvement entry. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, ///Defines the category of the involved party. pub party: PartyCategory, ///Defines contact status of the involved party. @@ -7293,8 +7276,7 @@ impl std::convert::TryFrom for RelationshipCategory { /// "date": { /// "title": "Date of the remediation", /// "description": "Contains the date from which the remediation is available.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the remediation", @@ -7368,7 +7350,7 @@ pub struct Remediation { pub category: CategoryOfTheRemediation, ///Contains the date from which the remediation is available. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, ///Contains a thorough human-readable discussion of the remediation. pub details: DetailsOfTheRemediation, ///Contains a list of entitlements. @@ -7469,8 +7451,7 @@ impl RestartRequiredByRemediation { /// "date": { /// "title": "Date of the revision", /// "description": "The date of the revision entry", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "legacy_version": { /// "title": "Legacy version of the revision", @@ -7497,7 +7478,7 @@ impl RestartRequiredByRemediation { #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub struct Revision { ///The date of the revision entry - pub date: chrono::DateTime, + pub date: String, ///Contains the version string used in an existing document with the same content. #[serde(default, skip_serializing_if = "Option::is_none")] pub legacy_version: Option, @@ -8737,8 +8718,7 @@ impl<'de> ::serde::Deserialize<'de> for TextualDescriptionOfTheProduct { /// "date": { /// "title": "Date of the threat", /// "description": "Contains the date when the assessment was done or the threat appeared.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the threat", @@ -8762,7 +8742,7 @@ pub struct Threat { pub category: CategoryOfTheThreat, ///Contains the date when the assessment was done or the threat appeared. #[serde(default, skip_serializing_if = "Option::is_none")] - pub date: Option>, + pub date: Option, ///Represents a thorough human-readable discussion of the threat. pub details: DetailsOfTheThreat, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -9037,8 +9017,7 @@ impl<'de> ::serde::Deserialize<'de> for TitleOfThisDocument { /// "current_release_date": { /// "title": "Current release date", /// "description": "The date when the current revision of this document was released", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "generator": { /// "title": "Document generator", @@ -9051,8 +9030,7 @@ impl<'de> ::serde::Deserialize<'de> for TitleOfThisDocument { /// "date": { /// "title": "Date of document generation", /// "description": "This SHOULD be the current date that the document was generated. Because documents are often generated internally by a document producer and exist for a nonzero amount of time before being released, this field MAY be different from the Initial Release Date and Current Release Date.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "engine": { /// "title": "Engine of document generation", @@ -9103,8 +9081,7 @@ impl<'de> ::serde::Deserialize<'de> for TitleOfThisDocument { /// "initial_release_date": { /// "title": "Initial release date", /// "description": "The date when this document was first published.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "revision_history": { /// "title": "Revision history", @@ -9123,8 +9100,7 @@ impl<'de> ::serde::Deserialize<'de> for TitleOfThisDocument { /// "date": { /// "title": "Date of the revision", /// "description": "The date of the revision entry", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "legacy_version": { /// "title": "Legacy version of the revision", @@ -9171,13 +9147,13 @@ pub struct Tracking { #[serde(default, skip_serializing_if = "Option::is_none")] pub aliases: Option>, ///The date when the current revision of this document was released - pub current_release_date: chrono::DateTime, + pub current_release_date: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub generator: Option, ///The ID is a simple label that provides for a wide range of numbering values, types, and schemes. Its value SHOULD be assigned and maintained by the original document issuing authority. pub id: UniqueIdentifierForTheDocument, ///The date when this document was first published. - pub initial_release_date: chrono::DateTime, + pub initial_release_date: String, ///Holds one revision item for each version of the CSAF document, including the initial one. pub revision_history: Vec, ///Defines the draft status of the document. @@ -9577,8 +9553,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "discovery_date": { /// "title": "Discovery date", /// "description": "Holds the date and time the vulnerability was originally discovered.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "flags": { /// "title": "List of flags", @@ -9595,8 +9570,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "date": { /// "title": "Date of the flag", /// "description": "Contains the date when assessment was done or the flag was assigned.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "group_ids": { /// "$ref": "#/$defs/product_groups_t" @@ -9675,8 +9649,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "date": { /// "title": "Date of involvement", /// "description": "Holds the date and time of the involvement entry.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "party": { /// "title": "Party category", @@ -9819,8 +9792,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "release_date": { /// "title": "Release date", /// "description": "Holds the date and time the vulnerability was originally released into the wild.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "remediations": { /// "title": "List of remediations", @@ -9852,8 +9824,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "date": { /// "title": "Date of the remediation", /// "description": "Contains the date from which the remediation is available.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the remediation", @@ -9947,8 +9918,7 @@ impl<'de> ::serde::Deserialize<'de> for VersionT { /// "date": { /// "title": "Date of the threat", /// "description": "Contains the date when the assessment was done or the threat appeared.", -/// "type": "string", -/// "format": "date-time" +/// "type": "string" /// }, /// "details": { /// "title": "Details of the threat", @@ -9989,7 +9959,7 @@ pub struct Vulnerability { pub cwes: Option>, ///Holds the date and time the vulnerability was originally discovered. #[serde(default, skip_serializing_if = "Option::is_none")] - pub discovery_date: Option>, + pub discovery_date: Option, ///Contains a list of machine readable flags. #[serde(default, skip_serializing_if = "Option::is_none")] pub flags: Option>, @@ -10012,7 +9982,7 @@ pub struct Vulnerability { pub references: Option, ///Holds the date and time the vulnerability was originally released into the wild. #[serde(default, skip_serializing_if = "Option::is_none")] - pub release_date: Option>, + pub release_date: Option, ///Contains a list of remediations. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub remediations: Vec, @@ -10707,7 +10677,7 @@ pub mod builder { } #[derive(Clone, Debug)] pub struct DocumentGenerator { - date: Result>, String>, + date: Result, String>, engine: Result, } impl Default for DocumentGenerator { @@ -10721,7 +10691,7 @@ pub mod builder { impl DocumentGenerator { pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -11094,7 +11064,7 @@ pub mod builder { } #[derive(Clone, Debug)] pub struct Flag { - date: Result>, String>, + date: Result, String>, group_ids: Result, String>, label: Result, product_ids: Result, String>, @@ -11112,7 +11082,7 @@ pub mod builder { impl Flag { pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -11519,7 +11489,7 @@ pub mod builder { } #[derive(Clone, Debug)] pub struct Involvement { - date: Result>, String>, + date: Result, String>, party: Result, status: Result, summary: Result, String>, @@ -11537,7 +11507,7 @@ pub mod builder { impl Involvement { pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -12359,7 +12329,7 @@ pub mod builder { #[derive(Clone, Debug)] pub struct Remediation { category: Result, - date: Result>, String>, + date: Result, String>, details: Result, entitlements: Result, String>, group_ids: Result, String>, @@ -12396,7 +12366,7 @@ pub mod builder { } pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -12567,7 +12537,7 @@ pub mod builder { } #[derive(Clone, Debug)] pub struct Revision { - date: Result, String>, + date: Result, legacy_version: Result, String>, number: Result, summary: Result, @@ -12585,7 +12555,7 @@ pub mod builder { impl Revision { pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.date = value @@ -12777,7 +12747,7 @@ pub mod builder { #[derive(Clone, Debug)] pub struct Threat { category: Result, - date: Result>, String>, + date: Result, String>, details: Result, group_ids: Result, String>, product_ids: Result, String>, @@ -12808,7 +12778,7 @@ pub mod builder { } pub fn date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.date = value @@ -12879,10 +12849,10 @@ pub mod builder { #[derive(Clone, Debug)] pub struct Tracking { aliases: Result>, String>, - current_release_date: Result, String>, + current_release_date: Result, generator: Result, String>, id: Result, - initial_release_date: Result, String>, + initial_release_date: Result, revision_history: Result, String>, status: Result, version: Result, @@ -12922,7 +12892,7 @@ pub mod builder { } pub fn current_release_date(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.current_release_date = value @@ -12958,7 +12928,7 @@ pub mod builder { } pub fn initial_release_date(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.initial_release_date = value @@ -13100,7 +13070,7 @@ pub mod builder { acknowledgments: Result, String>, cve: Result, String>, cwes: Result>, String>, - discovery_date: Result>, String>, + discovery_date: Result, String>, flags: Result>, String>, ids: Result>, String>, involvements: Result>, String>, @@ -13108,7 +13078,7 @@ pub mod builder { notes: Result, String>, product_status: Result, String>, references: Result, String>, - release_date: Result>, String>, + release_date: Result, String>, remediations: Result, String>, threats: Result, String>, title: Result, String>, @@ -13169,7 +13139,7 @@ pub mod builder { } pub fn discovery_date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.discovery_date = value @@ -13263,7 +13233,7 @@ pub mod builder { } pub fn release_date(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.release_date = value diff --git a/csaf-lib/src/csaf/getter_traits.rs b/csaf-lib/src/csaf/getter_traits.rs index dfeceff..81ef1c7 100644 --- a/csaf-lib/src/csaf/getter_traits.rs +++ b/csaf-lib/src/csaf/getter_traits.rs @@ -14,13 +14,67 @@ pub trait CsafTrait { /// The associated type representing the type of product tree in this CSAF structure. type ProductTreeType: ProductTreeTrait; + /// The associated type representing the type of document meta in this CSAF structure. + type DocumentType: DocumentTrait; + /// Returns the product tree of the CSAF document, if available. - fn get_product_tree(&self) -> Option; + fn get_product_tree(&self) -> &Option; /// Retrieves all vulnerabilities present in the CSAF document. - fn get_vulnerabilities(&self) -> Vec; + fn get_vulnerabilities(&self) -> &Vec; + + /// Retrieves the document meta present in the CSAF document. + fn get_document(&self) -> &Self::DocumentType; +} + +/// Trait representing document meta level information +pub trait DocumentTrait { + /// Type representing document tracking information + type TrackingType: TrackingTrait; + + /// Returns the tracking information for this document + fn get_tracking(&self) -> &Self::TrackingType; +} + +pub trait TrackingTrait { + /// Type representing document generator information + type GeneratorType: GeneratorTrait; + + /// Type representing revision history entries + type RevisionType: RevisionTrait; + + /// The release date of the latest version of this document + fn get_current_release_date(&self) -> &String; + + /// The initial release date of this document + fn get_initial_release_date(&self) -> &String; + + /// Returns the generator information for this document + fn get_generator(&self) -> &Option; + + /// Returns the revision history for this document + fn get_revision_history(&self) -> &Vec; } +/// Trait for accessing document generator information +pub trait GeneratorTrait { + /// Returns the date when this document was generated + fn get_date(&self) -> &Option; +} + +/// Trait for accessing revision history entry information +pub trait RevisionTrait { + /// Returns the date associated with this revision entry + fn get_date(&self) -> &String; + + /// Returns the number/identifier of this revision + fn get_number(&self) -> &String; + + /// Returns the summary of changes in this revision + fn get_summary(&self) -> &String; +} + + /// Trait representing an abstract vulnerability in a CSAF document. /// /// The `VulnerabilityTrait` defines the structure of a vulnerability and includes @@ -38,17 +92,47 @@ pub trait VulnerabilityTrait { /// The associated type representing the threat information. type ThreatType: ThreatTrait; + /// The type representing a vulnerability flag + type FlagType: FlagTrait; + + /// The type representing a vulnerability involvement + type InvolvementType: InvolvementTrait; + /// Retrieves a list of remediations associated with the vulnerability. - fn get_remediations(&self) -> Vec; + fn get_remediations(&self) -> &Vec; /// Retrieves the status of products affected by the vulnerability, if available. - fn get_product_status(&self) -> Option; + fn get_product_status(&self) -> &Option; /// Returns an optional vector of metrics related to the vulnerability. - fn get_metrics(&self) -> Option>; + fn get_metrics(&self) -> &Option>; /// Retrieves a list of potential threats related to the vulnerability. - fn get_threats(&self) -> Vec; + fn get_threats(&self) -> &Vec; + + /// Returns the date when this vulnerability was initially disclosed + fn get_release_date(&self) -> &Option; + + /// Returns the date when this vulnerability was initially discovered + fn get_discovery_date(&self) -> &Option; + + /// Returns all flags associated with this vulnerability + fn get_flags(&self) -> &Option>; + + /// Returns all involvements associated with this vulnerability + fn get_involvements(&self) -> &Option>; +} + +/// Trait for accessing vulnerability flags information +pub trait FlagTrait { + /// Returns the date associated with this vulnerability flag + fn get_date(&self) -> &Option; +} + +/// Trait for accessing vulnerability involvement information +pub trait InvolvementTrait { + /// Returns the date associated with this vulnerability involvement + fn get_date(&self) -> &Option; } /// Trait representing an abstract remediation in a CSAF document. @@ -62,10 +146,10 @@ pub trait RemediationTrait { fn get_category(&self) -> CategoryOfTheRemediation; /// Retrieves the product IDs directly affected by this remediation, if any. - fn get_product_ids(&self) -> Option>; + fn get_product_ids(&self) -> Option + '_>; /// Retrieves the product group IDs related to this remediation, if any. - fn get_group_ids(&self) -> Option>; + fn get_group_ids(&self) -> Option + '_>; /// Computes a set of all product IDs affected by this remediation, either /// directly or through product groups. @@ -82,7 +166,7 @@ pub trait RemediationTrait { None } else { let mut product_set: BTreeSet = match self.get_product_ids() { - Some(product_ids) => product_ids.iter().map(|id| (*id).to_owned()).collect(), + Some(product_ids) => product_ids.map(|id| (*id).to_owned()).collect(), None => BTreeSet::new(), }; if let Some(product_groups) = self.get_group_ids() { @@ -93,33 +177,36 @@ pub trait RemediationTrait { Some(product_set) } } + + /// Returns the date associated with this remediation + fn get_date(&self) -> &Option; } /// Trait representing an abstract product status in a CSAF document. pub trait ProductStatusTrait { /// Returns a reference to the list of first affected product IDs. - fn get_first_affected(&self) -> Option>; + fn get_first_affected(&self) -> Option + '_>; /// Returns a reference to the list of first fixed product IDs. - fn get_first_fixed(&self) -> Option>; + fn get_first_fixed(&self) -> Option + '_>; /// Returns a reference to the list of fixed product IDs. - fn get_fixed(&self) -> Option>; + fn get_fixed(&self) -> Option + '_>; /// Returns a reference to the list of known affected product IDs. - fn get_known_affected(&self) -> Option>; + fn get_known_affected(&self) -> Option + '_>; /// Returns a reference to the list of known not-affected product IDs. - fn get_known_not_affected(&self) -> Option>; + fn get_known_not_affected(&self) -> Option + '_>; /// Returns a reference to the list of last affected product IDs. - fn get_last_affected(&self) -> Option>; + fn get_last_affected(&self) -> Option + '_>; /// Returns a reference to the list of recommended product IDs. - fn get_recommended(&self) -> Option>; + fn get_recommended(&self) -> Option + '_>; /// Returns a reference to the list of product IDs currently under investigation. - fn get_under_investigation(&self) -> Option>; + fn get_under_investigation(&self) -> Option + '_>; /// Combines all affected product IDs into a `HashSet`. /// @@ -178,13 +265,16 @@ pub trait ProductStatusTrait { /// Trait representing an abstract metric in a CSAF document. pub trait MetricTrait { /// Retrieves a vector of product IDs associated with this metric. - fn get_products(&self) -> Vec<&String>; + fn get_products(&self) -> impl Iterator + '_; } /// Trait representing an abstract threat in a CSAF document. pub trait ThreatTrait { /// Retrieves a list of product IDs associated with this threat, if any. - fn get_product_ids(&self) -> Option>; + fn get_product_ids(&self) -> Option + '_>; + + /// Returns the date associated with this threat + fn get_date(&self) -> &Option; } /// Trait representing an abstract product tree in a CSAF document. @@ -229,7 +319,7 @@ pub trait BranchTrait { fn get_branches(&self) -> Option<&Vec>; /// Retrieves the full product name associated with this branch, if available. - fn get_product(&self) -> Option<&Self::FullProductNameType>; + fn get_product(&self) -> &Option; } /// Trait representing an abstract product group in a CSAF document. @@ -241,7 +331,7 @@ pub trait ProductGroupTrait { fn get_group_id(&self) -> &String; /// Retrieves a vector of product IDs contained within the product group. - fn get_product_ids(&self) -> Vec<&String>; + fn get_product_ids(&self) -> impl Iterator + '_; } /// Trait representing an abstract relationship in a product tree. diff --git a/csaf-lib/src/csaf/helpers.rs b/csaf-lib/src/csaf/helpers.rs index 9f4360a..78c7bd5 100644 --- a/csaf-lib/src/csaf/helpers.rs +++ b/csaf-lib/src/csaf/helpers.rs @@ -1,13 +1,18 @@ use crate::csaf::getter_traits::{CsafTrait, ProductGroupTrait, ProductTreeTrait}; use std::collections::BTreeSet; -pub fn resolve_product_groups(doc: &impl CsafTrait, product_groups: Vec<&String>) -> Option> { - doc.get_product_tree().map(|product_tree| { +pub fn resolve_product_groups<'a, I>(doc: &impl CsafTrait, product_groups: I) -> Option> +where + I: IntoIterator +{ + let product_groups: Vec<&String> = product_groups.into_iter().collect(); + + doc.get_product_tree().as_ref().map(|product_tree| { product_tree .get_product_groups() .iter() .filter(|x| product_groups.iter().any(|g| *g == x.get_group_id())) - .map(|x| x.get_product_ids().iter().map(|p| p.to_string()).collect::>()) + .map(|x| x.get_product_ids().map(|p| p.to_string()).collect::>()) .flatten() .collect() }) diff --git a/csaf-lib/src/csaf/product_helpers.rs b/csaf-lib/src/csaf/product_helpers.rs index 8c46d5c..eed9401 100644 --- a/csaf-lib/src/csaf/product_helpers.rs +++ b/csaf-lib/src/csaf/product_helpers.rs @@ -6,7 +6,7 @@ pub fn gather_product_references(doc: &impl CsafTrait) -> Vec<(String, String)> if let Some(pt) = doc.get_product_tree().as_ref() { // /product_tree/product_groups[]/product_ids[] for (g_i, g) in pt.get_product_groups().iter().enumerate() { - for (i_i, i) in g.get_product_ids().iter().enumerate() { + for (i_i, i) in g.get_product_ids().enumerate() { ids.push(((*i).to_owned(), format!("/product_tree/product_groups/{}/product_ids/{}", g_i, i_i))) } } @@ -28,43 +28,43 @@ pub fn gather_product_references(doc: &impl CsafTrait) -> Vec<(String, String)> // /vulnerabilities[]/product_status/recommended[] // /vulnerabilities[]/product_status/under_investigation[] if let Some(status) = v.get_product_status().as_ref() { - if let Some(fa) = status.get_first_affected().as_ref() { - for (x_i, x) in fa.iter().enumerate() { + if let Some(fa) = status.get_first_affected() { + for (x_i, x) in fa.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/product_status/first_affected/{}", v_i, x_i))); } } - if let Some(ff) = status.get_first_fixed().as_ref() { - for (x_i, x) in ff.iter().enumerate() { + if let Some(ff) = status.get_first_fixed() { + for (x_i, x) in ff.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/product_status/first_fixed/{}", v_i, x_i))); } } - if let Some(f) = status.get_fixed().as_ref() { - for (x_i, x) in f.iter().enumerate() { + if let Some(f) = status.get_fixed() { + for (x_i, x) in f.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/product_status/fixed/{}", v_i, x_i))); } } - if let Some(ka) = status.get_known_affected().as_ref() { - for (x_i, x) in ka.iter().enumerate() { + if let Some(ka) = status.get_known_affected() { + for (x_i, x) in ka.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/product_status/known_affected/{}", v_i, x_i))); } } - if let Some(kna) = status.get_known_not_affected().as_ref() { - for (x_i, x) in kna.iter().enumerate() { + if let Some(kna) = status.get_known_not_affected() { + for (x_i, x) in kna.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/product_status/known_not_affected/{}", v_i, x_i))); } } - if let Some(la) = status.get_last_affected().as_ref() { - for (x_i, x) in la.iter().enumerate() { + if let Some(la) = status.get_last_affected() { + for (x_i, x) in la.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/product_status/last_affected/{}", v_i, x_i))); } } - if let Some(r) = status.get_recommended().as_ref() { - for (x_i, x) in r.iter().enumerate() { + if let Some(r) = status.get_recommended() { + for (x_i, x) in r.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/product_status/recommended/{}", v_i, x_i))); } } - if let Some(ui) = status.get_under_investigation().as_ref() { - for (x_i, x) in ui.iter().enumerate() { + if let Some(ui) = status.get_under_investigation() { + for (x_i, x) in ui.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/product_status/under_investigation/{}", v_i, x_i))); } } @@ -72,8 +72,8 @@ pub fn gather_product_references(doc: &impl CsafTrait) -> Vec<(String, String)> // /vulnerabilities[]/remediations[]/product_ids[] for (rem_i, rem) in v.get_remediations().iter().enumerate() { - if let Some(product_ids) = rem.get_product_ids().as_ref() { - for (x_i, x) in product_ids.iter().enumerate() { + if let Some(product_ids) = rem.get_product_ids() { + for (x_i, x) in product_ids.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/remediations/{}/product_ids/{}", v_i, rem_i, x_i))); } } @@ -82,7 +82,7 @@ pub fn gather_product_references(doc: &impl CsafTrait) -> Vec<(String, String)> // /vulnerabilities[]/metrics[]/products[] if let Some(metrics) = v.get_metrics().as_ref() { for (metric_i, metric) in metrics.iter().enumerate() { - for (x_i, x) in metric.get_products().iter().enumerate() { + for (x_i, x) in metric.get_products().enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/metrics/{}/products/{}", v_i, metric_i, x_i))); } } @@ -90,8 +90,8 @@ pub fn gather_product_references(doc: &impl CsafTrait) -> Vec<(String, String)> // /vulnerabilities[]/threats[]/product_ids[] for (threat_i, threat) in v.get_threats().iter().enumerate() { - if let Some(product_ids) = threat.get_product_ids().as_ref() { - for (x_i, x) in product_ids.iter().enumerate() { + if let Some(product_ids) = threat.get_product_ids() { + for (x_i, x) in product_ids.enumerate() { ids.push(((*x).to_owned(), format!("/vulnerabilities/{}/threats/{}/product_ids/{}", v_i, threat_i, x_i))); } } diff --git a/csaf-lib/src/csaf/validations/mod.rs b/csaf-lib/src/csaf/validations/mod.rs index 19e4f46..c840154 100644 --- a/csaf-lib/src/csaf/validations/mod.rs +++ b/csaf-lib/src/csaf/validations/mod.rs @@ -2,4 +2,5 @@ pub mod test_6_1_01; pub mod test_6_1_02; pub mod test_6_1_34; pub mod test_6_1_35; -pub mod test_6_1_36; \ No newline at end of file +pub mod test_6_1_36; +pub mod test_6_1_37; \ No newline at end of file diff --git a/csaf-lib/src/csaf/validations/test_6_1_37.rs b/csaf-lib/src/csaf/validations/test_6_1_37.rs new file mode 100644 index 0000000..7aaf72e --- /dev/null +++ b/csaf-lib/src/csaf/validations/test_6_1_37.rs @@ -0,0 +1,138 @@ +use crate::csaf::getter_traits::{CsafTrait, DocumentTrait, FlagTrait, GeneratorTrait, InvolvementTrait, RemediationTrait, RevisionTrait, ThreatTrait, TrackingTrait, VulnerabilityTrait}; +use crate::csaf::validation::ValidationError; +use regex::Regex; +use std::sync::OnceLock; + +static RFC3339_REGEX: OnceLock = OnceLock::new(); + +fn get_rfc3339_regex() -> &'static Regex { + RFC3339_REGEX.get_or_init(|| + Regex::new(r"^((\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}(?:\.\d+)?)(Z|[+-]\d{2}:\d{2}))$").unwrap() + ) +} + +/// Validates that all date/time fields in the CSAF document conform to the required format +/// (ISO 8601 format with time zone or UTC). +/// +/// This function checks all date/time fields in the document, including tracking dates, +/// vulnerability disclosure/discovery dates, remediation dates, threat dates, etc. +pub fn test_6_1_37_date_and_time( + doc: &impl CsafTrait, +) -> Result<(), ValidationError> { + let tracking = doc.get_document().get_tracking(); + + // Check initial release date + check_datetime(tracking.get_initial_release_date(), "/document/tracking/initial_release_date")?; + + // Check current release date + check_datetime(tracking.get_current_release_date(), "/document/tracking/current_release_date")?; + + // Check generator date if present + if let Some(generator) = tracking.get_generator() { + if let Some(date) = generator.get_date() { + check_datetime(date, "/document/tracking/generator/date")?; + } + } + + // Check revision history dates if present + for (i_r, revision) in tracking.get_revision_history().iter().enumerate() { + check_datetime( + revision.get_date(), + &format!("/document/tracking/revision_history/{}/date", i_r) + )?; + } + + // Check vulnerability related dates + for (i_v, vuln) in doc.get_vulnerabilities().iter().enumerate() { + // Check disclosure date if present + if let Some(date) = vuln.get_release_date() { + check_datetime(date, &format!("/vulnerabilities/{}/release_date", i_v))?; + } + + // Check discovery date if present + if let Some(date) = vuln.get_discovery_date() { + check_datetime(date, &format!("/vulnerabilities/{}/discovery_date", i_v))?; + } + + // Check flags dates if present + if let Some(flags) = vuln.get_flags() { + for (i_f, flag) in flags.iter().enumerate() { + if let Some(date) = flag.get_date() { + check_datetime(date, &format!("/vulnerabilities/{}/flags/{}/date", i_v, i_f))?; + } + } + } + + // Check involvements dates if present + if let Some(involvements) = vuln.get_involvements() { + for (i_i, involvement) in involvements.iter().enumerate() { + if let Some(date) = involvement.get_date() { + check_datetime( + date, + &format!("/vulnerabilities/{}/involvements/{}/date", i_v, i_i) + )?; + } + } + } + + // Check remediations dates if present + for (i_r, remediation) in vuln.get_remediations().iter().enumerate() { + if let Some(date) = remediation.get_date() { + check_datetime(date, &format!("/vulnerabilities/{}/remediations/{}/date", i_v, i_r))?; + } + } + + // Check threats dates if present + for (i_t, threat) in vuln.get_threats().iter().enumerate() { + if let Some(date) = threat.get_date() { + check_datetime(date, &format!("/vulnerabilities/{}/threats/{}/date", i_v, i_t))?; + } + } + } + + Ok(()) +} + +fn check_datetime(date_time: &String, instance_path: &str) -> Result<(), ValidationError> { + if get_rfc3339_regex().is_match(date_time) { + Ok(()) + } else { + Err(ValidationError { + message: format!("Invalid date-time string {}, expected RFC3339-compliant format with non-empty timezone", date_time), + instance_path: instance_path.to_string(), + }) + } +} + +#[cfg(test)] +mod tests { + use crate::csaf::csaf2_1::loader::load_document; + use crate::csaf::validation::ValidationError; + use crate::csaf::validations::test_6_1_37::test_6_1_37_date_and_time; + + #[test] + fn test_test_6_1_37() { + for x in ["11"].iter() { + let doc = load_document(format!("../csaf/csaf_2.1/test/validator/data/mandatory/oasis_csaf_tc-csaf_2_1-2024-6-1-37-{}.json", x).as_str()).unwrap(); + assert_eq!( + Ok(()), + test_6_1_37_date_and_time(&doc) + ) + } + for (x, err) in [ + ("01", ValidationError { + message: "Invalid date-time string 2024-01-24 10:00:00.000Z, expected RFC3339-compliant format with non-empty timezone".to_string(), + instance_path: "/document/tracking/initial_release_date".to_string() + }), + ].iter() { + let doc_result = load_document(format!("../csaf/csaf_2.1/test/validator/data/mandatory/oasis_csaf_tc-csaf_2_1-2024-6-1-37-{}.json", x).as_str()); + match doc_result { + Ok(doc) => { + let result = test_6_1_37_date_and_time(&doc); + assert_eq!(result, Err(err.clone())); + }, + Err(error) => panic!("Unexpected error: {:?}", error), + } + } + } +}