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
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-04 16:28:45 +00:00
1"""CSAF Document general definitions."""
3from __future__ import annotations
5from collections.abc import Sequence
6from enum import Enum
7from typing import Annotated, List, Optional, no_type_check
9from pydantic import AnyUrl, BaseModel, Field, RootModel, field_validator, model_validator
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 """
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 ]
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
54class Acknowledgment(BaseModel):
55 """
56 Acknowledges contributions by describing those that contributed.
57 """
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
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
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."""
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
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
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
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."""
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
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
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."""
222 pass
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
246class ListOfProductIds(BaseModel):
247 """Specifies a list of product_ids to give context to the parent item."""
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 ]
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
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
288class NoteCategory(Enum):
289 """Choice of what kind of note this is."""
291 description = 'description'
292 details = 'details'
293 faq = 'faq'
294 general = 'general'
295 legal_disclaimer = 'legal_disclaimer'
296 other = 'other'
297 summary = 'summary'
300class Note(BaseModel):
301 """Is a place to put all manner of text blobs related to the current context."""
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
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."""
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
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."""
372 external = 'external'
373 self = 'self'
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."""
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 ]
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."""
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
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