Test Setup Failed
Pull Request — master (#35)
by Jan
04:01
created

oscap_report.scap_results_parser.data_structures   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 329
Duplicated Lines 0 %

Test Coverage

Coverage 80.87%

Importance

Changes 0
Metric Value
eloc 281
dl 0
loc 329
rs 6.96
c 0
b 0
f 0
wmc 53
ccs 148
cts 183
cp 0.8087

15 Methods

Rating   Name   Duplication   Size   Complexity  
D OvalNode._get_result_counts() 0 21 13
A Remediation.get_type() 0 10 1
A OvalTest.as_dict() 0 6 1
A OvalNode.as_json() 0 2 1
A OvalNode.log_oval_tree() 0 13 5
A OvalObject.as_dict() 0 6 1
A Rule.as_dict() 0 19 1
C OvalNode.evaluate_tree() 0 22 10
A Report.as_dict() 0 19 1
B Report.get_severity_of_failed_rules_stats() 0 18 6
A OvalNode.as_dict() 0 21 2
A Group.as_dict() 0 17 2
B Report.get_rule_results_stats() 0 25 6
A Remediation.as_dict() 0 8 1
A Report.get_failed_rules() 0 2 2

How to fix   Complexity   

Complexity

Complex classes like oscap_report.scap_results_parser.data_structures often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1 1
import json
2 1
import logging
3 1
from collections import Counter
4 1
from dataclasses import dataclass
5
6 1
from .exceptions import MissingProcessableRules
7 1
from .oval_result_eval import (EMPTY_RESULT, SHORT_RESULT_TO_FULL_RESULT,
8
                               OvalResult)
9
10
11 1
@dataclass
12 1
class Report:  # pylint: disable=R0902
13 1
    title: str = ""
14 1
    identity: str = ""
15 1
    profile_name: str = ""
16 1
    platform: str = ""
17 1
    target: str = ""
18 1
    cpe_platforms: str = ""
19 1
    scanner: str = ""
20 1
    scanner_version: str = ""
21 1
    benchmark_url: str = ""
22 1
    benchmark_id: str = ""
23 1
    benchmark_version: str = ""
24 1
    start_time: str = ""
25 1
    end_time: str = ""
26 1
    test_system: str = ""
27 1
    score: float = 0.0
28 1
    score_max: float = 0.0
29 1
    rules: dict = None
30
31 1
    def as_dict(self):
32
        return {
33
            "title": self.title,
34
            "profile_name": self.profile_name,
35
            "platform": self.platform,
36
            "target": self.target,
37
            "identit": self.identity,
38
            "cpe_platforms": self.cpe_platforms,
39
            "scanner": self.scanner,
40
            "scanner_version": self.scanner_version,
41
            "benchmark_url": self.benchmark_url,
42
            "benchmark_id": self.benchmark_id,
43
            "benchmark_version": self.benchmark_version,
44
            "start_time": self.start_time,
45
            "end_time": self.end_time,
46
            "test_system": self.test_system,
47
            "score": self.score,
48
            "score_max": self.score_max,
49
            "rules": self.rules,
50
        }
51
52 1
    def get_rule_results_stats(self):
53 1
        results_stats = {
54
            "fail": len(list(
55
                filter(lambda rule: rule.result.lower() == "fail", self.rules.values()))),
56
            "pass": len(list(
57
                filter(lambda rule: rule.result.lower() == "pass", self.rules.values()))),
58
            "unknown_error": len(list(
59
                filter(lambda rule:
60
                       rule.result.lower() in ("error", "unknown"), self.rules.values()))),
61
        }
62 1
        not_ignored_rules = len(list(
63
            filter(
64
                lambda rule: rule.result.lower() not in ("notselected", "notapplicable"),
65
                self.rules.values()
66
            )))
67 1
        if not_ignored_rules == 0:
68 1
            not_ignored_rules = 1
69 1
            logging.warning("There are no applicable or selected rules.")
70 1
        percent_per_rule = 100 / not_ignored_rules
71 1
        results_stats["other"] = not_ignored_rules - results_stats["fail"] - results_stats['pass']
72 1
        results_stats["fail_percent"] = results_stats["fail"] * percent_per_rule
73 1
        results_stats["pass_percent"] = results_stats["pass"] * percent_per_rule
74 1
        results_stats["other_percent"] = results_stats["other"] * percent_per_rule
75 1
        results_stats["sum_of_rules"] = not_ignored_rules
76 1
        return results_stats
77
78 1
    def get_severity_of_failed_rules_stats(self):
79 1
        failed_rules = self.get_failed_rules()
80 1
        count_of_failed_rules = len(failed_rules)
81 1
        if count_of_failed_rules == 0:
82 1
            raise MissingProcessableRules("There are no failed rules!")
83 1
        percent_per_rule = 100 / count_of_failed_rules
84 1
        severity_stats = {
85
            "low": sum(map(lambda rule: rule.severity.lower() == "low", failed_rules)),
86
            "medium": sum(map(lambda rule: rule.severity.lower() == "medium", failed_rules)),
87
            "high": sum(map(lambda rule: rule.severity.lower() == "high", failed_rules)),
88
            "unknown": sum(map(lambda rule: rule.severity.lower() == "unknown", failed_rules)),
89
        }
90 1
        severity_stats["low_percent"] = severity_stats["low"] * percent_per_rule
91 1
        severity_stats["medium_percent"] = severity_stats["medium"] * percent_per_rule
92 1
        severity_stats["high_percent"] = severity_stats["high"] * percent_per_rule
93 1
        severity_stats["unknown_percent"] = severity_stats["unknown"] * percent_per_rule
94 1
        severity_stats["sum_of_failed_rules"] = len(failed_rules)
95 1
        return severity_stats
96
97 1
    def get_failed_rules(self):
98 1
        return list(filter(lambda rule: rule.result.lower() == "fail", self.rules.values()))
99
100
101 1
@dataclass
102 1
class OvalObject():
103 1
    object_id: str = ""
104 1
    flag: str = ""
105 1
    object_type: str = ""
106 1
    object_data: dict = None
107
108 1
    def as_dict(self):
109 1
        return {
110
            "object_id": self.object_id,
111
            "flag": self.flag,
112
            "object_type": self.object_type,
113
            "object_data": self.object_data,
114
        }
115
116
117 1
@dataclass
118 1
class OvalTest():
119 1
    test_id: str = ""
120 1
    test_type: str = ""
121 1
    comment: str = ""
122 1
    oval_object: OvalObject = None
123
124 1
    def as_dict(self):
125 1
        return {
126
            "test_id": self.test_id,
127
            "test_type": self.test_type,
128
            "comment": self.comment,
129
            "oval_object": self.oval_object.as_dict(),
130
        }
131
132
133 1
@dataclass
134 1
class OvalNode:  # pylint: disable=R0902
135 1
    node_id: str
136 1
    node_type: str
137 1
    value: str
138 1
    negation: bool = False
139 1
    comment: str = ""
140 1
    tag: str = ""
141 1
    children: list = None
142 1
    test_info: OvalTest = None
143
144 1
    def as_dict(self):
145 1
        if not self.children:
146 1
            return {
147
                'node_id': self.node_id,
148
                'node_type': self.node_type,
149
                'value': self.value,
150
                'negation': self.negation,
151
                'comment': self.comment,
152
                'tag': self.tag,
153
                'test_info': self.test_info.as_dict(),
154
                'children': None
155
            }
156 1
        return {
157
            'node_id': self.node_id,
158
            'node_type': self.node_type,
159
            'value': self.value,
160
            'negation': self.negation,
161
            'comment': self.comment,
162
            'tag': self.tag,
163
            'test_info': None,
164
            'children': [child.as_dict() for child in self.children]
165
        }
166
167 1
    def as_json(self):
168
        return json.dumps(self.as_dict())
169
170 1
    def log_oval_tree(self, level=0):
171
        out = ""
172
        negation_str = ""
173
        if self.negation:
174
            negation_str = "not "
175
        if self.node_type != "value":
176
            out = "  " * level + self.node_type + " = " + negation_str + self.value
177
        else:
178
            out = "  " * level + self.node_id + " = " + negation_str + self.value
179
        logging.info(out)
180
        if self.children is not None:
181
            for child in self.children:
182
                child.log_oval_tree(level + 1)
183
184 1
    def _get_result_counts(self):
185 1
        result = Counter(EMPTY_RESULT)
186 1
        for child in self.children:
187 1
            if child.value == "true" and not child.negation:
188 1
                result["number_of_true"] += 1
189 1
            elif child.value == "true" and child.negation:
190
                result["number_of_false"] += 1
191 1
            elif child.value == "false" and not child.negation:
192 1
                result["number_of_false"] += 1
193
            elif child.value == "false" and child.negation:
194
                result["number_of_true"] += 1
195
            elif child.value == "not evaluated":
196
                result["number_of_noteval"] += 1
197
            elif child.value == "not applicable":
198
                result["number_of_notappl"] += 1
199
            else:
200
                if child.node_type != "value":
201
                    result["number_of_" + str(child.evaluate_tree())] += 1
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable str does not seem to be defined.
Loading history...
202
                else:
203
                    result["number_of_" + child.value] += 1
204 1
        return result
205
206 1
    def evaluate_tree(self):
207 1
        results_counts = self._get_result_counts()
208 1
        oval_result = OvalResult(**results_counts)
209 1
        out_result = None
210 1
        if oval_result.is_notapp_result():
211
            out_result = "notappl"
212
        else:
213 1
            if self.node_type.lower() == "or":
214 1
                out_result = oval_result.eval_operator_or()
215 1
            elif self.node_type.lower() == "and":
216 1
                out_result = oval_result.eval_operator_and()
217
            elif self.node_type.lower() == "one":
218
                out_result = oval_result.eval_operator_one()
219
            elif self.node_type.lower() == "xor":
220
                out_result = oval_result.eval_operator_xor()
221
222 1
        if out_result == "true" and self.negation:
223
            out_result = "false"
224 1
        elif out_result == "false" and self.negation:
225
            out_result = "true"
226
227 1
        return SHORT_RESULT_TO_FULL_RESULT.get(out_result, out_result)
228
229
230 1
@dataclass
231 1
class Rule:  # pylint: disable=R0902
232 1
    rule_id: str = ""
233 1
    title: str = ""
234 1
    result: str = ""
235 1
    multi_check: bool = False
236 1
    time: str = ""
237 1
    severity: str = ""
238 1
    identifiers: list = None
239 1
    references: list = None
240 1
    description: str = ""
241 1
    rationale: str = ""
242 1
    warnings: list = None
243 1
    platforms: list = None
244 1
    oval_definition_id: str = ""
245 1
    message: str = ""
246 1
    remediations: list = None
247 1
    oval_tree: OvalNode = None
248 1
    cpe_tree: OvalNode = None
249
250 1
    def as_dict(self):
251
        return {
252
            "rule_id": self.rule_id,
253
            "title": self.title,
254
            "result": self.result,
255
            "multi_check": self.multi_check,
256
            "time": self.time,
257
            "severity": self.severity,
258
            "identifiers": self.identifiers,
259
            "references": self.references,
260
            "description": self.description,
261
            "rationale": self.rationale,
262
            "warnings": self.warnings,
263
            "platforms": self.platforms,
264
            "oval_definition_id": self.oval_definition_id,
265
            "message": self.message,
266
            "remediations": self.remediations,
267
            "oval_tree": self.oval_tree.as_dict(),
268
            "cpe_tree": self.cpe_tree.as_dict(),
269
        }
270
271
272 1
@dataclass
273 1
class Remediation:
274 1
    remediation_id: str = ""
275 1
    system: str = ""
276 1
    complexity: str = ""
277 1
    disruption: str = ""
278 1
    strategy: str = ""
279 1
    fix: str = ""
280
281 1
    def as_dict(self):
282
        return {
283
            "remediation_id": self.remediation_id,
284
            "system": self.system,
285
            "complexity": self.complexity,
286
            "disruption": self.disruption,
287
            "strategy": self.strategy,
288
            "fix": self.fix,
289
        }
290
291 1
    def get_type(self):
292 1
        script_types = {
293
            "urn:xccdf:fix:script:sh": "Shell script",
294
            "urn:xccdf:fix:script:ansible": "Ansible snippet",
295
            "urn:xccdf:fix:script:puppet": "Puppet snippet",
296
            "urn:redhat:anaconda:pre": "Anaconda snippet",
297
            "urn:xccdf:fix:script:kubernetes": "Kubernetes snippet",
298
            "urn:redhat:osbuild:blueprint": "OSBuild Blueprint snippet",
299
        }
300 1
        return script_types.get(self.system, "script")
301
302
303 1
@dataclass
304 1
class Group:
305 1
    group_id: str = ""
306 1
    title: str = ""
307 1
    description: str = ""
308 1
    platforms: list = None
309 1
    rules_ids: list = None
310 1
    sub_groups: list = None
311
312 1
    def as_dict(self):
313
        if not self.sub_groups:
314
            return {
315
                "group_id": self.group_id,
316
                "title": self.title,
317
                "description": self.description,
318
                "platforms": self.platforms,
319
                "rules_ids": self.rules_ids,
320
                "sub_groups": None,
321
            }
322
        return {
323
            "group_id": self.group_id,
324
            "title": self.title,
325
            "description": self.description,
326
            "platforms": self.platforms,
327
            "rules_ids": self.rules_ids,
328
            "sub_groups": [sub_group.as_dict() for sub_group in self.sub_groups],
329
        }
330