Coverage for csaf/definitions.py: 80.89%

97 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-04 16:28:45 +00:00

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

2 

3from __future__ import annotations 

4 

5from collections.abc import Sequence 

6from enum import Enum 

7from typing import Annotated, List, Optional, no_type_check 

8 

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

10 

11 

12class Id(BaseModel): 

13 """ 

14 Gives the document producer a place to publish a unique label or tracking ID for the vulnerability 

15 (if such information exists). 

16 """ 

17 

18 system_name: Annotated[ 

19 str, 

20 Field( 

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

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

23 min_length=1, 

24 title='System name', 

25 ), 

26 ] 

27 text: Annotated[ 

28 str, 

29 Field( 

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

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

32 min_length=1, 

33 title='Text', 

34 ), 

35 ] 

36 

37 

38class Name( 

39 RootModel[ 

40 Annotated[ 

41 str, 

42 Field( 

43 description='Contains the name of a single person.', 

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

45 min_length=1, 

46 title='Name of entity being recognized', 

47 ), 

48 ] 

49 ] 

50): 

51 pass 

52 

53 

54class Acknowledgment(BaseModel): 

55 """ 

56 Acknowledges contributions by describing those that contributed. 

57 """ 

58 

59 names: Annotated[ 

60 Optional[List[Name]], 

61 Field( 

62 description='Contains the names of entities being recognized.', 

63 min_length=1, 

64 title='List of acknowledged names', 

65 ), 

66 ] = None 

67 organization: Annotated[ 

68 Optional[str], 

69 Field( 

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

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

72 min_length=1, 

73 title='Contributing organization', 

74 ), 

75 ] = None 

76 summary: Annotated[ 

77 Optional[str], 

78 Field( 

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

80 ' acknowledgment or acknowledged parties.', 

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

82 min_length=1, 

83 title='Summary of the acknowledgment', 

84 ), 

85 ] = None 

86 urls: Annotated[ 

87 Optional[List[AnyUrl]], 

88 Field( 

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

90 min_length=1, 

91 title='List of URLs', 

92 ), 

93 ] = None 

94 

95 @classmethod 

96 @no_type_check 

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

98 def check_len(cls, v): 

99 if not v: 

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

101 return v 

102 

103 

104class Acknowledgments( 

105 RootModel[ 

106 Annotated[ 

107 List[Acknowledgment], 

108 Field( 

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

110 min_length=1, 

111 title='List of acknowledgments', 

112 ), 

113 ] 

114 ] 

115): 

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

117 

118 @classmethod 

119 @no_type_check 

120 @model_validator(mode='before') 

121 def check_len(cls, v): 

122 if not v: 

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

124 return v 

125 

126 

127class ReferenceTokenForProductGroupInstance( 

128 RootModel[ 

129 Annotated[ 

130 str, 

131 Field( 

132 description=( 

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

134 ' other parts in the document.' 

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

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

137 ), 

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

139 min_length=1, 

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

141 ), 

142 ] 

143 ] 

144): 

145 pass 

146 

147 

148class ProductGroupId( 

149 RootModel[ 

150 Annotated[ 

151 str, 

152 Field( 

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

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

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

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

157 min_length=1, 

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

159 ), 

160 ] 

161 ] 

162): 

163 pass 

164 

165 

166class ProductGroupIds( 

167 RootModel[ 

168 Annotated[ 

169 List[ProductGroupId], 

170 Field( 

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

172 min_length=1, 

173 title='List of product_group_ids', 

174 ), 

175 ] 

176 ] 

177): 

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

179 

180 @classmethod 

181 @no_type_check 

182 @model_validator(mode='before') 

183 def check_len(cls, v): 

184 if not v: 

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

186 return v 

187 

188 

189class ProductId( 

190 RootModel[ 

191 Annotated[ 

192 str, 

193 Field( 

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

195 ' other parts in the document.' 

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

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

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

199 min_length=1, 

200 title='Reference token for product instance', 

201 ), 

202 ] 

203 ] 

204): 

205 pass 

206 

207 

208class Products( 

209 RootModel[ 

210 Annotated[ 

211 List[ProductId], 

212 Field( 

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

214 min_length=1, 

215 title='List of product_ids', 

216 ), 

217 ] 

218 ] 

219): 

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

221 

222 pass 

223 

224 

225class ReferenceTokenForProductInstance( 

226 RootModel[ 

227 Annotated[ 

228 str, 

229 Field( 

230 description=( 

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

232 ' parts in the document.' 

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

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

235 ), 

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

237 min_length=1, 

238 title='Reference token for product instance', 

239 ), 

240 ] 

241 ] 

242): 

243 pass 

244 

245 

246class ListOfProductIds(BaseModel): 

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

248 

249 product_ids: Annotated[ 

250 Sequence[ReferenceTokenForProductInstance], 

251 Field( 

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

253 # min_items=1, 

254 title='List of product_ids', 

255 ), 

256 ] 

257 

258 @classmethod 

259 @no_type_check 

260 @field_validator('product_ids') 

261 def check_len(cls, v): 

262 if not v: 

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

264 return v 

265 

266 

267class Lang( 

268 RootModel[ 

269 Annotated[ 

270 str, 

271 Field( 

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

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

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

275 pattern='^' 

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

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

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

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

280 title='Language type', 

281 ), 

282 ] 

283 ] 

284): 

285 pass 

286 

287 

288class NoteCategory(Enum): 

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

290 

291 description = 'description' 

292 details = 'details' 

293 faq = 'faq' 

294 general = 'general' 

295 legal_disclaimer = 'legal_disclaimer' 

296 other = 'other' 

297 summary = 'summary' 

298 

299 

300class Note(BaseModel): 

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

302 

303 audience: Annotated[ 

304 Optional[str], 

305 Field( 

306 description='Indicate who is intended to read it.', 

307 examples=[ 

308 'all', 

309 'executives', 

310 'operational management and system administrators', 

311 'safety engineers', 

312 ], 

313 min_length=1, 

314 title='Audience of note', 

315 ), 

316 ] = None 

317 category: Annotated[ 

318 NoteCategory, 

319 Field(description='Choice of what kind of note this is.', title='Note category'), 

320 ] 

321 text: Annotated[ 

322 str, 

323 Field( 

324 description='The contents of the note. Content varies depending on type.', 

325 min_length=1, 

326 title='Note contents', 

327 ), 

328 ] 

329 title: Annotated[ 

330 Optional[str], 

331 Field( 

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

333 examples=[ 

334 'Details', 

335 'Executive summary', 

336 'Technical summary', 

337 'Impact on safety systems', 

338 ], 

339 min_length=1, 

340 title='Title of note', 

341 ), 

342 ] = None 

343 

344 

345class Notes( 

346 RootModel[ 

347 Annotated[ 

348 List[Note], 

349 Field( 

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

351 min_length=1, 

352 title='List of notes', 

353 ), 

354 ] 

355 ] 

356): 

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

358 

359 @classmethod 

360 @no_type_check 

361 @model_validator(mode='before') 

362 def check_len(cls, v): 

363 if not v: 

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

365 return v 

366 

367 

368class ReferenceCategory(Enum): 

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

370 to an external resource.""" 

371 

372 external = 'external' 

373 self = 'self' 

374 

375 

376class Reference(BaseModel): 

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

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

379 

380 category: Annotated[ 

381 Optional[ReferenceCategory], 

382 Field( 

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

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

385 title='Category of reference', 

386 ), 

387 ] = ReferenceCategory.external 

388 summary: Annotated[ 

389 str, 

390 Field( 

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

392 min_length=1, 

393 title='Summary of the reference', 

394 ), 

395 ] 

396 url: Annotated[ 

397 AnyUrl, 

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

399 ] 

400 

401 

402class References( 

403 RootModel[ 

404 Annotated[ 

405 List[Reference], 

406 Field( 

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

408 min_length=1, 

409 title='List of references', 

410 ), 

411 ] 

412 ] 

413): 

414 """Holds a list of references.""" 

415 

416 @classmethod 

417 @no_type_check 

418 @model_validator(mode='before') 

419 def check_len(cls, v): 

420 if not v: 

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

422 return v 

423 

424 

425class Version( 

426 RootModel[ 

427 Annotated[ 

428 str, 

429 Field( 

430 description=( 

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

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

433 ), 

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

435 pattern=( 

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

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

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

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

440 ), 

441 title='Version', 

442 ), 

443 ] 

444 ] 

445): 

446 pass