Passed
Branch master (2b673d)
by Matěj
03:01
created

ssg.oval   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Test Coverage

Coverage 37.43%

Importance

Changes 0
Metric Value
eloc 196
dl 0
loc 291
ccs 64
cts 171
cp 0.3743
rs 8.72
c 0
b 0
f 0
wmc 46

10 Functions

Rating   Name   Duplication   Size   Complexity  
A append() 0 11 3
C _add_elements() 0 38 9
A find_testfile_or_exit() 0 9 2
A replace_external_vars() 0 24 3
A applicable_platforms() 0 16 2
A parse_options() 0 20 1
C main() 0 74 11
A read_ovaldefgroup_file() 0 5 2
A get_openscap_supported_oval_version() 0 9 3
C find_testfile() 0 34 10

How to fix   Complexity   

Complexity

Complex classes like ssg.oval 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 2
from __future__ import absolute_import
0 ignored issues
show
Coding Style introduced by
This module should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
2 2
from __future__ import print_function
3
4 2
import sys
5 2
import os
6 2
import re
7 2
import argparse
8 2
import tempfile
9 2
import subprocess
10
11 2
from .constants import oval_footer as footer
12 2
from .constants import oval_namespace as ovalns
13 2
from .rules import get_rule_dir_id, get_rule_dir_ovals, find_rule_dirs
14 2
from .xml import ElementTree as ET
15 2
from .xml import oval_generated_header
16 2
from .id_translate import IDTranslator
17
18 2
SHARED_OVAL = re.sub(r'ssg/.*', 'shared', __file__) + '/checks/oval/'
19 2
LINUX_OS_GUIDE = re.sub(r'ssg/.*', 'linux_os', __file__) + '/guide/'
20
21
22
# globals, to make recursion easier in case we encounter extend_definition
23 2
ET.register_namespace("oval", ovalns)
24 2
definitions = ET.Element("oval:definitions")
0 ignored issues
show
Coding Style Naming introduced by
The name definitions does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
25 2
tests = ET.Element("oval:tests")
0 ignored issues
show
Coding Style Naming introduced by
The name tests does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
26 2
objects = ET.Element("oval:objects")
0 ignored issues
show
Coding Style Naming introduced by
The name objects does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
27 2
states = ET.Element("oval:states")
0 ignored issues
show
Coding Style Naming introduced by
The name states does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
28 2
variables = ET.Element("oval:variables")
0 ignored issues
show
Coding Style Naming introduced by
The name variables does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
29 2
silent_mode = False
0 ignored issues
show
Coding Style Naming introduced by
The name silent_mode does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
30
31
32
# append new child ONLY if it's not a duplicate
33 2
def append(element, newchild):
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
34
    global silent_mode
0 ignored issues
show
Coding Style Naming introduced by
The name silent_mode does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
35
    newid = newchild.get("id")
36
    existing = element.find(".//*[@id='" + newid + "']")
37
    if existing is not None:
38
        if not silent_mode:
39
            sys.stderr.write("Notification: this ID is used more than once " +
40
                             "and should represent equivalent elements: " +
41
                             newid + "\n")
42
    else:
43
        element.append(newchild)
44
45
46 2
def _add_elements(body, header):
47
    """Add oval elements to the global Elements defined above"""
48
    global definitions
0 ignored issues
show
Coding Style Naming introduced by
The name definitions does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
49
    global tests
0 ignored issues
show
Coding Style Naming introduced by
The name tests does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
50
    global objects
0 ignored issues
show
Coding Style Naming introduced by
The name objects does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
51
    global states
0 ignored issues
show
Coding Style Naming introduced by
The name states does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
52
    global variables
0 ignored issues
show
Coding Style Naming introduced by
The name variables does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
53
54
    tree = ET.fromstring(header + body + footer)
55
    tree = replace_external_vars(tree)
56
    defname = None
57
    # parse new file(string) as an etree, so we can arrange elements
58
    # appropriately
59
    for childnode in tree.findall("./{%s}def-group/*" % ovalns):
60
        # print "childnode.tag is " + childnode.tag
61
        if childnode.tag is ET.Comment:
62
            continue
63
        if childnode.tag == ("{%s}definition" % ovalns):
64
            append(definitions, childnode)
65
            defname = childnode.get("id")
66
            # extend_definition is a special case:  must include a whole other
67
            # definition
68
            for defchild in childnode.findall(".//{%s}extend_definition"
69
                                              % ovalns):
70
                defid = defchild.get("definition_ref")
71
                extend_ref = find_testfile_or_exit(defid)
72
                includedbody = read_ovaldefgroup_file(extend_ref)
73
                # recursively add the elements in the other file
74
                _add_elements(includedbody, header)
75
        if childnode.tag.endswith("_test"):
76
            append(tests, childnode)
77
        if childnode.tag.endswith("_object"):
78
            append(objects, childnode)
79
        if childnode.tag.endswith("_state"):
80
            append(states, childnode)
81
        if childnode.tag.endswith("_variable"):
82
            append(variables, childnode)
83
    return defname
84
85
86 2
def applicable_platforms(oval_file):
87
    """
88
    Returns the applicable platforms for a given oval file
89
    """
90
91 2
    platforms = []
92 2
    header = oval_generated_header("applicable_platforms", "5.11", "0.0.1")
93 2
    body = read_ovaldefgroup_file(oval_file)
94 2
    oval_tree = ET.fromstring(header + body + footer)
95
96 2
    element_path = "./{%s}def-group/{%s}definition/{%s}metadata/{%s}affected/{%s}platform"
97 2
    element_ns_path = element_path % (ovalns, ovalns, ovalns, ovalns, ovalns)
98 2
    for node in oval_tree.findall(element_ns_path):
99 2
        platforms.append(node.text)
100
101 2
    return platforms
102
103
104 2
def replace_external_vars(tree):
105
    """Replace external_variables with local_variables, so the definition can be
106
       tested independently of an XCCDF file"""
107
108
    # external_variable is a special case: we turn it into a local_variable so
109
    # we can test
110
    for node in tree.findall(".//{%s}external_variable" % ovalns):
111
        print("External_variable with id : " + node.get("id"))
112
        extvar_id = node.get("id")
113
        # for envkey, envval in os.environ.iteritems():
114
        #     print envkey + " = " + envval
115
        # sys.exit()
116
        if extvar_id not in os.environ.keys():
117
            print("External_variable specified, but no value provided via "
118
                  "environment variable", file=sys.stderr)
119
            sys.exit(2)
120
        # replace tag name: external -> local
121
        node.tag = "{%s}local_variable" % ovalns
122
        literal = ET.Element("oval:literal_component")
123
        literal.text = os.environ[extvar_id]
124
        node.append(literal)
125
        # TODO: assignment of external_variable via environment vars, for
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
126
        # testing
127
    return tree
128
129
130 2
def find_testfile_or_exit(testfile):
131
    """Find OVAL files in CWD or shared/oval and calls sys.exit if the file is not found"""
132
    _testfile = find_testfile(testfile)
133
    if _testfile is None:
134
        print("ERROR: %s does not exist! Please specify a valid OVAL file." % testfile,
135
              file=sys.stderr)
136
        sys.exit(1)
137
    else:
138
        return _testfile
139
140
141 2
def find_testfile(oval_id):
142
    """
143
    Find OVAL file by id in CWD, SHARED_OVAL, or LINUX_OS_GUIDE. Understands rule
144
    directories and defaults to returning shared.xml over {{{ product }}}.xml.
145
146
    Returns path to OVAL file or None if not found.
147
    """
148 2
    if os.path.isfile(os.path.abspath(oval_id)):
149
        return os.path.abspath(oval_id)
150
151 2
    if oval_id.endswith(".xml"):
152 2
        oval_id, _ = os.path.splitext(oval_id)
153 2
        oval_id = os.path.basename(oval_id)
154
155 2
    candidates = [oval_id, "%s.xml" % oval_id]
156
157 2
    found_file = None
158 2
    for path in ['.', SHARED_OVAL, LINUX_OS_GUIDE]:
159 2
        for root, _, _ in os.walk(path):
160 2
            for candidate in candidates:
161 2
                search_file = os.path.join(root, candidate).strip()
162 2
                if os.path.isfile(search_file):
163
                    found_file = search_file
164
                    break
165
166 2
        for rule_dir in find_rule_dirs(path):
167 2
            rule_id = get_rule_dir_id(rule_dir)
168 2
            if rule_id == oval_id:
169 2
                ovals = get_rule_dir_ovals(rule_dir, product="shared")
170 2
                if ovals:
171 2
                    found_file = ovals[0]
172 2
                    break
173
174 2
    return found_file
175
176
177 2
def read_ovaldefgroup_file(testfile):
178
    """Read oval files"""
179 2
    with open(testfile, 'r') as test_file:
180 2
        body = test_file.read()
181 2
    return body
182
183
184 2
def get_openscap_supported_oval_version():
0 ignored issues
show
Coding Style Naming introduced by
The name get_openscap_supported_oval_version does not conform to the function naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
185
    try:
186
        from openscap import oscap_get_version
187
        if [int(x) for x in str(oscap_get_version()).split(".")] >= [1, 2, 0]:
188
            return "5.11"
189
    except ImportError:
190
        pass
191
192
    return "5.10"
193
194
195 2
def parse_options():
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
196
    usage = "usage: %(prog)s [options] definition_file.xml"
197
    parser = argparse.ArgumentParser(usage=usage)
198
    # only some options are on by default
199
200
    oscap_oval_version = get_openscap_supported_oval_version()
201
202
    parser.add_argument("--oval_version",
203
                        default=oscap_oval_version,
204
                        dest="oval_version", action="store",
205
                        help="OVAL version to use. Example: 5.11, 5.10, ... "
206
                             "If not supplied the highest version supported by "
207
                             "openscap will be used: %s" % (oscap_oval_version))
208
    parser.add_argument("-q", "--quiet", "--silent", default=False,
209
                        action="store_true", dest="silent_mode",
210
                        help="Don't show any output when testing OVAL files")
211
    parser.add_argument("xmlfile", metavar="XMLFILE", help="OVAL XML file")
212
    args = parser.parse_args()
213
214
    return args
215
216
217 2
def main():
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
218
    global definitions
0 ignored issues
show
Coding Style Naming introduced by
The name definitions does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
219
    global tests
0 ignored issues
show
Coding Style Naming introduced by
The name tests does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
220
    global objects
0 ignored issues
show
Coding Style Naming introduced by
The name objects does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
221
    global states
0 ignored issues
show
Coding Style Naming introduced by
The name states does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
222
    global variables
0 ignored issues
show
Coding Style Naming introduced by
The name variables does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
223
    global silent_mode
0 ignored issues
show
Coding Style Naming introduced by
The name silent_mode does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
224
225
    args = parse_options()
226
    silent_mode = args.silent_mode
227
    oval_version = args.oval_version
228
229
    testfile = args.xmlfile
230
    header = oval_generated_header("testoval.py", oval_version, "0.0.1")
231
    testfile = find_testfile_or_exit(testfile)
232
    body = read_ovaldefgroup_file(testfile)
233
234
    defname = _add_elements(body, header)
235
    if defname is None:
236
        print("Error while evaluating oval: defname not set; missing "
237
              "definitions section?")
238
        sys.exit(1)
239
240
    ovaltree = ET.fromstring(header + footer)
241
242
    # append each major element type, if it has subelements
243
    for element in [definitions, tests, objects, states, variables]:
244
        if list(element):
245
            ovaltree.append(element)
246
247
    # re-map all the element ids from meaningful names to meaningless
248
    # numbers
249
    testtranslator = IDTranslator("scap-security-guide.testing")
250
    ovaltree = testtranslator.translate(ovaltree)
251
    (ovalfile, fname) = tempfile.mkstemp(prefix=defname, suffix=".xml")
252
    os.write(ovalfile, ET.tostring(ovaltree))
253
    os.close(ovalfile)
254
255
    cmd = ['oscap', 'oval', 'eval', '--results', fname + '-results', fname]
256
    if not silent_mode:
257
        print("Evaluating with OVAL tempfile: " + fname)
258
        print("OVAL Schema Version: %s" % oval_version)
259
        print("Writing results to: " + fname + "-results")
260
        print("Running command: %s\n" % " ".join(cmd))
261
262
    oscap_child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
263
    cmd_out = oscap_child.communicate()[0]
264
265
    if isinstance(cmd_out, bytes):
266
        cmd_out = cmd_out.decode('utf-8')
267
268
    if not silent_mode:
269
        print(cmd_out, file=sys.stderr)
270
271
    if oscap_child.returncode != 0:
272
        if not silent_mode:
273
            print("Error launching 'oscap' command: return code %d" % oscap_child.returncode)
274
        sys.exit(2)
275
276
    if 'false' in cmd_out or 'error' in cmd_out:
277
        # at least one from the evaluated OVAL definitions evaluated to
278
        # 'false' result, exit with '1' to indicate OVAL scan FAIL result
279
        sys.exit(1)
280
281
    # perhaps delete tempfile?
282
    definitions = ET.Element("oval:definitions")
283
    tests = ET.Element("oval:tests")
284
    objects = ET.Element("oval:objects")
285
    states = ET.Element("oval:states")
286
    variables = ET.Element("oval:variables")
287
288
    # 'false' keyword wasn't found in oscap's command output
289
    # exit with '0' to indicate OVAL scan TRUE result
290
    sys.exit(0)
291