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
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 23:19:54 +00:00
1"""CSAF Document general definitions."""
3from __future__ import annotations
5from collections.abc import Sequence
6from datetime import datetime
7from enum import Enum
8from typing import Annotated, List, Optional, no_type_check
10from pydantic import AnyUrl, BaseModel, Field, RootModel, field_validator, model_validator
13class FlagCategory(Enum):
14 """
15 Defines the category of the machine readable label for flags.
16 """
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'
25class Flag(BaseModel):
26 """Contains product specific information in regard to this vulnerability as a single machine readable flag."""
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
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
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
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)."""
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
84class Id(BaseModel):
85 """Contains a single unique label or tracking ID for the vulnerability."""
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 ]
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)."""
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
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
149class Acknowledgment(BaseModel):
150 """
151 Acknowledges contributions by describing those that contributed.
152 """
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
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
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."""
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
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
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
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."""
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
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
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."""
317 pass
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
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."""
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
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
385class NoteCategory(Enum):
386 """Choice of what kind of note this is."""
388 description = 'description'
389 details = 'details'
390 faq = 'faq'
391 general = 'general'
392 legal_disclaimer = 'legal_disclaimer'
393 other = 'other'
394 summary = 'summary'
397class Note(BaseModel):
398 """Is a place to put all manner of text blobs related to the current context."""
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
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."""
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
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."""
469 external = 'external'
470 self = 'self'
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."""
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 ]
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."""
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
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