Source code for ssg.oval_object_model.oval_entities.definition

import logging

from ... import utils
from ...constants import BOOL_TO_STR, MULTI_PLATFORM_LIST, OVAL_NAMESPACES, STR_TO_BOOL
from ...xml import ElementTree
from ..general import (
    OVALBaseObject,
    OVALComponent,
    load_notes,
    required_attribute,
    is_product_name_in,
    get_product_name,
)


[docs] class GeneralCriteriaNode(OVALBaseObject): negate = False comment = "" applicability_check = False
[docs] def get_xml_element(self): el = ElementTree.Element(self.tag_name) if self.applicability_check: el.set("applicability_check", BOOL_TO_STR[self.applicability_check]) if self.negate: el.set("negate", BOOL_TO_STR[self.negate]) if self.comment != "": el.set("comment", self.comment) return el
[docs] def load_terminate_criteria(xml_el, class_, ref_prefix): terminate_criteria = class_( xml_el.tag, required_attribute(xml_el, "{}_ref".format(ref_prefix)), ) terminate_criteria.negate = STR_TO_BOOL.get(xml_el.get("negate"), False) terminate_criteria.comment = xml_el.get("comment", "") terminate_criteria.applicability_check = STR_TO_BOOL.get( xml_el.get("applicability_check"), False ) return terminate_criteria
[docs] class TerminateCriteriaNode(GeneralCriteriaNode): prefix_ref = "" def __init__(self, tag, ref): super(TerminateCriteriaNode, self).__init__(tag) self.ref = ref
[docs] def get_xml_element(self): el = super(TerminateCriteriaNode, self).get_xml_element() el.set("{}_ref".format(self.prefix_ref), self.ref) return el
[docs] def translate_id(self, translator, store_defname=False): self.ref = translator.generate_id( "{}{}".format(self.namespace, self.prefix_ref), self.ref )
# -----
[docs] class Criterion(TerminateCriteriaNode): prefix_ref = "test"
# -----
[docs] class ExtendDefinition(TerminateCriteriaNode): prefix_ref = "definition"
# -----
[docs] def load_criteria(oval_criteria_xml_el): criteria = Criteria(oval_criteria_xml_el.tag) criteria.operator = oval_criteria_xml_el.get("operator", "AND") criteria.negate = STR_TO_BOOL.get(oval_criteria_xml_el.get("negate"), False) criteria.comment = oval_criteria_xml_el.get("comment", "") criteria.applicability_check = STR_TO_BOOL.get( oval_criteria_xml_el.get("applicability_check"), False ) for child_node_el in oval_criteria_xml_el: if child_node_el.tag.endswith("criteria"): criteria.add_child_criteria_node(load_criteria(child_node_el)) elif child_node_el.tag.endswith("criterion"): criteria.add_child_criteria_node( load_terminate_criteria(child_node_el, Criterion, "test") ) elif child_node_el.tag.endswith("extend_definition"): criteria.add_child_criteria_node( load_terminate_criteria(child_node_el, ExtendDefinition, "definition") ) else: logging.warning("Unknown element '{}'\n".format(child_node_el.tag)) return criteria
[docs] class Criteria(GeneralCriteriaNode): operator = "AND" def __init__(self, tag): super(Criteria, self).__init__(tag) self.child_criteria_nodes = []
[docs] def add_child_criteria_node(self, child): if not isinstance(child, (Criteria, Criterion, ExtendDefinition)): raise TypeError("Unexpected child type of Criteria!") self.child_criteria_nodes.append(child)
[docs] def get_xml_element(self): criteria_el = super(Criteria, self).get_xml_element() criteria_el.set("operator", self.operator) for child_criteria_node in self.child_criteria_nodes: criteria_el.append(child_criteria_node.get_xml_element()) return criteria_el
def _get_reference(self, ref_type): out = [] for child_criteria_node in self.child_criteria_nodes: if isinstance(child_criteria_node, ref_type): out.append(child_criteria_node.ref) elif isinstance(child_criteria_node, Criteria): out.extend(child_criteria_node._get_reference(ref_type)) return out
[docs] def get_test_references(self): return self._get_reference(Criterion)
[docs] def get_extend_definition_references(self): return self._get_reference(ExtendDefinition)
[docs] def translate_id(self, translator, store_defname=False): for child_criteria_node in self.child_criteria_nodes: child_criteria_node.translate_id(translator)
# -----
[docs] def load_references(all_reference_elements): out = [] for ref_el in all_reference_elements: ref = Reference( ref_el.tag, required_attribute(ref_el, "source"), required_attribute(ref_el, "ref_id"), ) ref.ref_url = ref_el.get("ref_url", "") out.append(ref) return out if out else None
[docs] class Reference(OVALBaseObject): ref_url = "" def __init__(self, tag, source, ref_id): super(Reference, self).__init__(tag) self.source = source self.ref_id = ref_id
[docs] def get_xml_element(self): reference_el = ElementTree.Element(self.tag_name) reference_el.set("ref_id", self.ref_id) reference_el.set("source", self.source) if self.ref_url != "": reference_el.set("ref_url", self.ref_url) return reference_el
# ----- def _get_tag(el): if el is None: return None return el.tag def _get_list_of_affected(affected_el, element_name): elements = affected_el.findall( "./{%s}%s" % (OVAL_NAMESPACES.definition, element_name) ) if len(elements) == 0: return None return [el.text for el in elements]
[docs] def load_affected(all_affected_elements): out = [] for affected_el in all_affected_elements: affected = Affected( affected_el.tag, required_attribute(affected_el, "family"), ) affected.platform_tag = _get_tag( affected_el.find("./{%s}platform" % OVAL_NAMESPACES.definition) ) affected.product_tag = _get_tag( affected_el.find("./{%s}product" % OVAL_NAMESPACES.definition) ) affected.platforms = _get_list_of_affected(affected_el, "platform") affected.products = _get_list_of_affected(affected_el, "product") out.append(affected) return out if out else None
[docs] class Affected(OVALBaseObject): platform_tag = "platform" product_tag = "product" platforms = None products = None def __init__(self, tag, family): super(Affected, self).__init__(tag) self.family = family
[docs] def finalize_affected_platforms(self, type_, full_name): """ Depending on your use-case of OVAL you may not need the <affected> element. Such use-cases including using OVAL as a check engine for XCCDF benchmarks. Since the XCCDF Benchmarks use cpe:platform with CPE IDs, the affected element in OVAL definitions is redundant and just bloats the files. This function removes all *irrelevant* affected platform elements from given OVAL tree. It then adds one platform of the product we are building. """ setattr(self, "{}s".format(type_), [full_name]) setattr( self, "{}_tag".format(type_), "{%s}%s" % (OVAL_NAMESPACES.definition, type_) )
def _is_in_platforms(self, multi_prod, product): for platform in self.platforms if self.platforms is not None else []: if multi_prod in platform and product in MULTI_PLATFORM_LIST: return True return False
[docs] def is_applicable_for_product(self, product_): """ Based on the <platform> specifier of the OVAL check determine if this OVAL check is applicable for this product. Return 'True' if so, 'False' otherwise """ product, product_version = utils.parse_name(product_) # Define general platforms multi_platforms = ["multi_platform_all", "multi_platform_" + product] # First test if OVAL check isn't for 'multi_platform_all' or # 'multi_platform_' + product for multi_prod in multi_platforms: if self._is_in_platforms(multi_prod, product): return True product_name = get_product_name(product, product_version) # Test if this OVAL check is for the concrete product version if is_product_name_in(self.platforms, product_name): return True if is_product_name_in(self.products, product_name): return True # OVAL check isn't neither a multi platform one, nor isn't applicable # for this product => return False to indicate that return False
def _add_to_affected_element(self, affected_el, elements, tag): for platform in elements if elements is not None else []: platform_el = ElementTree.Element(tag) platform_el.text = platform affected_el.append(platform_el)
[docs] def get_xml_element(self): affected_el = ElementTree.Element(self.tag_name) affected_el.set("family", self.family) self._add_to_affected_element(affected_el, self.platforms, self.platform_tag) self._add_to_affected_element(affected_el, self.products, self.product_tag) return affected_el
# ----- def _get_string_of(element, definition_id, type_): out = "" if element.text is not None: out = element.text else: logging.info( "OVAL definition '{0}' have empty a {1}, which is mandatory".format( definition_id, type_ ) ) return out
[docs] def load_metadata(oval_metadata_xml_el, definition_id): title_el = oval_metadata_xml_el.find("./{%s}title" % OVAL_NAMESPACES.definition) title_str = _get_string_of(title_el, definition_id, "title") description_el = oval_metadata_xml_el.find( "./{%s}description" % OVAL_NAMESPACES.definition ) description_str = _get_string_of(description_el, definition_id, "description") all_affected_elements = oval_metadata_xml_el.findall( "./{%s}affected" % OVAL_NAMESPACES.definition ) all_reference_elements = oval_metadata_xml_el.findall( "./{%s}reference" % OVAL_NAMESPACES.definition ) metadata = Metadata(oval_metadata_xml_el.tag) metadata.title = title_str metadata.description = description_str metadata.array_of_affected = load_affected(all_affected_elements) metadata.array_of_references = load_references(all_reference_elements) return metadata
[docs] class Metadata(OVALBaseObject): array_of_affected = None array_of_references = None title = "" description = "" title_tag = "title" description_tag = "description"
[docs] def finalize_affected_platforms(self, type_, full_name): """ Depending on your use-case of OVAL you may not need the <affected> element. Such use-cases including using OVAL as a check engine for XCCDF benchmarks. Since the XCCDF Benchmarks use cpe:platform with CPE IDs, the affected element in OVAL definitions is redundant and just bloats the files. This function removes all *irrelevant* affected platform elements from given OVAL tree. It then adds one platform of the product we are building. """ for affected in self.array_of_affected: affected.finalize_affected_platforms(type_, full_name)
[docs] def is_applicable_for_product(self, product): """ Based on the <platform> specifier of the OVAL check determine if this OVAL check is applicable for this product. Return 'True' if so, 'False' otherwise """ for affected in self.array_of_affected: if affected.is_applicable_for_product(product): return True return False
[docs] def add_reference(self, ref_id, source): if self.array_of_references is None: self.array_of_references = [] self.array_of_references.append( Reference( "{%s}reference" % OVAL_NAMESPACES.definition, source, ref_id, ) )
@staticmethod def _add_sub_elements_from_arrays(el, array): for item in array if array is not None else []: el.append(item.get_xml_element())
[docs] def get_xml_element(self): metadata_el = ElementTree.Element(self.tag_name) title_el = ElementTree.Element("{}{}".format(self.namespace, self.title_tag)) title_el.text = self.title metadata_el.append(title_el) self._add_sub_elements_from_arrays(metadata_el, self.array_of_affected) self._add_sub_elements_from_arrays(metadata_el, self.array_of_references) description_el = ElementTree.Element( "{}{}".format(self.namespace, self.description_tag) ) description_el.text = self.description metadata_el.append(description_el) return metadata_el
# -----
[docs] def load_definition(oval_definition_xml_el): metadata_el = oval_definition_xml_el.find( "./{%s}metadata" % OVAL_NAMESPACES.definition ) notes_el = oval_definition_xml_el.find("./{%s}notes" % OVAL_NAMESPACES.definition) criteria_el = oval_definition_xml_el.find( "./{%s}criteria" % OVAL_NAMESPACES.definition ) definition_id = required_attribute(oval_definition_xml_el, "id") definition = Definition( oval_definition_xml_el.tag, definition_id, required_attribute(oval_definition_xml_el, "class"), load_metadata(metadata_el, definition_id), ) definition.deprecated = STR_TO_BOOL.get( oval_definition_xml_el.get("deprecated", ""), False ) definition.notes = load_notes(notes_el) definition.version = required_attribute(oval_definition_xml_el, "version") definition.criteria = load_criteria(criteria_el) return definition
[docs] class Definition(OVALComponent): criteria = None def __init__(self, tag, id_, class_, metadata): super(Definition, self).__init__(tag, id_) self.class_ = class_ self.metadata = metadata
[docs] def check_affected(self): if ( self.metadata.array_of_affected is None or not self.metadata.array_of_affected ): raise ValueError( "Definition '{}' doesn't contain OVAL 'affected' element".format( self.id_ ) )
[docs] def is_applicable_for_product(self, product): return self.metadata.is_applicable_for_product(product)
[docs] def get_xml_element(self): definition_el = super(Definition, self).get_xml_element() definition_el.set("class", self.class_) definition_el.append(self.metadata.get_xml_element()) if self.criteria: definition_el.append(self.criteria.get_xml_element()) return definition_el
[docs] def translate_id(self, translator, store_defname=False): super(Definition, self).translate_id(translator, store_defname) self.criteria.translate_id(translator) if store_defname: self.metadata.add_reference(self.name, translator.content_id)