Coverage for turvallisuusneuvonta/csaf/definitions.py: 73.68%
119 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-18 20:29:38 +00:00
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-18 20:29:38 +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 person.',
139 examples=['Albert Einstein', 'Johann Sebastian Bach'],
140 min_length=1,
141 title='Name of entity being recognized',
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 entities 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 ]
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 """
343 Specifies a list of product_ids to give context to the parent item.
344 """
346 product_ids: Annotated[
347 Sequence[ReferenceTokenForProductInstance],
348 Field(
349 description='Specifies a list of product_ids to give context to the parent item.',
350 # min_length=1,
351 title='List of product_ids',
352 ),
353 ]
355 @classmethod
356 @no_type_check
357 @field_validator('product_ids')
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 """
387 Choice of what kind of note this is.
388 """
390 description = 'description'
391 details = 'details'
392 faq = 'faq'
393 general = 'general'
394 legal_disclaimer = 'legal_disclaimer'
395 other = 'other'
396 summary = 'summary'
399class Note(BaseModel):
400 """
401 Is a place to put all manner of text blobs related to the current context.
402 """
404 audience: Annotated[
405 Optional[str],
406 Field(
407 description='Indicate who is intended to read it.',
408 examples=[
409 'all',
410 'executives',
411 'operational management and system administrators',
412 'safety engineers',
413 ],
414 min_length=1,
415 title='Audience of note',
416 ),
417 ] = None
418 category: Annotated[
419 NoteCategory,
420 Field(description='Choice of what kind of note this is.', title='Note category'),
421 ]
422 text: Annotated[
423 str,
424 Field(
425 description='The contents of the note. Content varies depending on type.',
426 min_length=1,
427 title='Note contents',
428 ),
429 ]
430 title: Annotated[
431 Optional[str],
432 Field(
433 description='Provides a concise description of what is contained in the text of the note.',
434 examples=[
435 'Details',
436 'Executive summary',
437 'Technical summary',
438 'Impact on safety systems',
439 ],
440 min_length=1,
441 title='Title of note',
442 ),
443 ] = None
446class Notes(
447 RootModel[
448 Annotated[
449 List[Note],
450 Field(
451 description='Contains notes which are specific to the current context.',
452 min_length=1,
453 title='List of notes',
454 ),
455 ]
456 ]
457):
458 """Contains notes which are specific to the current context."""
460 @classmethod
461 @no_type_check
462 @model_validator(mode='before')
463 def check_len(cls, v):
464 if not v:
465 raise ValueError('mandatory element present but empty')
466 return v
469class ReferenceCategory(Enum):
470 """
471 Indicates whether the reference points to the same document or vulnerability in focus (depending on scope) or
472 to an external resource.
473 """
475 external = 'external'
476 self = 'self'
479class Reference(BaseModel):
480 """
481 Holds any reference to conferences, papers, advisories, and other resources that are related and considered
482 related to either a surrounding part of or the entire document and to be of value to the document consumer.
483 """
485 category: Annotated[
486 Optional[ReferenceCategory],
487 Field(
488 description='Indicates whether the reference points to the same document or vulnerability in focus'
489 ' (depending on scope) or to an external resource.',
490 title='Category of reference',
491 ),
492 ] = ReferenceCategory.external
493 summary: Annotated[
494 str,
495 Field(
496 description='Indicates what this reference refers to.',
497 min_length=1,
498 title='Summary of the reference',
499 ),
500 ]
501 url: Annotated[
502 AnyUrl,
503 Field(description='Provides the URL for the reference.', title='URL of reference'),
504 ]
507class References(
508 RootModel[
509 Annotated[
510 List[Reference],
511 Field(
512 description='Holds a list of references.',
513 min_length=1,
514 title='List of references',
515 ),
516 ]
517 ]
518):
519 """Holds a list of references."""
521 pass
524class Version(
525 RootModel[
526 Annotated[
527 str,
528 Field(
529 description=(
530 'Specifies a version string to denote clearly the evolution of the content of the document.'
531 ' Format must be either integer or semantic versioning.'
532 ),
533 examples=['1', '4', '0.9.0', '1.4.3', '2.40.0+21AF26D3'],
534 pattern=(
535 '^(0|[1-9][0-9]*)$|^((0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)'
536 '(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)'
537 '(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?'
538 '(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)$'
539 ),
540 title='Version',
541 ),
542 ]
543 ]
544):
545 pass