Repository: oasis-open/cti-python-stix2
Pull request #521 (2021-07-12 18:59:24)

Fix custom markings
The main fix in this PR is for custom markings.  The changes which had been made to `@CustomMarking` to support the new extension-based custom markings are backed out.  The new way to do this is to create a normal extension via `@CustomExtension` and use the extension with a marking definition, the same way as for any other object.  `@CustomMarking` is now only used for the old-style custom markings.

Other misc changes include:
- Update the extensions jupyter notebook to correct documentation w.r.t. the above changes
- Update unit tests for custom markings w.r.t. the above changes
- Update extension registration to check toplevel property names in the same way as nested property names
- Add a unit test for an interesting oddball case where an extension can enable itself.  The library supports this for now.



Patch:
diff --git a/docs/guide/extensions.ipynb b/docs/guide/extensions.ipynb
index 9dcbdb2..9bd1c56 100644
--- a/docs/guide/extensions.ipynb
+++ b/docs/guide/extensions.ipynb
@@ -59,11 +59,11 @@
    "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",
+    "This page is specific for the STIX Extensions mechanism defined in STIX 2.1 CS 03. For the deprecated STIX Customization mechanisms see the [Custom](custom.ipynb) section.\n",
     "\n",
     "### Top Level Property Extensions\n",
     "\n",
-    "The example below shows how to create an `indicator` object with a `top-level-property-extension`.  "
+    "The example below shows how to create an `indicator` object with a `toplevel-property-extension`.  Because an unregistered toplevel property extension is present, any unrecognized toplevel properties are assumed to be extension properties.  So the library lets them pass. "
    ]
   },
   {
@@ -74,116 +74,11 @@
     {
      "data": {
       "text/html": [
-       "<style type=\"text/css\">pre { line-height: 125%; margin: 0; }\n",
-       "td.linenos pre { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }\n",
-       "span.linenos { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }\n",
-       "td.linenos pre.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }\n",
-       "span.linenos.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }\n",
-       ".highlight .hll { background-color: #ffffcc }\n",
-       ".highlight { background: #f8f8f8; }\n",
-       ".highlight .c { color: #408080; font-style: italic } /* Comment */\n",
-       ".highlight .err { border: 1px solid #FF0000 } /* Error */\n",
-       ".highlight .k { color: #008000; font-weight: bold } /* Keyword */\n",
-       ".highlight .o { color: #666666 } /* Operator */\n",
-       ".highlight .ch { color: #408080; font-style: italic } /* Comment.Hashbang */\n",
-       ".highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */\n",
-       ".highlight .cp { color: #BC7A00 } /* Comment.Preproc */\n",
-       ".highlight .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */\n",
-       ".highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */\n",
-       ".highlight .cs { color: #408080; font-style: italic } /* Comment.Special */\n",
-       ".highlight .gd { color: #A00000 } /* Generic.Deleted */\n",
-       ".highlight .ge { font-style: italic } /* Generic.Emph */\n",
-       ".highlight .gr { color: #FF0000 } /* Generic.Error */\n",
-       ".highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
-       ".highlight .gi { color: #00A000 } /* Generic.Inserted */\n",
-       ".highlight .go { color: #888888 } /* Generic.Output */\n",
-       ".highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
-       ".highlight .gs { font-weight: bold } /* Generic.Strong */\n",
-       ".highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
-       ".highlight .gt { color: #0044DD } /* Generic.Traceback */\n",
-       ".highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
-       ".highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
-       ".highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
-       ".highlight .kp { color: #008000 } /* Keyword.Pseudo */\n",
-       ".highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
-       ".highlight .kt { color: #B00040 } /* Keyword.Type */\n",
-       ".highlight .m { color: #666666 } /* Literal.Number */\n",
-       ".highlight .s { color: #BA2121 } /* Literal.String */\n",
-       ".highlight .na { color: #7D9029 } /* Name.Attribute */\n",
-       ".highlight .nb { color: #008000 } /* Name.Builtin */\n",
-       ".highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
-       ".highlight .no { color: #880000 } /* Name.Constant */\n",
-       ".highlight .nd { color: #AA22FF } /* Name.Decorator */\n",
-       ".highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */\n",
-       ".highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */\n",
-       ".highlight .nf { color: #0000FF } /* Name.Function */\n",
-       ".highlight .nl { color: #A0A000 } /* Name.Label */\n",
-       ".highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
-       ".highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
-       ".highlight .nv { color: #19177C } /* Name.Variable */\n",
-       ".highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
-       ".highlight .w { color: #bbbbbb } /* Text.Whitespace */\n",
-       ".highlight .mb { color: #666666 } /* Literal.Number.Bin */\n",
-       ".highlight .mf { color: #666666 } /* Literal.Number.Float */\n",
-       ".highlight .mh { color: #666666 } /* Literal.Number.Hex */\n",
-       ".highlight .mi { color: #666666 } /* Literal.Number.Integer */\n",
-       ".highlight .mo { color: #666666 } /* Literal.Number.Oct */\n",
-       ".highlight .sa { color: #BA2121 } /* Literal.String.Affix */\n",
-       ".highlight .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
-       ".highlight .sc { color: #BA2121 } /* Literal.String.Char */\n",
-       ".highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
-       ".highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
-       ".highlight .s2 { color: #BA2121 } /* Literal.String.Double */\n",
-       ".highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */\n",
-       ".highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
-       ".highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */\n",
-       ".highlight .sx { color: #008000 } /* Literal.String.Other */\n",
-       ".highlight .sr { color: #BB6688 } /* Literal.String.Regex */\n",
-       ".highlight .s1 { color: #BA2121 } /* Literal.String.Single */\n",
-       ".highlight .ss { color: #19177C } /* Literal.String.Symbol */\n",
-       ".highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
-       ".highlight .fm { color: #0000FF } /* Name.Function.Magic */\n",
-       ".highlight .vc { color: #19177C } /* Name.Variable.Class */\n",
-       ".highlight .vg { color: #19177C } /* Name.Variable.Global */\n",
-       ".highlight .vi { color: #19177C } /* Name.Variable.Instance */\n",
-       ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n",
-       ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */</style><div class=\"highlight\"><pre><span></span><span class=\"p\">{</span>\n",
-       "    <span class=\"nt\">&quot;type&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;extension-definition&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;spec_version&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2.1&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;id&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;extension-definition--dd73de4f-a7f3-49ea-8ec1-8e884196b7a8&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;created_by_ref&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;identity--11b76a96-5d2b-45e0-8a5a-f6994f370731&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;created&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2014-02-20T09:16:08.000Z&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;modified&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2014-02-20T09:16:08.000Z&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;name&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;New SDO 1&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;description&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;This schema adds two properties to a STIX object at the toplevel&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;schema&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;https://www.example.com/schema-foo-1a/v1/&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;version&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;1.2.1&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;extension_types&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n",
-       "        <span class=\"s2\">&quot;toplevel-property-extension&quot;</span>\n",
-       "    <span class=\"p\">],</span>\n",
-       "    <span class=\"nt\">&quot;extension_properties&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n",
-       "        <span class=\"s2\">&quot;toxicity&quot;</span><span class=\"p\">,</span>\n",
-       "        <span class=\"s2\">&quot;rank&quot;</span>\n",
-       "    <span class=\"p\">]</span>\n",
-       "<span class=\"p\">}</span>\n",
-       "</pre></div>\n"
-      ],
-      "text/plain": [
-       "<IPython.core.display.HTML object>"
-      ]
-     },
-     "execution_count": 3,
-     "metadata": {},
-     "output_type": "execute_result"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<style type=\"text/css\">pre { line-height: 125%; margin: 0; }\n",
-       "td.linenos pre { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }\n",
-       "span.linenos { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }\n",
-       "td.linenos pre.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }\n",
-       "span.linenos.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }\n",
+       "<style type=\"text/css\">pre { line-height: 125%; }\n",
+       "td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
+       "span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
+       "td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
+       "span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
        ".highlight .hll { background-color: #ffffcc }\n",
        ".highlight { background: #f8f8f8; }\n",
        ".highlight .c { color: #408080; font-style: italic } /* Comment */\n",
@@ -289,22 +184,6 @@
    "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",
@@ -320,13 +199,12 @@
     "    pattern_type='stix',\n",
     "    valid_from='2014-02-20T09:00:00.000000Z',\n",
     "    extensions={\n",
-    "        extension_definition1.id : {\n",
+    "        \"extension-definition--dd73de4f-a7f3-49ea-8ec1-8e884196b7a8\" : {\n",
     "            'extension_type': 'toplevel-property-extension',\n",
     "        },\n",
     "    }\n",
     ")\n",
     "\n",
-    "print(extension_definition1.serialize(pretty=True))\n",
     "print(indicator.serialize(pretty=True))"
    ]
   },
@@ -336,7 +214,7 @@
    "source": [
     "### Using CustomExtension decorator\n",
     "\n",
-    "However, in order to prevent repetitive instantiation of the same extension, the `@CustomExtension` decorator can be used to register the `extension-definition` with stix2. Use the `extension_type` class variable to define what kind of extension it is. Then its id can be passed into objects that use this extension."
+    "However, in order to define which properties are actually included with an extension, the `@CustomExtension` decorator can be used to register an extension type and its properties with stix2. Use the `extension_type` class variable to define what kind of extension it is. Then its id can be passed into objects that use this extension."
    ]
   },
   {
@@ -500,7 +378,7 @@
     "\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",
+    "Creating an instance of an extension object **does not** mean it is registered in the library. Please use the appropriate decorator for this step: `@CustomExtension`, `@CustomObject`, `@CustomObservable`, `@CustomMarking`\n",
     "\n",
     "---"
    ]
@@ -632,9 +510,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Using CustomMarking for Extension Definition\n",
+    "### Custom Markings\n",
     "\n",
-    "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."
+    "The example below show how to create a user-defined marking based on an extension.  The STIX `marking-definition` object is essentially a base upon which you build the particulars of your marking, via an extension.  This is done in the same way as any other extension.  Marking definitions are no different in this regard.  The below example illustrates an alternative to building the extension entirely as a dictionary: it can also be built by instantiating the registered class."
    ]
   },
   {
@@ -645,11 +523,11 @@
     {
      "data": {
       "text/html": [
-       "<style type=\"text/css\">pre { line-height: 125%; margin: 0; }\n",
-       "td.linenos pre { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }\n",
-       "span.linenos { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }\n",
-       "td.linenos pre.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }\n",
-       "span.linenos.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }\n",
+       "<style type=\"text/css\">pre { line-height: 125%; }\n",
+       "td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
+       "span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
+       "td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
+       "span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
        ".highlight .hll { background-color: #ffffcc }\n",
        ".highlight { background: #f8f8f8; }\n",
        ".highlight .c { color: #408080; font-style: italic } /* Comment */\n",
@@ -721,12 +599,13 @@
        ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */</style><div class=\"highlight\"><pre><span></span><span class=\"p\">{</span>\n",
        "    <span class=\"nt\">&quot;type&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;marking-definition&quot;</span><span class=\"p\">,</span>\n",
        "    <span class=\"nt\">&quot;spec_version&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2.1&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;id&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;marking-definition--28417f9f-1963-4e7f-914d-233f8fd4829f&quot;</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;created&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2021-03-31T21:54:46.652069Z&quot;</span><span class=\"p\">,</span>\n",
+       "    <span class=\"nt\">&quot;id&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;marking-definition--9155f07c-dd4c-4320-be6a-0701311c3b84&quot;</span><span class=\"p\">,</span>\n",
+       "    <span class=\"nt\">&quot;created&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2021-07-12T00:56:31.47566Z&quot;</span><span class=\"p\">,</span>\n",
        "    <span class=\"nt\">&quot;name&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;This is the name of my favorite Marking&quot;</span><span class=\"p\">,</span>\n",
        "    <span class=\"nt\">&quot;extensions&quot;</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n",
        "        <span class=\"nt\">&quot;extension-definition--a932fcc6-e032-176c-126f-cb970a5a1fff&quot;</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n",
-       "            <span class=\"nt\">&quot;extension_type&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;property-extension&quot;</span>\n",
+       "            <span class=\"nt\">&quot;extension_type&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;property-extension&quot;</span><span class=\"p\">,</span>\n",
+       "            <span class=\"nt\">&quot;some_marking_field&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;value&quot;</span>\n",
        "        <span class=\"p\">}</span>\n",
        "    <span class=\"p\">}</span>\n",
        "<span class=\"p\">}</span>\n",
@@ -742,24 +621,23 @@
     }
    ],
    "source": [
-    "from stix2 import registry\n",
+    "import stix2\n",
+    "import stix2.properties\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",
+    "@stix2.CustomExtension(MARKING_EXTENSION_ID, [\n",
+    "    ('some_marking_field', stix2.properties.StringProperty(required=True))\n",
+    "])\n",
     "class MyFavMarking:\n",
-    "    pass\n",
-    "\n",
-    "ext_class = registry.class_for_type(MARKING_EXTENSION_ID, '2.1')\n",
+    "    extension_type = 'property-extension'\n",
     "\n",
-    "my_favorite_marking = MyFavMarking(\n",
+    "my_favorite_marking = stix2.MarkingDefinition(\n",
     "    name='This is the name of my favorite Marking',\n",
     "    extensions={\n",
-    "        MARKING_EXTENSION_ID: ext_class(some_marking_field='value')\n",
+    "        MARKING_EXTENSION_ID: MyFavMarking(\n",
+    "            some_marking_field='value'\n",
+    "        )\n",
     "    }\n",
     ")\n",
     "\n",
@@ -908,7 +786,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -922,7 +800,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.2"
+   "version": "3.8.1"
   }
  },
  "nbformat": 4,
diff --git a/docs/guide/parsing.ipynb b/docs/guide/parsing.ipynb
index 0e89126..452f570 100644
--- a/docs/guide/parsing.ipynb
+++ b/docs/guide/parsing.ipynb
@@ -64,7 +64,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Parsing STIX content is as easy as calling the [parse()](../api/stix2.parsing.rst#stix2.parsing.parse) function on a JSON string, dictionary, or file-like object. It will automatically determine the type of the object. The STIX objects within `bundle` objects, and any cyber observables contained within `observed-data` objects will be parsed as well.\n",
+    "Parsing STIX content is as easy as calling the [parse()](../api/stix2.parsing.rst#stix2.parsing.parse) function on a JSON string, dictionary, or file-like object. It will automatically determine the type of the object. The STIX objects within `bundle` objects will be parsed as well.\n",
     "\n",
     "**Parsing a string**"
    ]
@@ -77,8 +77,13 @@
     {
      "data": {
       "text/html": [
-       "<style type=\"text/css\">.highlight .hll { background-color: #ffffcc }\n",
-       ".highlight  { background: #f8f8f8; }\n",
+       "<style type=\"text/css\">pre { line-height: 125%; }\n",
+       "td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
+       "span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
+       "td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
+       "span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
+       ".highlight .hll { background-color: #ffffcc }\n",
+       ".highlight { background: #f8f8f8; }\n",
        ".highlight .c { color: #408080; font-style: italic } /* Comment */\n",
        ".highlight .err { border: 1px solid #FF0000 } /* Error */\n",
        ".highlight .k { color: #008000; font-weight: bold } /* Keyword */\n",
@@ -159,8 +164,13 @@
     {
      "data": {
       "text/html": [
-       "<style type=\"text/css\">.highlight .hll { background-color: #ffffcc }\n",
-       ".highlight  { background: #f8f8f8; }\n",
+       "<style type=\"text/css\">pre { line-height: 125%; }\n",
+       "td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
+       "span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\n",
+       "td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
+       "span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n",
+       ".highlight .hll { background-color: #ffffcc }\n",
+       ".highlight { background: #f8f8f8; }\n",
        ".highlight .c { color: #408080; font-style: italic } /* Comment */\n",
        ".highlight .err { border: 1px solid #FF0000 } /* Error */\n",
        ".highlight .k { color: #008000; font-weight: bold } /* Keyword */\n",
@@ -236,16 +246,9 @@
        "    <span class=\"nt\">&quot;first_observed&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2015-12-21T19:00:00Z&quot;</span><span class=\"p\">,</span>\n",
        "    <span class=\"nt\">&quot;last_observed&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2015-12-21T19:00:00Z&quot;</span><span class=\"p\">,</span>\n",
        "    <span class=\"nt\">&quot;number_observed&quot;</span><span class=\"p\">:</span> <span class=\"mi\">50</span><span class=\"p\">,</span>\n",
-       "    <span class=\"nt\">&quot;objects&quot;</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n",
-       "        <span class=\"nt\">&quot;0&quot;</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n",
-       "            <span class=\"nt\">&quot;type&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;file&quot;</span><span class=\"p\">,</span>\n",
-       "            <span class=\"nt\">&quot;id&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;file--5d0833b7-065e-571f-8bf2-657cb9569570&quot;</span><span class=\"p\">,</span>\n",
-       "            <span class=\"nt\">&quot;spec_version&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;2.1&quot;</span><span class=\"p\">,</span>\n",
-       "            <span class=\"nt\">&quot;hashes&quot;</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n",
-       "                <span class=\"nt\">&quot;SHA-256&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;0969de02ecf8a5f003e3f6d063d848c8a193aada092623f8ce408c15bcb5f038&quot;</span>\n",
-       "            <span class=\"p\">}</span>\n",
-       "        <span class=\"p\">}</span>\n",
-       "    <span class=\"p\">}</span>\n",
+       "    <span class=\"nt\">&quot;object_refs&quot;</span><span class=\"p\">:</span> <span class=\"p\">[</span>\n",
+       "        <span class=\"s2\">&quot;file--5d2dc832-b137-4e8c-97b2-5b00c18be611&quot;</span>\n",
+       "    <span class=\"p\">]</span>\n",
        "<span class=\"p\">}</span>\n",
        "</pre></div>\n"
       ],
@@ -270,14 +273,9 @@
     "    \"first_observed\": \"2015-12-21T19:00:00Z\",\n",
     "    \"last_observed\": \"2015-12-21T19:00:00Z\",\n",
     "    \"number_observed\": 50,\n",
-    "    \"objects\": {\n",
-    "        \"0\": {\n",
-    "            \"type\": \"file\",\n",
-    "            \"hashes\": {\n",
-    "                \"SHA-256\": \"0969de02ecf8a5f003e3f6d063d848c8a193aada092623f8ce408c15bcb5f038\"\n",
-    "            }\n",
-    "        }\n",
-    "    }\n",
+    "    \"object_refs\": [\n",
+    "        \"file--5d2dc832-b137-4e8c-97b2-5b00c18be611\"\n",
+    "    ]\n",
     "}\"\"\"\n",
     "\n",
     "obj = parse(input_string)\n",
@@ -732,7 +730,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -746,7 +744,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.0a6"
+   "version": "3.8.1"
   }
  },
  "nbformat": 4,
diff --git a/stix2/custom.py b/stix2/custom.py
index adef768..44a0349 100644
--- a/stix2/custom.py
+++ b/stix2/custom.py
@@ -3,9 +3,10 @@ from collections import OrderedDict
 from .base import _cls_init
 from .properties import EnumProperty
 from .registration import (
-    _get_extension_class, _register_extension, _register_marking,
-    _register_object, _register_observable,
+    _register_extension, _register_marking, _register_object,
+    _register_observable,
 )
+from .registry import class_for_type
 
 
 def _get_properties_dict(properties):
@@ -34,7 +35,7 @@ def _custom_object_builder(cls, type, properties, version, base_class):
             if ext and version != '2.0':
                 if 'extensions' not in self._inner:
                     self._inner['extensions'] = {}
-                self._inner['extensions'][ext] = _get_extension_class(ext, version)()
+                self._inner['extensions'][ext] = class_for_type(ext, version, "extensions")()
 
     _CustomObject.__name__ = cls.__name__
 
@@ -53,11 +54,6 @@ def _custom_marking_builder(cls, type, properties, version, base_class):
         def __init__(self, **kwargs):
             base_class.__init__(self, **kwargs)
             _cls_init(cls, self, kwargs)
-            ext = getattr(self, 'with_extension', None)
-            if ext and version != '2.0':
-                if 'extensions' not in self._inner:
-                    self._inner['extensions'] = {}
-                self._inner['extensions'][ext] = _get_extension_class(ext, version)()
 
     _CustomMarking.__name__ = cls.__name__
 
@@ -85,7 +81,7 @@ def _custom_observable_builder(cls, type, properties, version, base_class, id_co
             if ext and version != '2.0':
                 if 'extensions' not in self._inner:
                     self._inner['extensions'] = {}
-                self._inner['extensions'][ext] = _get_extension_class(ext, version)()
+                self._inner['extensions'][ext] = class_for_type(ext, version, "extensions")()
 
     _CustomObservable.__name__ = cls.__name__
 
diff --git a/stix2/registration.py b/stix2/registration.py
index 28d43ba..9fb2f49 100644
--- a/stix2/registration.py
+++ b/stix2/registration.py
@@ -1,3 +1,4 @@
+import itertools
 import re
 
 from . import registry, version
@@ -132,11 +133,6 @@ def _register_observable(new_observable, version=version.DEFAULT_VERSION):
     OBJ_MAP_OBSERVABLE[new_observable._type] = new_observable
 
 
-def _get_extension_class(extension_uuid, version):
-    """Retrieve a registered class Extension"""
-    return registry.STIX2_OBJ_MAPS[version]['extensions'].get(extension_uuid)
-
-
 def _register_extension(
     new_extension, version=version.DEFAULT_VERSION,
 ):
@@ -149,7 +145,12 @@ def _register_extension(
 
     """
     ext_type = new_extension._type
-    properties = new_extension._properties
+
+    # Need to check both toplevel and nested properties
+    prop_groups = [new_extension._properties]
+    if hasattr(new_extension, "_toplevel_properties"):
+        prop_groups.append(new_extension._toplevel_properties)
+    prop_names = itertools.chain.from_iterable(prop_groups)
 
     _validate_type(ext_type, version)
 
@@ -166,7 +167,7 @@ def _register_extension(
                 ext_type,
             )
 
-        for prop_name in properties.keys():
+        for prop_name in prop_names:
             if not re.match(PREFIX_21_REGEX, prop_name):
                 raise ValueError("Property name '%s' must begin with an alpha character." % prop_name)
 
diff --git a/stix2/v21/common.py b/stix2/v21/common.py
index 6ff36b9..55c4a05 100644
--- a/stix2/v21/common.py
+++ b/stix2/v21/common.py
@@ -252,7 +252,7 @@ OBJ_MAP_MARKING = {
 }
 
 
-def CustomMarking(type='x-custom-marking', properties=None, extension_name=None):
+def CustomMarking(type='x-custom-marking', properties=None):
     """Custom STIX Marking decorator.
 
     Example:
@@ -267,16 +267,6 @@ def CustomMarking(type='x-custom-marking', properties=None, extension_name=None)
 
     """
     def wrapper(cls):
-        if extension_name:
-            @CustomExtension(type=extension_name, properties=properties)
-            class NameExtension:
-                extension_type = 'property-extension'
-
-            extension = extension_name.split('--')[1]
-            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
 


Test patch:
diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py
index 637c79f..13eeba9 100644
--- a/stix2/test/v21/test_custom.py
+++ b/stix2/test/v21/test_custom.py
@@ -24,6 +24,33 @@ IDENTITY_CUSTOM_PROP = stix2.v21.Identity(
 )
 
 
+@contextlib.contextmanager
+def _register_extension(ext, props):
+    """
+    A contextmanager useful for registering an extension and then ensuring
+    it gets unregistered again.  A random extension-definition STIX ID is
+    generated for the extension and yielded as the contextmanager's value.
+
+    :param ext: The class which would normally be decorated with the
+        CustomExtension decorator.
+    :param props: Properties as would normally be passed into the
+        CustomExtension decorator.
+    """
+
+    ext_def_id = "extension-definition--" + str(uuid.uuid4())
+
+    stix2.v21.CustomExtension(
+        ext_def_id,
+        props,
+    )(ext)
+
+    try:
+        yield ext_def_id
+    finally:
+        # "unregister" the extension
+        del stix2.registry.STIX2_OBJ_MAPS["2.1"]["extensions"][ext_def_id]
+
+
 def test_identity_custom_property():
     identity = stix2.v21.Identity(
         id=IDENTITY_ID,
@@ -1645,54 +1672,64 @@ def test_registered_new_extension_sco_allow_custom_false():
 
 
 def test_registered_new_extension_marking_allow_custom_false():
-    @stix2.v21.CustomMarking(
-        'my-favorite-marking', [
-            ('some_marking_field', stix2.properties.StringProperty(required=True)),
-        ], 'extension-definition--a932fcc6-e032-176c-126f-cb970a5a1fff',
-    )
+
     class MyFavMarking:
-        pass
+        extension_type = "property-extension"
 
-    my_favorite_marking = {
-        'type': 'marking-definition',
-        'spec_version': '2.1',
-        'id': 'marking-definition--f9dbe89c-0030-4a9d-8b78-0dcd0a0de874',
-        'name': 'This is the name of my favorite Marking',
-        'extensions': {
-            'extension-definition--a932fcc6-e032-176c-126f-cb970a5a1fff': {
-                'extension_type': 'property-extension',
-                'some_marking_field': 'value',
-            },
-        },
+    props = {
+        'some_marking_field': stix2.properties.StringProperty(required=True),
     }
 
-    marking_object = stix2.parse(my_favorite_marking)
-    assert isinstance(marking_object, stix2.v21.MarkingDefinition)
-    assert isinstance(
-        marking_object.extensions['extension-definition--a932fcc6-e032-176c-126f-cb970a5a1fff'],
-        stix2.v21.EXT_MAP['extension-definition--a932fcc6-e032-176c-126f-cb970a5a1fff'],
-    )
+    with _register_extension(MyFavMarking, props) as ext_def_id:
 
-    marking_serialized = marking_object.serialize(sort_keys=True)
-    assert '"extensions": {"extension-definition--a932fcc6-e032-176c-126f-cb970a5a1fff": ' \
-           '{"extension_type": "property-extension", "some_marking_field": "value"}}' in marking_serialized
+        my_favorite_marking = {
+            'type': 'marking-definition',
+            'spec_version': '2.1',
+            'id': 'marking-definition--f9dbe89c-0030-4a9d-8b78-0dcd0a0de874',
+            'name': 'This is the name of my favorite Marking',
+            'extensions': {
+                ext_def_id: {
+                    'extension_type': 'property-extension',
+                    'some_marking_field': 'value',
+                },
+            },
+        }
 
+        marking_object = stix2.parse(my_favorite_marking)
+        assert isinstance(marking_object, stix2.v21.MarkingDefinition)
+        assert isinstance(
+            marking_object.extensions[ext_def_id],
+            stix2.v21.EXT_MAP[ext_def_id],
+        )
 
-@contextlib.contextmanager
-def _register_extension(ext, props):
+        marking_serialized = marking_object.serialize(sort_keys=True)
+        assert '"extensions": {{"{}": ' \
+               '{{"extension_type": "property-extension", "some_marking_field": "value"}}}}'.format(ext_def_id) in marking_serialized
 
-    ext_def_id = "extension-definition--" + str(uuid.uuid4())
 
-    stix2.v21.CustomExtension(
-        ext_def_id,
-        props,
-    )(ext)
+def test_custom_marking_toplevel_properties():
+    class CustomMarking:
+        extension_type = "toplevel-property-extension"
 
-    try:
-        yield ext_def_id
-    finally:
-        # "unregister" the extension
-        del stix2.registry.STIX2_OBJ_MAPS["2.1"]["extensions"][ext_def_id]
+    props = {
+        "foo": stix2.properties.StringProperty(required=True),
+    }
+
+    with _register_extension(CustomMarking, props) as ext_def_id:
+
+        marking_dict = {
+            "type": "marking-definition",
+            "spec_version": "2.1",
+            "foo": "hello",
+            "extensions": {
+                ext_def_id: {
+                    "extension_type": "toplevel-property-extension",
+                },
+            },
+        }
+
+        marking = stix2.parse(marking_dict)
+        assert marking.foo == "hello"
 
 
 def test_nested_ext_prop_meta():
@@ -1880,6 +1917,64 @@ def test_toplevel_ext_prop_meta():
             )
 
 
+def test_toplevel_extension_includes_extensions():
+    """
+    Test whether the library allows an extension to enable extension support
+    itself. :)  I.e. a toplevel property extension which adds the "extensions"
+    property.
+    """
+
+    class ExtensionsExtension:
+        extension_type = "toplevel-property-extension"
+
+    ext_props = {
+        "extensions": stix2.properties.ExtensionsProperty(spec_version="2.1"),
+    }
+
+    with _register_extension(ExtensionsExtension, ext_props) as ext_id:
+
+        # extension-definition is not defined with an "extensions" property.
+        obj_dict = {
+            "type": "extension-definition",
+            "spec_version": "2.1",
+            "created_by_ref": "identity--8a1fd5dd-4586-4ded-bd39-04bda62f8415",
+            "name": "my extension",
+            "version": "1.2.3",
+            "schema": "add extension support to an object!",
+            "extension_types": ["toplevel-property-extension"],
+            "extensions": {
+                ext_id: {
+                    "extension_type": "toplevel-property-extension",
+                },
+            },
+        }
+
+        stix2.parse(obj_dict)
+
+
+def test_invalid_extension_prop_name():
+
+    with pytest.raises(ValueError):
+        @stix2.v21.common.CustomExtension(
+            "extension-definition--0530fdbd-0fa3-42ab-90cf-660e0abad370",
+            [
+                ("7foo", stix2.properties.StringProperty()),
+            ],
+        )
+        class CustomExt:
+            extension_type = "property-extension"
+
+    with pytest.raises(ValueError):
+        @stix2.v21.common.CustomExtension(
+            "extension-definition--0530fdbd-0fa3-42ab-90cf-660e0abad370",
+            [
+                ("7foo", stix2.properties.StringProperty()),
+            ],
+        )
+        class CustomExt:  # noqa: F811
+            extension_type = "toplevel-property-extension"
+
+
 def test_allow_custom_propagation():
     obj_dict = {
         "type": "bundle",