Coverage for csaf/definitions.py: 71.43%

124 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-01 23:19:54 +00:00

1"""CSAF Document general definitions.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Sequence 

6from datetime import datetime 

7from enum import Enum 

8from typing import Annotated, List, Optional, no_type_check 

9 

10from pydantic import AnyUrl, BaseModel, Field, RootModel, field_validator, model_validator 

11 

12 

13class FlagCategory(Enum): 

14 """ 

15 Defines the category of the machine readable label for flags. 

16 """ 

17 

18 component_not_present = 'component_not_present' 

19 inline_mitigations_already_exist = 'inline_mitigations_already_exist' 

20 vulnerable_code_cannot_be_controlled_by_adversary = 'vulnerable_code_cannot_be_controlled_by_adversary' 

21 vulnerable_code_not_in_execute_path = 'vulnerable_code_not_in_execute_path' 

22 vulnerable_code_not_present = 'vulnerable_code_not_present' 

23 

24 

25class Flag(BaseModel): 

26 """Contains product specific information in regard to this vulnerability as a single machine readable flag.""" 

27 

28 date: Annotated[ 

29 Optional[datetime], 

30 Field( 

31 description='Contains the date when assessment was done or the flag was assigned.', 

32 title='Date of the flag', 

33 ), 

34 ] = None 

35 

36 group_ids: Annotated[ 

37 Optional[ProductGroupIds], 

38 Field( 

39 description='Specifies a list of product_group_ids to give context to the parent item.', 

40 title='List of product_group_ids', 

41 ), 

42 ] = None 

43 

44 label: Annotated[ 

45 FlagCategory, 

46 Field( 

47 description='Specifies the machine readable label.', 

48 title='Label of the flag', 

49 ), 

50 ] 

51 product_ids: Annotated[ 

52 Optional[ListOfProductIds], 

53 Field( 

54 description='Specifies a list of product_ids to give context to the parent item.', 

55 title='List of product_ids', 

56 ), 

57 ] = None 

58 

59 

60class Flags( 

61 RootModel[ 

62 Annotated[ 

63 List[Flag], 

64 Field( 

65 description='Contains a list of machine readable flags.', 

66 min_length=1, 

67 # unique_items=True, 

68 title='List of flags', 

69 ), 

70 ] 

71 ] 

72): 

73 """Represents a list of unique labels or tracking IDs for the vulnerability (if such information exists).""" 

74 

75 @classmethod 

76 @no_type_check 

77 @model_validator(mode='before') 

78 def check_len(cls, v): 

79 if not v: 

80 raise ValueError('optional element present but empty') 

81 return v 

82 

83 

84class Id(BaseModel): 

85 """Contains a single unique label or tracking ID for the vulnerability.""" 

86 

87 system_name: Annotated[ 

88 str, 

89 Field( 

90 description='Indicates the name of the vulnerability tracking or numbering system.', 

91 examples=['Cisco Bug ID', 'GitHub Issue'], 

92 min_length=1, 

93 title='System name', 

94 ), 

95 ] 

96 text: Annotated[ 

97 str, 

98 Field( 

99 description='Is unique label or tracking ID for the vulnerability (if such information exists).', 

100 examples=['CSCso66472', 'oasis-tcs/csaf#210'], 

101 min_length=1, 

102 title='Text', 

103 ), 

104 ] 

105 

106 

107class Ids( 

108 RootModel[ 

109 Annotated[ 

110 List[Id], 

111 Field( 

112 description=( 

113 'Represents a list of unique labels or tracking IDs for the vulnerability' 

114 ' (if such information exists).' 

115 ), 

116 min_length=1, 

117 title='List of IDs', 

118 ), 

119 ] 

120 ] 

121): 

122 """Represents a list of unique labels or tracking IDs for the vulnerability (if such information exists).""" 

123 

124 @classmethod 

125 @no_type_check 

126 @model_validator(mode='before') 

127 def check_len(cls, v): 

128 if not v: 

129 raise ValueError('optional element present but empty') 

130 return v 

131 

132 

133class Name( 

134 RootModel[ 

135 Annotated[ 

136 str, 

137 Field( 

138 description='Contains the name of a single contributor being recognized.', 

139 examples=['Albert Einstein', 'Johann Sebastian Bach'], 

140 min_length=1, 

141 title='Name of the contributor', 

142 ), 

143 ] 

144 ] 

145): 

146 pass 

147 

148 

149class Acknowledgment(BaseModel): 

150 """ 

151 Acknowledges contributions by describing those that contributed. 

152 """ 

153 

154 names: Annotated[ 

155 Optional[List[Name]], 

156 Field( 

157 description='Contains the names of contributors being recognized.', 

158 min_length=1, 

159 title='List of acknowledged names', 

160 ), 

161 ] = None 

162 organization: Annotated[ 

163 Optional[str], 

164 Field( 

165 description='Contains the name of a contributing organization being recognized.', 

166 examples=['CISA', 'Google Project Zero', 'Talos'], 

167 min_length=1, 

168 title='Contributing organization', 

169 ), 

170 ] = None 

171 summary: Annotated[ 

172 Optional[str], 

173 Field( 

174 description='SHOULD represent any contextual details the document producers wish to make known about the' 

175 ' acknowledgment or acknowledged parties.', 

176 examples=['First analysis of Coordinated Multi-Stream Attack (CMSA)'], 

177 min_length=1, 

178 title='Summary of the acknowledgment', 

179 ), 

180 ] = None 

181 urls: Annotated[ 

182 Optional[List[AnyUrl]], 

183 Field( 

184 description='Specifies a list of URLs or location of the reference to be acknowledged.', 

185 min_length=1, 

186 title='List of URLs', 

187 ), 

188 ] = None 

189 

190 @classmethod 

191 @no_type_check 

192 @field_validator('names', 'organization', 'summary', 'urls') 

193 def check_len(cls, v): 

194 if not v: 

195 raise ValueError('optional element present but empty') 

196 return v 

197 

198 

199class Acknowledgments( 

200 RootModel[ 

201 Annotated[ 

202 List[Acknowledgment], 

203 Field( 

204 description='Contains a list of acknowledgment elements.', 

205 min_length=1, 

206 title='List of acknowledgments', 

207 ), 

208 ] 

209 ] 

210): 

211 """Contains a list of acknowledgment elements.""" 

212 

213 @classmethod 

214 @no_type_check 

215 @model_validator(mode='before') 

216 def check_len(cls, v): 

217 if not v: 

218 raise ValueError('optional element present but empty') 

219 return v 

220 

221 

222class ReferenceTokenForProductGroupInstance( 

223 RootModel[ 

224 Annotated[ 

225 str, 

226 Field( 

227 description=( 

228 'Token required to identify a group of products so that it can be referred to from' 

229 ' other parts in the document.' 

230 ' There is no predefined or required format for the product_group_id' 

231 ' as long as it uniquely identifies a group in the context of the current document.' 

232 ), 

233 examples=['CSAFGID-0001', 'CSAFGID-0002', 'CSAFGID-0020'], 

234 min_length=1, 

235 title='Reference token for product group instance', 

236 ), 

237 ] 

238 ] 

239): 

240 pass 

241 

242 

243class ProductGroupId( 

244 RootModel[ 

245 Annotated[ 

246 str, 

247 Field( 

248 description='Token required to identify a group of products so that it can be referred to from other' 

249 ' parts in the document. There is no predefined or required format for the product_group_id' 

250 ' as long as it uniquely identifies a group in the context of the current document.', 

251 examples=['CSAFGID-0001', 'CSAFGID-0002', 'CSAFGID-0020'], 

252 min_length=1, 

253 title='Reference token for product group instance', 

254 ), 

255 ] 

256 ] 

257): 

258 pass 

259 

260 

261class ProductGroupIds( 

262 RootModel[ 

263 Annotated[ 

264 List[ProductGroupId], 

265 Field( 

266 description='Specifies a list of product_group_ids to give context to the parent item.', 

267 min_length=1, 

268 title='List of product_group_ids', 

269 ), 

270 ] 

271 ] 

272): 

273 """Specifies a list of product_group_ids to give context to the parent item.""" 

274 

275 @classmethod 

276 @no_type_check 

277 @model_validator(mode='before') 

278 def check_len(cls, v): 

279 if not v: 

280 raise ValueError('mandatory element present but empty') 

281 return v 

282 

283 

284class ProductId( 

285 RootModel[ 

286 Annotated[ 

287 str, 

288 Field( 

289 description='Token required to identify a full_product_name so that it can be referred to from' 

290 ' other parts in the document.' 

291 ' There is no predefined or required format for the product_id as long as it' 

292 ' uniquely identifies a product in the context of the current document.', 

293 examples=['CSAFPID-0004', 'CSAFPID-0008'], 

294 min_length=1, 

295 title='Reference token for product instance', 

296 ), 

297 ] 

298 ] 

299): 

300 pass 

301 

302 

303class Products( 

304 RootModel[ 

305 Annotated[ 

306 List[ProductId], 

307 Field( 

308 description='Specifies a list of product_ids to give context to the parent item.', 

309 min_length=1, 

310 title='List of product_ids', 

311 ), 

312 ] 

313 ] 

314): 

315 """Specifies a list of product_ids to give context to the parent item.""" 

316 

317 pass 

318 

319 

320class ReferenceTokenForProductInstance( 

321 RootModel[ 

322 Annotated[ 

323 str, 

324 Field( 

325 description=( 

326 'Token required to identify a full_product_name so that it can be referred to from other' 

327 ' parts in the document.' 

328 ' There is no predefined or required format for the product_id as long as it uniquely' 

329 ' identifies a product in the context of the current document.' 

330 ), 

331 examples=['CSAFPID-0004', 'CSAFPID-0008'], 

332 min_length=1, 

333 title='Reference token for product instance', 

334 ), 

335 ] 

336 ] 

337): 

338 pass 

339 

340 

341class ListOfProductIds( 

342 RootModel[ 

343 Annotated[ 

344 Sequence[ReferenceTokenForProductInstance], 

345 Field( 

346 description=('Specifies a list of product_ids to give context to the parent item.'), 

347 min_length=1, 

348 title='List of product_ids', 

349 ), 

350 ] 

351 ] 

352): 

353 """Specifies a list of product_ids to give context to the parent item.""" 

354 

355 @classmethod 

356 @no_type_check 

357 @model_validator(mode='before') 

358 def check_len(cls, v): 

359 if not v: 

360 raise ValueError('mandatory element present but empty') 

361 return v 

362 

363 

364class Lang( 

365 RootModel[ 

366 Annotated[ 

367 str, 

368 Field( 

369 description='Identifies a language, corresponding to IETF BCP 47 / RFC 5646. See IETF language' 

370 ' registry: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry', 

371 examples=['de', 'en', 'fr', 'frc', 'jp'], 

372 pattern='^' 

373 '(([A-Za-z]{2,3}(-[A-Za-z]{3}(-[A-Za-z]{3}){0,2})?|[A-Za-z]{4,8})(-[A-Za-z]{4})?(-([A-Za-z]{2}|' 

374 '[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-[A-WY-Za-wy-z0-9](-[A-Za-z0-9]{2,8})+)*' 

375 '(-[Xx](-[A-Za-z0-9]{1,8})+)?|[Xx](-[A-Za-z0-9]{1,8})+|[Ii]-[Dd][Ee][Ff][Aa][Uu][Ll][Tt]|' 

376 '[Ii]-[Mm][Ii][Nn][Gg][Oo])$', 

377 title='Language type', 

378 ), 

379 ] 

380 ] 

381): 

382 pass 

383 

384 

385class NoteCategory(Enum): 

386 """Choice of what kind of note this is.""" 

387 

388 description = 'description' 

389 details = 'details' 

390 faq = 'faq' 

391 general = 'general' 

392 legal_disclaimer = 'legal_disclaimer' 

393 other = 'other' 

394 summary = 'summary' 

395 

396 

397class Note(BaseModel): 

398 """Is a place to put all manner of text blobs related to the current context.""" 

399 

400 audience: Annotated[ 

401 Optional[str], 

402 Field( 

403 description='Indicates who is intended to read it.', 

404 examples=[ 

405 'all', 

406 'executives', 

407 'operational management and system administrators', 

408 'safety engineers', 

409 ], 

410 min_length=1, 

411 title='Audience of note', 

412 ), 

413 ] = None 

414 category: Annotated[ 

415 NoteCategory, 

416 Field(description='Contains the information of what kind of note this is.', title='Note category'), 

417 ] 

418 text: Annotated[ 

419 str, 

420 Field( 

421 description='Holds the content of the note. Content varies depending on type.', 

422 min_length=1, 

423 title='Note content', 

424 ), 

425 ] 

426 title: Annotated[ 

427 Optional[str], 

428 Field( 

429 description='Provides a concise description of what is contained in the text of the note.', 

430 examples=[ 

431 'Details', 

432 'Executive summary', 

433 'Technical summary', 

434 'Impact on safety systems', 

435 ], 

436 min_length=1, 

437 title='Title of note', 

438 ), 

439 ] = None 

440 

441 

442class Notes( 

443 RootModel[ 

444 Annotated[ 

445 List[Note], 

446 Field( 

447 description='Contains notes which are specific to the current context.', 

448 min_length=1, 

449 title='List of notes', 

450 ), 

451 ] 

452 ] 

453): 

454 """Contains notes which are specific to the current context.""" 

455 

456 @classmethod 

457 @no_type_check 

458 @model_validator(mode='before') 

459 def check_len(cls, v): 

460 if not v: 

461 raise ValueError('mandatory element present but empty') 

462 return v 

463 

464 

465class ReferenceCategory(Enum): 

466 """Indicates whether the reference points to the same document or vulnerability in focus (depending on scope) or 

467 to an external resource.""" 

468 

469 external = 'external' 

470 self = 'self' 

471 

472 

473class Reference(BaseModel): 

474 """Holds any reference to conferences, papers, advisories, and other resources that are related and considered 

475 related to either a surrounding part of or the entire document and to be of value to the document consumer.""" 

476 

477 category: Annotated[ 

478 Optional[ReferenceCategory], 

479 Field( 

480 description='Indicates whether the reference points to the same document or vulnerability in focus' 

481 ' (depending on scope) or to an external resource.', 

482 title='Category of reference', 

483 ), 

484 ] = ReferenceCategory.external 

485 summary: Annotated[ 

486 str, 

487 Field( 

488 description='Indicates what this reference refers to.', 

489 min_length=1, 

490 title='Summary of the reference', 

491 ), 

492 ] 

493 url: Annotated[ 

494 AnyUrl, 

495 Field(description='Provides the URL for the reference.', title='URL of reference'), 

496 ] 

497 

498 

499class References( 

500 RootModel[ 

501 Annotated[ 

502 List[Reference], 

503 Field( 

504 description='Holds a list of references.', 

505 min_length=1, 

506 title='List of references', 

507 ), 

508 ] 

509 ] 

510): 

511 """Holds a list of references.""" 

512 

513 @classmethod 

514 @no_type_check 

515 @model_validator(mode='before') 

516 def check_len(cls, v): 

517 if not v: 

518 raise ValueError('mandatory element present but empty') 

519 return v 

520 

521 

522class Version( 

523 RootModel[ 

524 Annotated[ 

525 str, 

526 Field( 

527 description=( 

528 'Specifies a version string to denote clearly the evolution of the content of the document.' 

529 ' Format must be either integer or semantic versioning.' 

530 ), 

531 examples=['1', '4', '0.9.0', '1.4.3', '2.40.0+21AF26D3'], 

532 pattern=( 

533 '^(0|[1-9][0-9]*)$|^((0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)' 

534 '(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)' 

535 '(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?' 

536 '(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)$' 

537 ), 

538 title='Version', 

539 ), 

540 ] 

541 ] 

542): 

543 pass