1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
use crate::csaf::types::csaf_datetime::CsafDateTime::{Invalid, Valid};
use crate::csaf::types::csaf_datetime::ValidCsafDateTime;
use crate::csaf_traits::{CsafTrait, InvolvementTrait, VulnerabilityTrait, WithOptionalDate};
use crate::schema::csaf2_1::schema::PartyCategory;
use crate::validation::{IntoValidationError, ValidationError};
use std::collections::HashMap;
fn generate_duplicate_involvement_error(
date: &Option<ValidCsafDateTime>,
party: &PartyCategory,
vul_r: usize,
inv_r: usize,
) -> ValidationError {
let date_str = date
.as_ref()
.map_or("none (optional property not present)".to_string(), |d| d.to_string());
ValidationError {
message: format!("Duplicate usage of tuple of involvement date '{date_str}' and party '{party}'"),
instance_path: format!("/vulnerabilities/{vul_r}/involvements/{inv_r}"),
}
}
/// Test 6.1.24: Multiple Definition in Involvements
///
/// Vulnerability items must not contain the same tuples of the `/vulnerabilities[]/involvements[]/date`
/// and `/vulnerabilities[]/involvements[]/party` fields.
pub fn test_6_1_24_multiple_definition_in_involvements(doc: &impl CsafTrait) -> Result<(), Vec<ValidationError>> {
let vulnerabilities = doc.get_vulnerabilities();
// Check if there are any vulnerabilities, if there aren't, this test can be skipped
if vulnerabilities.is_empty() {
// This will be WasSkipped later (#409)
return Ok(());
}
let mut errors: Option<Vec<ValidationError>> = None;
// Iterate over all vulnerabilities and their involvements
for (vuln_i, vulnerability) in vulnerabilities.iter().enumerate() {
if let Some(involvements) = vulnerability.get_involvements() {
// Map involvement path indices to (date, party) tuples
// HashMap key is a tuple of Option<ValidCsafDateTime>, with None being used if the optional date is not present,
// and the PartyCategory enum, value is a vector of involvement indices
type DatePartyPathsMap = HashMap<(Option<ValidCsafDateTime>, PartyCategory), Vec<usize>>;
let mut date_party_paths_map: Option<DatePartyPathsMap> = None;
for (inv_i, involvement) in involvements.iter().enumerate() {
// if the involvement does have a date, check if it's valid
let date = match involvement.get_date() {
// If the date is invalid, generate an error and skip this involvement (this will be a non-determinable later, #409)
// TODO: #409 this will need to be handled differently between CSAF 2.0 and 2.1, as 2.1 provides this check already in 6.1.37,
// while for 2.0, we will need to do that precondition check here (and throw the respective errors, while for 2.1 warnings should be sufficient)
Some(Invalid(err)) => {
errors.get_or_insert_default().push(
err.into_validation_error(&format!("/vulnerabilities/{vuln_i}/involvements/{inv_i}/date")),
);
continue;
},
// If the date is valid, use the parsed date as hash key
Some(Valid(date)) => Some(date),
// If the date is not present, use None as hash key
None => None,
};
let party = involvement.get_party();
let paths = date_party_paths_map
.get_or_insert_default()
.entry((date, party))
.or_default();
paths.push(inv_i);
}
// If there were any non-skipped involvements
if let Some(date_party_paths_map) = date_party_paths_map {
// Generate errors for (date, party) tuples with multiple involvement paths indices
for ((date, party), paths) in &date_party_paths_map {
if paths.len() > 1 {
errors.get_or_insert_default().extend(
paths
.iter()
.map(|path| generate_duplicate_involvement_error(date, party, vuln_i, *path)),
);
}
}
}
}
}
errors.map_or(Ok(()), Err)
}
crate::test_validation::impl_validator!(ValidatorForTest6_1_24, test_6_1_24_multiple_definition_in_involvements);
#[cfg(test)]
mod tests {
use super::*;
use crate::csaf::types::csaf_datetime::CsafDateTime::{self, Invalid};
use crate::csaf2_0::testcases::TESTS_2_0;
use crate::csaf2_1::testcases::TESTS_2_1;
use std::str::FromStr;
#[test]
fn test_test_6_1_24() {
// Case 01: One vulnerability, two involvements, same date, same party, different status
// Case 02: One vulnerability, two involvements, same date, same party, same status
// Case 11: Two vulnerabilities, one involvement each, same date, same party, different status
// Case 12: Two vulnerabilities, one involvement each, same date, same party, same status
// Case S01: One vulnerability, two involvements without date, same party, different status
// Case S02: One vulnerability, one involvement, date is malformed
// Case S03: One Vulnerability, three involvements, two with same date, party, different status
// Case S04: One Vulnerability, 4 involvements, 2 pairwise same date, party, different status
// Case S11: One Vulnerability, Two involvements, same date, different party
// Case S12: One Vulnerability, Two involvements, different date, same party
// Case S13: One Vulnerability, No involvements
// TODO: I left out the coverage of status having no influence here.
// For CSAF 2.0 and 2.1, different errors are generated, as they have different "default" dates
// Shared values for the test cases
let default_date_csaf_20 = Some(ValidCsafDateTime::from_str("2021-04-23T10:00:00.000Z").unwrap());
let alternate_date_csaf_20 = Some(ValidCsafDateTime::from_str("2021-04-24T10:00:00.000Z").unwrap());
let default_date_csaf_21 = Some(ValidCsafDateTime::from_str("2023-08-23T10:00:00.000Z").unwrap());
let alternate_date_csaf_21 = Some(ValidCsafDateTime::from_str("2023-08-24T10:00:00.000Z").unwrap());
let vendor = PartyCategory::Vendor;
let discoverer = PartyCategory::Discoverer;
// Date-independent test cases
let case_s01 = Err(vec![
generate_duplicate_involvement_error(&None, &vendor, 0, 0),
generate_duplicate_involvement_error(&None, &vendor, 0, 1),
]);
let Invalid(case_s02_err) = CsafDateTime::from("not-a-valid-date") else {
unreachable!()
};
let case_s02 = Err(vec![
case_s02_err.into_validation_error("/vulnerabilities/0/involvements/0/date"),
]);
TESTS_2_0.test_6_1_24.expect(
// case_01
Err(vec![
generate_duplicate_involvement_error(&default_date_csaf_20, &vendor, 0, 0),
generate_duplicate_involvement_error(&default_date_csaf_20, &vendor, 0, 1),
]),
// case_02
Err(vec![
generate_duplicate_involvement_error(&default_date_csaf_20, &vendor, 0, 0),
generate_duplicate_involvement_error(&default_date_csaf_20, &vendor, 0, 1),
]),
case_s01.clone(),
case_s02.clone(),
// case_s03
Err(vec![
generate_duplicate_involvement_error(&alternate_date_csaf_20, &vendor, 0, 0),
generate_duplicate_involvement_error(&alternate_date_csaf_20, &vendor, 0, 1),
]),
// case_s04
Err(vec![
generate_duplicate_involvement_error(&default_date_csaf_20, &discoverer, 0, 1),
generate_duplicate_involvement_error(&default_date_csaf_20, &discoverer, 0, 3),
generate_duplicate_involvement_error(&alternate_date_csaf_20, &vendor, 0, 0),
generate_duplicate_involvement_error(&alternate_date_csaf_20, &vendor, 0, 2),
]),
Ok(()), // case_11
Ok(()), // case_12
Ok(()), // case_s11
Ok(()), // case_s12
Ok(()), // case_s13
);
TESTS_2_1.test_6_1_24.expect(
// case_01
Err(vec![
generate_duplicate_involvement_error(&default_date_csaf_21, &vendor, 0, 0),
generate_duplicate_involvement_error(&default_date_csaf_21, &vendor, 0, 1),
]),
// case_02
Err(vec![
generate_duplicate_involvement_error(&default_date_csaf_21, &vendor, 0, 0),
generate_duplicate_involvement_error(&default_date_csaf_21, &vendor, 0, 1),
]),
case_s01,
case_s02,
// case_s03
Err(vec![
generate_duplicate_involvement_error(&alternate_date_csaf_21, &vendor, 0, 0),
generate_duplicate_involvement_error(&alternate_date_csaf_21, &vendor, 0, 1),
]),
// case_s04
Err(vec![
generate_duplicate_involvement_error(&default_date_csaf_21, &discoverer, 0, 1),
generate_duplicate_involvement_error(&default_date_csaf_21, &discoverer, 0, 3),
generate_duplicate_involvement_error(&alternate_date_csaf_21, &vendor, 0, 0),
generate_duplicate_involvement_error(&alternate_date_csaf_21, &vendor, 0, 2),
]),
Ok(()), // case_11
Ok(()), // case_12
Ok(()), // case_s11
Ok(()), // case_s12
Ok(()), // case_s13
);
}
}