Passed
Push — master ( ea7c4a...d61422 )
by Matěj
01:18 queued 12s
created

transform_benchmark_to_pcidss.main()   F

Complexity

Conditions 26

Size

Total Lines 146
Code Lines 101

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 101
nop 0
dl 0
loc 146
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like transform_benchmark_to_pcidss.main() 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
#!/usr/bin/env python2
2
3
# Copyright 2016 Red Hat Inc., Durham, North Carolina.
4
#
5
# This library is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU Lesser General Public
7
# License as published by the Free Software Foundation; either
8
# version 2 of the License, or (at your option) any later version.
9
#
10
# This library is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
# Lesser General Public License for more details.
14
#
15
# You should have received a copy of the GNU Lesser General Public
16
# License along with this library; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
#
19
# Authors:
20
#      Martin Preisler <[email protected]>
21
22
import logging
23
try:
24
    from xml.etree import cElementTree as ElementTree
25
except ImportError:
26
    from xml.etree import ElementTree as ElementTree
27
import json
28
import sys
29
import os
30
import copy
31
32
import ssg.constants
33
34
XCCDF_NAMESPACE = ssg.constants.XCCDF12_NS
35
FILENAME = "PCI_DSS_v3.pdf"
36
REMOTE_URL = "https://www.pcisecuritystandards.org/documents/PCI_DSS_v3-1.pdf"
37
38
39
def construct_xccdf_group(id_, desc, children, rules, rule_usage_map):
40
    ret = ElementTree.Element("{%s}Group" % (XCCDF_NAMESPACE))
41
    ret.set("id", ssg.constants.OSCAP_GROUP_PCIDSS + "-%s" % (id_))
42
    ret.set("selected", "true")
43
    title = ElementTree.Element("{%s}title" % (XCCDF_NAMESPACE))
44
    title.text = id_
45
    ret.append(title)
46
    description = ElementTree.Element("{%s}description" % (XCCDF_NAMESPACE))
47
    description.text = desc
48
    ret.append(description)
49
50
    for rule in rules:
51
        pci_dss_req_related = False
52
        for ref in rule.findall("./{%s}reference" % (XCCDF_NAMESPACE)):
53
            if ref.get("href") == REMOTE_URL and \
54
                    ref.text == "Req-" + id_:
55
                pci_dss_req_related = True
56
                break
57
58
        if pci_dss_req_related:
59
            suffix = ""
60
            if rule.get("id") not in rule_usage_map:
61
                rule_usage_map[rule.get("id")] = 1
62
            else:
63
                rule_usage_map[rule.get("id")] += 1
64
                suffix = "_%i" % (rule_usage_map[rule.get("id")])
65
66
            copied_rule = copy.deepcopy(rule)
67
            copied_rule.set("id", rule.get("id") + suffix)
68
            ret.append(copied_rule)
69
70
    for child_id, child_desc, child_children in children:
71
        child_element = construct_xccdf_group(
72
            child_id, child_desc, child_children,
73
            rules, rule_usage_map
74
        )
75
        ret.append(child_element)
76
77
    return ret
78
79
80
def main():
81
    logging.basicConfig(format='%(levelname)s:%(message)s',
82
                        level=logging.DEBUG)
83
84
    if len(sys.argv) < 4:
85
        sys.stderr.write("transform_benchmark_to_pcidss.py PCI_DSS.json "
86
                         "SOURCE_XCCDF DESTINATION_XCCDF\n")
87
        sys.exit(1)
88
89
    id_tree = None
90
    with open(sys.argv[1], "r") as f:
91
        id_tree = json.load(f)
92
93
    benchmark = ElementTree.parse(sys.argv[2])
94
95
    rules = []
96
    for rule in \
97
            benchmark.findall(".//{%s}Rule" % (XCCDF_NAMESPACE)):
98
        rules.append(rule)
99
    rule_usage_map = {}
100
101
    # only PCI-DSS related rules in that list, to speed-up processing
102
    filtered_rules = []
103
    for rule in rules:
104
        for ref in rule.findall("./{%s}reference" % (XCCDF_NAMESPACE)):
105
            if ref.get("href") == REMOTE_URL:
106
                filtered_rules.append(rule)
107
                break
108
109
    values = []
110
    for value in \
111
            benchmark.findall(".//{%s}Value" % (XCCDF_NAMESPACE)):
112
        values.append(value)
113
114
    # decide on usage of .iter or .getiterator method of elementtree class.
115
    # getiterator is deprecated in Python 3.9, but iter is not available in
116
    # older versions
117
    if getattr(benchmark, "iter", None) == None:
118
        parent_map = dict((c, p) for p in benchmark.getiterator() for c in p)
119
    else:
120
        parent_map = dict((c, p) for p in benchmark.iter() for c in p)
121
    for rule in \
122
            benchmark.findall(".//{%s}Rule" % (XCCDF_NAMESPACE)):
123
        parent_map[rule].remove(rule)
124
    for value in \
125
            benchmark.findall(".//{%s}Value" % (XCCDF_NAMESPACE)):
126
        parent_map[value].remove(value)
127
    for group in \
128
            benchmark.findall(".//{%s}Group" % (XCCDF_NAMESPACE)):
129
        parent_map[group].remove(group)
130
131
    root_element = benchmark.getroot()
132
    for id_, desc, children in id_tree:
133
        element = \
134
            construct_xccdf_group(id_, desc, children,
135
                                  filtered_rules, rule_usage_map)
136
        root_element.append(element)
137
138
    if len(values) > 0:
139
        group = ElementTree.Element("{%s}Group" % (XCCDF_NAMESPACE))
140
        group.set("id", ssg.constants.OSCAP_GROUP_VAL)
141
        group.set("selected", "true")
142
        title = ElementTree.Element("{%s}title" % (XCCDF_NAMESPACE))
143
        title.text = "Values"
144
        group.append(title)
145
        description = ElementTree.Element("{%s}description" % (XCCDF_NAMESPACE))
146
        description.text = "Group of values used in PCI-DSS profile"
147
        group.append(description)
148
149
        for value in values:
150
            copied_value = copy.deepcopy(value)
151
            group.append(copied_value)
152
153
        root_element.append(group)
154
155
    unused_rules = []
156
    for rule in rules:
157
        if rule.get("id") not in rule_usage_map:
158
            # this rule wasn't added yet, it would be lost unless we added it
159
            # to a special non-PCI-DSS group
160
            unused_rules.append(rule)
161
162
            for ref in rule.findall("./{%s}reference" % (XCCDF_NAMESPACE)):
163
                if ref.get("href") == REMOTE_URL:
164
                    logging.error(
165
                        "Rule '%s' references PCI-DSS '%s' but doesn't match "
166
                        "any Group ID in our requirement tree. Perhaps it's "
167
                        "referencing something we don't consider applicable on "
168
                        "the Operating System level?",
169
                        rule.get("id"), ref.text
170
                    )
171
                    sys.exit(1)
172
173
    if len(unused_rules) > 0:
174
        logging.warning(
175
            "%i rules don't reference PCI-DSS!" % (len(unused_rules))
176
        )
177
178
        group = ElementTree.Element("{%s}Group" % (XCCDF_NAMESPACE))
179
        group.set("id", ssg.constants.OSCAP_GROUP_NON_PCI)
180
        group.set("selected", "true")
181
        title = ElementTree.Element("{%s}title" % (XCCDF_NAMESPACE))
182
        title.text = "Non PCI-DSS"
183
        group.append(title)
184
        description = ElementTree.Element("{%s}description" % (XCCDF_NAMESPACE))
185
        description.text = "Rules that are not part of PCI-DSS"
186
        group.append(description)
187
188
        for rule in unused_rules:
189
            copied_rule = copy.deepcopy(rule)
190
            group.append(copied_rule)
191
192
        root_element.append(group)
193
194
    # change the Benchmark ID to avoid validation issues
195
    root_element.set(
196
        "id",
197
        root_element.get("id").replace("_benchmark_", "_benchmark_PCIDSS-")
198
    )
199
200
    for title_element in \
201
            root_element.findall("./{%s}title" % (XCCDF_NAMESPACE)):
202
        title_element.text += " (PCI-DSS centric)"
203
204
    # filter out all profiles except PCI-DSS
205
    for profile in \
206
            benchmark.findall("./{%s}Profile" % (XCCDF_NAMESPACE)):
207
        if profile.get("id").endswith("pci-dss"):
208
            # change the profile ID to avoid validation issues
209
            profile.set(
210
                "id",
211
                profile.get("id").replace("pci-dss", "pci-dss_centric")
212
            )
213
        else:
214
            root_element.remove(profile)
215
            continue
216
217
        # filter out old group selectors from the PCI-DSS profile
218
        for select in profile.findall("./{%s}select" % (XCCDF_NAMESPACE)):
219
            if select.get("idref").startswith(ssg.constants.OSCAP_GROUP):
220
                # we will remove all group selectors, all PCI-DSS groups are
221
                # selected by default so we don't need any in the final
222
                # PCI-DSS Benchmark
223
                profile.remove(select)
224
225
    benchmark.write(sys.argv[3])
226
227
if __name__ == "__main__":
228
    main()
229