Example #1
0
    def test_clean(self):
        root = utils.get_etree_root(self.xml)
        results = self.UPDATER().clean(root)

        doc = results.document.as_element()
        disallowed = self.DISALLOWED_KLASS.find(doc)
        self.assertEqual(len(disallowed), 0)
Example #2
0
    def test_clean(self):
        root = utils.get_etree_root(self.xml)
        results = self.UPDATER().clean(root)

        doc = results.document.as_element()
        disallowed = self.DISALLOWED_KLASS.find(doc)
        self.assertEqual(len(disallowed), 0)
Example #3
0
    def check_update(self, root, options=None):
        """Determines if the input document can be upgraded.

        Args:
            root: The XML document. This can be a filename, a file-like object,
                an instance of ``etree._Element`` or an instance of
                ``etree._ElementTree``.
            options (optional): A ``ramrod.UpdateOptions`` instance. If
                ``None``, ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.

        Raises:
            .UnknownVersionError: If the input document does not have a
                version.
            .InvalidVersionError: If the version of the input document
                does not match the `VERSION` class-level attribute value.
            .UpdateError: If the input document contains fields which
                cannot be updated or constructs with non-unique IDs are discovered.

        """
        root = utils.get_etree_root(root)
        options = options or DEFAULT_UPDATE_OPTIONS

        if options.check_versions:
            self._check_version(root)
            self._cybox_updater._check_version(root)  # noqa

        disallowed = self._get_disallowed(root)

        if not disallowed:
            return

        raise errors.UpdateError(
            message="Found untranslatable fields in source document.",
            disallowed=disallowed)
    def check_update(self, root, options=None):
        """Determines if the input document can be upgraded.

        Args:
            root: The XML document. This can be a filename, a file-like object,
                an instance of ``etree._Element`` or an instance of
                ``etree._ElementTree``.
            options (optional): A ``ramrod.UpdateOptions`` instance. If
                ``None``, ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.

        Raises:
            .UnknownVersionError: If the input document does not have a
                version.
            .InvalidVersionError: If the version of the input document
                does not match the `VERSION` class-level attribute value.
            .UpdateError: If the input document contains fields which
                cannot be updated or constructs with non-unique IDs are discovered.

        """
        root = utils.get_etree_root(root)
        options = options or DEFAULT_UPDATE_OPTIONS

        if options.check_versions:
            self._check_version(root)
            self._cybox_updater._check_version(root)  # noqa

        duplicates = self._get_duplicates(root)
        disallowed = self._get_disallowed(root)

        if not (disallowed or duplicates):
            return

        error = "Found duplicate or untranslatable fields in source document."
        raise errors.UpdateError(message=error, disallowed=disallowed, duplicates=duplicates)
Example #5
0
def update(doc, from_=None, to_=None, options=None, force=False):
    """Updates a CybOX document to align with a given version of the CybOX
    Language.

    Args:
        doc: A CybOX document filename, file-like object, ``etree._Element``, or
            ``etree._ElementTree``.
        from_ (optional, string): The base version for the update process. If
            ``None``, an attempt will be made to extract the version number
            from `doc`.
        to_ (optional, string): The version to update to. If ``None``, the
            latest version of CybOX is assumed.
        options (optional): A :class:`ramrod.UpdateOptions` instance. If
            ``None``, ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.
        force (boolean): Forces the update process. This may result in content
            being removed during the update process and could result in
            schema-invalid content. **Use at your own risk!**

    Returns:
        An instance of ``ramrod.UpdateResults``.

    Raises:
        .UpdateError: If any of the following conditions are encountered:

            * The `from_` or `to_` versions are invalid.
            * An untranslatable field is encountered and `force` is ``False``.
            * A non-unique ID is encountered and `force` is ``False``.
        .InvalidVersionError: If the source document version and the
            `from_` value do not match and `force` is ``False``.
        .UnknownVersionError: If the source document does not contain
            version information and `force` is ``False``.

    """
    root = utils.get_etree_root(doc)
    versions = common.CYBOX_VERSIONS
    from_ = from_ or BaseCyboxUpdater.get_version(root)
    to_ = to_ or versions[-1]  # The latest version if not specified

    utils.validate_versions(from_, to_, versions)

    removed, remapped = [], {}
    idx = versions.index

    for version in versions[idx(from_):idx(to_)]:
        updater   = CYBOX_UPDATERS[version]
        result    = updater().update(root, options=options, force=force)
        root      = result.document.as_element()

        # Update record of removed and remapped fields
        removed.extend(result.removed)
        remapped.update(result.remapped_ids)

    result = results.UpdateResults(
        document=root,
        removed=removed,
        remapped_ids=remapped
    )

    return result
Example #6
0
    def test_lists(self):
        nsmap = {'AddressObj': 'http://cybox.mitre.org/objects#AddressObject-2'}
        updater = UPDATER()
        root = utils.get_etree_root(self._xml)

        updater._update_lists(root)
        address_value = root.xpath('.//AddressObj:Address_Value', namespaces=nsmap)[0].text
        self.assertEqual(address_value, self.NEW_DELIMITER.join(self.ATTACKERS))
Example #7
0
    def test_commas(self):
        nsmap = {'EmailMessageObj': 'http://cybox.mitre.org/objects#EmailMessageObject-2'}
        updater = UPDATER()
        root = utils.get_etree_root(self._xml)

        updater._update_lists(root)
        subject = root.xpath('.//EmailMessageObj:Subject', namespaces=nsmap)[0].text
        self.assertEqual(subject, self.UNESCAPED)
    def test_attrib_value(self):
        root = utils.get_etree_root(self.xml)
        self.TRANS_KLASS.translate(root)

        updated_nodes = root.xpath(self.TRANS_XPATH,
                                namespaces=self.UPDATER.NSMAP)

        for node in updated_nodes:
            self.assertEqual(node.attrib['datatype'], 'string')
Example #9
0
    def test_attrib_value(self):
        root = utils.get_etree_root(self.xml)
        self.TRANS_KLASS.translate(root)

        updated_nodes = root.xpath(self.TRANS_XPATH,
                                   namespaces=self.UPDATER.NSMAP)

        for node in updated_nodes:
            self.assertEqual(node.attrib['datatype'], 'string')
Example #10
0
def update(doc, from_=None, to_=None, options=None, force=False):
    """Updates a CybOX document to align with a given version of the CybOX
    Language.

    Args:
        doc: A CybOX document filename, file-like object, ``etree._Element``, or
            ``etree._ElementTree``.
        from_ (optional, string): The base version for the update process. If
            ``None``, an attempt will be made to extract the version number
            from `doc`.
        to_ (optional, string): The version to update to. If ``None``, the
            latest version of CybOX is assumed.
        options (optional): A :class:`ramrod.UpdateOptions` instance. If
            ``None``, ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.
        force (boolean): Forces the update process. This may result in content
            being removed during the update process and could result in
            schema-invalid content. **Use at your own risk!**

    Returns:
        An instance of ``ramrod.UpdateResults``.

    Raises:
        .UpdateError: If any of the following conditions are encountered:

            * The `from_` or `to_` versions are invalid.
            * An untranslatable field is encountered and `force` is ``False``.
            * A non-unique ID is encountered and `force` is ``False``.
        .InvalidVersionError: If the source document version and the
            `from_` value do not match and `force` is ``False``.
        .UnknownVersionError: If the source document does not contain
            version information and `force` is ``False``.

    """
    root = utils.get_etree_root(doc)
    versions = common.CYBOX_VERSIONS
    from_ = from_ or BaseCyboxUpdater.get_version(root)
    to_ = to_ or versions[-1]  # The latest version if not specified

    utils.validate_versions(from_, to_, versions)

    removed, remapped = [], {}
    idx = versions.index

    for version in versions[idx(from_):idx(to_)]:
        updater = CYBOX_UPDATERS[version]
        result = updater().update(root, options=options, force=force)
        root = result.document.as_element()

        # Update record of removed and remapped fields
        removed.extend(result.removed)
        remapped.update(result.remapped_ids)

    result = results.UpdateResults(document=root,
                                   removed=removed,
                                   remapped_ids=remapped)

    return result
Example #11
0
    def test_trans(self):
         root = utils.get_etree_root(self.xml)
         self.TRANS_KLASS.translate(root)

         updated_nodes = root.xpath(self.TRANS_XPATH,
                                    namespaces=self.UPDATER.NSMAP)

         for node in updated_nodes:
             if self.TRANS_VALUE:
                self.assertEqual(node.text, self.TRANS_VALUE)
             else:
                 self.assertTrue(node != None)
Example #12
0
    def test_trans(self):
        root = utils.get_etree_root(self.xml)
        self.TRANS_KLASS.translate(root)

        updated_nodes = root.xpath(self.TRANS_XPATH,
                                   namespaces=self.UPDATER.NSMAP)

        for node in updated_nodes:
            if self.TRANS_VALUE:
                self.assertEqual(node.text, self.TRANS_VALUE)
            else:
                self.assertTrue(node != None)
Example #13
0
    def test_lists(self):
        nsmap = {
            'AddressObj': 'http://cybox.mitre.org/objects#AddressObject-2'
        }
        updater = UPDATER()
        root = utils.get_etree_root(self._xml)

        updater._update_lists(root)
        address_value = root.xpath('.//AddressObj:Address_Value',
                                   namespaces=nsmap)[0].text
        self.assertEqual(address_value,
                         self.NEW_DELIMITER.join(self.ATTACKERS))
Example #14
0
    def test_commas(self):
        nsmap = {
            'EmailMessageObj':
            'http://cybox.mitre.org/objects#EmailMessageObject-2'
        }
        updater = UPDATER()
        root = utils.get_etree_root(self._xml)

        updater._update_lists(root)
        subject = root.xpath('.//EmailMessageObj:Subject',
                             namespaces=nsmap)[0].text
        self.assertEqual(subject, self.UNESCAPED)
Example #15
0
    def check_update(self, root, options=None):
        """Determines if the input document can be upgraded.

        Args:
            root: The XML document. This can be a filename, a file-like object,
                an instance of ``etree._Element`` or an instance of
                ``etree._ElementTree``.
            options (optional): A ``ramrod.UpdateOptions`` instance. If
                ``None``, ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.

        Raises:
            .UnknownVersionError: If the input document does not have a
                version.
            .InvalidVersionError: If the version of the input document
                does not match the `VERSION` class-level attribute value.

        """
        root = utils.get_etree_root(root)
        options = options or DEFAULT_UPDATE_OPTIONS

        if options.check_versions:
            self._check_version(root)
Example #16
0
    def check_update(self, root, options=None):
        """Determines if the input document can be upgraded.

        Args:
            root: The XML document. This can be a filename, a file-like object,
                an instance of ``etree._Element`` or an instance of
                ``etree._ElementTree``.
            options (optional): A ``ramrod.UpdateOptions`` instance. If
                ``None``, ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.

        Raises:
            .UnknownVersionError: If the input document does not have a
                version.
            .InvalidVersionError: If the version of the input document
                does not match the `VERSION` class-level attribute value.

        """
        root = utils.get_etree_root(root)
        options = options or DEFAULT_UPDATE_OPTIONS

        if options.check_versions:
            self._check_version(root)
Example #17
0
    def clean(self, root, options=None):
        """Removes disallowed elements from `root` and remaps non-unique
        IDs to unique IDs for the sake of schema-validation.

        Removed items can be retrieved via the ``removed`` attribute on the
        return value:

        >>> results = updater.clean(root)
        >>> print results.removed
        (<Element at 0xffdcf234>, <Element at 0xffdcf284>)

        Items which have been reassigned IDs can be retrieved via the
        ``remappped_ids`` attribute on the return value:

        >>> results = updater.clean(root)
        >>> print results.remapped_ids
        {'example:Observable-duplicate': [<Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67e64>, <Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67f2c>, <Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67f54>, <Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67f7c>, <Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67fa4>]}

        Note:
            This does not remap ``idref`` attributes to new ID values because
            it is impossible to determine which entity the ``idref`` was
            pointing to.

        Args:
            root: The XML document. This can be a filename, a file-like object,
                an instance of ``etree._Element`` or an instance of
                ``etree._ElementTree``.
            options (optional): A :class:`ramrod.UpdateOptions` instance. If
                ``None``,  ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.

        Returns:
            An instance of ``ramrod.UpdateResults``.

        """
        root = utils.get_etree_root(root, make_copy=True)
        results = self._clean(root, options)
        return results
Example #18
0
    def test_vocab_update(self):
        root = utils.get_etree_root(self.xml)
        self.VOCAB_KLASS.update(root)

        vocabs = self.VOCAB_KLASS.find(root)
        self.assertEqual(len(vocabs), 0)
Example #19
0
 def test_get_version(self):
     root = utils.get_etree_root(self._versions)
     version = UPDATER.get_version(root)
     self.assertEqual(version, UPDATER.VERSION)
Example #20
0
 def test_get_version(self):
     root = utils.get_etree_root(self._versions)
     version = UPDATER.get_version(root)
     self.assertEqual(version, UPDATER.VERSION)
Example #21
0
def get_version(doc):
    """Returns the version number for input CybOX document."""
    root = utils.get_etree_root(doc)
    return BaseCyboxUpdater.get_version(root)
Example #22
0
 def test_optional_find(self):
     root = utils.get_etree_root(self.xml)
     optionals = self.OPTIONAL_KLASS.find(root)
     self.assertEqual(len(optionals), self.OPTIONAL_COUNT)
Example #23
0
 def test_disallowed_find(self):
     root = utils.get_etree_root(self.xml)
     disallowed = self.DISALLOWED_KLASS.find(root)
     self.assertEqual(len(disallowed), self.DISALLOWED_COUNT)
Example #24
0
 def test_optional_removal(self):
     root = utils.get_etree_root(self.xml)
     updater = self.UPDATER()
     updater._update_optionals(root)
     optionals = self.OPTIONAL_KLASS.find(root)
     self.assertEqual(len(optionals), 0)
Example #25
0
 def test_trans_find(self):
     root = utils.get_etree_root(self.xml)
     to_trans = self.TRANS_KLASS._find(root)
     self.assertEqual(len(to_trans), self.TRANS_COUNT)
Example #26
0
 def test_optional_removal(self):
     root = utils.get_etree_root(self.xml)
     updater = self.UPDATER()
     updater._update_optionals(root)
     optionals = self.OPTIONAL_KLASS.find(root)
     self.assertEqual(len(optionals), 0)
Example #27
0
 def test_trans_find(self):
     root = utils.get_etree_root(self.xml)
     to_trans = self.TRANS_KLASS._find(root)
     self.assertEqual(len(to_trans), self.TRANS_COUNT)
Example #28
0
    def test_vocab_update(self):
        root = utils.get_etree_root(self.xml)
        self.VOCAB_KLASS.update(root)

        vocabs = self.VOCAB_KLASS.find(root)
        self.assertEqual(len(vocabs), 0)
Example #29
0
 def test_disallowed_find(self):
     root = utils.get_etree_root(self.xml)
     disallowed = self.DISALLOWED_KLASS.find(root)
     self.assertEqual(len(disallowed), self.DISALLOWED_COUNT)
Example #30
0
 def test_find(self):
     root = utils.get_etree_root(self.xml)
     vocabs = self.VOCAB_KLASS.find(root)
     self.assertEqual(len(vocabs), self.VOCAB_COUNT)
Example #31
0
 def test_find(self):
     root = utils.get_etree_root(self.xml)
     vocabs = self.VOCAB_KLASS.find(root)
     self.assertEqual(len(vocabs), self.VOCAB_COUNT)
Example #32
0
def update(doc, from_=None, to_=None, options=None, force=False):
    """Updates an input STIX or CybOX document to align with a newer version
    of the STIX/CybOX schemas.

    This will perform the following updates:

    * Update namespaces
    * Update schemalocations
    * Update construct versions (``STIX_Package``, ``Observables``, etc.)
    * Update controlled vocabularies and fix typos
    * Translate structures to new XSD datatype instances where possible.
    * Remove empty instances of attributes and elements which were required
      in one version of the language and declared optional in another.

    Args:
        doc: A STIX or CybOX document filename, file-like object,
            ``etree._Element`` or ``etree._ElementTree`` object instance.
        to_ (optional, string): The expected output version of the update
            process. If not specified, the latest language version will be
            assumed.
        from_ (optional, string): The version to update from. If not specified,
            the `from_` version will be retrieved from the input document.
        options (optional): A :class:`ramrod.UpdateOptions` instance. If
            ``None``, ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.
        force (boolean): Attempt to force the update process if the document
            contains untranslatable fields.

    Returns:
        An instance of ``ramrod.UpdateResults``

    Raises:
        ramrod.UpdateError: If any of the following occur:

            * The input `doc` does not contain a ``STIX_Package``
              or ``Observables`` root-level node.
            * If`force` is ``False`` and an untranslatable field or
              non-unique ID is found in the input `doc`.
        ramrod.InvalidVersionError: If the input document contains a version
            attribute that is incompatible with a STIX/CybOX Updater class
            instance.
        ramrod.UnknownVersionError: If `from_` was not specified and the input
            document does not contain a version attribute.

    """
    import ramrod.stix as stix
    import ramrod.cybox as cybox

    root = utils.get_etree_root(doc)
    name = QName(root).localname

    update_methods = {
        'STIX_Package': stix.update,
        'Observables': cybox.update,
    }

    try:
        options = options or DEFAULT_UPDATE_OPTIONS
        from_ = from_ or _get_version(root)  # _get_version raises KeyError
        func = update_methods[name]
    except KeyError:
        error = (
            "Document root node must be one of %s. Found: '%s'" %
            (update_methods.keys(), name)
        )
        raise UpdateError(error)

    updated = func(root, from_, to_, options, force)
    return updated
Example #33
0
 def test_optional_find(self):
     root = utils.get_etree_root(self.xml)
     optionals = self.OPTIONAL_KLASS.find(root)
     self.assertEqual(len(optionals), self.OPTIONAL_COUNT)
Example #34
0
def get_version(doc):
    """Returns the version number for input CybOX document."""
    root = utils.get_etree_root(doc)
    return BaseCyboxUpdater.get_version(root)
Example #35
0
    def update(self, root, options=None, force=False):
        """Attempts to update `root` to the next version of its language
        specification.

        If `force` is set to True, items may be removed during the
        translation process and IDs may be reassigned if they are not
        unique within the document.

        Note:
            This does not remap ``idref`` attributes to new ID values because
            it is impossible to determine which entity the ``idref`` was
            pointing to.

        Removed items can be retrieved via the ``removed`` attribute on the
        return value:

        >>> results = updater.update(root, force=True)
        >>> print results.removed
        (<Element at 0xffdcf234>, <Element at 0xffdcf284>)

        Items which have been reassigned IDs can be retrieved via the
        ``remappped_ids`` attribute on the return value:

        >>> results = updater.update(root, force=True)
        >>> print results.remapped_ids
        {'example:Observable-duplicate-id-1': [<Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67e64>, <Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67f2c>, <Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67f54>, <Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67f7c>, <Element {http://cybox.mitre.org/cybox-2}Observable at 0xffd67fa4>]}

        Args:
            root: The XML document. This can be a filename, a file-like object,
                an instance of ``etree._Element`` or an instance of
                ``etree._ElementTree``.
            options: A :class:`ramrod.UpdateOptions` instance. If ``None``,
                ``ramrod.DEFAULT_UPDATE_OPTIONS`` will be used.
            force: Forces the update process to complete by potentially
                removing untranslatable xml nodes and/or remapping non-unique
                IDs. This may result in non-schema=conformant XML. **USE AT
                YOUR OWN RISK!**

        Returns:
            An instance of ``ramrod.UpdateResults``.

        Raises:
            ramrod.UpdateError: If untranslatable fields or non-unique IDs are
                discovered in `root` and `force` is ``False``.
            ramrod.UnknownVersionError: If the `root` node contains no version
                information.
            ramrod.InvalidVersionError: If the `root` node contains invalid
                version information (e.g., the class expects v1.0 content and
                the `root` node contains v1.1 content).

        """
        root = utils.get_etree_root(root, make_copy=True)
        options = options or DEFAULT_UPDATE_OPTIONS

        try:
            self.check_update(root, options)
            updated = self._update(root, options)
            results = self._create_update_results(updated)
        except (UpdateError, UnknownVersionError, InvalidVersionError):
            if force:
                results = self._force_update(root, options)
            else:
                raise

        return results