Coverage for csaf/definitions.py: 71.63%

125 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-18 20:12:48 +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(BaseModel): 

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

343 

344 product_ids: Annotated[ 

345 Sequence[ReferenceTokenForProductInstance], 

346 Field( 

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

348 # min_items=1, 

349 title='List of product_ids', 

350 ), 

351 ] 

352 

353 @classmethod 

354 @no_type_check 

355 @field_validator('product_ids') 

356 def check_len(cls, v): 

357 if not v: 

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

359 return v 

360 

361 

362class Lang( 

363 RootModel[ 

364 Annotated[ 

365 str, 

366 Field( 

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

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

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

370 pattern='^' 

371 '(([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}|' 

372 '[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})+)*' 

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

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

375 title='Language type', 

376 ), 

377 ] 

378 ] 

379): 

380 pass 

381 

382 

383class NoteCategory(Enum): 

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

385 

386 description = 'description' 

387 details = 'details' 

388 faq = 'faq' 

389 general = 'general' 

390 legal_disclaimer = 'legal_disclaimer' 

391 other = 'other' 

392 summary = 'summary' 

393 

394 

395class Note(BaseModel): 

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

397 

398 audience: Annotated[ 

399 Optional[str], 

400 Field( 

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

402 examples=[ 

403 'all', 

404 'executives', 

405 'operational management and system administrators', 

406 'safety engineers', 

407 ], 

408 min_length=1, 

409 title='Audience of note', 

410 ), 

411 ] = None 

412 category: Annotated[ 

413 NoteCategory, 

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

415 ] 

416 text: Annotated[ 

417 str, 

418 Field( 

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

420 min_length=1, 

421 title='Note content', 

422 ), 

423 ] 

424 title: Annotated[ 

425 Optional[str], 

426 Field( 

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

428 examples=[ 

429 'Details', 

430 'Executive summary', 

431 'Technical summary', 

432 'Impact on safety systems', 

433 ], 

434 min_length=1, 

435 title='Title of note', 

436 ), 

437 ] = None 

438 

439 

440class Notes( 

441 RootModel[ 

442 Annotated[ 

443 List[Note], 

444 Field( 

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

446 min_length=1, 

447 title='List of notes', 

448 ), 

449 ] 

450 ] 

451): 

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

453 

454 @classmethod 

455 @no_type_check 

456 @model_validator(mode='before') 

457 def check_len(cls, v): 

458 if not v: 

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

460 return v 

461 

462 

463class ReferenceCategory(Enum): 

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

465 to an external resource.""" 

466 

467 external = 'external' 

468 self = 'self' 

469 

470 

471class Reference(BaseModel): 

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

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

474 

475 category: Annotated[ 

476 Optional[ReferenceCategory], 

477 Field( 

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

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

480 title='Category of reference', 

481 ), 

482 ] = ReferenceCategory.external 

483 summary: Annotated[ 

484 str, 

485 Field( 

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

487 min_length=1, 

488 title='Summary of the reference', 

489 ), 

490 ] 

491 url: Annotated[ 

492 AnyUrl, 

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

494 ] 

495 

496 

497class References( 

498 RootModel[ 

499 Annotated[ 

500 List[Reference], 

501 Field( 

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

503 min_length=1, 

504 title='List of references', 

505 ), 

506 ] 

507 ] 

508): 

509 """Holds a list of references.""" 

510 

511 @classmethod 

512 @no_type_check 

513 @model_validator(mode='before') 

514 def check_len(cls, v): 

515 if not v: 

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

517 return v 

518 

519 

520class Version( 

521 RootModel[ 

522 Annotated[ 

523 str, 

524 Field( 

525 description=( 

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

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

528 ), 

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

530 pattern=( 

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

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

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

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

535 ), 

536 title='Version', 

537 ), 

538 ] 

539 ] 

540): 

541 pass