def validate(self, doc, version=None): """Checks that a STIX document aligns with `suggested authoring practices`_. .. _suggested authoring practices: http://stixproject.github.io/documentation/suggested-practices/ Args: doc: The STIX document. Can be a filename, file-like object, lxml._Element, or lxml._ElementTree instance. version: The version of the STIX document. This will determine the set of best practice rules to check. If ``None`` an attempt will be made to extract the version from `doc`. Returns: An instance of :class:`.BestPracticeValidationResults`. Raises: .UnknownSTIXVersionError: If `version` was ``None`` and `doc` did not contain any version information. .InvalidSTIXVersionError: If discovered version or `version` argument contains an invalid STIX version number. .ValidationError: If there are any issues parsing `doc`. """ root = utils.get_etree_root(doc) version = version or self._get_version(doc) stix.check_version(version) results = self._run_rules(root, version) return results
def test_get_etree_root_element(self): sio = StringIO(XML) tree = etree.parse(sio) newroot = tree.getroot() root = utils.get_etree_root(newroot) lname = etree.QName(root).localname self.assertEqual(lname, XML_ROOT_LOCALNAME)
def _build_required_imports(self, doc, schemaloc=False): root = utils.get_etree_root(doc) if schemaloc: return self._parse_schemaloc(root) return self._get_required_schemas(root)
def get_version(doc): """Returns the version of the `observables` ``Observables`` node. Returns: A dotted-decimal a version string from the ``cybox_major``, ``cybox_minor`` and ``cybox_update`` attribute values. Raises: UnknownVersionError: If `observables` does not contain any of the following attributes: * ``cybox_major_version`` * ``cybox_minor_version`` * ``cybox_update_version`` """ observables = utils.get_etree_root(doc) cybox_major = observables.attrib.get(TAG_CYBOX_MAJOR) cybox_minor = observables.attrib.get(TAG_CYBOX_MINOR) cybox_update = observables.attrib.get(TAG_CYBOX_UPDATE) if not any((cybox_major, cybox_minor, cybox_update)): error = "The input CybOX document has no version information." raise errors.UnknownCyboxVersionError(error) if cybox_update not in (None, '0'): version = "%s.%s.%s" % (cybox_major, cybox_minor, cybox_update) else: version = "%s.%s" % (cybox_major, cybox_minor) return version
def _build_schematron(self, sch, phase=None): """Attempts to build an ``lxml.isoschematron.Schematron`` instance from `sch`. Args: sch: A Schematron document filename, file-like object, etree._Element, or etree._ElementTree. Returns: A ``lxml.isoschematron.Schematron`` instance for `sch`. """ if sch is None: raise ValueError("Input schematron document cannot be None") root = utils.get_etree_root(sch) schematron = lxml.isoschematron.Schematron( root, phase=phase, store_report=True, store_xslt=True, store_schematron=True ) return schematron
def get_xml_validator_class(doc): """Returns the XML validator class required to validate the input `doc`. Args: doc: An XML document. This can be a filename, file-like object, ``etree._Element``, or ``etree._ElementTree`` instance. Returns: An XML Schema validator class (not object instance) which provides validation functionality required to validate `doc`. """ root = utils.get_etree_root(doc) if utils.is_stix(root): return STIXSchemaValidator if utils.is_cybox(root): return CyboxSchemaValidator ns = utils.get_namespace(root) error = ( "Unable determine validator class for input type. Root element " "namespace: {0}" ).format(ns) raise errors.ValidationError(error)
def get_xml_validator_class(doc): """Returns the XML validator class required to validate the input `doc`. Args: doc: An XML document. This can be a filename, file-like object, ``etree._Element``, or ``etree._ElementTree`` instance. Returns: An XML Schema validator class (not object instance) which provides validation functionality required to validate `doc`. """ root = utils.get_etree_root(doc) if utils.is_stix(root): return STIXSchemaValidator if utils.is_cybox(root): return CyboxSchemaValidator ns = utils.get_namespace(root) error = ("Unable determine validator class for input type. Root element " "namespace: {0}").format(ns) raise errors.ValidationError(error)
def test_get_schemaloc_pairs_raises(self): sio = StringIO(XML) root = utils.get_etree_root(sio) self.assertRaises( KeyError, utils.get_schemaloc_pairs, root )
def _get_line(self): """Returns the line number in the input document associated with this error. """ root = utils.get_etree_root(self._doc) xpath = self._xpath_location nsmap = self._error.nsmap node = root.xpath(xpath, namespaces=nsmap)[0] return node.sourceline
def idref_timestamp_resolves(root, idref, timestamp, namespaces): """Determines if an `idref` and `timestamp` pair resolve to an XML component under `root`. """ root = utils.get_etree_root(root) timestamp = utils.parse_timestamp(timestamp) xpath = "//*[@id='{0}']".format(idref) nodes = root.xpath(xpath, namespaces=namespaces) return any(utils.is_equal_timestamp(timestamp, node) for node in nodes)
def inner(*args, **kwargs): try: doc = args[1] except IndexError: doc = kwargs['doc'] # Get the root element for the input doc root = utils.get_etree_root(doc) # Check that the root is a valid CybOX root-level element check_root(root) return func(*args, **kwargs)
def _check_stix(*args, **kwargs): try: doc = args[1] except IndexError: doc = kwargs['doc'] # Get the root element for the input doc root = utils.get_etree_root(doc) # Check that the root is a valid STIX root-level element check_root(root) return func(*args, **kwargs)
def get_version(doc): """Returns the version of a STIX instnace document. Args: doc: A STIX filename, file-like object, etree._Element or etree._ElementTree instance. Returns: The version of the document. Raises: KeyError: If the document does not contain a ``version`` attribute on the root node. .ValidationError: If there are any issues parsing `doc`. """ root = utils.get_etree_root(doc) return root.attrib['version']
def _build_uber_schema(self, doc, schemaloc=False): """Builds a schema which is made up of ``xs:import`` directives for each schema required to validate `doc`. If schemaloc is ``True``, the ``xsi:schemaLocation`` attribute values are used to create the ``xs:import`` directives. If ``False``, the initialization schema directory is used. Returns: An ``etree.XMLSchema`` instance used to validate `doc`. Raise: .XMLSchemaImportError: If an error occurred while building the dictionary of namespace to schemalocation mappings used to drive the uber schema creation. """ root = utils.get_etree_root(doc) imports = self._build_required_imports(root, schemaloc) if not imports: raise errors.XMLSchemaImportError( "Cannot validate document. Error occurred while determining " "schemas required for validation." ) xsd = etree.fromstring( """ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://stix.mitre.org/tools/validator" elementFormDefault="qualified" attributeFormDefault="qualified"/> """ ) for ns, loc in imports.iteritems(): loc = loc.replace("\\", "/") attrib = { 'namespace': ns, 'schemaLocation': loc } import_ = etree.Element(TAG_XS_IMPORT, attrib=attrib) xsd.append(import_) return etree.XMLSchema(xsd)
def test_ts_tz_resolves(self): root = utils.get_etree_root(StringIO(TS_TZ_RESOLVES)) offset_resolves = common.idref_timestamp_resolves( root=root, idref="example:campaign-1", timestamp="2015-04-14T15:24:19.416203+00:00", namespaces=common.get_stix_namespaces('1.1.1')) zulu_resolves = common.idref_timestamp_resolves( root=root, idref="example:campaign-1", timestamp="2015-04-14T15:24:19.416203Z", namespaces=common.get_stix_namespaces('1.1.1')) self.assertTrue(zulu_resolves) self.assertTrue(offset_resolves)
def validate(self, doc): """Validates an XML instance document against a STIX profile. Args: doc: The STIX document. This can be a filename, file-like object, ``etree._Element``, or ``etree._ElementTree`` instance. Returns: An instance of :class:`.ProfileValidationResults`. Raises: .ValidationError: If there are any issues parsing `doc`. """ root = utils.get_etree_root(doc) is_valid = self._schematron.validate(root) svrl_report = self._schematron.validation_report results = ProfileValidationResults(is_valid, root, svrl_report) return results
def test_ts_tz_resolves(self): root = utils.get_etree_root(StringIO(TS_TZ_RESOLVES)) offset_resolves = common.idref_timestamp_resolves( root=root, idref="example:campaign-1", timestamp="2015-04-14T15:24:19.416203+00:00", namespaces=common.get_stix_namespaces('1.1.1') ) zulu_resolves = common.idref_timestamp_resolves( root=root, idref="example:campaign-1", timestamp="2015-04-14T15:24:19.416203Z", namespaces=common.get_stix_namespaces('1.1.1') ) self.assertTrue(zulu_resolves) self.assertTrue(offset_resolves)
def _build_uber_schema(self, doc, schemaloc=False): """Builds a schema which is made up of ``xs:import`` directives for each schema required to validate `doc`. If schemaloc is ``True``, the ``xsi:schemaLocation`` attribute values are used to create the ``xs:import`` directives. If ``False``, the initialization schema directory is used. Returns: An ``etree.XMLSchema`` instance used to validate `doc`. Raise: .XMLSchemaImportError: If an error occurred while building the dictionary of namespace to schemalocation mappings used to drive the uber schema creation. """ root = utils.get_etree_root(doc) imports = self._build_required_imports(root, schemaloc) if not imports: raise errors.XMLSchemaImportError( "Cannot validate document. Error occurred while determining " "schemas required for validation." ) xsd = etree.fromstring( """ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://stix.mitre.org/tools/validator" elementFormDefault="qualified" attributeFormDefault="qualified"/> """ ) for ns, loc in iteritems(imports): loc = loc.replace("\\", "/") attrib = {'namespace': ns, 'schemaLocation':loc} import_ = etree.Element(xmlconst.TAG_XS_IMPORT, attrib=attrib) xsd.append(import_) return etree.XMLSchema(xsd)
def validate(self, doc, version=None, schemaloc=False): """Performs XML Schema validation against a STIX document. Args: doc: The STIX document. This can be a filename, file-like object, ``etree._Element``, or ``etree._ElementTree`` instance. version: The version of the STIX document. If ``None`` an attempt will be made to extract the version from `doc`. schemaloc: If ``True``, the ``xsi:schemaLocation`` attribute on `doc` will be used to drive the validation. Returns: An instance of :class:`.XmlValidationResults`. Raises: .UnknownSTIXVersionError: If `version` is ``None`` and `doc` does not contain STIX version information. .InvalidSTIXVersionError: If `version` is an invalid STIX version or `doc` contains an invalid STIX version number. .ValidationError: If the class was not initialized with a schema directory and `schemaloc` is ``False``. .XMLSchemaImportError: If an error occurs while processing the schemas required for validation. .XMLSchemaIncludeError: If an error occurs while processing ``xs:include`` directives. .ValidationError: If there are any issues parsing `doc`. """ root = utils.get_etree_root(doc) if schemaloc: validator = self._xml_validators[self._KEY_SCHEMALOC] elif self._is_user_defined: validator = self._xml_validators[self._KEY_USER_DEFINED] else: version = version or self._get_version(doc) stix.check_version(version) validator = self._xml_validators[version] results = validator.validate(root, schemaloc) return results
def _build_include_graph(self, schema_paths): """Builds a graph of ``xs:include`` directive sources and targets for the schemas contained by the `schema_paths` list. Args: schema_paths: A list of schema file paths Returns: A graph representing ``xs:include`` statements found within the schemas in `schema_paths`. """ graph = collections.defaultdict(list) for fp in schema_paths: root = utils.get_etree_root(fp) includes = self._get_includes(fp, root) graph[fp].extend(includes) return graph
def validate(self, doc): """Validates an XML instance document `doc` using Schematron rules. Args: doc: An XML instance document. This can be a filename, file-like object, ``etree._Element`` or ``etree._ElementTree`` instance. Returns: An instance of :class:`.SchematronValidationResults`. Raises: .ValidationError: If there are any issues parsing `doc`. """ root = utils.get_etree_root(doc) is_valid = self.schematron.validate(root) svrl_report = self.schematron.validation_report return SchematronValidationResults(is_valid, root, svrl_report)
def get_version(doc): """Returns the version of a STIX instnace document. Args: doc: A STIX filename, file-like object, etree._Element or etree._ElementTree instance. Returns: The version of the document. Raises: .UnknownSTIXVersionError: If the document does not contain a ``version`` attribute on the root node. .ValidationError: If there are any issues parsing `doc`. """ root = utils.get_etree_root(doc) try: return root.attrib['version'] except KeyError: error = "Document did not contain a 'version' attribute" raise errors.UnknownSTIXVersionError(error)
def validate(self, doc, version=None): """Checks that a STIX document aligns with `suggested authoring practices`_. .. _suggested authoring practices: http://stixproject.github.io/documentation/suggested-practices/ Args: doc: The STIX document. Can be a filename, file-like object, lxml._Element, or lxml._ElementTree instance. version: The version of the STIX document. This will determine the set of best practice rules to check. If ``None`` an attempt will be made to extract the version from `doc`. Returns: An instance of :class:`.BestPracticeValidationResults`. Raises: .UnknownSTIXVersionError: If `version` was ``None`` and `doc` did not contain any version information. .InvalidSTIXVersionError: If discovered version or `version` argument contains an invalid STIX version number. .ValidationError: If there are any issues parsing `doc`. """ # Get the element for the input document root = utils.get_etree_root(doc) # Get the STIX version for the input `doc` if one is not passed in. version = version or common.get_version(root) # Check that the version number is a valid STIX version number common.check_version(version) # Run the best practice checks applicable for the STIX version number. results = self._run_rules(root, version) # Return the results return results
def validate(self, doc): """Validates an XML instance document `doc` using Schematron rules. Args: doc: An XML instance document. This can be a filename, file-like object, ``etree._Element`` or ``etree._ElementTree`` instance. Returns: An instance of :class:`.SchematronValidationResults`. Raises: .ValidationError: If there are any issues parsing `doc`. """ root = utils.get_etree_root(doc) is_valid = self._schematron.validate(root) svrl_report = self._schematron.validation_report return SchematronValidationResults(is_valid=is_valid, doc=root, svrl_report=svrl_report)
def _build_schematron(self, sch, phase=None): """Attempts to build an ``lxml.isoschematron.Schematron`` instance from `sch`. Args: sch: A Schematron document filename, file-like object, etree._Element, or etree._ElementTree. Returns: A ``lxml.isoschematron.Schematron`` instance for `sch`. """ if sch is None: raise ValueError("Input schematron document cannot be None") root = utils.get_etree_root(sch) schematron = lxml.isoschematron.Schematron(root, phase=phase, store_report=True, store_xslt=True, store_schematron=True) return schematron
def validate(self, doc, schemaloc=False): """Validates an XML instance document. Args: doc: An XML instance document. This can be a filename, file-like object, ``etree._Element``, or ``etree._ElementTree``. schemaloc: If ``True``, the document will be validated using the ``xsi:schemaLocation`` attribute found on the instance document root. Returns: An instance of :class:`.XmlValidationResults`. Raises: .ValidationError: If the class was not initialized with a schema directory and `schemaloc` is ``False`` or if there are any issues parsing `doc`. .XMLSchemaIncludeError: If an error occurs while processing the schemas required for validation. .XMLSchemaIncludeError: If an error occurs while processing ``xs:include`` directives. """ if not (schemaloc or self._schemalocs): raise errors.ValidationError( "No schemas to validate against! Try instantiating " "XmlValidator with use_schemaloc=True or setting the " "schema_dir param in __init__" ) root = utils.get_etree_root(doc) xsd = self._build_uber_schema(root, schemaloc) is_valid = xsd.validate(root) return XmlValidationResults(is_valid, xsd.error_log)
def test_get_schemaloc_pairs_raises(self): sio = StringIO(XML) root = utils.get_etree_root(sio) self.assertRaises(KeyError, utils.get_schemaloc_pairs, root)
def test_get_schemaloc_pairs(self): sio = StringIO(XML_SCHEMALOC) root = utils.get_etree_root(sio) pairs = utils.get_schemaloc_pairs(root) self.assertEqual(2, len(pairs))
def test_get_etree_root_elementree(self): tree = etree.fromstring(XML) root = utils.get_etree_root(tree) lname = etree.QName(root).localname self.assertEqual(lname, XML_ROOT_LOCALNAME)
def test_get_etree_root_stringio(self): sio = StringIO(XML) root = utils.get_etree_root(sio) lname = etree.QName(root).localname self.assertEqual(lname, XML_ROOT_LOCALNAME)
def test_get_schemaloc_pairs(self): sio = StringIO(XML_SCHEMALOC) root = utils.get_etree_root(sio) pairs = utils.get_schemaloc_pairs(root) self.assertEqual(2, len(list(pairs)))