Test Failed
Push — master ( ad184d...ba033d )
by Milan
01:39 queued 15s
created

ssg.cce.CCEFile.read_cces()   A

Complexity

Conditions 4

Size

Total Lines 10
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 14.7187

Importance

Changes 0
Metric Value
cc 4
eloc 10
nop 1
dl 0
loc 10
ccs 1
cts 8
cp 0.125
crap 14.7187
rs 9.9
c 0
b 0
f 0
1 2
import re
2 2
import random
3 2
import os
4
5
6 2
CCE_POOLS = dict()
7
8
9 2
class CCEFile:
10 2
    def __init__(self, project_root=None):
11
        if not project_root:
12
            project_root = os.path.join(
13
                os.path.dirname(os.path.abspath(__file__)), "..")
14
        self.project_root = project_root
15
16 2
    @property
17
    def absolute_path(self):
18
        raise NotImplementedError()
19
20 2
    def line_to_cce(self, line):
21
        return line
22
23 2
    def line_isnt_cce(self, cce, line):
24
        return line != cce
25
26 2
    def read_cces(self):
27
        with open(self.absolute_path, "r") as f:
28
            cces = f.read().splitlines()
29
        for cce in cces:
30
            if not is_cce_value_valid(cce):
31
                msg = (
32
                    "Invalid CCE detected in {cce_path}: {cce}"
33
                    .format(cce=cce, cce_path=self.absolute_path))
34
                raise RuntimeError(msg)
35
        return cces
36
37 2
    def remove_cce_from_file(self, cce):
38
        file_lines = self.read_cces()
39
        lines_except_cce = [
40
            line for line in file_lines
41
            if self.line_isnt_cce(cce, line)
42
        ]
43
        with open(self.absolute_path, "w") as f:
44
            f.write("\n".join(lines_except_cce) + "\n")
45
46 2
    def random_cce(self):
47
        cces = self.read_cces()
48
        random.shuffle(cces)
49
        return cces[0].strip()
50
51
52 2
class RedhatCCEFile(CCEFile):
53 2
    @property
54
    def absolute_path(self):
55
        return os.path.join(self.project_root, "shared", "references", "cce-redhat-avail.txt")
56
57
58 2
CCE_POOLS["redhat"] = RedhatCCEFile
59
60
61 2
def is_cce_format_valid(cceid):
62
    """
63
    IF CCE ID IS IN VALID FORM (either 'CCE-XXXX-X' or 'CCE-XXXXX-X'
64
    where each X is a digit, and the final X is a check-digit)
65
    based on Requirement A17:
66
67
    http://people.redhat.com/swells/nist-scap-validation/scap-val-requirements-1.2.html
68
    """
69 2
    match = re.match(r'^CCE-\d{4,5}-\d$', cceid)
70 2
    return match is not None
71
72
73 2
def is_cce_value_valid(cceid):
74
    # For context, see:
75
    # https://github.com/ComplianceAsCode/content/issues/3044#issuecomment-420844095
76
77
    # concat(substr ... , substr ...) -- just remove non-digit characters.
78
    # Since we've already validated format, this hack suffices:
79 2
    cce = re.sub(r'(CCE|-)', '', cceid)
80
81
    # The below is an implementation of Luhn's algorithm as this is what the
82
    # XPath code does.
83
84
    # First, map string numbers to integers. List cast is necessary to be able
85
    # to index it.
86 2
    digits = list(map(int, cce))
87
88
    # Even indices are doubled. Coerce to list for list addition. However,
89
    # XPath uses 1-indexing so "evens" and "odds" are swapped from Python.
90
    # We handle both the idiv and the mod here as well; note that we only
91
    # hvae to do this for evens: no single digit is above 10, so the idiv
92
    # always returns 0 and the mod always returns the original number.
93 2
    evens = list(map(lambda i: (i*2)//10 + (i*2) % 10, digits[-2::-2]))
94 2
    odds = digits[-1::-2]
95
96
    # The checksum value is now the sum of the evens and the odds.
97 2
    value = sum(evens + odds) % 10
98
99
    # Valid CCE <=> value == 0
100
    return value == 0
101