Coverage for muuntaa/product.py: 0.00%
67 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-21 12:16:47 +00:00
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-21 12:16:47 +00:00
1"""Products type."""
3import logging
5import lxml.objectify # nosec B410
7from muuntaa.dialect import BRANCH_TYPE, RELATION_TYPE
8from muuntaa.subtree import Subtree
10RootType = lxml.objectify.ObjectifiedElement
13class Products(Subtree):
14 """Represents the Products type.
16 (
17 /cvrf:cvrfdoc/prod:ProductTree,
18 )
19 """
21 def __init__(self) -> None:
22 super().__init__()
23 if self.tree.get('product_tree') is None:
24 self.tree['product_tree'] = {}
25 self.hook = self.tree['product_tree']
27 def always(self, root: RootType) -> None:
28 pass
30 def sometimes(self, root: RootType) -> None:
31 self._handle_full_product_names(root)
32 self._handle_relationships(root)
33 self._handle_product_groups(root)
35 branches = self._handle_branches_recursive(root)
36 if branches is not None:
37 self.hook['branches'] = branches
39 @staticmethod
40 def _get_full_product_name(fpn_elem: RootType) -> dict[str, dict[str, str]]:
41 fpn = {'product_id': fpn_elem.attrib['ProductID'], 'name': fpn_elem.text}
43 if fpn_elem.attrib.get('CPE'):
44 fpn['product_identification_helper'] = {'cpe': fpn_elem.attrib['CPE']} # type: ignore
46 return fpn # type: ignore
48 @classmethod
49 def _get_branch_type(cls, branch_type: str): # type: ignore
50 if branch_type in ['Realm', 'Resource']:
51 logging.warning(
52 'Input branch type %s is no longer supported in CSAF. Converting to product_name', branch_type
53 )
55 return BRANCH_TYPE[branch_type] # TODO implement consistent key error reaction strategy
57 def _handle_full_product_names(self, root: RootType) -> None:
58 if full_product_name := root.FullProductName:
59 self.hook['full_product_names'] = [
60 self._get_full_product_name(fpn_elem) for fpn_elem in full_product_name # type: ignore
61 ]
63 def _handle_relationships(self, root: RootType) -> None:
64 if relationship := root.Relationship:
65 relationships = []
66 for entry in relationship:
67 # Take the first entry only as the full_product_name.
68 first_prod_name = entry.FullProductName[0] # type: ignore
69 if len(entry.FullProductName) > 1: # type: ignore
70 # ... in addition, log a warning on information loss during conversion of product relationships.
71 logging.warning(
72 'Input line %s: Relationship contains more FullProductNames.'
73 ' Taking only the first one, since CSAF expects only 1 value here',
74 entry.sourceline,
75 )
76 rel_to_add = {
77 'category': RELATION_TYPE[entry.attrib['RelationType']], # type: ignore
78 'product_reference': entry.attrib['ProductReference'],
79 'relates_to_product_reference': entry.attrib['RelatesToProductReference'],
80 'full_product_name': self._get_full_product_name(first_prod_name),
81 }
82 relationships.append(rel_to_add)
84 self.hook['relationships'] = relationships
86 def _handle_product_groups(self, root: RootType) -> None:
87 if product_groups := root.ProductGroups:
88 records = []
89 for product_group in product_groups.Group:
90 product_ids = [x.text for x in product_group.ProductID] # type: ignore
91 record = {
92 'group_id': product_group.attrib['GroupID'],
93 'product_ids': product_ids,
94 }
95 if summary := product_group.Description: # type: ignore
96 record['summary'] = summary.text
97 records.append(record)
99 self.hook['product_groups'] = records
101 def _handle_branches_recursive(self, root: RootType): # type: ignore
102 """Process the branches (any branch can contain either list of other branches or a single FullProductName)."""
103 if not root.Branch and not root.FullProductName:
104 return None # No branches to process
106 if 'Branch' in root.tag and (full_product_name := root.FullProductName):
107 # Inside Branch (not in the top ProductTree element, where FullProductName can also occur)
108 leaf_branch = {
109 'name': root.attrib['Name'],
110 'category': self._get_branch_type(root.attrib['Type']), # type: ignore
111 'product': self._get_full_product_name(full_product_name),
112 }
113 return leaf_branch # Current root is the leaf branch
115 if branch := root.Branch:
116 branches = []
117 for entry in branch:
118 if entry.FullProductName: # type: ignore
119 branches.append(self._handle_branches_recursive(entry)) # type: ignore
120 else:
121 branches.append(
122 {
123 'name': entry.attrib['Name'],
124 'category': self._get_branch_type(entry.attrib['Type']), # type: ignore
125 'branches': self._handle_branches_recursive(entry), # type: ignore
126 }
127 )
128 return branches