1
|
|
|
# Copyright 2022, Red Hat, Inc. |
2
|
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later |
3
|
|
|
|
4
|
1 |
|
import logging |
5
|
1 |
|
import uuid |
6
|
|
|
|
7
|
1 |
|
from lxml.etree import Element |
8
|
|
|
|
9
|
1 |
|
from openscap_report.dataclasses import dataclass |
10
|
|
|
|
11
|
1 |
|
from ..data_structures import OvalNode |
12
|
1 |
|
from ..exceptions import MissingOVALResult |
13
|
1 |
|
from ..namespaces import NAMESPACES |
14
|
1 |
|
from .oval_test_parser import OVALTestParser |
15
|
|
|
|
16
|
1 |
|
STR_TO_BOOL = {'true': True, 'false': False} |
17
|
1 |
|
STR_NEGATION_BOOL = {'true': 'false', 'false': 'true'} |
18
|
|
|
|
19
|
|
|
|
20
|
1 |
|
@dataclass |
21
|
1 |
|
class OVALReport: |
22
|
1 |
|
oval_report_id: str |
23
|
1 |
|
oval_report_element: Element |
24
|
1 |
|
oval_results_element: Element |
25
|
1 |
|
oval_test_parser: OVALTestParser |
26
|
|
|
|
27
|
|
|
|
28
|
1 |
|
class OVALResultParser: |
29
|
1 |
|
def __init__(self, root, oval_var_id_to_value_id, ref_values): |
30
|
1 |
|
self.root = root |
31
|
1 |
|
self.oval_var_id_to_value_id = oval_var_id_to_value_id |
32
|
1 |
|
self.ref_values = ref_values |
33
|
1 |
|
self.oval_reports = self._get_oval_reports() |
34
|
1 |
|
logging.info(self.oval_reports) |
35
|
|
|
|
36
|
1 |
|
def _get_oval_reports(self): |
37
|
1 |
|
oval_reports = {} |
38
|
1 |
|
reports = self.root.find('.//arf:reports', NAMESPACES) |
39
|
1 |
|
if reports is None: |
40
|
1 |
|
raise MissingOVALResult("all_OVAL_results") |
41
|
|
|
|
42
|
1 |
|
for report_element in reports: |
43
|
1 |
|
report_id = report_element.get("id") |
44
|
1 |
|
if "oval" in report_id: |
45
|
1 |
|
oval_results = self._get_oval_results(report_element) |
46
|
1 |
|
oval_test_parser = OVALTestParser( |
47
|
|
|
report_element, self.oval_var_id_to_value_id, self.ref_values |
48
|
|
|
) |
49
|
1 |
|
oval_reports[report_id] = OVALReport( |
50
|
|
|
report_id, |
51
|
|
|
report_element, |
52
|
|
|
oval_results, |
53
|
|
|
oval_test_parser |
54
|
|
|
) |
55
|
1 |
|
return oval_reports |
56
|
|
|
|
57
|
1 |
|
def _get_oval_results(self, oval_report): |
58
|
1 |
|
return oval_report.find( |
59
|
|
|
('.//XMLSchema:oval_results/XMLSchema:results/' |
60
|
|
|
'XMLSchema:system/XMLSchema:definitions'), NAMESPACES) |
61
|
|
|
|
62
|
1 |
|
def get_oval_trees_by_oval_reports(self): |
63
|
1 |
|
dict_of_oval_reports = {} |
64
|
1 |
|
for report_id, report in self.oval_reports.items(): |
65
|
1 |
|
dict_of_oval_results = {} |
66
|
1 |
|
for definition in report.oval_results_element: |
67
|
1 |
|
id_definition = definition.get('definition_id') |
68
|
1 |
|
criteria_result = definition[0] |
69
|
1 |
|
dict_of_oval_results[id_definition] = self._build_node( |
70
|
|
|
criteria_result, |
71
|
|
|
"Definition", |
72
|
|
|
id_definition, |
73
|
|
|
report_id |
74
|
|
|
) |
75
|
1 |
|
dict_of_oval_reports[report_id] = self._fill_extend_definition(dict_of_oval_results) |
76
|
1 |
|
return dict_of_oval_reports |
77
|
|
|
|
78
|
1 |
|
@staticmethod |
79
|
1 |
|
def _get_negation(node): |
80
|
1 |
|
negation = False |
81
|
1 |
|
if node.get('negate') is not None: |
82
|
1 |
|
negation = STR_TO_BOOL[node.get('negate')] |
83
|
1 |
|
return negation |
84
|
|
|
|
85
|
1 |
|
@staticmethod |
86
|
1 |
|
def _get_result(negation, tree): |
87
|
|
|
""" |
88
|
|
|
This method removes the negation of |
89
|
|
|
the result. Because negation is already |
90
|
|
|
included in the result in ARF file. |
91
|
|
|
""" |
92
|
1 |
|
result = tree.get('result') |
93
|
1 |
|
if negation and result in ('true', 'false'): |
94
|
1 |
|
result = STR_NEGATION_BOOL[result] |
95
|
1 |
|
return result |
96
|
|
|
|
97
|
1 |
|
def _get_extend_definition_node(self, child): |
98
|
1 |
|
negation = self._get_negation(child) |
99
|
1 |
|
result_of_node = self._get_result(negation, child) |
100
|
1 |
|
return OvalNode( |
101
|
|
|
node_id=child.get('definition_ref'), |
102
|
|
|
node_type="extend_definition", |
103
|
|
|
value=result_of_node, |
104
|
|
|
negation=negation, |
105
|
|
|
tag="Extend definition", |
106
|
|
|
) |
107
|
|
|
|
108
|
1 |
|
def _get_test_node(self, child, oval_report_id): |
109
|
1 |
|
negation = self._get_negation(child) |
110
|
1 |
|
result_of_node = self._get_result(negation, child) |
111
|
1 |
|
test_id = child.get('test_ref') |
112
|
1 |
|
oval_test_parser = self.oval_reports[oval_report_id].oval_test_parser |
113
|
1 |
|
return OvalNode( |
114
|
|
|
node_id=test_id, |
115
|
|
|
node_type="value", |
116
|
|
|
value=result_of_node, |
117
|
|
|
negation=negation, |
118
|
|
|
tag="Test", |
119
|
|
|
test_info=oval_test_parser.get_test_info(test_id), |
120
|
|
|
) |
121
|
|
|
|
122
|
1 |
|
def _build_node(self, tree, tag, id_definition, oval_report_id): |
123
|
1 |
|
negation = self._get_negation(tree) |
124
|
1 |
|
node = OvalNode( |
125
|
|
|
node_id=id_definition, |
126
|
|
|
node_type=tree.get('operator'), |
127
|
|
|
negation=negation, |
128
|
|
|
value=self._get_result(negation, tree), |
129
|
|
|
tag=tag, |
130
|
|
|
children=[], |
131
|
|
|
) |
132
|
1 |
|
for child in tree: |
133
|
1 |
|
if child.get('operator') is not None: |
134
|
1 |
|
node.children.append( |
135
|
|
|
self._build_node( |
136
|
|
|
child, |
137
|
|
|
"Criteria", |
138
|
|
|
f"no-id-criteria-{uuid.uuid4()}", |
139
|
|
|
oval_report_id |
140
|
|
|
) |
141
|
|
|
) |
142
|
|
|
else: |
143
|
1 |
|
if child.get('definition_ref') is not None: |
144
|
1 |
|
node.children.append(self._get_extend_definition_node(child)) |
145
|
|
|
else: |
146
|
1 |
|
node.children.append(self._get_test_node(child, oval_report_id)) |
147
|
1 |
|
return node |
148
|
|
|
|
149
|
1 |
|
def _fill_extend_definition(self, dict_of_oval_definitions): |
150
|
1 |
|
out = {} |
151
|
1 |
|
for id_definition, definition in dict_of_oval_definitions.items(): |
152
|
1 |
|
out[id_definition] = self._fill_extend_definition_help( |
153
|
|
|
definition, dict_of_oval_definitions) |
154
|
1 |
|
return out |
155
|
|
|
|
156
|
1 |
|
def _fill_extend_definition_help(self, node, dict_of_oval_definitions): |
157
|
1 |
|
out = OvalNode( |
158
|
|
|
node_id=node.node_id, |
159
|
|
|
node_type=node.node_type, |
160
|
|
|
negation=node.negation, |
161
|
|
|
value=node.value, |
162
|
|
|
tag=node.tag, |
163
|
|
|
children=[], |
164
|
|
|
) |
165
|
1 |
|
for child in node.children: |
166
|
1 |
|
if child.node_type in ("AND", "OR", "ONE", "XOR"): |
167
|
1 |
|
out.children.append( |
168
|
|
|
self._fill_extend_definition_help(child, dict_of_oval_definitions)) |
169
|
1 |
|
elif child.node_type == "extend_definition": |
170
|
1 |
|
out.children.append( |
171
|
|
|
self._find_definition_by_id(child, dict_of_oval_definitions)) |
172
|
|
|
else: |
173
|
1 |
|
out.children.append(child) |
174
|
1 |
|
return out |
175
|
|
|
|
176
|
1 |
|
def _find_definition_by_id(self, node, dict_of_oval_definitions): |
177
|
1 |
|
extend_definition_id = node.node_id |
178
|
1 |
|
dict_of_oval_definitions[extend_definition_id].negation = node.negation |
179
|
1 |
|
dict_of_oval_definitions[extend_definition_id].tag = node.tag |
180
|
1 |
|
return self._fill_extend_definition_help( |
181
|
|
|
dict_of_oval_definitions[extend_definition_id], dict_of_oval_definitions) |
182
|
|
|
|