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

1"""Products type.""" 

2 

3import logging 

4 

5import lxml.objectify # nosec B410 

6 

7from muuntaa.dialect import BRANCH_TYPE, RELATION_TYPE 

8from muuntaa.subtree import Subtree 

9 

10RootType = lxml.objectify.ObjectifiedElement 

11 

12 

13class Products(Subtree): 

14 """Represents the Products type. 

15 

16 ( 

17 /cvrf:cvrfdoc/prod:ProductTree, 

18 ) 

19 """ 

20 

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'] 

26 

27 def always(self, root: RootType) -> None: 

28 pass 

29 

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) 

34 

35 branches = self._handle_branches_recursive(root) 

36 if branches is not None: 

37 self.hook['branches'] = branches 

38 

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} 

42 

43 if fpn_elem.attrib.get('CPE'): 

44 fpn['product_identification_helper'] = {'cpe': fpn_elem.attrib['CPE']} # type: ignore 

45 

46 return fpn # type: ignore 

47 

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 ) 

54 

55 return BRANCH_TYPE[branch_type] # TODO implement consistent key error reaction strategy 

56 

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 ] 

62 

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) 

83 

84 self.hook['relationships'] = relationships 

85 

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) 

98 

99 self.hook['product_groups'] = records 

100 

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 

105 

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 

114 

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