Skip to content

Commit

Permalink
Merge pull request #235 from jan-cerny/collected_items
Browse files Browse the repository at this point in the history
Show collected items data
  • Loading branch information
Mab879 committed Jun 26, 2024
2 parents 813a440 + ecf485a commit 4c53b29
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,76 @@ function get_OVAL_object_info_heading(oval_object) {
return div;
}

function generate_collected_items(collected_items, div) {
const h1 = H1.cloneNode();
h1.textContent = "Collected Items:";
h1.className = "pf-c-title pf-m-lg";
div.appendChild(h1);

if (collected_items == null) {
const explanation = DIV.cloneNode();
explanation.textContent = "No items have been collected from the target.";
div.appendChild(explanation);
return;
}

const table_div = DIV.cloneNode();
table_div.className = "pf-c-scroll-inner-wrapper oval-test-detail-table";
div.appendChild(table_div);

const table = TABLE.cloneNode();
table.className = "pf-c-table pf-m-compact pf-m-grid-md";
table.setAttribute("role", "grid");
table_div.appendChild(table);

const table_thead = THEAD.cloneNode();
const row = ROW.cloneNode();
row.setAttribute("role", "row");
table_thead.appendChild(row);

const fragment = document.createDocumentFragment();
const header_col = HEADER_COL.cloneNode();
header_col.setAttribute("role", "columnheader");
header_col.setAttribute("scope", "col");
header_col.className = "pf-m-truncate pf-m-fit-content";

for (const item of collected_items.header) {
const clone_header_col = header_col.cloneNode();
clone_header_col.textContent = format_header_item(item);
fragment.appendChild(clone_header_col);
}
row.appendChild(fragment);
table.appendChild(table_thead);

const tbody = TBODY.cloneNode();
tbody.setAttribute("role", "rowgroup");
const rows_fragment = document.createDocumentFragment();

const col = COL.cloneNode();
col.setAttribute("role", "cell");
col.className = "pf-m-truncate pf-m-fit-content";

for (const entry of collected_items.entries) {
const clone_of_row = row.cloneNode();
rows_fragment.appendChild(clone_of_row);
const cols_fragment = document.createDocumentFragment();
for (const value of entry) {
const clone_col = col.cloneNode();
clone_col.textContent = value;
cols_fragment.appendChild(clone_col);
}
clone_of_row.appendChild(cols_fragment);
}
tbody.appendChild(rows_fragment);
table.appendChild(tbody);

if (collected_items.message != null) {
const msg = DIV.cloneNode();
msg.textContent = collected_items.message;
div.appendChild(msg);
}
}

function generate_OVAL_object(test_info, oval_object, div) {
if (oval_object === undefined) {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -836,13 +906,15 @@ function get_OVAL_test_info(test_info) {

div.appendChild(get_spacer());

generate_collected_items(test_info.oval_object.collected_items, div);

if (test_info.oval_states.length > 0) {
div.appendChild(get_spacer());
div.appendChild(get_OVAL_state_heading());
}

for (const oval_state of test_info.oval_states) {
generate_OVAL_state(test_info, oval_state, div);
div.appendChild(get_spacer());
}
return div;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .group import Group
from .identifier import Identifier
from .oval_definition import OvalDefinition
from .oval_items import OVALItems
from .oval_node import OvalNode
from .oval_object import OvalObject, OvalObjectMessage
from .oval_reference import OvalReference
Expand Down
16 changes: 16 additions & 0 deletions openscap_report/scap_results_parser/data_structures/oval_items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2024, Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later

from typing import List, Tuple

from openscap_report.dataclasses import asdict, dataclass, field


@dataclass
class OVALItems:
header: Tuple[str] = field(default_factory=tuple)
entries: List[Tuple[str]] = field(default_factory=list)
message: str = None

def as_dict(self):
return asdict(self)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from typing import Dict

from openscap_report.dataclasses import asdict, dataclass, field
from openscap_report.scap_results_parser.data_structures.oval_items import \
OVALItems


@dataclass
Expand All @@ -20,6 +22,7 @@ class OvalObject:
comment: str = ""
object_type: str = ""
object_data: Dict[str, str] = field(default_factory=dict)
collected_items: OVALItems = None

def as_dict(self):
return asdict(self)
1 change: 1 addition & 0 deletions openscap_report/scap_results_parser/namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
'cpe-dict': 'http://cpe.mitre.org/dictionary/2.0',
'ds': 'http://scap.nist.gov/schema/scap/source/1.2',
'cpe-lang': 'http://cpe.mitre.org/language/2.0',
'ind-sys': 'http://oval.mitre.org/XMLSchema/oval-system-characteristics-5#independent'
}
1 change: 1 addition & 0 deletions openscap_report/scap_results_parser/parsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .group_parser import GroupParser
from .known_references import KNOWN_REFERENCES, update_references
from .oval_definition_parser import OVALDefinitionParser
from .oval_items_parser import OVALItemsParser
from .oval_object_parser import OVALObjectParser
from .oval_result_parser import OVALResultParser
from .oval_state_parser import OVALStateParser
Expand Down
71 changes: 71 additions & 0 deletions openscap_report/scap_results_parser/parsers/oval_items_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright 2024, Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later

from ..data_structures import OVALItems
from ..namespaces import NAMESPACES
from .shared_static_methods_of_parser import SharedStaticMethodsOfParser

PROCURED_ITEMS_LIMIT = 100


class OVALItemsParser:
def __init__(self, collected_objects, system_data):
self.collected_objects = collected_objects
self.system_data = system_data

def _get_item(self, item_ref):
item_el = self.system_data.get(item_ref)
item_data = {}
for child_el in item_el:
if child_el.text and child_el.text.strip():
key = SharedStaticMethodsOfParser.get_key_of_xml_element(child_el)
item_data[key] = child_el.text
return item_data

def _get_items(self, references):
items = []
for reference_el in references:
item_ref = reference_el.get("item_ref")
item = self._get_item(item_ref)
items.append(item)
return items

@staticmethod
def _get_header(items):
header = []
for item in items:
for key in item.keys():
if key not in header:
header.append(key)
return tuple(header)

@staticmethod
def _get_entries(header, items):
entries = []
for item in items:
entry = []
for key in header:
entry.append(item.get(key, ""))
entries.append(tuple(entry))
return entries

def get_oval_items(self, object_id):
collected_object_el = self.collected_objects.get(object_id)
if collected_object_el is None:
return None
references = collected_object_el.findall(
"oval-characteristics:reference", NAMESPACES
)
if len(references) == 0:
return None
items = self._get_items(references)
header = self._get_header(items)
entries = self._get_entries(header, items)
message = None
len_entries = len(entries)
if len_entries > PROCURED_ITEMS_LIMIT:
entries = entries[:PROCURED_ITEMS_LIMIT]
message = (
f"Collected {len_entries} items, showing only first "
f"{PROCURED_ITEMS_LIMIT} items")
return OVALItems(header=header, entries=entries, message=message)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..data_structures import OvalObject, OvalObjectMessage
from ..namespaces import NAMESPACES
from .oval_endpoint_information_parser import OVALEndpointInformation
from .oval_items_parser import OVALItemsParser
from .shared_static_methods_of_parser import SharedStaticMethodsOfParser


Expand All @@ -14,6 +15,7 @@ def __init__(self, objects, collected_objects, system_data):
self.objects = objects
self.collected_objects = collected_objects
self.system_data = system_data
self.item_parser = OVALItemsParser(collected_objects, system_data)

def _get_oval_message(self, xml_collected_object):
message = xml_collected_object.find(
Expand All @@ -33,6 +35,7 @@ def get_object(self, id_object):
"comment": xml_object.get("comment", ""),
"object_type": SharedStaticMethodsOfParser.get_key_of_xml_element(xml_object),
"object_data": self._get_items(xml_object),
"collected_items": self.item_parser.get_oval_items(id_object)
}
xml_collected_object = self._get_collected_object_xml(id_object)

Expand Down
119 changes: 119 additions & 0 deletions tests/unit_tests/test_oval_items_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright 2024, Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later

import pytest
from lxml import etree

from openscap_report.scap_results_parser.namespaces import NAMESPACES
from openscap_report.scap_results_parser.parsers.oval_items_parser import \
OVALItemsParser


# pylint: disable = too-many-locals
@pytest.fixture(name="parser")
def fixture_parser():
sc_ns = NAMESPACES["oval-characteristics"]
is_ns = NAMESPACES["ind-sys"]

o1_id = "oval:example:obj:1"
o2_id = "oval:example:obj:2"
o3_id = "oval:example:obj:3"
i1_id = "11117777"
i2_id = "11117777"
i3_id = "33339999"

o1 = etree.Element(
"{%s}object" % sc_ns, nsmap=NAMESPACES, id=o1_id, version="1",
flag="complete")
o1_r1 = etree.Element(
"{%s}reference" % sc_ns, nsmap=NAMESPACES, item_ref=i1_id)
o1.append(o1_r1)

o2 = etree.Element(
"{%s}object" % sc_ns, nsmap=NAMESPACES, id=o2_id, version="1",
flag="complete")
o2_r1 = etree.Element(
"{%s}reference" % sc_ns, nsmap=NAMESPACES, item_ref=i2_id)
o2.append(o2_r1)
o2_r2 = etree.Element(
"{%s}reference" % sc_ns, nsmap=NAMESPACES, item_ref=i3_id)
o2.append(o2_r2)

o3 = etree.Element(
"{%s}object" % sc_ns, nsmap=NAMESPACES, id=o3_id, version="1",
flag="does not exist")

collected_objects = {
o1_id: o1,
o2_id: o2,
o3_id: o3
}

i1 = etree.Element(
"{%s}textfilecontent_item" % is_ns, nsmap=NAMESPACES, id=i1_id,
status="exists")
i1_filepath = etree.Element("{%s}filepath" % is_ns, nsmap=NAMESPACES)
i1_filepath.text = "/var/cities"
i1.append(i1_filepath)
i1_text = etree.Element("{%s}text" % is_ns, nsmap=NAMESPACES)
i1_text.text = "Paris"
i1.append(i1_text)

i2 = etree.Element(
"{%s}textfilecontent_item" % is_ns, nsmap=NAMESPACES, id=i2_id,
status="exists")
i2_filepath = etree.Element("{%s}filepath" % is_ns, nsmap=NAMESPACES)
i2_filepath.text = "/var/cities"
i2.append(i2_filepath)
i2_text = etree.Element("{%s}text" % is_ns, nsmap=NAMESPACES)
i2_text.text = "London"
i2.append(i2_text)

i3 = etree.Element(
"{%s}textfilecontent_item" % is_ns, nsmap=NAMESPACES, id=i3_id,
status="exists")
i3_filepath = etree.Element("{%s}filepath" % is_ns, nsmap=NAMESPACES)
i3_filepath.text = "/var/cities"
i3.append(i3_filepath)
i3_text = etree.Element("{%s}text" % is_ns, nsmap=NAMESPACES)
i3_text.text = "Prague"
i3.append(i3_text)

system_data = {
i1_id: i1,
i2_id: i2,
i3_id: i3,
}

return OVALItemsParser(collected_objects, system_data)


@pytest.mark.unit_test
def test_oval_items_parser_single(parser):
oi = parser.get_oval_items("oval:example:obj:1")
assert oi is not None
assert oi.header == ("filepath", "text")
assert len(oi.entries) == 1
assert oi.entries[0] == ("/var/cities", "London")


@pytest.mark.unit_test
def test_oval_items_parser_multiple(parser):
oi = parser.get_oval_items("oval:example:obj:2")
assert oi is not None
assert oi.header == ("filepath", "text")
assert len(oi.entries) == 2
assert oi.entries[0] == ("/var/cities", "London")
assert oi.entries[1] == ("/var/cities", "Prague")


@pytest.mark.unit_test
def test_oval_items_parser_dne(parser):
oi = parser.get_oval_items("oval:example:obj:3")
assert oi is None


@pytest.mark.unit_test
def test_oval_items_parser_wrong_object_id(parser):
oi = parser.get_oval_items("oval:example:obj:666")
assert oi is None

0 comments on commit 4c53b29

Please sign in to comment.