Examples
========
This page includes some basic examples of creating and parsing STIX content.
There are a couple things we do in these examples for purposes of demonstration
that shouldn't be done in production code:
* When calling ``to_xml()``, we use ``include_namespaces=False``. This is to
make the example output easier to read, but means the resulting output
cannot be successfully parsed. The XML parser doesn't know what namespaces
to use if they aren't included. In production code, you should explicitly
set ``include_namespaces`` to ``True`` or omit it entirely (``True`` is the
default).
* In some examples, we use ``set_id_method(IDGenerator.METHOD_INT)`` to make
IDs for STIX constructs easier to read and cross-reference within the XML
document. In production code, you should omit this statement, which causes
random UUIDs to be created instead, or create explicit IDs yourself for STIX
constructs.
See the `STIX Idioms `_
documentation for more great examples of how to use **python-stix**.
Creating a STIX Package
-----------------------
.. testcode::
from stix.core import STIXPackage, STIXHeader
from stix.utils import IDGenerator, set_id_method
set_id_method(IDGenerator.METHOD_INT) # For testing and demonstration only!
stix_package = STIXPackage()
stix_header = STIXHeader()
stix_header.description = "Getting Started!"
stix_package.stix_header = stix_header
print stix_package.to_xml(include_namespaces=False)
Which outputs:
.. testoutput::
Getting Started!
ID Namespaces
-------------
By default, **python-stix** sets the default ID namespace to
``http://example.com`` with an alias of ``example``. This results in STIX
id declarations that look like
``id="example:Package-2813128d-f45e-41f7-b10a-20a5656e3785"``.
To change this, use the ``stix.utils.set_id_namespace()`` method which takes
a dictionary as a parameter.
.. testcode::
from stix.core import STIXPackage
from stix.utils import set_id_namespace
NAMESPACE = {"http://MY-NAMESPACE.com" : "myNS"}
set_id_namespace(NAMESPACE) # new ids will be prefixed by "myNS"
stix_package = STIXPackage() # id will be created automatically
print stix_package.to_xml()
Which outputs:
.. testoutput::
Success! The ``xmlns:myNS="http://MY-NAMESPACE.com"`` matches our ``NAMESPACE``
dictionary and the ``id`` attribute includes the ``myNS`` namespace alias.
Working With CybOX
~~~~~~~~~~~~~~~~~~
If you are creating CybOX entities such as ``Observables``, you'll want to set
the ID namespace for ``python-cybox`` as well.
Note that **python-stix** and ``python-cybox`` treat namespaces slightly
differently (for now anyway). Where **python-stix** uses Python dictionaries,
``python-cybox`` uses the ``cybox.utils.Namespace`` class to represent a
namespace.
.. testcode::
from cybox.utils import set_id_namespace, Namespace
from cybox.core import Observable
NAMESPACE = Namespace("http://MY-NAMESPACE.com", "myNS")
set_id_namespace(NAMESPACE)
obs = Observable()
print obs.to_xml()
Which outputs:
.. testoutput::
Success (again)! The ``xmlns:myNS="http://MY-NAMESPACE.com"`` matches our
``Namespace`` object and the ``id`` attribute includes the ``myNS`` namespace
alias.
Controlled Vocabularies: VocabString
------------------------------------
Many fields in STIX leverage the ``stixCommon:ControlledVocabularyStringType``,
which acts as a base type for controlled vocabulary implementations. The STIX
language defines a set of default controlled vocabularies which are found in
the ``stix_default_vocabs.xsd`` XML Schema file.
The **python-stix** library contains a ``stix.common.vocabs`` module, which
defines the ``VocabString`` class implementation of the schema
``ControlledVocabularyStringType`` as well as ``VocabString`` implementations
which correspond to default controlled vocabularies.
For example, the ``stix_default_vocabularies.xsd`` schema defines a controlled
vocabulary for STIX Package Intents: ``PackageIntentVocab-1.0``. The
``stix.common.vocabs`` module contains an analogous ``PackageIntent`` class,
which acts as a derivation of ``VocabString``.
Each ``VocabString`` implementation contains:
* A static list of class-level term attributes, each beginning with ``TERM_`
(e.g., ``TERM_INDICATORS``)
* A tuple containing all allowed vocabulary terms: ``ALLOWED_VALUES``, which is
use for input validation
* Methods found on ``stix.Entity``, such as ``to_xml()``, ``to_dict()``,
``from_dict()``, etc.
Interacting With VocabString Fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following sections define ways of interacting with VocabString fields.
Default Vocabulary Terms
########################
The STIX Language often suggested a default controlled vocabulary type for a
given controlled vocabulary field. Each controlled vocabulary contains an
enumeration of allowed terms.
Each ``VocabString`` implementation found in the ``stix.common.vocabs`` module
contains static class-level attributes for each vocabulary term. When setting
controlled vocabulary field values, it is recommended that users take advantage
of these class-level attributes.
The following demonstrates setting the ``Package_Intent`` field with a default
vocabulary term. Note that the ``STIXHeader.package_intents`` property returns
a list. As such, we use the ``append()`` method to add terms. Other STIX
controlled vocabulary fields may only allow one value rather than a list of
values.
.. testcode::
from stix.core import STIXHeader
from stix.common.vocabs import PackageIntent
header = STIXHeader()
header.package_intents.append(PackageIntent.TERM_INDICATORS)
print header.to_xml(include_namespaces=False)
Which outputs:
.. testoutput::
Indicators
Non-Default Vocabulary Terms
############################
Though it is suggested, STIX content authors are not required to use the default
controlled vocabulary for a given field. As such, **python-stix** allows users
to pass in non-default values for controlled vocabulary fields.
To set a controlled vocabulary to a non-default vocabulary term, pass a
``VocabString`` instance into a controlled vocabulary field.
A raw ``VocabString`` field will contain no ``xsi:type`` information or
``ALLOWED_VALUES`` members, which removes the input and schema validation
requirements.
.. testcode::
from stix.core import STIXHeader
from stix.common.vocabs import VocabString, PackageIntent
header = STIXHeader()
non_default_term = VocabString("NON-DEFAULT VOCABULARY TERM")
header.package_intents.append(non_default_term)
print header.to_xml(include_namespaces=False)
Which outputs:
.. testoutput::
NON-DEFAULT VOCABULARY TERM
Notice that the ```` field does not have an ``xsi:type``
attribute. As such, this field can contain any string value and is not bound
by a controlled vocabulary enumeration of terms.
Working With Custom Controlled Vocabularies
###########################################
STIX allows content authors and developers to extend the
``ControlledVocabularyStringType`` schema type for the definition of new
controlled vocabularies. The **python-stix** library allows developers to
create and register Python types which mirror the custom XML Schema vocabulary
types.
XSD Example
"""""""""""
The following XML Schema example shows the definition of a a new custom
controlled vocabulary schema type. Instances of this schema type could be
used wherever a ``ControlledVocabularyStringType`` instance is expected
(e.g., the ``STIX_Header/Package_Intent`` field).
.. code-block:: xml
Filename: customVocabs.xsd
XML Instance Sample
"""""""""""""""""""
The following STIX XML instance document shows a potential use of this field.
Note the ``xsi:type=customVocabs:CustomVocab-1.0`` on the ``Package_Intent``
field.
.. code-block:: xml
Filename: customVocabs.xml
FOO
Python Code
"""""""""""
To parse content which uses custom controlled vocabularies, Python developers
don't have to do anything special--you just call ``STIXPackage.from_xml()`` on
the input and all the namespaces, ``xsi:types``, etc. are attached to each
instance of ``VocabString``. When serializing the document, the input namespaces
and ``xsi:type`` attributes are retained!
However, to `create` new content which utilizes a schema defined and enforced
custom controlled vocabulary, developers must create a :class:`.VocabString`
implementation which mirrors the schema definition.
For our ``CustomVocab-1.0`` schema type, the Python would look like this:
.. code-block:: python
from stix.common import vocabs
# Create a custom vocabulary type
class CustomVocab(vocabs.VocabString):
_namespace = 'http://customvocabs.com/vocabs-1'
_XSI_TYPE = 'customVocabs:CustomVocab-1.0'
_ALLOWED_VALUES = ('FOO', 'BAR')
# Register the type as a VocabString
vocabs.add_vocab(CustomVocab)
As you can see, we can express a lot of the same information found in the
XML Schema definition, just with a lot less typing!
* ``_namespace``: The ``targetNamespace`` for our custom vocabulary
* ``_XSI_TYPE``: The ``xsi:type`` attribute value to write out for instances
of this vocabulary.
* ``_ALLOWED_VALUES``: A ``tuple`` of allowable values for this vocabulary.
.. note::
The call to ``add_vocab()`` registers the class and its ``xsi:type`` as a
``VocabString`` implementation so **python-stix** will know to build
instances of ``CustomVocab`` when parsed content contains
``CustomVocab-1.0`` content. You must call ``add_vocab()`` to register
your class prior to parsing content if you want the parser to build
instances of your custom vocabulary class!
.. code-block:: python
# builtin
from StringIO import StringIO
# python-stix modules
from stix.core import STIXPackage
from stix.common import vocabs
XML = \
"""
FOO
"""
# Create a VocabString class for our CustomVocab-1.0 vocabulary which
class CustomVocab(vocabs.VocabString):
_namespace = 'http://customvocabs.com/vocabs-1'
_XSI_TYPE = 'customVocabs:CustomVocab-1.0'
_ALLOWED_VALUES = ('FOO', 'BAR')
# Register our Custom Vocabulary class so parsing builds instances of
# CustomVocab
vocabs.add_vocab(CustomVocab)
# Parse the input document
sio = StringIO(XML)
package = STIXPackage.from_xml(sio)
# Retrieve the first (and only) Package_Intent entry
package_intent = package.stix_header.package_intents[0]
# Print information about the input Package_Intent
print type(package_intent), package_intent.xsi_type, package_intent
# Add another Package Intent
bar = CustomVocab('BAR')
package.stix_header.add_package_intent(bar)
# This will include the 'BAR' CustomVocab entry
print package.to_xml()