diff --git a/docs/guide/extensions.ipynb b/docs/guide/extensions.ipynb
new file mode 100644
index 0000000..0283981
--- /dev/null
+++ b/docs/guide/extensions.ipynb
@@ -0,0 +1,784 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "nbsphinx": "hidden"
+ },
+ "outputs": [],
+ "source": [
+ "# Delete this cell to re-enable tracebacks\n",
+ "import sys\n",
+ "ipython = get_ipython()\n",
+ "\n",
+ "def hide_traceback(exc_tuple=None, filename=None, tb_offset=None,\n",
+ " exception_only=False, running_compiled_code=False):\n",
+ " etype, value, tb = sys.exc_info()\n",
+ " value.__cause__ = None # suppress chained exceptions\n",
+ " return ipython._showtraceback(etype, value, ipython.InteractiveTB.get_exception_only(etype, value))\n",
+ "\n",
+ "ipython.showtraceback = hide_traceback"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "nbsphinx": "hidden"
+ },
+ "outputs": [],
+ "source": [
+ "# JSON output syntax highlighting\n",
+ "from __future__ import print_function\n",
+ "from pygments import highlight\n",
+ "from pygments.lexers import JsonLexer, TextLexer\n",
+ "from pygments.formatters import HtmlFormatter\n",
+ "from IPython.display import display, HTML\n",
+ "from IPython.core.interactiveshell import InteractiveShell\n",
+ "\n",
+ "InteractiveShell.ast_node_interactivity = \"all\"\n",
+ "\n",
+ "def json_print(inpt):\n",
+ " string = str(inpt)\n",
+ " formatter = HtmlFormatter()\n",
+ " if string[0] == '{':\n",
+ " lexer = JsonLexer()\n",
+ " else:\n",
+ " lexer = TextLexer()\n",
+ " return HTML('{}'.format(\n",
+ " formatter.get_style_defs('.highlight'),\n",
+ " highlight(string, lexer, formatter)))\n",
+ "\n",
+ "globals()['print'] = json_print"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "STIX Extensions\n",
+ "\n",
+ "This page is specific for the STIX Extensions mechanism defined in STIX 2.1 CS 02, for the deprecated STIX Customization mechanisms see the [Custom](custom.ipynb) section.\n",
+ "\n",
+ "The example below shows how to create an `indicator` object with a `top-level-property-extension`. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
{\n",
+ " "type": "extension-definition",\n",
+ " "spec_version": "2.1",\n",
+ " "id": "extension-definition--dd73de4f-a7f3-49ea-8ec1-8e884196b7a8",\n",
+ " "created_by_ref": "identity--11b76a96-5d2b-45e0-8a5a-f6994f370731",\n",
+ " "created": "2014-02-20T09:16:08.000Z",\n",
+ " "modified": "2014-02-20T09:16:08.000Z",\n",
+ " "name": "New SDO 1",\n",
+ " "description": "This schema adds two properties to a STIX object at the toplevel",\n",
+ " "schema": "https://www.example.com/schema-foo-1a/v1/",\n",
+ " "version": "1.2.1",\n",
+ " "extension_types": [\n",
+ " "toplevel-property-extension"\n",
+ " ],\n",
+ " "extension_properties": [\n",
+ " "toxicity",\n",
+ " "rank"\n",
+ " ]\n",
+ "}\n",
+ "
\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "text/html": [
+ "{\n",
+ " "type": "indicator",\n",
+ " "spec_version": "2.1",\n",
+ " "id": "indicator--e97bfccf-8970-4a3c-9cd1-5b5b97ed5d0c",\n",
+ " "created": "2014-02-20T09:16:08.989Z",\n",
+ " "modified": "2014-02-20T09:16:08.989Z",\n",
+ " "name": "File hash for Poison Ivy variant",\n",
+ " "description": "This file hash indicates that a sample of Poison Ivy is present.",\n",
+ " "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",\n",
+ " "pattern_type": "stix",\n",
+ " "pattern_version": "2.1",\n",
+ " "valid_from": "2014-02-20T09:00:00Z",\n",
+ " "labels": [\n",
+ " "malicious-activity"\n",
+ " ],\n",
+ " "extensions": {\n",
+ " "extension-definition--dd73de4f-a7f3-49ea-8ec1-8e884196b7a8": {\n",
+ " "extension_type": "toplevel-property-extension"\n",
+ " }\n",
+ " },\n",
+ " "rank": 5,\n",
+ " "toxicity": 8\n",
+ "}\n",
+ "
\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import stix2\n",
+ "\n",
+ "extension_definition1 = stix2.v21.ExtensionDefinition(\n",
+ " id=\"extension-definition--dd73de4f-a7f3-49ea-8ec1-8e884196b7a8\",\n",
+ " created_by_ref=\"identity--11b76a96-5d2b-45e0-8a5a-f6994f370731\",\n",
+ " created=\"2014-02-20T09:16:08.000Z\",\n",
+ " modified=\"2014-02-20T09:16:08.000Z\",\n",
+ " name=\"New SDO 1\",\n",
+ " description=\"This schema adds two properties to a STIX object at the toplevel\",\n",
+ " schema=\"https://www.example.com/schema-foo-1a/v1/\",\n",
+ " version=\"1.2.1\",\n",
+ " extension_types=[\"toplevel-property-extension\"],\n",
+ " extension_properties=[\n",
+ " \"toxicity\",\n",
+ " \"rank\",\n",
+ " ],\n",
+ ")\n",
+ "\n",
+ "indicator = stix2.v21.Indicator(\n",
+ " id='indicator--e97bfccf-8970-4a3c-9cd1-5b5b97ed5d0c',\n",
+ " created='2014-02-20T09:16:08.989000Z',\n",
+ " modified='2014-02-20T09:16:08.989000Z',\n",
+ " name='File hash for Poison Ivy variant',\n",
+ " description='This file hash indicates that a sample of Poison Ivy is present.',\n",
+ " labels=[\n",
+ " 'malicious-activity',\n",
+ " ],\n",
+ " rank=5,\n",
+ " toxicity=8,\n",
+ " pattern='[file:hashes.\\'SHA-256\\' = \\'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c\\']',\n",
+ " pattern_type='stix',\n",
+ " valid_from='2014-02-20T09:00:00.000000Z',\n",
+ " extensions={\n",
+ " extension_definition1.id : {\n",
+ " 'extension_type': 'toplevel-property-extension',\n",
+ " },\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "print(extension_definition1.serialize(pretty=True))\n",
+ "print(indicator.serialize(pretty=True))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "However, in order to prevent repetitive instantiation of the same extension, the `@CustomExtension` when used in a class for registering the `extension-definition` with stix2, it allows passing the id. Use the `extension_type` class variable to define what kind of extension it is."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "{\n",
+ " "type": "indicator",\n",
+ " "spec_version": "2.1",\n",
+ " "id": "indicator--e97bfccf-8970-4a3c-9cd1-5b5b97ed5d0c",\n",
+ " "created": "2014-02-20T09:16:08.989Z",\n",
+ " "modified": "2014-02-20T09:16:08.989Z",\n",
+ " "name": "File hash for Poison Ivy variant",\n",
+ " "description": "This file hash indicates that a sample of Poison Ivy is present.",\n",
+ " "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",\n",
+ " "pattern_type": "stix",\n",
+ " "pattern_version": "2.1",\n",
+ " "valid_from": "2014-02-20T09:00:00Z",\n",
+ " "labels": [\n",
+ " "malicious-activity"\n",
+ " ],\n",
+ " "extensions": {\n",
+ " "extension-definition--dd73de4f-a7f3-49ea-8ec1-8e884196b7a8": {\n",
+ " "extension_type": "toplevel-property-extension"\n",
+ " }\n",
+ " },\n",
+ " "rank": 5,\n",
+ " "toxicity": 8\n",
+ "}\n",
+ "
\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "TOPLEVEL_EXTENSION_DEFINITION_ID = 'extension-definition--dd73de4f-a7f3-49ea-8ec1-8e884196b7a8'\n",
+ "\n",
+ "@stix2.v21.CustomExtension(\n",
+ " TOPLEVEL_EXTENSION_DEFINITION_ID, [\n",
+ " ('rank', stix2.properties.IntegerProperty(required=True)),\n",
+ " ('toxicity', stix2.properties.IntegerProperty(required=True)),\n",
+ " ],\n",
+ ")\n",
+ "class ExtensionTopLevel:\n",
+ " extension_type = 'toplevel-property-extension'\n",
+ "\n",
+ "indicator = stix2.v21.Indicator(\n",
+ " id='indicator--e97bfccf-8970-4a3c-9cd1-5b5b97ed5d0c',\n",
+ " created='2014-02-20T09:16:08.989000Z',\n",
+ " modified='2014-02-20T09:16:08.989000Z',\n",
+ " name='File hash for Poison Ivy variant',\n",
+ " description='This file hash indicates that a sample of Poison Ivy is present.',\n",
+ " labels=[\n",
+ " 'malicious-activity',\n",
+ " ],\n",
+ " rank=5,\n",
+ " toxicity=8,\n",
+ " pattern='[file:hashes.\\'SHA-256\\' = \\'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c\\']',\n",
+ " pattern_type='stix',\n",
+ " valid_from='2014-02-20T09:00:00.000000Z',\n",
+ " extensions={\n",
+ " TOPLEVEL_EXTENSION_DEFINITION_ID : {\n",
+ " 'extension_type': 'toplevel-property-extension',\n",
+ " },\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "print(indicator.serialize(pretty=True))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Similarly, now when registering new objects `@CustomObject` now supports passing an `extension-definition` id. \n",
+ "\n",
+ "---\n",
+ "**Note:**\n",
+ "Creating an instance of an extension-definition object **does not** mean it is registered in the library. Please use the appropriate decorator for this step: `@CustomExtension`, `@CustomObject`, `@CustomObservable`, `@CustomMarking`\n",
+ "\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "{\n",
+ " "type": "my-favorite-sco",\n",
+ " "spec_version": "2.1",\n",
+ " "id": "my-favorite-sco--f9dbe89c-0030-4a9d-8b78-0dcd0a0de874",\n",
+ " "name": "This is the name of my favorite SCO",\n",
+ " "some_network_protocol_field": "value",\n",
+ " "extensions": {\n",
+ " "extension-definition--150c1738-28c9-44d0-802d-70523218240b": {\n",
+ " "extension_type": "new-sco"\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "
\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "@stix2.v21.CustomObservable(\n",
+ " 'my-favorite-sco', [\n",
+ " ('name', stix2.properties.StringProperty(required=True)),\n",
+ " ('some_network_protocol_field', stix2.properties.StringProperty(required=True)),\n",
+ " ], ['name', 'some_network_protocol_field'], 'extension-definition--150c1738-28c9-44d0-802d-70523218240b',\n",
+ ")\n",
+ "class MyFavSCO:\n",
+ " pass\n",
+ "\n",
+ "my_favorite_sco = MyFavSCO(\n",
+ " id='my-favorite-sco--f9dbe89c-0030-4a9d-8b78-0dcd0a0de874',\n",
+ " name='This is the name of my favorite SCO',\n",
+ " some_network_protocol_field='value',\n",
+ ")\n",
+ "\n",
+ "print(my_favorite_sco.serialize(pretty=True))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The example below shows the use for MarkingDefinition extensions. Currently this is only supported as a `property-extension`. Now, as another option to building the `extensions` as a dictionary, it can also be built with objects as shown below by extracting the registered class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "{\n",
+ " "type": "marking-definition",\n",
+ " "spec_version": "2.1",\n",
+ " "id": "marking-definition--28417f9f-1963-4e7f-914d-233f8fd4829f",\n",
+ " "created": "2021-03-31T21:54:46.652069Z",\n",
+ " "name": "This is the name of my favorite Marking",\n",
+ " "extensions": {\n",
+ " "extension-definition--a932fcc6-e032-176c-126f-cb970a5a1fff": {\n",
+ " "extension_type": "property-extension"\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "
\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from stix2 import registry\n",
+ "\n",
+ "MARKING_EXTENSION_ID = 'extension-definition--a932fcc6-e032-176c-126f-cb970a5a1fff'\n",
+ "\n",
+ "@stix2.v21.CustomMarking(\n",
+ " 'my-favorite-marking', [\n",
+ " ('some_marking_field', stix2.properties.StringProperty(required=True)),\n",
+ " ], MARKING_EXTENSION_ID,\n",
+ ")\n",
+ "class MyFavMarking:\n",
+ " pass\n",
+ "\n",
+ "ext_class = registry.class_for_type(MARKING_EXTENSION_ID, '2.1')\n",
+ "\n",
+ "my_favorite_marking = MyFavMarking(\n",
+ " name='This is the name of my favorite Marking',\n",
+ " extensions={\n",
+ " MARKING_EXTENSION_ID: ext_class(some_marking_field='value')\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "print(my_favorite_marking.serialize(pretty=True))"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Edit Metadata",
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/overview.rst b/docs/overview.rst
index 0396ee8..f342c48 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -24,7 +24,7 @@ To accomplish these goals, and to incorporate lessons learned while developing
where users would create an object and then assign attributes to it, in
``stix2`` all properties must be provided when creating the object.
2. Where necessary, library objects should act like ``dict``'s. When treated as
- a ``str``, the JSON reprentation of the object should be used.
+ a ``str``, the JSON representation of the object should be used.
3. Core Python data types (including numeric types, ``datetime``) should be used
when appropriate, and serialized to the correct format in JSON as specified
in the STIX 2 spec.
diff --git a/stix2/registry.py b/stix2/registry.py
index 90e2826..7cfc9ef 100644
--- a/stix2/registry.py
+++ b/stix2/registry.py
@@ -69,9 +69,12 @@ def class_for_type(stix_type, stix_version, category=None):
if class_map:
cls = class_map.get(stix_type)
else:
- cls = cat_map["objects"].get(stix_type) \
- or cat_map["observables"].get(stix_type) \
- or cat_map["markings"].get(stix_type)
+ cls = (
+ cat_map["objects"].get(stix_type) or
+ cat_map["observables"].get(stix_type) or
+ cat_map["markings"].get(stix_type) or
+ cat_map["extensions"].get(stix_type)
+ )
# Left "observable-extensions" out; it has a different
# substructure. A version->category->type lookup would result
diff --git a/stix2/test/v21/test_extension_definition.py b/stix2/test/v21/test_extension_definition.py
index 7aac866..97513c4 100644
--- a/stix2/test/v21/test_extension_definition.py
+++ b/stix2/test/v21/test_extension_definition.py
@@ -54,7 +54,7 @@ def test_extension_definition_example():
"description": "This schema creates a new object type called my-favorite-sdo-1",
"schema": "https://www.example.com/schema-my-favorite-sdo-1/v1/",
"version": "1.2.1",
- "extension_types": ["new-sdo"]
+ "extension_types": ["new-sdo"],
},
],
)
@@ -75,7 +75,8 @@ def test_parse_extension_definition(data):
def test_parse_no_type():
with pytest.raises(stix2.exceptions.ParseError):
- stix2.parse("""{
+ stix2.parse(
+ """{
"id": "{EXTENSION_DEFINITION_IDS[0]}",
"spec_version": "2.1",
"name": "New SDO 1",
diff --git a/stix2/v21/common.py b/stix2/v21/common.py
index 83d6fa4..a18774d 100644
--- a/stix2/v21/common.py
+++ b/stix2/v21/common.py
@@ -273,6 +273,7 @@ def CustomMarking(type='x-custom-marking', properties=None, extension_name=None)
extension = extension.replace('-', '')
NameExtension.__name__ = 'ExtensionDefinition' + extension
cls.with_extension = extension_name
+ return _custom_marking_builder(cls, type, MarkingDefinition._properties, '2.1', _STIXBase21)
return _custom_marking_builder(cls, type, properties, '2.1', _STIXBase21)
return wrapper