Coverage for turvallisuusneuvonta/csaf/definitions.py: 82.27%

91 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-05 19:27:17 +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 ] 

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 """ 

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

249 """ 

250 

251 product_ids: Annotated[ 

252 Sequence[ReferenceTokenForProductInstance], 

253 Field( 

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

255 # min_length=1, 

256 title='List of product_ids', 

257 ), 

258 ] 

259 

260 @classmethod 

261 @no_type_check 

262 @field_validator('product_ids') 

263 def check_len(cls, v): 

264 if not v: 

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

266 return v 

267 

268 

269class Lang( 

270 RootModel[ 

271 Annotated[ 

272 str, 

273 Field( 

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

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

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

277 pattern='^' 

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

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

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

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

282 title='Language type', 

283 ), 

284 ] 

285 ] 

286): 

287 pass 

288 

289 

290class NoteCategory(Enum): 

291 """ 

292 Choice of what kind of note this is. 

293 """ 

294 

295 description = 'description' 

296 details = 'details' 

297 faq = 'faq' 

298 general = 'general' 

299 legal_disclaimer = 'legal_disclaimer' 

300 other = 'other' 

301 summary = 'summary' 

302 

303 

304class Note(BaseModel): 

305 """ 

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

307 """ 

308 

309 audience: Annotated[ 

310 Optional[str], 

311 Field( 

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

313 examples=[ 

314 'all', 

315 'executives', 

316 'operational management and system administrators', 

317 'safety engineers', 

318 ], 

319 min_length=1, 

320 title='Audience of note', 

321 ), 

322 ] = None 

323 category: Annotated[ 

324 NoteCategory, 

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

326 ] 

327 text: Annotated[ 

328 str, 

329 Field( 

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

331 min_length=1, 

332 title='Note contents', 

333 ), 

334 ] 

335 title: Annotated[ 

336 Optional[str], 

337 Field( 

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

339 examples=[ 

340 'Details', 

341 'Executive summary', 

342 'Technical summary', 

343 'Impact on safety systems', 

344 ], 

345 min_length=1, 

346 title='Title of note', 

347 ), 

348 ] = None 

349 

350 

351class Notes( 

352 RootModel[ 

353 Annotated[ 

354 List[Note], 

355 Field( 

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

357 min_length=1, 

358 title='List of notes', 

359 ), 

360 ] 

361 ] 

362): 

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

364 

365 @classmethod 

366 @no_type_check 

367 @model_validator(mode='before') 

368 def check_len(cls, v): 

369 if not v: 

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

371 return v 

372 

373 

374class ReferenceCategory(Enum): 

375 """ 

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

377 to an external resource. 

378 """ 

379 

380 external = 'external' 

381 self = 'self' 

382 

383 

384class Reference(BaseModel): 

385 """ 

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

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

388 """ 

389 

390 category: Annotated[ 

391 Optional[ReferenceCategory], 

392 Field( 

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

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

395 title='Category of reference', 

396 ), 

397 ] = ReferenceCategory.external 

398 summary: Annotated[ 

399 str, 

400 Field( 

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

402 min_length=1, 

403 title='Summary of the reference', 

404 ), 

405 ] 

406 url: Annotated[ 

407 AnyUrl, 

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

409 ] 

410 

411 

412class References( 

413 RootModel[ 

414 Annotated[ 

415 List[Reference], 

416 Field( 

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

418 min_length=1, 

419 title='List of references', 

420 ), 

421 ] 

422 ] 

423): 

424 """Holds a list of references.""" 

425 

426 pass 

427 

428 

429class Version( 

430 RootModel[ 

431 Annotated[ 

432 str, 

433 Field( 

434 description=( 

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

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

437 ), 

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

439 pattern=( 

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

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

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

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

444 ), 

445 title='Version', 

446 ), 

447 ] 

448 ] 

449): 

450 pass