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
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-18 20:12:48 +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(BaseModel):
342 """Specifies a list of product_ids to give context to the parent item."""
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 ]
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
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
383class NoteCategory(Enum):
384 """Choice of what kind of note this is."""
386 description = 'description'
387 details = 'details'
388 faq = 'faq'
389 general = 'general'
390 legal_disclaimer = 'legal_disclaimer'
391 other = 'other'
392 summary = 'summary'
395class Note(BaseModel):
396 """Is a place to put all manner of text blobs related to the current context."""
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
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."""
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
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."""
467 external = 'external'
468 self = 'self'
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."""
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 ]
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."""
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
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