cpe_generate.main()   F
last analyzed

Complexity

Conditions 24

Size

Total Lines 178
Code Lines 113

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 600

Importance

Changes 0
Metric Value
cc 24
eloc 113
nop 0
dl 0
loc 178
ccs 0
cts 108
cp 0
crap 600
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 cpe_generate.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/python3
2
3
from __future__ import print_function
4
5
import fnmatch
6
import sys
7
import os
8
import ssg
9
import argparse
10
11
import ssg.build_cpe
12
import ssg.id_translate
13
import ssg.products
14
import ssg.xml
15
import ssg.yaml
16
from ssg.constants import XCCDF12_NS
17
18
# This script requires two arguments: an OVAL file and a CPE dictionary file.
19
# It is designed to extract any inventory definitions and the tests, states,
20
# objects and variables it references and then write them into a standalone
21
# OVAL CPE file, along with a synchronized CPE dictionary file.
22
23
oval_ns = "http://oval.mitre.org/XMLSchema/oval-definitions-5"
24
cpe_ns = "http://cpe.mitre.org/dictionary/2.0"
25
26
27
def parse_args():
28
    p = argparse.ArgumentParser(description="This script takes as input an "
29
        "OVAL file and a CPE dictionary file and extracts any inventory "
30
        "definitions and the tests, states objects and variables it "
31
        "references, and then write them into a standalone OVAL CPE file, "
32
        "along with a synchronized CPE dictionary file.")
33
    p.add_argument(
34
        "--product-yaml",
35
        help="YAML file with information about the product we are building. "
36
        "e.g.: ~/scap-security-guide/rhel7/product.yml "
37
        "needed for autodetection of profile root"
38
    )
39
    p.add_argument("idname", help="Identifier prefix")
40
    p.add_argument("cpeoutdir", help="Artifact output directory")
41
    p.add_argument("shorthandfile", help="shorthand xml to generate "
42
                   "the CPE dictionary from")
43
    p.add_argument("ovalfile", help="OVAL file to process")
44
    p.add_argument("--cpe-items-dir", help="the directory where compiled CPE items are stored")
45
46
    return p.parse_args()
47
48
49
def main():
50
    args = parse_args()
51
52
    # parse oval file
53
    ovaltree = ssg.xml.parse_file(args.ovalfile)
54
55
    # extract inventory definitions
56
    # making (dubious) assumption that all inventory defs are CPE
57
    defs = ovaltree.find("./{%s}definitions" % oval_ns)
58
    inventory_defs = []
59
    for el in defs.findall(".//{%s}definition" % oval_ns):
60
        if el.get("class") != "inventory":
61
            continue
62
        inventory_defs.append(el)
63
64
    # Keep the list of 'id' attributes from untranslated inventory def elements
65
    inventory_defs_id_attrs = []
66
67
    defs.clear()
68
    [defs.append(inventory_def) for inventory_def in inventory_defs]
69
    # Fill in that list
70
    inventory_defs_id_attrs = \
71
        [inventory_def.get("id") for inventory_def in inventory_defs]
72
73
    tests = ovaltree.find("./{%s}tests" % oval_ns)
74
    cpe_tests = ssg.build_cpe.extract_referred_nodes(defs, tests, "test_ref")
75
    tests.clear()
76
    [tests.append(cpe_test) for cpe_test in cpe_tests]
77
78
    states = ovaltree.find("./{%s}states" % oval_ns)
79
    cpe_states = ssg.build_cpe.extract_referred_nodes(tests, states, "state_ref")
80
    states.clear()
81
    [states.append(cpe_state) for cpe_state in cpe_states]
82
83
    objects = ovaltree.find("./{%s}objects" % oval_ns)
84
    cpe_objects = ssg.build_cpe.extract_referred_nodes(tests, objects, "object_ref")
85
    env_objects = ssg.build_cpe.extract_referred_nodes(objects, objects, "id")
86
    objects.clear()
87
    [objects.append(cpe_object) for cpe_object in cpe_objects]
88
89
    variables = ovaltree.find("./{%s}variables" % oval_ns)
90
    if variables is not None:
91
        ref_var_elems = set(ssg.build_cpe.extract_referred_nodes(objects, variables, "var_ref"))
92
        ref_var_elems.update(ssg.build_cpe.extract_referred_nodes(states, variables, "var_ref"))
93
94
        if ref_var_elems:
95
            variables.clear()
96
            has_external_vars = False
97
            for var in ref_var_elems:
98
                if (var.tag == "{%s}external_variable" % oval_ns):
99
                    error_msg = "Error: External variable (%s) is referenced in a"
100
                    "CPE OVAL check\n" % var.get('id')
101
                    sys.stderr.write(error_msg)
102
                    has_external_vars = True
103
104
                variables.append(var)
105
106
                # Make sure to inlude objects referenced by a local variable
107
                # But env_obj will return no objects if the local variable doesn't
108
                # reference any object via object_ref
109
                env_obj = ssg.build_cpe.extract_env_obj(env_objects, var)
110
                if env_obj:
111
                    objects.append(env_obj)
112
113
            if has_external_vars:
114
                error_msg = "Error: External variables cannot be used by CPE OVAL checks.\n"
115
                sys.stderr.write(error_msg)
116
                sys.exit(1)
117
        else:
118
            ovaltree.remove(variables)
119
120
    # turn IDs into meaningless numbers
121
    translator = ssg.id_translate.IDTranslator(args.idname)
122
    ovaltree = translator.translate(ovaltree)
123
124
    product_yaml = ssg.products.load_product_yaml(args.product_yaml)
125
    product = product_yaml["product"]
126
    newovalfile = args.idname + "-" + product + "-" + os.path.basename(args.ovalfile)
127
    newovalfile = newovalfile.replace("cpe-oval-unlinked", "cpe-oval")
128
    ssg.xml.ElementTree.ElementTree(ovaltree).write(args.cpeoutdir + "/" + newovalfile)
129
130
    # Lets scrape the shorthand for the list of platforms referenced
131
    benchmark_cpe_names = set()
132
    shorthandtree = ssg.xml.parse_file(args.shorthandfile)
133
    for platform in shorthandtree.findall(".//{%s}platform" % XCCDF12_NS):
134
        cpe_name = platform.get("idref")
135
        # skip CPE AL platforms (they are handled later)
136
        # this is temporary solution until we get rid of old type of platforms in the benchmark
137
        if cpe_name.startswith("#"):
138
            continue
139
        benchmark_cpe_names.add(cpe_name)
140
    # add CPE names used by factref elements in CPEAL platforms
141
    for factref in shorthandtree.findall(
142
            ".//ns1:fact-ref", {"ns1":ssg.constants.PREFIX_TO_NS["cpe-lang"]}):
143
        cpe_factref_name = factref.get("name")
144
        benchmark_cpe_names.add(cpe_factref_name)
145
146
    product_cpes = ssg.build_cpe.ProductCPEs()
147
    product_cpes.load_cpes_from_directory_tree(args.cpe_items_dir, product_yaml)
148
    cpe_list = ssg.build_cpe.CPEList()
149
    for cpe_name in benchmark_cpe_names:
150
        cpe_list.add(product_cpes.get_cpe(cpe_name))
151
152
    cpedict_filename = "ssg-" + product + "-cpe-dictionary.xml"
153
    cpedict_path = os.path.join(args.cpeoutdir, cpedict_filename)
154
    cpe_list.to_file(cpedict_path, newovalfile)
155
156
    # replace and sync IDs, href filenames in input cpe dictionary file
157
    cpedicttree = ssg.xml.parse_file(cpedict_path)
158
    for check in cpedicttree.findall(".//{%s}check" % cpe_ns):
159
        checkhref = check.get("href")
160
        # If CPE OVAL references another OVAL file
161
        if checkhref == 'filename':
162
            # Sanity check -- Verify the referenced OVAL is truly defined
163
            # somewhere in the (sub)directory tree below CWD. In correct
164
            # scenario is should be located:
165
            # * either in input/oval/*.xml
166
            # * or copied by former run of "combine_ovals.py" script from
167
            #   shared/ directory into build/ subdirectory
168
            refovalfilename = check.text
169
            refovalfilefound = False
170
            for dirpath, dirnames, filenames in os.walk(os.curdir, topdown=True):
171
                dirnames.sort()
172
                filenames.sort()
173
                # Case when referenced OVAL file exists
174
                for location in fnmatch.filter(filenames, refovalfilename + '.xml'):
175
                    refovalfilefound = True
176
                    break                     # break from the inner for loop
177
178
                if refovalfilefound:
179
                    break                     # break from the outer for loop
180
181
            shared_dir = \
182
                os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
183
            if shared_dir is not None:
184
                for dirpath, dirnames, filenames in os.walk(shared_dir, topdown=True):
185
                    dirnames.sort()
186
                    filenames.sort()
187
                    # Case when referenced OVAL file exists
188
                    for location in fnmatch.filter(filenames, refovalfilename + '.xml'):
189
                        refovalfilefound = True
190
                        break                     # break from the inner for loop
191
192
                    if refovalfilefound:
193
                        break                     # break from the outer for loop
194
195
            # Referenced OVAL doesn't exist in the subdirtree below CWD:
196
            # * there's either typo in the refenced OVAL filename, or
197
            # * is has been forgotten to be placed into input/oval, or
198
            # * the <platform> tag of particular shared/ OVAL wasn't modified
199
            #   to include the necessary referenced file.
200
            # Therefore display an error and exit with failure in such cases
201
            if not refovalfilefound:
202
                error_msg = "\n\tError: Can't locate \"%s\" OVAL file in the \
203
                \n\tlist of OVAL checks for this product! Exiting..\n" % refovalfilename
204
                sys.stderr.write(error_msg)
205
                # sys.exit(1)
206
        check.set("href", os.path.basename(newovalfile))
207
208
        # Sanity check to verify if inventory check OVAL id is present in the
209
        # list of known "id" attributes of inventory definitions. If not it
210
        # means provided ovalfile (sys.argv[1]) doesn't contain this OVAL
211
        # definition (it wasn't included due to <platform> tag restrictions)
212
        # Therefore display an error and exit with failure, since otherwise
213
        # we might end up creating invalid $(ID)-$(PROD)-cpe-oval.xml file
214
        if check.text not in inventory_defs_id_attrs:
215
            error_msg = "\n\tError: Can't locate \"%s\" definition in \"%s\". \
216
            \n\tEnsure <platform> element is configured properly for \"%s\".  \
217
            \n\tExiting..\n" % (check.text, args.ovalfile, check.text)
218
            sys.stderr.write(error_msg)
219
            sys.exit(1)
220
221
        # Referenced OVAL checks passed both of the above sanity tests
222
        check.text = translator.generate_id("{" + oval_ns + "}definition", check.text)
223
224
    ssg.xml.ElementTree.ElementTree(cpedicttree).write(cpedict_path)
225
226
    sys.exit(0)
227
228
229
if __name__ == "__main__":
230
    main()
231