diff --git a/openscap_report/report_generators/html_templates/js/oval_graph_generation_script.js b/openscap_report/report_generators/html_templates/js/oval_graph_generation_script.js index 5f889bc8..3b95aad9 100644 --- a/openscap_report/report_generators/html_templates/js/oval_graph_generation_script.js +++ b/openscap_report/report_generators/html_templates/js/oval_graph_generation_script.js @@ -28,6 +28,14 @@ function show_OVAL_details(event) { // eslint-disable-line no-unused-vars event.currentTarget.param_this.setAttribute('aria-label', event.currentTarget.param_this.textContent); } +function show_OVAL_referenced_endpoints(event) { // eslint-disable-line no-unused-vars + event.currentTarget.param_this.classList.toggle('pf-m-expanded'); + event.currentTarget.param_this.setAttribute('aria-expanded', event.currentTarget.param_this.aria_expanded === "false"? "true": "false"); + event.currentTarget.param_content.classList.toggle('pf-m-expanded'); + hide_or_show(event.currentTarget.param_content); // eslint-disable-line no-undef + +} + // OVAL graph generation constants @@ -90,9 +98,11 @@ const COL = document.createElement("td"); const HEADER_COL = document.createElement("th"); const H1 = document.createElement("h1"); +const H3 = document.createElement("h3"); const BR = document.createElement("br"); const B = document.createElement("b"); +const HR = document.createElement("hr"); const SMALL = document.createElement("small"); const I = document.createElement("i"); @@ -616,7 +626,7 @@ function get_OVAL_object_info_heading(oval_object) { return div; } -function generate_OVAL_object(oval_object, div) { +function generate_OVAL_object(test_info, oval_object, div) { if (oval_object === undefined) { // eslint-disable-next-line no-console console.error("Error: The test information has no OVAL Objects."); @@ -628,11 +638,14 @@ function generate_OVAL_object(oval_object, div) { div.appendChild(table_div); generate_property_elements(table_div, oval_object, oval_object.object_data); + + if (oval_object.object_id in test_info.map_referenced_oval_endpoints) { + generate_referenced_endpoints(test_info, oval_object.object_id, table_div); + } } function get_OVAL_state_heading() { const div = DIV.cloneNode(); - div.appendChild(BR.cloneNode()); const h1 = H1.cloneNode(); h1.textContent ='OVAL State definitions: '; h1.className = "pf-c-title pf-m-lg"; @@ -640,23 +653,32 @@ function get_OVAL_state_heading() { return div; } -function get_OVAL_state_info(oval_state) { +function get_OVAL_state_info(oval_state, off_heading=false) { const div = DIV.cloneNode(); - + if(off_heading) { + const h1 = H1.cloneNode(); + h1.textContent ='OVAL State definition: '; + h1.className = "pf-c-title pf-m-lg"; + div.appendChild(h1); + } div.appendChild(get_label("pf-m-blue", `OVAL State ID: ${oval_state.state_id}\u00A0`, undefined, "", "", oval_state.comment)); return div; } -function generate_OVAL_state(oval_state, div) { +// eslint-disable-next-line max-params +function generate_OVAL_state(test_info, oval_state, div, off_heading=false) { if (oval_state === null) { return; } - div.appendChild(get_OVAL_state_info(oval_state)); + div.appendChild(get_OVAL_state_info(oval_state, off_heading)); const table_div = DIV.cloneNode(); table_div.className = "pf-c-scroll-inner-wrapper oval-test-detail-table"; div.appendChild(table_div); generate_property_elements(table_div, oval_state, oval_state.state_data); + if (oval_state.state_id in test_info.map_referenced_oval_endpoints) { + generate_referenced_endpoints(test_info, oval_state.state_id, table_div); + } } function get_OVAL_variable_info_heading(oval_variable) { @@ -672,7 +694,7 @@ function get_OVAL_variable_info_heading(oval_variable) { return div; } -function generate_OVAL_variable(oval_variable, div) { +function generate_OVAL_variable(test_info, oval_variable, div) { if (oval_variable === null) { return; } @@ -682,6 +704,9 @@ function generate_OVAL_variable(oval_variable, div) { div.appendChild(table_div); generate_property_elements(table_div, oval_variable, oval_variable.variable_data); + if (oval_variable.variable_id in test_info.map_referenced_oval_endpoints) { + generate_referenced_endpoints(test_info, oval_variable.variable_id, table_div); + } } function generate_OVAL_error_message(test_info, div) { @@ -714,33 +739,84 @@ function generate_OVAL_error_message(test_info, div) { div.appendChild(BR.cloneNode()); } -function generate_referenced_endpoints(test_info, div) { - if (Object.keys(test_info.referenced_oval_endpoints).length > 0) { - const h1 = H1.cloneNode(); - h1.textContent ='Referenced endpoints: '; - h1.className = "pf-c-title pf-m-lg"; - div.appendChild(BR.cloneNode()); - div.appendChild(h1); - for (const [id, endpoint] of Object.entries(test_info.referenced_oval_endpoints)) { // eslint-disable-line array-element-newline - if(id.includes(":var:")) { - generate_OVAL_variable(endpoint, div); - } else if(id.includes(":obj:")) { - generate_OVAL_object(endpoint, div); - } else if(id.includes(":ste:")) { - div.appendChild(BR.cloneNode()); - const heading = H1.cloneNode(); - heading.textContent ='OVAL State definition: '; - heading.className = "pf-c-title pf-m-lg"; - div.appendChild(heading); - generate_OVAL_state(endpoint, div); - } else { - // eslint-disable-next-line no-console - console.error("Not implemented endpoint type!"); - } - } +function add_header_referenced_endpoints_and_get_body(div) { + const container = DIV.cloneNode(); + container.className = "pf-c-accordion pf-m-bordered"; + + const h3 = H3.cloneNode(); + const button = BUTTON.cloneNode(); + button.className = "pf-c-accordion__toggle pf-m-expanded"; + button.setAttribute("type", "button"); + button.setAttribute("aria-expanded","false"); + button.addEventListener("click", show_OVAL_referenced_endpoints, false); + button.param_this = button; + + const title = SPAN.cloneNode(); + title.className = "pf-c-accordion__toggle-text"; + title.textContent = "Referenced endpoints"; + + button.appendChild(title); + const icon = SPAN.cloneNode(); + icon.className = "pf-c-accordion__toggle-icon"; + const i = ICON.cloneNode(); + i.className = "fas fa-angle-right"; + i.setAttribute("aria-hidden", "true"); + icon.appendChild(i); + button.appendChild(icon); + + h3.appendChild(button); + + const content = DIV.cloneNode(); + content.className = "pf-c-accordion__expanded-content"; + content.style.display = "none"; + const body = DIV.cloneNode(); + body.className = "pf-c-accordion__expanded-content-body"; + content.appendChild(body); + button.param_content = content; + + container.appendChild(h3); + container.appendChild(content); + + div.appendChild(container); + return body; +} + +function generate_endpoint(id, test_info, body) { + const endpoint = test_info.referenced_oval_endpoints[id]; + + if(id.includes(":var:")) { + generate_OVAL_variable(test_info, endpoint, body); + } else if(id.includes(":obj:")) { + generate_OVAL_object(test_info, endpoint, body); + } else if(id.includes(":ste:")) { + generate_OVAL_state(test_info, endpoint, body, true); + } else { + // eslint-disable-next-line no-console + console.error("Not implemented endpoint type!"); } } +function generate_referenced_endpoints(test_info, children_id, div) { + if (test_info.map_referenced_oval_endpoints[children_id].length == 0) { + return; + } + + const body = add_header_referenced_endpoints_and_get_body(div); + + for (const id of test_info.map_referenced_oval_endpoints[children_id]) { + generate_endpoint(id, test_info, body); + } +} + +function get_spacer() { + const hr = HR.cloneNode(); + const spacer = DIV.cloneNode(); + spacer.appendChild(BR.cloneNode()); + spacer.appendChild(hr); + spacer.appendChild(BR.cloneNode()); + return spacer; +} + function get_OVAL_test_info(test_info) { const div = DIV.cloneNode(); div.className = "pf-c-accordion__expanded-content-body"; @@ -756,16 +832,18 @@ function get_OVAL_test_info(test_info) { generate_OVAL_error_message(test_info, div); } - generate_OVAL_object(test_info.oval_object, div); + generate_OVAL_object(test_info, test_info.oval_object, div); + + div.appendChild(get_spacer()); if (test_info.oval_states.length > 0) { div.appendChild(get_OVAL_state_heading()); } for (const oval_state of test_info.oval_states) { - generate_OVAL_state(oval_state, div); + generate_OVAL_state(test_info, oval_state, div); + div.appendChild(get_spacer()); } - generate_referenced_endpoints(test_info, div); return div; } diff --git a/openscap_report/scap_results_parser/data_structures/oval_test.py b/openscap_report/scap_results_parser/data_structures/oval_test.py index b14bac0b..79a3a74d 100644 --- a/openscap_report/scap_results_parser/data_structures/oval_test.py +++ b/openscap_report/scap_results_parser/data_structures/oval_test.py @@ -21,6 +21,7 @@ class OvalTest: # pylint: disable=R0902 referenced_oval_endpoints: Dict[ str, Union[OvalObject, OvalState, OvalVariable] ] = field(default_factory=dict) + map_referenced_oval_endpoints: Dict[str, list] = field(default_factory=dict) def as_dict(self): return asdict(self) diff --git a/openscap_report/scap_results_parser/parsers/oval_test_parser.py b/openscap_report/scap_results_parser/parsers/oval_test_parser.py index 36e083f0..160b9885 100644 --- a/openscap_report/scap_results_parser/parsers/oval_test_parser.py +++ b/openscap_report/scap_results_parser/parsers/oval_test_parser.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later import logging +from collections import defaultdict from ..data_structures import OvalTest from ..namespaces import NAMESPACES @@ -78,43 +79,57 @@ def _get_variables_by_id(self): return self._get_data_by_id(data) @staticmethod - def _iter_over_data_and_get_references(dict_, out): + def _iter_over_data_and_get_references(dict_, out, data_id, map_referenced_oval_endpoints): for key, value in dict_.items(): if isinstance(value, dict): - OVALTestParser._iter_over_data_and_get_references(value, out) + OVALTestParser._iter_over_data_and_get_references( + value, out, data_id, map_referenced_oval_endpoints + ) else: matches_key = ["object_reference", "var_ref", "object_ref", "filter"] matches_val = [":var:", ":obj:", ":ste:"] if any(s in key for s in matches_key) and any(s in value for s in matches_val): out.append(value) + if value not in map_referenced_oval_endpoints[data_id]: + map_referenced_oval_endpoints[data_id].append(value) - def _resolve_reference(self, ref_id, new_ref, out): + def _resolve_reference(self, ref_id, new_ref, out, map_referenced_oval_endpoints): if ":var:" in ref_id: variable = self.variable_parser.get_variable(ref_id) - self._iter_over_data_and_get_references(variable.variable_data, new_ref) + self._iter_over_data_and_get_references( + variable.variable_data, new_ref, ref_id, map_referenced_oval_endpoints + ) out[ref_id] = variable elif ":obj:" in ref_id: object_ = self.objects_parser.get_object(ref_id) - self._iter_over_data_and_get_references(object_.object_data, new_ref) + self._iter_over_data_and_get_references( + object_.object_data, new_ref, ref_id, map_referenced_oval_endpoints + ) out[ref_id] = object_ elif ":ste:" in ref_id: state = self.states_parser.get_state(ref_id) - self._iter_over_data_and_get_references(state.state_data, new_ref) + self._iter_over_data_and_get_references( + state.state_data, new_ref, ref_id, map_referenced_oval_endpoints + ) out[ref_id] = state else: logging.warning(ref_id) - def _get_referenced_endpoints(self, oval_object, oval_states): + def _get_referenced_endpoints(self, oval_object, oval_states, map_referenced_oval_endpoints): references = [] object_data = oval_object.object_data if oval_object is not None else {} - self._iter_over_data_and_get_references(object_data, references) + self._iter_over_data_and_get_references( + object_data, references, oval_object.object_id, map_referenced_oval_endpoints, + ) for state in oval_states: - self._iter_over_data_and_get_references(state.state_data, references) + self._iter_over_data_and_get_references( + state.state_data, references, state.state_id, map_referenced_oval_endpoints, + ) out = {} while len(references) != 0: ref = references.pop() - self._resolve_reference(ref, references, out) + self._resolve_reference(ref, references, out, map_referenced_oval_endpoints) return out def get_test_info(self, test_id): @@ -134,7 +149,11 @@ def get_test_info(self, test_id): for oval_state_el in list_state_of_test: oval_states.append(self.states_parser.get_state(oval_state_el.get("state_ref", ""))) - referenced_oval_endpoints = self._get_referenced_endpoints(oval_object, oval_states) + map_referenced_oval_endpoints = defaultdict(list) + + referenced_oval_endpoints = self._get_referenced_endpoints( + oval_object, oval_states, map_referenced_oval_endpoints + ) return OvalTest( test_id=test_id, @@ -145,4 +164,5 @@ def get_test_info(self, test_id): oval_object=oval_object, oval_states=oval_states, referenced_oval_endpoints=referenced_oval_endpoints, + map_referenced_oval_endpoints=dict(map_referenced_oval_endpoints), )