Test Failed
Pull Request — master (#14)
by Jan
03:04
created

oscap_report.scap_results_parser.data_structures   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 285
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 246
dl 0
loc 285
rs 8.8
c 0
b 0
f 0
wmc 45

13 Methods

Rating   Name   Duplication   Size   Complexity  
B Report.get_severity_of_failed_rules_stats() 0 15 6
A Report.as_dict() 0 18 1
B Report.get_rule_results_stats() 0 21 5
A OvalObject.as_dict() 0 6 1
B Remediation.get_type() 0 12 6
A OvalTest.as_dict() 0 6 1
A OvalNode.log_oval_tree() 0 10 4
A Rule.as_dict() 0 18 1
C OvalObject.filter_permissions() 0 34 9
A OvalNode.as_dict() 0 21 2
A OvalObject.get_header_from_object_data() 0 7 4
A Remediation.as_dict() 0 8 1
A OvalObject.filtr_object_data_item() 0 8 4

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
import logging
2
from dataclasses import dataclass
3
4
5
@dataclass
6
class Report:  # pylint: disable=R0902
7
    title: str = ""
8
    identity: str = ""
9
    profile_name: str = ""
10
    target: str = ""
11
    cpe_platforms: str = ""
12
    scanner: str = ""
13
    scanner_version: str = ""
14
    benchmark_url: str = ""
15
    benchmark_id: str = ""
16
    benchmark_version: str = ""
17
    start_time: str = ""
18
    end_time: str = ""
19
    test_system: str = ""
20
    score: float = 0.0
21
    score_max: float = 0.0
22
    rules: dict = None
23
24
    def as_dict(self):
25
        return {
26
            "title": self.title,
27
            "profile_name": self.profile_name,
28
            "target": self.target,
29
            "identit": self.identity,
30
            "cpe_platforms": self.cpe_platforms,
31
            "scanner": self.scanner,
32
            "scanner_version": self.scanner_version,
33
            "benchmark_url": self.benchmark_url,
34
            "benchmark_id": self.benchmark_id,
35
            "benchmark_version": self.benchmark_version,
36
            "start_time": self.start_time,
37
            "end_time": self.end_time,
38
            "test_system": self.test_system,
39
            "score": self.score,
40
            "score_max": self.score_max,
41
            "rules": self.rules,
42
        }
43
44
    def get_rule_results_stats(self):
45
        results_stats = {
46
            "fail": len(list(
47
                filter(lambda rule: rule.result.lower() == "fail", self.rules.values()))),
48
            "pass": len(list(
49
                filter(lambda rule: rule.result.lower() == "pass", self.rules.values()))),
50
            "unknown_error": len(list(
51
                filter(lambda rule:
52
                       rule.result.lower() in ("error", "unknown"), self.rules.values()))),
53
        }
54
        not_ignored_rules = len(list(
55
            filter(
56
                lambda rule: rule.result.lower() not in ("notselected", "notapplicable"),
57
                self.rules.values()
58
            )))
59
        percent_per_rule = 100 / not_ignored_rules
60
        results_stats["other"] = not_ignored_rules - results_stats["fail"] - results_stats['pass']
61
        results_stats["fail_percent"] = results_stats["fail"] * percent_per_rule
62
        results_stats["pass_percent"] = results_stats["pass"] * percent_per_rule
63
        results_stats["other_percent"] = results_stats["other"] * percent_per_rule
64
        return results_stats
65
66
    def get_severity_of_failed_rules_stats(self):
67
        failed_rules = list(
68
            filter(lambda rule: rule.result.lower() == "fail", self.rules.values()))
69
        percent_per_rule = 100 / len(failed_rules)
70
        severity_stats = {
71
            "low": sum(map(lambda rule: rule.severity.lower() == "low", failed_rules)),
72
            "medium": sum(map(lambda rule: rule.severity.lower() == "medium", failed_rules)),
73
            "high": sum(map(lambda rule: rule.severity.lower() == "high", failed_rules)),
74
            "unknown": sum(map(lambda rule: rule.severity.lower() == "unknown", failed_rules)),
75
        }
76
        severity_stats["low_percent"] = severity_stats["low"] * percent_per_rule
77
        severity_stats["medium_percent"] = severity_stats["medium"] * percent_per_rule
78
        severity_stats["high_percent"] = severity_stats["high"] * percent_per_rule
79
        severity_stats["unknown_percent"] = severity_stats["unknown"] * percent_per_rule
80
        return severity_stats
81
82
83
@dataclass
84
class OvalObject():
85
    object_id: str = ""
86
    flag: str = ""
87
    object_type: str = ""
88
    object_data: dict = None
89
90
    def as_dict(self):
91
        return {
92
            "object_id": self.object_id,
93
            "flag": self.flag,
94
            "object_type": self.object_type,
95
            "object_data": self.object_data,
96
        }
97
98
    def get_header_from_object_data(self):
99
        out = []
100
        for item in self.object_data:
101
            for key in self.filtr_object_data_item(item):
102
                if key not in out:
103
                    out.append(key)
104
        return out
105
106
    def filtr_object_data_item(self, item):
107
        if self.object_type == "textfilecontent54_object":
108
            if "filepath" in item and "text" in item:
109
                return {
110
                    "filepath": item["filepath"],
111
                    "content": item["text"]
112
                }
113
        return self.filter_permissions(item)
114
115
    def filter_permissions(self, item):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
116
        permission = {
117
            "uread": None,
118
            "uwrite": None,
119
            "uexec": None,
120
            "gread": None,
121
            "gwrite": None,
122
            "gexec": None,
123
            "oread": None,
124
            "owrite": None,
125
            "oexec": None,
126
        }
127
        out = {}
128
        for key, value in item.items():
129
            if key in permission:
130
                permission[key] = value
131
            else:
132
                out[key] = value
133
        if None in permission.values():
134
            return out
135
        permission_str = "<code>"
136
        for key, value in permission.items():
137
            if value:
138
                if "read" in key:
139
                    permission_str += "r"
140
                elif "write" in key:
141
                    permission_str += "w"
142
                elif "exec" in key:
143
                    permission_str += "x"
144
                else:
145
                    permission_str += "-"
146
        permission_str += "</code>"
147
        out["permission"] = permission_str
148
        return out
149
150
151
@dataclass
152
class OvalTest():
153
    test_id: str = ""
154
    test_type: str = ""
155
    comment: str = ""
156
    oval_object: OvalObject = None
157
158
    def as_dict(self):
159
        return {
160
            "test_id": self.test_id,
161
            "test_type": self.test_type,
162
            "comment": self.comment,
163
            "oval_object": self.oval_object,
164
        }
165
166
167
@dataclass
168
class OvalNode:  # pylint: disable=R0902
169
    node_id: str
170
    node_type: str
171
    value: str
172
    negation: bool = False
173
    comment: str = ""
174
    tag: str = ""
175
    test_result_details: dict = None
176
    children: list = None
177
    test_info: OvalTest = None
178
179
    def as_dict(self):
180
        if not self.children:
181
            return {
182
                'node_id': self.node_id,
183
                'type': self.node_type,
184
                'value': self.value,
185
                'negation': self.negation,
186
                'comment': self.comment,
187
                'tag': self.tag,
188
                'test_result_details': self.test_result_details,
189
                'child': None
190
            }
191
        return {
192
            'node_id': self.node_id,
193
            'type': self.node_type,
194
            'value': self.value,
195
            'negation': self.negation,
196
            'comment': self.comment,
197
            'tag': self.tag,
198
            'test_result_details': self.test_result_details,
199
            'child': [child.as_dict() for child in self.children]
200
        }
201
202
    def log_oval_tree(self, level=0):
203
        out = ""
204
        if self.node_type != "value":
205
            out = "  " * level + self.node_type + " = " + self.value
206
        else:
207
            out = "  " * level + self.node_id + " = " + self.value
208
        logging.info(out)
209
        if self.children is not None:
210
            for child in self.children:
211
                child.log_oval_tree(level + 1)
212
213
214
@dataclass
215
class Rule:  # pylint: disable=R0902
216
    rule_id: str = ""
217
    title: str = ""
218
    result: str = ""
219
    multi_check: bool = False
220
    time: str = ""
221
    severity: str = ""
222
    identifiers: list = None
223
    references: list = None
224
    description: str = ""
225
    rationale: str = ""
226
    warnings: list = None
227
    platform: str = ""
228
    oval_definition_id: str = ""
229
    message: str = ""
230
    remediations: list = None
231
    oval_tree: OvalNode = None
232
233
    def as_dict(self):
234
        return {
235
            "rule_id": self.rule_id,
236
            "title": self.title,
237
            "result": self.result,
238
            "multi_check": self.multi_check,
239
            "time": self.time,
240
            "severity": self.severity,
241
            "identifiers": self.identifiers,
242
            "references": self.references,
243
            "description": self.description,
244
            "rationale": self.rationale,
245
            "warnings": self.warnings,
246
            "platform": self.platform,
247
            "oval_definition_id": self.oval_definition_id,
248
            "message": self.message,
249
            "remediations": self.remediations,
250
            "oval_tree": self.oval_tree,
251
        }
252
253
254
@dataclass
255
class Remediation:
256
    remediation_id: str = ""
257
    system: str = ""
258
    complexity: str = ""
259
    disruption: str = ""
260
    strategy: str = ""
261
    fix: str = ""
262
263
    def as_dict(self):
264
        return {
265
            "remediation_id": self.remediation_id,
266
            "system": self.system,
267
            "complexity": self.complexity,
268
            "disruption": self.disruption,
269
            "strategy": self.strategy,
270
            "fix": self.fix,
271
        }
272
273
    def get_type(self):
274
        if self.system == "urn:xccdf:fix:script:sh":
275
            return "Shell script"
276
        if self.system == "urn:xccdf:fix:script:ansible":
277
            return "Ansible snippet"
278
        if self.system == "urn:xccdf:fix:script:puppet":
279
            return "Puppet snippet"
280
        if self.system == "urn:redhat:anaconda:pre":
281
            return "Anaconda snippet"
282
        if self.system == "urn:xccdf:fix:script:kubernetes":
283
            return "Kubernetes snippet"
284
        return self.system
285