Version: 1.2.0.4
Controlled Vocabularies¶
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. This is generated via thevocabs.register_vocab()
class decorator. - Methods found on
stix.Entity
, such asto_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.
from stix.core import STIXHeader
from stix.common.vocabs import PackageIntent
header = STIXHeader()
header.package_intents.append(PackageIntent.TERM_INDICATORS)
print(header.to_xml())
Which outputs:
<stix:STIXHeaderType>
<stix:Package_Intent xsi:type="stixVocabs:PackageIntentVocab-1.0">Indicators</stix:Package_Intent>
</stix:STIXHeaderType>
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.
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())
Which outputs:
<stix:STIXHeaderType>
<stix:Package_Intent>NON-DEFAULT VOCABULARY TERM</stix:Package_Intent>
</stix:STIXHeaderType>
Notice that the <stix:Package_Intent>
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).
Filename: customVocabs.xsd
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:customVocabs="http://customvocabs.com/vocabs-1"
xmlns:stixVocabs="http://stix.mitre.org/default_vocabularies-1"
xmlns:stixCommon="http://stix.mitre.org/common-1"
targetNamespace="http://customvocabs.com/vocabs-1"
elementFormDefault="qualified"
version="1.2"
xml:lang="English">
<xs:import namespace="http://stix.mitre.org/common-1" schemaLocation="http://stix.mitre.org/XMLSchema/common/1.2/stix_common.xsd"/>
<xs:complexType name="CustomVocab-1.0">
<xs:simpleContent>
<xs:restriction base="stixCommon:ControlledVocabularyStringType">
<xs:simpleType>
<xs:union memberTypes="customVocabs:CustomEnum-1.0"/>
</xs:simpleType>
<xs:attribute name="vocab_name" type="xs:string" use="optional" fixed="Test Vocab"/>
<xs:attribute name="vocab_reference" type="xs:anyURI" use="optional" fixed="http://example.com/TestVocab"/>
</xs:restriction>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="CustomEnum-1.0">
<xs:restriction base="xs:string">
<xs:enumeration value="FOO"/>
<xs:enumeration value="BAR"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
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.
Filename: customVocabs.xml
<stix:STIX_Package
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:stixExample="http://stix.mitre.org/example"
xmlns:stix="http://stix.mitre.org/stix-1"
xmlns:customVocabs="http://customvocabs.com/vocabs-1"
xsi:schemaLocation="
http://stix.mitre.org/stix-1 /path/to/stix_core.xsd
http://customvocabs.com/vocabs-1 /path/to/customVocabs.xsd"
id="stixExample:STIXPackage-33fe3b22-0201-47cf-85d0-97c02164528d"
version="1.2">
<stix:STIX_Header>
<stix:Package_Intent xsi:type="customVocabs:CustomVocab-1.0">FOO</stix:Package_Intent>
</stix:STIX_Header>
</stix:STIX_Package>
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 VocabString
implementation which mirrors the schema definition.
For our CustomVocab-1.0
schema type, the Python would look like this:
from stix.common import vocabs
# Create a custom vocabulary type
@vocabs.register_vocab
class CustomVocab(vocabs.VocabString):
_namespace = 'http://customvocabs.com/vocabs-1'
_XSI_TYPE = 'customVocabs:CustomVocab-1.0'
# Valid terms
TERM_FOO = 'FOO'
TERM_BAR = 'BAR'
As you can see, we can express a lot of the same information found in the XML Schema definition, but in Python!
_namespace
: ThetargetNamespace
for our custom vocabulary_XSI_TYPE
: Thexsi:type
attribute value to write out for instances of this vocabulary.TERM_FOO|BAR
: Allowable terms for the vocabulary. These terms are collected for input validation.
Note
The @register_vocab
class decorator 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.
This also inspects the class attributes for any that begin with
TERM_
and collects their values for the purpose of input validation.
Warning
Before python-stix 1.2.0.0, users registered custom VocabString
implementations via the stix.common.vocabs.add_vocab()
method. This
method still exists but is considered DEPRECATED in favor of the
stix.common.vocabs.register_vocab()
class decorator.
# builtin
from StringIO import StringIO
# python-stix modules
from stix.core import STIXPackage
from stix.common.vocabs import VocabString, register_vocab
from mixbox.namespaces import register_namespace, Namespace
XML = \
"""
<stix:STIX_Package
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:stix="http://stix.mitre.org/stix-1"
xmlns:customVocabs="http://customvocabs.com/vocabs-1"
xmlns:example="http://example.com/"
xsi:schemaLocation="
http://stix.mitre.org/stix-1 /path/to/stix_core.xsd
http://customvocabs.com/vocabs-1 /path/to/customVocabs.xsd"
id="example:STIXPackage-33fe3b22-0201-47cf-85d0-97c02164528d"
version="1.2">
<stix:STIX_Header>
<stix:Package_Intent xsi:type="customVocabs:CustomVocab-1.0">FOO</stix:Package_Intent>
</stix:STIX_Header>
</stix:STIX_Package>
"""
# Create a VocabString class for our CustomVocab-1.0 vocabulary which
@register_vocab
class CustomVocab(VocabString):
_namespace = 'http://customvocabs.com/vocabs-1'
_XSI_TYPE = 'customVocabs:CustomVocab-1.0'
TERM_FOO = 'FOO'
TERM_BAR = 'BAR'
register_namespace(Namespace(CustomVocab._namespace, "customVocabNS"))
# 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('%s %s %s' % (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())