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