diff --git a/docs/guide/creating.ipynb b/docs/guide/creating.ipynb index 83efb56..92a3336 100644 --- a/docs/guide/creating.ipynb +++ b/docs/guide/creating.ipynb @@ -144,12 +144,12 @@ ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n", ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "indicator",\n",
-       "    "id": "indicator--548af3be-39d7-4a3e-93c2-1a63cccf8951",\n",
-       "    "created": "2018-04-05T18:32:24.193Z",\n",
-       "    "modified": "2018-04-05T18:32:24.193Z",\n",
+       "    "id": "indicator--2f3d4926-163d-4aef-bcd2-19dea96916ae",\n",
+       "    "created": "2019-05-13T13:14:48.509Z",\n",
+       "    "modified": "2019-05-13T13:14:48.509Z",\n",
        "    "name": "File hash for malware variant",\n",
        "    "pattern": "[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']",\n",
-       "    "valid_from": "2018-04-05T18:32:24.193659Z",\n",
+       "    "valid_from": "2019-05-13T13:14:48.509629Z",\n",
        "    "labels": [\n",
        "        "malicious-activity"\n",
        "    ]\n",
@@ -465,9 +465,9 @@
        ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n",
        ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "malware",\n",
-       "    "id": "malware--3d7f0c1c-616a-4868-aa7b-150821d2a429",\n",
-       "    "created": "2018-04-05T18:32:46.584Z",\n",
-       "    "modified": "2018-04-05T18:32:46.584Z",\n",
+       "    "id": "malware--1f2aba70-f0ae-49cd-9267-6fcb1e43be67",\n",
+       "    "created": "2019-05-13T13:15:04.698Z",\n",
+       "    "modified": "2019-05-13T13:15:04.698Z",\n",
        "    "name": "Poison Ivy",\n",
        "    "labels": [\n",
        "        "remote-access-trojan"\n",
@@ -588,12 +588,12 @@
        ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n",
        ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "relationship",\n",
-       "    "id": "relationship--34ddc7b4-4965-4615-b286-1c8bbaa1e7db",\n",
-       "    "created": "2018-04-05T18:32:49.474Z",\n",
-       "    "modified": "2018-04-05T18:32:49.474Z",\n",
+       "    "id": "relationship--80c174fa-36d1-47c2-9a9d-ce0c636bedcc",\n",
+       "    "created": "2019-05-13T13:15:13.152Z",\n",
+       "    "modified": "2019-05-13T13:15:13.152Z",\n",
        "    "relationship_type": "indicates",\n",
-       "    "source_ref": "indicator--548af3be-39d7-4a3e-93c2-1a63cccf8951",\n",
-       "    "target_ref": "malware--3d7f0c1c-616a-4868-aa7b-150821d2a429"\n",
+       "    "source_ref": "indicator--2f3d4926-163d-4aef-bcd2-19dea96916ae",\n",
+       "    "target_ref": "malware--1f2aba70-f0ae-49cd-9267-6fcb1e43be67"\n",
        "}\n",
        "
\n" ], @@ -700,12 +700,12 @@ ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n", ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "relationship",\n",
-       "    "id": "relationship--0a646403-f7e7-4cfd-b945-cab5cde05857",\n",
-       "    "created": "2018-04-05T18:32:51.417Z",\n",
-       "    "modified": "2018-04-05T18:32:51.417Z",\n",
+       "    "id": "relationship--47395d23-dedd-45d4-8db1-c9ffaf44493d",\n",
+       "    "created": "2019-05-13T13:15:16.566Z",\n",
+       "    "modified": "2019-05-13T13:15:16.566Z",\n",
        "    "relationship_type": "indicates",\n",
-       "    "source_ref": "indicator--548af3be-39d7-4a3e-93c2-1a63cccf8951",\n",
-       "    "target_ref": "malware--3d7f0c1c-616a-4868-aa7b-150821d2a429"\n",
+       "    "source_ref": "indicator--2f3d4926-163d-4aef-bcd2-19dea96916ae",\n",
+       "    "target_ref": "malware--1f2aba70-f0ae-49cd-9267-6fcb1e43be67"\n",
        "}\n",
        "
\n" ], @@ -810,26 +810,26 @@ ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n", ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "bundle",\n",
-       "    "id": "bundle--f83477e5-f853-47e1-a267-43f3aa1bd5b0",\n",
+       "    "id": "bundle--388c9b2c-936c-420a-baa5-04f48d682a01",\n",
        "    "spec_version": "2.0",\n",
        "    "objects": [\n",
        "        {\n",
        "            "type": "indicator",\n",
-       "            "id": "indicator--548af3be-39d7-4a3e-93c2-1a63cccf8951",\n",
-       "            "created": "2018-04-05T18:32:24.193Z",\n",
-       "            "modified": "2018-04-05T18:32:24.193Z",\n",
+       "            "id": "indicator--2f3d4926-163d-4aef-bcd2-19dea96916ae",\n",
+       "            "created": "2019-05-13T13:14:48.509Z",\n",
+       "            "modified": "2019-05-13T13:14:48.509Z",\n",
        "            "name": "File hash for malware variant",\n",
        "            "pattern": "[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']",\n",
-       "            "valid_from": "2018-04-05T18:32:24.193659Z",\n",
+       "            "valid_from": "2019-05-13T13:14:48.509629Z",\n",
        "            "labels": [\n",
        "                "malicious-activity"\n",
        "            ]\n",
        "        },\n",
        "        {\n",
        "            "type": "malware",\n",
-       "            "id": "malware--3d7f0c1c-616a-4868-aa7b-150821d2a429",\n",
-       "            "created": "2018-04-05T18:32:46.584Z",\n",
-       "            "modified": "2018-04-05T18:32:46.584Z",\n",
+       "            "id": "malware--1f2aba70-f0ae-49cd-9267-6fcb1e43be67",\n",
+       "            "created": "2019-05-13T13:15:04.698Z",\n",
+       "            "modified": "2019-05-13T13:15:04.698Z",\n",
        "            "name": "Poison Ivy",\n",
        "            "labels": [\n",
        "                "remote-access-trojan"\n",
@@ -837,12 +837,12 @@
        "        },\n",
        "        {\n",
        "            "type": "relationship",\n",
-       "            "id": "relationship--34ddc7b4-4965-4615-b286-1c8bbaa1e7db",\n",
-       "            "created": "2018-04-05T18:32:49.474Z",\n",
-       "            "modified": "2018-04-05T18:32:49.474Z",\n",
+       "            "id": "relationship--80c174fa-36d1-47c2-9a9d-ce0c636bedcc",\n",
+       "            "created": "2019-05-13T13:15:13.152Z",\n",
+       "            "modified": "2019-05-13T13:15:13.152Z",\n",
        "            "relationship_type": "indicates",\n",
-       "            "source_ref": "indicator--548af3be-39d7-4a3e-93c2-1a63cccf8951",\n",
-       "            "target_ref": "malware--3d7f0c1c-616a-4868-aa7b-150821d2a429"\n",
+       "            "source_ref": "indicator--2f3d4926-163d-4aef-bcd2-19dea96916ae",\n",
+       "            "target_ref": "malware--1f2aba70-f0ae-49cd-9267-6fcb1e43be67"\n",
        "        }\n",
        "    ]\n",
        "}\n",
@@ -863,6 +863,249 @@
     "bundle = Bundle(indicator, malware, relationship)\n",
     "print(bundle)"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Creating Cyber Observable References\n",
+    "Cyber Observable Objects have properties that can reference other Cyber Observable Objects. In order to create those references, use the ``_valid_refs`` property as shown in the following examples. It should be noted that ``_valid_refs`` is necessary when creating references to Cyber Observable Objects since some embedded references can only point to certain types, and ``_valid_refs`` helps ensure consistency. \n",
+    "\n",
+    "There are two cases."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Case 1: Specifying the type of the Cyber Observable Objects being referenced\n",
+    "In the following example, the IPv4Address object has its ``resolves_to_refs`` property specified. As per the spec, this property's value must be a list of reference(s) to MACAddress objects. In this case, those references are strings that state the type of the Cyber Observable Object being referenced, and are provided in ``_valid_refs``."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "
{\n",
+       "    "type": "ipv4-addr",\n",
+       "    "value": "177.60.40.7",\n",
+       "    "resolves_to_refs": [\n",
+       "        "1",\n",
+       "        "2"\n",
+       "    ]\n",
+       "}\n",
+       "
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from stix2 import IPv4Address\n", + "\n", + "ip4 = IPv4Address(\n", + " _valid_refs={\"1\": \"mac-addr\", \"2\": \"mac-addr\"},\n", + " value=\"177.60.40.7\",\n", + " resolves_to_refs=[\"1\", \"2\"]\n", + ")\n", + "\n", + "print(ip4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Case 2: Specifying the name of the Cyber Observable Objects being referenced\n", + "The following example is just like the one provided in Case 1 above, with one key difference: instead of using strings to specify the type of the Cyber Observable Objects being referenced in ``_valid_refs``, the referenced Cyber Observable Objects are created beforehand and then their names are provided in ``_valid_refs``." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
{\n",
+       "    "type": "ipv4-addr",\n",
+       "    "value": "177.60.40.7",\n",
+       "    "resolves_to_refs": [\n",
+       "        "1",\n",
+       "        "2"\n",
+       "    ]\n",
+       "}\n",
+       "
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from stix2 import MACAddress\n", + "\n", + "mac_addr_a = MACAddress(value=\"a1:b2:c3:d4:e5:f6\")\n", + "mac_addr_b = MACAddress(value=\"a7:b8:c9:d0:e1:f2\")\n", + "\n", + "ip4_valid_refs = IPv4Address(\n", + " _valid_refs={\"1\": mac_addr_a, \"2\": mac_addr_b},\n", + " value=\"177.60.40.7\",\n", + " resolves_to_refs=[\"1\", \"2\"]\n", + ")\n", + "\n", + "print(ip4_valid_refs)" + ] } ], "metadata": { @@ -881,7 +1124,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/docs/guide/markings.ipynb b/docs/guide/markings.ipynb index 8230daf..44e023a 100644 --- a/docs/guide/markings.ipynb +++ b/docs/guide/markings.ipynb @@ -1310,6 +1310,212 @@ "source": [ "malware.is_marked(TLP_WHITE.id, 'description')" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Extracting Lang Data Markings or marking-definition Data Markings\n", + "\n", + "If you need a specific kind of marking, you can also filter them using the API. By default the library will get both types of markings by default. You can choose between `lang=True/False` or `marking_ref=True/False` depending on your use-case." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"type\": \"indicator\",\n", + " \"spec_version\": \"2.1\",\n", + " \"id\": \"indicator--634ef462-d6b5-48bc-9d9f-b46a6919227c\",\n", + " \"created\": \"2019-05-03T18:36:44.354Z\",\n", + " \"modified\": \"2019-05-03T18:36:44.354Z\",\n", + " \"description\": \"Una descripcion sobre este indicador\",\n", + " \"indicator_types\": [\n", + " \"malware\"\n", + " ],\n", + " \"pattern\": \"[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']\",\n", + " \"valid_from\": \"2019-05-03T18:36:44.354443Z\",\n", + " \"object_marking_refs\": [\n", + " \"marking-definition--f88d31f6-486f-44da-b317-01333bde0b82\"\n", + " ],\n", + " \"granular_markings\": [\n", + " {\n", + " \"lang\": \"es\",\n", + " \"selectors\": [\n", + " \"description\"\n", + " ]\n", + " },\n", + " {\n", + " \"marking_ref\": \"marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da\",\n", + " \"selectors\": [\n", + " \"description\"\n", + " ]\n", + " }\n", + " ]\n", + "}\n", + "['es', 'marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da']\n", + "['marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da']\n", + "['es']\n" + ] + } + ], + "source": [ + "from stix2 import v21\n", + "\n", + "v21_indicator = v21.Indicator(\n", + " description=\"Una descripcion sobre este indicador\",\n", + " pattern=\"[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']\",\n", + " object_marking_refs=['marking-definition--f88d31f6-486f-44da-b317-01333bde0b82'],\n", + " indicator_types=['malware'],\n", + " granular_markings=[\n", + " {\n", + " 'selectors': ['description'],\n", + " 'lang': 'es'\n", + " },\n", + " {\n", + " 'selectors': ['description'],\n", + " 'marking_ref': 'marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da'\n", + " }\n", + " ]\n", + ")\n", + "print(v21_indicator)\n", + "\n", + "# Gets both lang and marking_ref markings for 'description'\n", + "print(v21_indicator.get_markings('description'))\n", + "\n", + "# Exclude lang markings from results\n", + "print(v21_indicator.get_markings('description', lang=False))\n", + "\n", + "# Exclude marking-definition markings from results\n", + "print(v21_indicator.get_markings('description', marking_ref=False))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this same manner, calls to `clear_markings` and `set_markings` also have the ability to operate in for one or both types of markings." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"type\": \"indicator\",\n", + " \"spec_version\": \"2.1\",\n", + " \"id\": \"indicator--a612665a-2df4-4fd2-851c-7fbb8c92339a\",\n", + " \"created\": \"2019-05-03T19:13:59.010Z\",\n", + " \"modified\": \"2019-05-03T19:15:41.173Z\",\n", + " \"description\": \"Una descripcion sobre este indicador\",\n", + " \"indicator_types\": [\n", + " \"malware\"\n", + " ],\n", + " \"pattern\": \"[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']\",\n", + " \"valid_from\": \"2019-05-03T19:13:59.010624Z\",\n", + " \"object_marking_refs\": [\n", + " \"marking-definition--f88d31f6-486f-44da-b317-01333bde0b82\"\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "print(v21_indicator.clear_markings(\"description\")) # By default, both types of markings will be removed" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"type\": \"indicator\",\n", + " \"spec_version\": \"2.1\",\n", + " \"id\": \"indicator--982aeb4d-4dd3-4b04-aa50-a1d00c31986c\",\n", + " \"created\": \"2019-05-03T19:19:26.542Z\",\n", + " \"modified\": \"2019-05-03T19:20:51.818Z\",\n", + " \"description\": \"Una descripcion sobre este indicador\",\n", + " \"indicator_types\": [\n", + " \"malware\"\n", + " ],\n", + " \"pattern\": \"[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']\",\n", + " \"valid_from\": \"2019-05-03T19:19:26.542267Z\",\n", + " \"object_marking_refs\": [\n", + " \"marking-definition--f88d31f6-486f-44da-b317-01333bde0b82\"\n", + " ],\n", + " \"granular_markings\": [\n", + " {\n", + " \"lang\": \"es\",\n", + " \"selectors\": [\n", + " \"description\"\n", + " ]\n", + " }\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "# If lang is False, no lang markings will be removed\n", + "print(v21_indicator.clear_markings(\"description\", lang=False))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"type\": \"indicator\",\n", + " \"spec_version\": \"2.1\",\n", + " \"id\": \"indicator--de0316d6-38e1-43c2-af4f-649305251864\",\n", + " \"created\": \"2019-05-03T19:40:21.459Z\",\n", + " \"modified\": \"2019-05-03T19:40:26.431Z\",\n", + " \"description\": \"Una descripcion sobre este indicador\",\n", + " \"indicator_types\": [\n", + " \"malware\"\n", + " ],\n", + " \"pattern\": \"[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']\",\n", + " \"valid_from\": \"2019-05-03T19:40:21.459582Z\",\n", + " \"object_marking_refs\": [\n", + " \"marking-definition--f88d31f6-486f-44da-b317-01333bde0b82\"\n", + " ],\n", + " \"granular_markings\": [\n", + " {\n", + " \"marking_ref\": \"marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da\",\n", + " \"selectors\": [\n", + " \"description\"\n", + " ]\n", + " }\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "# If marking_ref is False, no marking-definition markings will be removed\n", + "print(v21_indicator.clear_markings(\"description\", marking_ref=False))" + ] } ], "metadata": { diff --git a/stix2/base.py b/stix2/base.py index e90b9ff..c1a9bf1 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -310,7 +310,10 @@ class _Observable(_STIXBase): allowed_types = prop.valid_types try: - ref_type = self._STIXBase__valid_refs[ref] + try: + ref_type = self._STIXBase__valid_refs[ref].type + except AttributeError: + ref_type = self._STIXBase__valid_refs[ref] except TypeError: raise ValueError("'%s' must be created with _valid_refs as a dict, not a list." % self.__class__.__name__) diff --git a/stix2/exceptions.py b/stix2/exceptions.py index 231eeb6..f1f1c09 100644 --- a/stix2/exceptions.py +++ b/stix2/exceptions.py @@ -203,3 +203,16 @@ class MarkingNotFoundError(STIXError, AssertionError): def __str__(self): msg = "Marking {0} was not found in {1}!" return msg.format(self.key, self.cls.__class__.__name__) + + +class TLPMarkingDefinitionError(STIXError, AssertionError): + """Marking violation. The marking-definition for TLP MUST follow the mandated instances from the spec.""" + + def __init__(self, user_obj, spec_obj): + super(TLPMarkingDefinitionError, self).__init__() + self.user_obj = user_obj + self.spec_obj = spec_obj + + def __str__(self): + msg = "Marking {0} does not match spec marking {1}!" + return msg.format(self.user_obj, self.spec_obj) diff --git a/stix2/markings/__init__.py b/stix2/markings/__init__.py index 79d1012..6d09f81 100644 --- a/stix2/markings/__init__.py +++ b/stix2/markings/__init__.py @@ -22,7 +22,7 @@ Note: from stix2.markings import granular_markings, object_markings -def get_markings(obj, selectors=None, inherited=False, descendants=False): +def get_markings(obj, selectors=None, inherited=False, descendants=False, marking_ref=True, lang=True): """ Get all markings associated to the field(s) specified by selectors. @@ -30,10 +30,13 @@ def get_markings(obj, selectors=None, inherited=False, descendants=False): obj: An SDO or SRO object. selectors: string or list of selectors strings relative to the SDO or SRO in which the properties appear. - inherited: If True, include object level markings and granular markings - inherited relative to the properties. - descendants: If True, include granular markings applied to any children - relative to the properties. + inherited (bool): If True, include object level markings and granular + markings inherited relative to the properties. + descendants (bool): If True, include granular markings applied to any + children relative to the properties. + marking_ref (bool): If False, excludes markings that use + ``marking_ref`` property. + lang (bool): If False, excludes markings that use ``lang`` property. Returns: list: Marking identifiers that matched the selectors expression. @@ -51,6 +54,8 @@ def get_markings(obj, selectors=None, inherited=False, descendants=False): selectors, inherited, descendants, + marking_ref, + lang, ) if inherited: @@ -59,7 +64,7 @@ def get_markings(obj, selectors=None, inherited=False, descendants=False): return list(set(results)) -def set_markings(obj, marking, selectors=None): +def set_markings(obj, marking, selectors=None, marking_ref=True, lang=True): """ Remove all markings associated with selectors and appends a new granular marking. Refer to `clear_markings` and `add_markings` for details. @@ -70,6 +75,10 @@ def set_markings(obj, marking, selectors=None): properties selected by `selectors`. selectors: string or list of selectors strings relative to the SDO or SRO in which the properties appear. + marking_ref (bool): If False, markings that use the ``marking_ref`` + property will not be removed. + lang (bool): If False, markings that use the ``lang`` property + will not be removed. Returns: A new version of the given SDO or SRO with specified markings removed @@ -83,7 +92,7 @@ def set_markings(obj, marking, selectors=None): if selectors is None: return object_markings.set_markings(obj, marking) else: - return granular_markings.set_markings(obj, marking, selectors) + return granular_markings.set_markings(obj, marking, selectors, marking_ref, lang) def remove_markings(obj, marking, selectors=None): @@ -144,7 +153,7 @@ def add_markings(obj, marking, selectors=None): return granular_markings.add_markings(obj, marking, selectors) -def clear_markings(obj, selectors=None): +def clear_markings(obj, selectors=None, marking_ref=True, lang=True): """ Remove all markings associated with the selectors. @@ -152,6 +161,10 @@ def clear_markings(obj, selectors=None): obj: An SDO or SRO object. selectors: string or list of selectors strings relative to the SDO or SRO in which the field(s) appear(s). + marking_ref (bool): If False, markings that use the ``marking_ref`` + property will not be removed. + lang (bool): If False, markings that use the ``lang`` property + will not be removed. Raises: InvalidSelectorError: If `selectors` fail validation. @@ -169,7 +182,7 @@ def clear_markings(obj, selectors=None): if selectors is None: return object_markings.clear_markings(obj) else: - return granular_markings.clear_markings(obj, selectors) + return granular_markings.clear_markings(obj, selectors, marking_ref, lang) def is_marked(obj, marking=None, selectors=None, inherited=False, descendants=False): @@ -182,10 +195,11 @@ def is_marked(obj, marking=None, selectors=None, inherited=False, descendants=Fa properties selected by `selectors`. selectors: string or list of selectors strings relative to the SDO or SRO in which the field(s) appear(s). - inherited: If True, include object level markings and granular markings - inherited to determine if the properties is/are marked. - descendants: If True, include granular markings applied to any children - of the given selector to determine if the properties is/are marked. + inherited (bool): If True, include object level markings and granular + markings inherited to determine if the properties is/are marked. + descendants (bool): If True, include granular markings applied to any + children of the given selector to determine if the properties + is/are marked. Returns: bool: True if ``selectors`` is found on internal SDO or SRO collection. @@ -228,7 +242,7 @@ def is_marked(obj, marking=None, selectors=None, inherited=False, descendants=Fa return result -class _MarkingsMixin(): +class _MarkingsMixin(object): pass diff --git a/stix2/markings/granular_markings.py b/stix2/markings/granular_markings.py index 09c3d37..5456f83 100644 --- a/stix2/markings/granular_markings.py +++ b/stix2/markings/granular_markings.py @@ -2,10 +2,10 @@ from stix2 import exceptions from stix2.markings import utils -from stix2.utils import new_version +from stix2.utils import is_marking, new_version -def get_markings(obj, selectors, inherited=False, descendants=False): +def get_markings(obj, selectors, inherited=False, descendants=False, marking_ref=True, lang=True): """ Get all granular markings associated to with the properties. @@ -13,10 +13,13 @@ def get_markings(obj, selectors, inherited=False, descendants=False): obj: An SDO or SRO object. selectors: string or list of selector strings relative to the SDO or SRO in which the properties appear. - inherited: If True, include markings inherited relative to the + inherited (bool): If True, include markings inherited relative to the properties. - descendants: If True, include granular markings applied to any children - relative to the properties. + descendants (bool): If True, include granular markings applied to any + children relative to the properties. + marking_ref (bool): If False, excludes markings that use + ``marking_ref`` property. + lang (bool): If False, excludes markings that use ``lang`` property. Raises: InvalidSelectorError: If `selectors` fail validation. @@ -43,13 +46,18 @@ def get_markings(obj, selectors, inherited=False, descendants=False): (user_selector.startswith(marking_selector) and inherited), # Catch inherited selectors. (marking_selector.startswith(user_selector) and descendants), ]): # Catch descendants selectors - refs = marking.get('marking_ref', []) - results.update([refs]) + ref = marking.get('marking_ref') + lng = marking.get('lang') + + if ref and marking_ref: + results.add(ref) + if lng and lang: + results.add(lng) return list(results) -def set_markings(obj, marking, selectors): +def set_markings(obj, marking, selectors, marking_ref=True, lang=True): """ Remove all granular markings associated with selectors and append a new granular marking. Refer to `clear_markings` and `add_markings` for details. @@ -60,19 +68,25 @@ def set_markings(obj, marking, selectors): SRO in which the properties appear. marking: identifier or list of marking identifiers that apply to the properties selected by `selectors`. + marking_ref (bool): If False, markings that use the ``marking_ref`` + property will not be removed. + lang (bool): If False, markings that use the ``lang`` property + will not be removed. Returns: A new version of the given SDO or SRO with specified markings removed and new ones added. """ - obj = clear_markings(obj, selectors) + obj = clear_markings(obj, selectors, marking_ref, lang) return add_markings(obj, marking, selectors) def remove_markings(obj, marking, selectors): """ - Remove a granular marking from the granular_markings collection. + Remove a granular marking from the granular_markings collection. The method + makes a best-effort attempt to distinguish between a marking-definition + or language granular marking. Args: obj: An SDO or SRO object. @@ -103,7 +117,10 @@ def remove_markings(obj, marking, selectors): to_remove = [] for m in marking: - to_remove.append({'marking_ref': m, 'selectors': selectors}) + if is_marking(m): + to_remove.append({'marking_ref': m, 'selectors': selectors}) + else: + to_remove.append({'lang': m, 'selectors': selectors}) remove = utils.build_granular_marking(to_remove).get('granular_markings') @@ -124,7 +141,9 @@ def remove_markings(obj, marking, selectors): def add_markings(obj, marking, selectors): """ - Append a granular marking to the granular_markings collection. + Append a granular marking to the granular_markings collection. The method + makes a best-effort attempt to distinguish between a marking-definition + or language granular marking. Args: obj: An SDO or SRO object. @@ -146,7 +165,10 @@ def add_markings(obj, marking, selectors): granular_marking = [] for m in marking: - granular_marking.append({'marking_ref': m, 'selectors': sorted(selectors)}) + if is_marking(m): + granular_marking.append({'marking_ref': m, 'selectors': sorted(selectors)}) + else: + granular_marking.append({'lang': m, 'selectors': sorted(selectors)}) if obj.get('granular_markings'): granular_marking.extend(obj.get('granular_markings')) @@ -156,7 +178,7 @@ def add_markings(obj, marking, selectors): return new_version(obj, granular_markings=granular_marking, allow_custom=True) -def clear_markings(obj, selectors): +def clear_markings(obj, selectors, marking_ref=True, lang=True): """ Remove all granular markings associated with the selectors. @@ -164,6 +186,10 @@ def clear_markings(obj, selectors): obj: An SDO or SRO object. selectors: string or list of selectors strings relative to the SDO or SRO in which the properties appear. + marking_ref (bool): If False, markings that use the ``marking_ref`` + property will not be removed. + lang (bool): If False, markings that use the ``lang`` property + will not be removed. Raises: InvalidSelectorError: If `selectors` fail validation. @@ -184,11 +210,12 @@ def clear_markings(obj, selectors): granular_markings = utils.expand_markings(granular_markings) - sdo = utils.build_granular_marking( - [{'selectors': selectors, 'marking_ref': 'N/A'}], - ) + granular_dict = utils.build_granular_marking([ + {'selectors': selectors, 'marking_ref': 'N/A'}, + {'selectors': selectors, 'lang': 'N/A'}, + ]) - clear = sdo.get('granular_markings', []) + clear = granular_dict.get('granular_markings', []) if not any( clear_selector in sdo_selectors.get('selectors', []) @@ -201,10 +228,13 @@ def clear_markings(obj, selectors): for granular_marking in granular_markings: for s in selectors: if s in granular_marking.get('selectors', []): - marking_refs = granular_marking.get('marking_ref') + ref = granular_marking.get('marking_ref') + lng = granular_marking.get('lang') - if marking_refs: + if ref and marking_ref: granular_marking['marking_ref'] = '' + if lng and lang: + granular_marking['lang'] = '' granular_markings = utils.compress_markings(granular_markings) @@ -222,11 +252,12 @@ def is_marked(obj, marking=None, selectors=None, inherited=False, descendants=Fa obj: An SDO or SRO object. marking: identifier or list of marking identifiers that apply to the properties selected by `selectors`. - selectors: string or list of selectors strings relative to the SDO or - SRO in which the properties appear. - inherited: If True, return markings inherited from the given selector. - descendants: If True, return granular markings applied to any children - of the given selector. + selectors (bool): string or list of selectors strings relative to the + SDO or SRO in which the properties appear. + inherited (bool): If True, return markings inherited from the given + selector. + descendants (bool): If True, return granular markings applied to any + children of the given selector. Raises: InvalidSelectorError: If `selectors` fail validation. @@ -262,9 +293,12 @@ def is_marked(obj, marking=None, selectors=None, inherited=False, descendants=Fa (marking_selector.startswith(user_selector) and descendants), ]): # Catch descendants selectors marking_ref = granular_marking.get('marking_ref', '') + lang = granular_marking.get('lang', '') if marking and any(x == marking_ref for x in marking): markings.update([marking_ref]) + if marking and any(x == lang for x in marking): + markings.update([lang]) marked = True diff --git a/stix2/markings/utils.py b/stix2/markings/utils.py index d8bbf1d..b1c103b 100644 --- a/stix2/markings/utils.py +++ b/stix2/markings/utils.py @@ -4,7 +4,7 @@ import collections import six -from stix2 import exceptions +from stix2 import exceptions, utils def _evaluate_expression(obj, selector): @@ -121,10 +121,15 @@ def compress_markings(granular_markings): if granular_marking.get('marking_ref'): map_[granular_marking.get('marking_ref')].update(granular_marking.get('selectors')) + if granular_marking.get('lang'): + map_[granular_marking.get('lang')].update(granular_marking.get('selectors')) + compressed = \ [ - {'marking_ref': marking_ref, 'selectors': sorted(selectors)} - for marking_ref, selectors in six.iteritems(map_) + {'marking_ref': item, 'selectors': sorted(selectors)} + if utils.is_marking(item) else + {'lang': item, 'selectors': sorted(selectors)} + for item, selectors in six.iteritems(map_) ] return compressed @@ -174,13 +179,22 @@ def expand_markings(granular_markings): for marking in granular_markings: selectors = marking.get('selectors') marking_ref = marking.get('marking_ref') + lang = marking.get('lang') - expanded.extend( - [ - {'marking_ref': marking_ref, 'selectors': [selector]} - for selector in selectors - ], - ) + if marking_ref: + expanded.extend( + [ + {'marking_ref': marking_ref, 'selectors': [selector]} + for selector in selectors + ], + ) + if lang: + expanded.extend( + [ + {'lang': lang, 'selectors': [selector]} + for selector in selectors + ], + ) return expanded @@ -240,3 +254,81 @@ def iterpath(obj, path=None): path.pop() path.pop() + + +def check_tlp_marking(marking_obj, spec_version): + # Specific TLP Marking validation case. + + if marking_obj["definition_type"] == "tlp": + color = marking_obj["definition"]["tlp"] + + if color == "white": + if spec_version == '2.0': + w = ( + '{"created": "2017-01-20T00:00:00.000Z", "definition": {"tlp": "white"}, "definition_type": "tlp",' + ' "id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9", "type": "marking-definition"}' + ) + else: + w = ( + '{"created": "2017-01-20T00:00:00.000Z", "definition": {"tlp": "white"}, "definition_type": "tlp",' + ' "id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9", "type": "marking-definition",' + ' "spec_version": "2.1"}' + ) + if marking_obj["id"] != "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9": + raise exceptions.TLPMarkingDefinitionError(marking_obj["id"], w) + elif utils.format_datetime(marking_obj["created"]) != "2017-01-20T00:00:00.000Z": + raise exceptions.TLPMarkingDefinitionError(utils.format_datetime(marking_obj["created"]), w) + + elif color == "green": + if spec_version == '2.0': + g = ( + '{"created": "2017-01-20T00:00:00.000Z", "definition": {"tlp": "green"}, "definition_type": "tlp",' + ' "id": "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", "type": "marking-definition"}' + ) + else: + g = ( + '{"created": "2017-01-20T00:00:00.000Z", "definition": {"tlp": "green"}, "definition_type": "tlp",' + ' "id": "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", "type": "marking-definition",' + ' "spec_version": "2.1"}' + ) + if marking_obj["id"] != "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da": + raise exceptions.TLPMarkingDefinitionError(marking_obj["id"], g) + elif utils.format_datetime(marking_obj["created"]) != "2017-01-20T00:00:00.000Z": + raise exceptions.TLPMarkingDefinitionError(utils.format_datetime(marking_obj["created"]), g) + + elif color == "amber": + if spec_version == '2.0': + a = ( + '{"created": "2017-01-20T00:00:00.000Z", "definition": {"tlp": "amber"}, "definition_type": "tlp",' + ' "id": "marking-definition--f88d31f6-486f-44da-b317-01333bde0b82", "type": "marking-definition"}' + ) + else: + a = ( + '{"created": "2017-01-20T00:00:00.000Z", "definition": {"tlp": "amber"}, "definition_type": "tlp",' + ' "id": "marking-definition--f88d31f6-486f-44da-b317-01333bde0b82", "type": "marking-definition",' + ' "spec_version": "2.1"}' + ) + if marking_obj["id"] != "marking-definition--f88d31f6-486f-44da-b317-01333bde0b82": + raise exceptions.TLPMarkingDefinitionError(marking_obj["id"], a) + elif utils.format_datetime(marking_obj["created"]) != "2017-01-20T00:00:00.000Z": + raise exceptions.TLPMarkingDefinitionError(utils.format_datetime(marking_obj["created"]), a) + + elif color == "red": + if spec_version == '2.0': + r = ( + '{"created": "2017-01-20T00:00:00.000Z", "definition": {"tlp": "red"}, "definition_type": "tlp",' + ' "id": "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed", "type": "marking-definition"}' + ) + else: + r = ( + '{"created": "2017-01-20T00:00:00.000Z", "definition": {"tlp": "red"}, "definition_type": "tlp",' + ' "id": "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed", "type": "marking-definition",' + ' "spec_version": "2.1"}' + ) + if marking_obj["id"] != "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed": + raise exceptions.TLPMarkingDefinitionError(marking_obj["id"], r) + elif utils.format_datetime(marking_obj["created"]) != "2017-01-20T00:00:00.000Z": + raise exceptions.TLPMarkingDefinitionError(utils.format_datetime(marking_obj["created"]), r) + + else: + raise exceptions.TLPMarkingDefinitionError(marking_obj["id"], "Does not match any TLP Marking definition") diff --git a/stix2/properties.py b/stix2/properties.py index 7f8cb63..53b5937 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -190,11 +190,12 @@ class CallableValues(list): class StringProperty(Property): def __init__(self, **kwargs): - self.string_type = text_type super(StringProperty, self).__init__(**kwargs) def clean(self, value): - return self.string_type(value) + if not isinstance(value, string_types): + return text_type(value) + return value class TypeProperty(Property): @@ -454,21 +455,22 @@ class EnumProperty(StringProperty): super(EnumProperty, self).__init__(**kwargs) def clean(self, value): - value = super(EnumProperty, self).clean(value) - if value not in self.allowed: - raise ValueError("value '{}' is not valid for this enumeration.".format(value)) - return self.string_type(value) + cleaned_value = super(EnumProperty, self).clean(value) + if cleaned_value not in self.allowed: + raise ValueError("value '{}' is not valid for this enumeration.".format(cleaned_value)) + + return cleaned_value class PatternProperty(StringProperty): def clean(self, value): - str_value = super(PatternProperty, self).clean(value) - errors = run_validator(str_value) + cleaned_value = super(PatternProperty, self).clean(value) + errors = run_validator(cleaned_value) if errors: raise ValueError(str(errors[0])) - return self.string_type(value) + return cleaned_value class ObservableProperty(Property): diff --git a/stix2/test/v20/test_bundle.py b/stix2/test/v20/test_bundle.py index 72523bb..57c189e 100644 --- a/stix2/test/v20/test_bundle.py +++ b/stix2/test/v20/test_bundle.py @@ -236,3 +236,100 @@ def test_bundle_with_different_spec_objects(): stix2.v20.Bundle(objects=data) assert "Spec version 2.0 bundles don't yet support containing objects of a different spec version." in str(excinfo.value) + + +def test_bundle_obj_id_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + mal_list = bundle.get_obj("malware--00000000-0000-4000-8000-000000000003") + assert bundle.objects[1] == mal_list[0] + assert len(mal_list) == 1 + + +@pytest.mark.parametrize( + "bundle_data", [{ + "type": "bundle", + "id": "bundle--00000000-0000-4000-8000-000000000007", + "spec_version": "2.0", + "objects": [ + { + "type": "indicator", + "id": "indicator--00000000-0000-4000-8000-000000000001", + "created": "2017-01-01T12:34:56.000Z", + "modified": "2017-01-01T12:34:56.000Z", + "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", + "valid_from": "2017-01-01T12:34:56Z", + "labels": [ + "malicious-activity", + ], + }, + { + "type": "malware", + "id": "malware--00000000-0000-4000-8000-000000000003", + "created": "2017-01-01T12:34:56.000Z", + "modified": "2017-01-01T12:34:56.000Z", + "name": "Cryptolocker1", + "labels": [ + "ransomware", + ], + }, + { + "type": "malware", + "id": "malware--00000000-0000-4000-8000-000000000003", + "created": "2017-01-01T12:34:56.000Z", + "modified": "2017-12-21T12:34:56.000Z", + "name": "CryptolockerOne", + "labels": [ + "ransomware", + ], + }, + { + "type": "relationship", + "id": "relationship--00000000-0000-4000-8000-000000000005", + "created": "2017-01-01T12:34:56.000Z", + "modified": "2017-01-01T12:34:56.000Z", + "relationship_type": "indicates", + "source_ref": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7", + "target_ref": "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e", + }, + ], + }], +) +def test_bundle_objs_ids_found(bundle_data): + bundle = stix2.parse(bundle_data) + + mal_list = bundle.get_obj("malware--00000000-0000-4000-8000-000000000003") + assert bundle.objects[1] == mal_list[0] + assert bundle.objects[2] == mal_list[1] + assert len(mal_list) == 2 + + +def test_bundle_getitem_overload_property_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + assert bundle.type == "bundle" + assert bundle['type'] == "bundle" + + +def test_bundle_getitem_overload_obj_id_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + mal_list = bundle["malware--00000000-0000-4000-8000-000000000003"] + assert bundle.objects[1] == mal_list[0] + assert len(mal_list) == 1 + + +def test_bundle_obj_id_not_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + with pytest.raises(KeyError) as excinfo: + bundle.get_obj('non existent') + assert "does not match the id property of any of the bundle" in str(excinfo.value) + + +def test_bundle_getitem_overload_obj_id_not_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + with pytest.raises(KeyError) as excinfo: + bundle['non existent'] + assert "neither a property on the bundle nor does it match the id property" in str(excinfo.value) diff --git a/stix2/test/v20/test_datastore_filesystem.py b/stix2/test/v20/test_datastore_filesystem.py index 86846c4..25de37e 100644 --- a/stix2/test/v20/test_datastore_filesystem.py +++ b/stix2/test/v20/test_datastore_filesystem.py @@ -450,6 +450,8 @@ def test_filesystem_attempt_stix_file_overwrite(fs_store): def test_filesystem_sink_marking(fs_sink): marking = stix2.v20.MarkingDefinition( + id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", + created="2017-01-20T00:00:00.000Z", definition_type="tlp", definition=stix2.v20.TLPMarking(tlp="green"), ) @@ -583,6 +585,8 @@ def test_filesystem_store_add_invalid_object(fs_store): def test_filesystem_store_add_marking(fs_store): marking = stix2.v20.MarkingDefinition( + id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", + created="2017-01-20T00:00:00.000Z", definition_type="tlp", definition=stix2.v20.TLPMarking(tlp="green"), ) diff --git a/stix2/test/v20/test_marking_definition.py b/stix2/test/v20/test_marking_definition.py new file mode 100644 index 0000000..20575fa --- /dev/null +++ b/stix2/test/v20/test_marking_definition.py @@ -0,0 +1,133 @@ + +import pytest + +from stix2 import exceptions +from stix2.v20 import ( + TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, MarkingDefinition, TLPMarking, +) + + +def test_bad_id_marking_tlp_white(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--4c9faac1-3558-43d2-919e-95c88d3bc332', + definition_type='tlp', + definition=TLPMarking(tlp='white'), + ) + + +def test_bad_id_marking_tlp_green(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--93023361-d3cf-4666-bca2-8c017948dc3d', + definition_type='tlp', + definition=TLPMarking(tlp='green'), + ) + + +def test_bad_id_marking_tlp_amber(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--05e32101-a940-42ba-8fe9-39283b999ce4', + definition_type='tlp', + definition=TLPMarking(tlp='amber'), + ) + + +def test_bad_id_marking_tlp_red(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--9eceb00c-c158-43f4-87f8-1e3648de17e2', + definition_type='tlp', + definition=TLPMarking(tlp='red'), + ) + + +def test_bad_created_marking_tlp_white(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9', + definition_type='tlp', + definition=TLPMarking(tlp='white'), + ) + + +def test_bad_created_marking_tlp_green(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da', + definition_type='tlp', + definition=TLPMarking(tlp='green'), + ) + + +def test_bad_created_marking_tlp_amber(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--f88d31f6-486f-44da-b317-01333bde0b82', + definition_type='tlp', + definition=TLPMarking(tlp='amber'), + ) + + +def test_bad_created_marking_tlp_red(): + with pytest.raises(exceptions.TLPMarkingDefinitionError) as excinfo: + MarkingDefinition( + id='marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed', + definition_type='tlp', + definition=TLPMarking(tlp='red'), + ) + + assert "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed" in str(excinfo.value) + + +def test_successful_tlp_white(): + white = MarkingDefinition( + id='marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9', + created='2017-01-20T00:00:00.000Z', + definition_type='tlp', + definition=TLPMarking(tlp='white'), + ) + + assert white.serialize(sort_keys=True) == TLP_WHITE.serialize(sort_keys=True) + + +def test_successful_tlp_green(): + green = MarkingDefinition( + id='marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da', + created='2017-01-20T00:00:00.000Z', + definition_type='tlp', + definition=TLPMarking(tlp='green'), + ) + + assert green.serialize(sort_keys=True) == TLP_GREEN.serialize(sort_keys=True) + + +def test_successful_tlp_amber(): + amber = MarkingDefinition( + id='marking-definition--f88d31f6-486f-44da-b317-01333bde0b82', + created='2017-01-20T00:00:00.000Z', + definition_type='tlp', + definition=TLPMarking(tlp='amber'), + ) + + assert amber.serialize(sort_keys=True) == TLP_AMBER.serialize(sort_keys=True) + + +def test_successful_tlp_red(): + red = MarkingDefinition( + id='marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed', + created='2017-01-20T00:00:00.000Z', + definition_type='tlp', + definition=TLPMarking(tlp='red'), + ) + + assert red.serialize(sort_keys=True) == TLP_RED.serialize(sort_keys=True) + + +def test_unknown_tlp_marking(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + definition_type='tlp', + definition=TLPMarking(tlp='gray'), + ) diff --git a/stix2/test/v20/test_observed_data.py b/stix2/test/v20/test_observed_data.py index 84cdf72..95daf22 100644 --- a/stix2/test/v20/test_observed_data.py +++ b/stix2/test/v20/test_observed_data.py @@ -960,6 +960,24 @@ def test_ip4_address_example(): assert ip4.resolves_to_refs == ["4", "5"] +def test_ip4_address_valid_refs(): + mac1 = stix2.v20.MACAddress( + value="a1:b2:c3:d4:e5:f6", + ) + mac2 = stix2.v20.MACAddress( + value="a7:b8:c9:d0:e1:f2", + ) + + ip4 = stix2.v20.IPv4Address( + _valid_refs={"1": mac1, "2": mac2}, + value="177.60.40.7", + resolves_to_refs=["1", "2"], + ) + + assert ip4.value == "177.60.40.7" + assert ip4.resolves_to_refs == ["1", "2"] + + def test_ip4_address_example_cidr(): ip4 = stix2.v20.IPv4Address(value="198.51.100.0/24") diff --git a/stix2/test/v21/constants.py b/stix2/test/v21/constants.py index bbce32c..b0ba1ef 100644 --- a/stix2/test/v21/constants.py +++ b/stix2/test/v21/constants.py @@ -31,6 +31,12 @@ MARKING_IDS = [ "marking-definition--68520ae2-fefe-43a9-84ee-2c2a934d2c7d", "marking-definition--2802dfb1-1019-40a8-8848-68d0ec0e417f", ] +MARKING_LANGS = [ + "en", + "es", + "de", + "ja", +] RELATIONSHIP_IDS = [ 'relationship--06520621-5352-4e6a-b976-e8fa3d437ffd', 'relationship--181c9c09-43e6-45dd-9374-3bec192f05ef', diff --git a/stix2/test/v21/test_bundle.py b/stix2/test/v21/test_bundle.py index 7adea92..47d0a7a 100644 --- a/stix2/test/v21/test_bundle.py +++ b/stix2/test/v21/test_bundle.py @@ -207,3 +207,103 @@ def test_stix_object_property(): identity = stix2.v21.Identity(name="test", identity_class="individual") assert prop.clean(identity) is identity + + +def test_bundle_obj_id_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + mal_list = bundle.get_obj("malware--00000000-0000-4000-8000-000000000003") + assert bundle.objects[1] == mal_list[0] + assert len(mal_list) == 1 + + +@pytest.mark.parametrize( + "bundle_data", [{ + "type": "bundle", + "id": "bundle--00000000-0000-4000-8000-000000000007", + "objects": [ + { + "type": "indicator", + "spec_version": "2.1", + "id": "indicator--00000000-0000-4000-8000-000000000001", + "created": "2017-01-01T12:34:56.000Z", + "modified": "2017-01-01T12:34:56.000Z", + "indicator_types": [ + "malicious-activity", + ], + "pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']", + "valid_from": "2017-01-01T12:34:56Z", + }, + { + "type": "malware", + "spec_version": "2.1", + "id": "malware--00000000-0000-4000-8000-000000000003", + "created": "2017-01-01T12:34:56.000Z", + "modified": "2017-01-01T12:34:56.000Z", + "name": "Cryptolocker1", + "malware_types": [ + "ransomware", + ], + }, + { + "type": "malware", + "spec_version": "2.1", + "id": "malware--00000000-0000-4000-8000-000000000003", + "created": "2017-01-01T12:34:56.000Z", + "modified": "2017-12-21T12:34:56.000Z", + "name": "CryptolockerOne", + "malware_types": [ + "ransomware", + ], + }, + { + "type": "relationship", + "spec_version": "2.1", + "id": "relationship--00000000-0000-4000-8000-000000000005", + "created": "2017-01-01T12:34:56.000Z", + "modified": "2017-01-01T12:34:56.000Z", + "relationship_type": "indicates", + "source_ref": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7", + "target_ref": "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e", + }, + ], + }], +) +def test_bundle_objs_ids_found(bundle_data): + bundle = stix2.parse(bundle_data) + + mal_list = bundle.get_obj("malware--00000000-0000-4000-8000-000000000003") + assert bundle.objects[1] == mal_list[0] + assert bundle.objects[2] == mal_list[1] + assert len(mal_list) == 2 + + +def test_bundle_getitem_overload_property_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + assert bundle.type == "bundle" + assert bundle['type'] == "bundle" + + +def test_bundle_getitem_overload_obj_id_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + mal_list = bundle["malware--00000000-0000-4000-8000-000000000003"] + assert bundle.objects[1] == mal_list[0] + assert len(mal_list) == 1 + + +def test_bundle_obj_id_not_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + with pytest.raises(KeyError) as excinfo: + bundle.get_obj('non existent') + assert "does not match the id property of any of the bundle" in str(excinfo.value) + + +def test_bundle_getitem_overload_obj_id_not_found(): + bundle = stix2.parse(EXPECTED_BUNDLE) + + with pytest.raises(KeyError) as excinfo: + bundle['non existent'] + assert "neither a property on the bundle nor does it match the id property" in str(excinfo.value) diff --git a/stix2/test/v21/test_datastore_filesystem.py b/stix2/test/v21/test_datastore_filesystem.py index 2404f3f..34b1088 100644 --- a/stix2/test/v21/test_datastore_filesystem.py +++ b/stix2/test/v21/test_datastore_filesystem.py @@ -421,6 +421,8 @@ def test_filesystem_sink_add_objects_list(fs_sink, fs_source): def test_filesystem_sink_marking(fs_sink): marking = stix2.v21.MarkingDefinition( + id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", + created="2017-01-20T00:00:00.000Z", definition_type="tlp", definition=stix2.v21.TLPMarking(tlp="green"), ) @@ -554,6 +556,8 @@ def test_filesystem_store_add_invalid_object(fs_store): def test_filesystem_store_add_marking(fs_store): marking = stix2.v21.MarkingDefinition( + id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", + created="2017-01-20T00:00:00.000Z", definition_type="tlp", definition=stix2.v21.TLPMarking(tlp="green"), ) diff --git a/stix2/test/v21/test_granular_markings.py b/stix2/test/v21/test_granular_markings.py index 9f7234e..e178f86 100644 --- a/stix2/test/v21/test_granular_markings.py +++ b/stix2/test/v21/test_granular_markings.py @@ -5,7 +5,7 @@ from stix2.exceptions import MarkingNotFoundError from stix2.v21 import TLP_RED, Malware from .constants import MALWARE_MORE_KWARGS as MALWARE_KWARGS_CONST -from .constants import MARKING_IDS +from .constants import MARKING_IDS, MARKING_LANGS """Tests for the Data Markings API.""" @@ -111,6 +111,37 @@ def test_add_marking_mark_multiple_selector_multiple_refs(): assert m in after["granular_markings"] +def test_add_marking_mark_multiple_selector_multiple_refs_mixed(): + before = Malware( + **MALWARE_KWARGS + ) + after = Malware( + granular_markings=[ + { + "selectors": ["description", "name"], + "marking_ref": MARKING_IDS[0], + }, + { + "selectors": ["description", "name"], + "marking_ref": MARKING_IDS[1], + }, + { + "selectors": ["description", "name"], + "lang": MARKING_LANGS[0], + }, + { + "selectors": ["description", "name"], + "lang": MARKING_LANGS[1], + }, + ], + **MALWARE_KWARGS + ) + before = markings.add_markings(before, [MARKING_IDS[0], MARKING_IDS[1], MARKING_LANGS[0], MARKING_LANGS[1]], ["description", "name"]) + + for m in before["granular_markings"]: + assert m in after["granular_markings"] + + def test_add_marking_mark_another_property_same_marking(): before = Malware( granular_markings=[ @@ -376,6 +407,98 @@ def test_get_markings_positional_arguments_combinations(data): assert set(markings.get_markings(data, "x.z.foo2", False, True)) == set(["10"]) +GET_MARKINGS_TEST_DATA_LANGS = { + "a": 333, + "b": "value", + "c": [ + 17, + "list value", + { + "g": "nested", + "h": 45, + }, + ], + "x": { + "y": [ + "hello", + 88, + ], + "z": { + "foo1": "bar", + "foo2": 65, + }, + }, + "granular_markings": [ + { + "marking_ref": "m1", + "selectors": ["a"], + }, + { + "marking_ref": "m2", + "selectors": ["c"], + }, + { + "marking_ref": "m3", + "selectors": ["c.[1]"], + }, + { + "marking_ref": "m4", + "selectors": ["c.[2]"], + }, + { + "marking_ref": "m5", + "selectors": ["c.[2].g"], + }, + { + "marking_ref": "m6", + "selectors": ["x"], + }, + { + "lang": "l7", + "selectors": ["x.y"], + }, + { + "marking_ref": "m8", + "selectors": ["x.y.[1]"], + }, + { + "lang": "l9", + "selectors": ["x.z"], + }, + { + "marking_ref": "m9", + "selectors": ["x.z"], + }, + { + "marking_ref": "m10", + "selectors": ["x.z.foo2"], + }, + ], +} + + +@pytest.mark.parametrize("data", [GET_MARKINGS_TEST_DATA_LANGS]) +def test_get_markings_multiple_selectors_langs(data): + """Test multiple selectors return combination of markings.""" + total = markings.get_markings(data, ["x.y", "x.z"]) + xy_markings = markings.get_markings(data, ["x.y"]) + xz_markings = markings.get_markings(data, ["x.z"]) + + assert set(xy_markings).issubset(total) + assert set(xz_markings).issubset(total) + assert set(xy_markings).union(xz_markings).issuperset(total) + + +@pytest.mark.parametrize("data", [GET_MARKINGS_TEST_DATA_LANGS]) +def test_get_markings_multiple_selectors_with_options(data): + """Test multiple selectors return combination of markings.""" + total = markings.get_markings(data, ["x.y", "x.z"], lang=False) + xz_markings = markings.get_markings(data, ["x.z"], marking_ref=False) + + assert len(total) == 1 + assert len(xz_markings) == 1 + + @pytest.mark.parametrize( "data", [ ( @@ -455,6 +578,38 @@ def test_remove_marking_mark_one_selector_from_multiple_ones(): assert m in after["granular_markings"] +def test_remove_marking_mark_one_selector_from_multiple_ones_mixed(): + after = Malware( + granular_markings=[ + { + "selectors": ["description"], + "marking_ref": MARKING_IDS[0], + }, + { + "selectors": ["description"], + "lang": MARKING_LANGS[0], + }, + ], + **MALWARE_KWARGS + ) + before = Malware( + granular_markings=[ + { + "selectors": ["description", "modified"], + "marking_ref": MARKING_IDS[0], + }, + { + "selectors": ["description", "modified"], + "lang": MARKING_LANGS[0], + }, + ], + **MALWARE_KWARGS + ) + before = markings.remove_markings(before, [MARKING_IDS[0], MARKING_LANGS[0]], ["modified"]) + for m in before["granular_markings"]: + assert m in after["granular_markings"] + + def test_remove_marking_mark_one_selector_markings_from_multiple_ones(): after = Malware( granular_markings=[ @@ -592,6 +747,10 @@ IS_MARKED_TEST_DATA = [ "selectors": ["malware_types", "description"], "marking_ref": MARKING_IDS[3], }, + { + "selectors": ["name"], + "lang": MARKING_LANGS[1], + }, ], **MALWARE_KWARGS ), @@ -609,6 +768,10 @@ IS_MARKED_TEST_DATA = [ "selectors": ["malware_types", "description"], "marking_ref": MARKING_IDS[3], }, + { + "selectors": ["name"], + "lang": MARKING_LANGS[1], + }, ], **MALWARE_KWARGS ), @@ -620,6 +783,7 @@ def test_is_marked_smoke(data): """Smoke test is_marked call does not fail.""" assert markings.is_marked(data, selectors=["description"]) assert markings.is_marked(data, selectors=["modified"]) is False + assert markings.is_marked(data, selectors=["name"]) @pytest.mark.parametrize( @@ -666,6 +830,7 @@ def test_is_marked_valid_selector_and_refs(data): """Test that a valid selector returns True when marking_refs match.""" assert markings.is_marked(data, [MARKING_IDS[1]], ["description"]) assert markings.is_marked(data, [MARKING_IDS[1]], ["modified"]) is False + assert markings.is_marked(data, [MARKING_LANGS[1]], ["name"]) @pytest.mark.parametrize("data", IS_MARKED_TEST_DATA) @@ -870,6 +1035,28 @@ def test_set_marking_mark_one_selector_multiple_refs(): assert m in after["granular_markings"] +def test_set_marking_mark_one_selector_multiple_lang_refs(): + before = Malware( + **MALWARE_KWARGS + ) + after = Malware( + granular_markings=[ + { + "selectors": ["description"], + "lang": MARKING_LANGS[0], + }, + { + "selectors": ["description"], + "lang": MARKING_LANGS[1], + }, + ], + **MALWARE_KWARGS + ) + before = markings.set_markings(before, [MARKING_LANGS[0], MARKING_LANGS[1]], ["description"]) + for m in before["granular_markings"]: + assert m in after["granular_markings"] + + def test_set_marking_mark_multiple_selector_one_refs(): before = Malware( granular_markings=[ @@ -894,6 +1081,38 @@ def test_set_marking_mark_multiple_selector_one_refs(): assert m in after["granular_markings"] +def test_set_marking_mark_multiple_mixed_markings(): + before = Malware( + granular_markings=[ + { + "selectors": ["description", "modified"], + "marking_ref": MARKING_IDS[1], + }, + { + "selectors": ["description", "modified"], + "lang": MARKING_LANGS[2], + }, + ], + **MALWARE_KWARGS + ) + after = Malware( + granular_markings=[ + { + "selectors": ["description", "modified"], + "marking_ref": MARKING_IDS[2], + }, + { + "selectors": ["description", "modified"], + "lang": MARKING_LANGS[3], + }, + ], + **MALWARE_KWARGS + ) + before = markings.set_markings(before, [MARKING_IDS[2], MARKING_LANGS[3]], ["description", "modified"]) + for m in before["granular_markings"]: + assert m in after["granular_markings"] + + def test_set_marking_mark_multiple_selector_multiple_refs_from_none(): before = Malware( **MALWARE_KWARGS diff --git a/stix2/test/v21/test_marking_definition.py b/stix2/test/v21/test_marking_definition.py new file mode 100644 index 0000000..c497e99 --- /dev/null +++ b/stix2/test/v21/test_marking_definition.py @@ -0,0 +1,133 @@ + +import pytest + +from stix2 import exceptions +from stix2.v21 import ( + TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, MarkingDefinition, TLPMarking, +) + + +def test_bad_id_marking_tlp_white(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--4c9faac1-3558-43d2-919e-95c88d3bc332', + definition_type='tlp', + definition=TLPMarking(tlp='white'), + ) + + +def test_bad_id_marking_tlp_green(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--93023361-d3cf-4666-bca2-8c017948dc3d', + definition_type='tlp', + definition=TLPMarking(tlp='green'), + ) + + +def test_bad_id_marking_tlp_amber(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--05e32101-a940-42ba-8fe9-39283b999ce4', + definition_type='tlp', + definition=TLPMarking(tlp='amber'), + ) + + +def test_bad_id_marking_tlp_red(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--9eceb00c-c158-43f4-87f8-1e3648de17e2', + definition_type='tlp', + definition=TLPMarking(tlp='red'), + ) + + +def test_bad_created_marking_tlp_white(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9', + definition_type='tlp', + definition=TLPMarking(tlp='white'), + ) + + +def test_bad_created_marking_tlp_green(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da', + definition_type='tlp', + definition=TLPMarking(tlp='green'), + ) + + +def test_bad_created_marking_tlp_amber(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + id='marking-definition--f88d31f6-486f-44da-b317-01333bde0b82', + definition_type='tlp', + definition=TLPMarking(tlp='amber'), + ) + + +def test_bad_created_marking_tlp_red(): + with pytest.raises(exceptions.TLPMarkingDefinitionError) as excinfo: + MarkingDefinition( + id='marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed', + definition_type='tlp', + definition=TLPMarking(tlp='red'), + ) + + assert "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed" in str(excinfo.value) + + +def test_successful_tlp_white(): + white = MarkingDefinition( + id='marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9', + created='2017-01-20T00:00:00.000Z', + definition_type='tlp', + definition=TLPMarking(tlp='white'), + ) + + assert white.serialize(sort_keys=True) == TLP_WHITE.serialize(sort_keys=True) + + +def test_successful_tlp_green(): + green = MarkingDefinition( + id='marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da', + created='2017-01-20T00:00:00.000Z', + definition_type='tlp', + definition=TLPMarking(tlp='green'), + ) + + assert green.serialize(sort_keys=True) == TLP_GREEN.serialize(sort_keys=True) + + +def test_successful_tlp_amber(): + amber = MarkingDefinition( + id='marking-definition--f88d31f6-486f-44da-b317-01333bde0b82', + created='2017-01-20T00:00:00.000Z', + definition_type='tlp', + definition=TLPMarking(tlp='amber'), + ) + + assert amber.serialize(sort_keys=True) == TLP_AMBER.serialize(sort_keys=True) + + +def test_successful_tlp_red(): + red = MarkingDefinition( + id='marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed', + created='2017-01-20T00:00:00.000Z', + definition_type='tlp', + definition=TLPMarking(tlp='red'), + ) + + assert red.serialize(sort_keys=True) == TLP_RED.serialize(sort_keys=True) + + +def test_unknown_tlp_marking(): + with pytest.raises(exceptions.TLPMarkingDefinitionError): + MarkingDefinition( + definition_type='tlp', + definition=TLPMarking(tlp='gray'), + ) diff --git a/stix2/test/v21/test_markings.py b/stix2/test/v21/test_markings.py index 7793889..bd247e6 100644 --- a/stix2/test/v21/test_markings.py +++ b/stix2/test/v21/test_markings.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import datetime as dt import pytest @@ -54,7 +56,7 @@ EXPECTED_GRANULAR_MARKING = """{ ] }""" -EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{ +EXPECTED_CAMPAIGN_WITH_GRANULAR_REF_MARKINGS = """{ "type": "campaign", "spec_version": "2.1", "id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", @@ -74,6 +76,27 @@ EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{ }""" +EXPECTED_CAMPAIGN_WITH_GRANULAR_LANG_MARKINGS = u"""{ + "type": "campaign", + "spec_version": "2.1", + "id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", + "created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c", + "created": "2016-04-06T20:03:00.000Z", + "modified": "2016-04-06T20:03:00.000Z", + "name": "Bank Attack", + "description": "Weitere Informationen über Banküberfall", + "lang": "en", + "granular_markings": [ + { + "lang": "de", + "selectors": [ + "description" + ] + } + ] +}""" + + def test_marking_def_example_with_tlp(): assert str(TLP_WHITE) == EXPECTED_TLP_MARKING_DEFINITION @@ -161,7 +184,7 @@ def test_campaign_with_granular_markings_example(): ), ], ) - assert str(campaign) == EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS + assert str(campaign) == EXPECTED_CAMPAIGN_WITH_GRANULAR_REF_MARKINGS @pytest.mark.parametrize( @@ -273,3 +296,26 @@ def test_campaign_add_markings(): ) campaign = campaign.add_markings(TLP_WHITE) assert campaign.object_marking_refs[0] == TLP_WHITE.id + + +def test_campaign_with_granular_lang_markings_example(): + campaign = stix2.v21.Campaign( + id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", + created_by_ref=IDENTITY_ID, + created="2016-04-06T20:03:00Z", + modified="2016-04-06T20:03:00Z", + name="Bank Attack", + lang="en", + description="Weitere Informationen über Banküberfall", + granular_markings=[ + stix2.v21.GranularMarking( + lang="de", + selectors=["description"], + ), + ], + ) + + # In order to provide the same representation, we need to disable escaping + # in json.dumps(). https://docs.python.org/3/library/json.html#json.dumps + # or https://docs.python.org/2/library/json.html#json.dumps + assert campaign.serialize(pretty=True, ensure_ascii=False) == EXPECTED_CAMPAIGN_WITH_GRANULAR_LANG_MARKINGS diff --git a/stix2/test/v21/test_observed_data.py b/stix2/test/v21/test_observed_data.py index 5d0f9b1..864dd7a 100644 --- a/stix2/test/v21/test_observed_data.py +++ b/stix2/test/v21/test_observed_data.py @@ -940,6 +940,24 @@ def test_ip4_address_example(): assert ip4.resolves_to_refs == ["4", "5"] +def test_ip4_address_valid_refs(): + mac1 = stix2.v21.MACAddress( + value="a1:b2:c3:d4:e5:f6", + ) + mac2 = stix2.v21.MACAddress( + value="a7:b8:c9:d0:e1:f2", + ) + + ip4 = stix2.v21.IPv4Address( + _valid_refs={"1": mac1, "2": mac2}, + value="177.60.40.7", + resolves_to_refs=["1", "2"], + ) + + assert ip4.value == "177.60.40.7" + assert ip4.resolves_to_refs == ["1", "2"] + + def test_ip4_address_example_cidr(): ip4 = stix2.v21.IPv4Address(value="198.51.100.0/24") diff --git a/stix2/test/v21/test_properties.py b/stix2/test/v21/test_properties.py index 298a8df..e4fa4a0 100644 --- a/stix2/test/v21/test_properties.py +++ b/stix2/test/v21/test_properties.py @@ -450,6 +450,11 @@ def test_enum_property_valid(value): assert enum_prop.clean('b') +def test_enum_property_clean(): + enum_prop = EnumProperty(['1']) + assert enum_prop.clean(1) == '1' + + def test_enum_property_invalid(): enum_prop = EnumProperty(['a', 'b', 'c']) with pytest.raises(ValueError): diff --git a/stix2/v20/bundle.py b/stix2/v20/bundle.py index 1a8323f..ab97f27 100644 --- a/stix2/v20/bundle.py +++ b/stix2/v20/bundle.py @@ -40,3 +40,21 @@ class Bundle(_STIXBase): self._properties['objects'].contained.interoperability = interoperability super(Bundle, self).__init__(**kwargs) + + def get_obj(self, obj_uuid): + if "objects" in self._inner: + found_objs = [elem for elem in self.objects if elem.id == obj_uuid] + if found_objs == []: + raise KeyError("'%s' does not match the id property of any of the bundle's objects" % obj_uuid) + return found_objs + else: + raise KeyError("There are no objects in this empty bundle") + + def __getitem__(self, key): + try: + return super(Bundle, self).__getitem__(key) + except KeyError: + try: + return self.get_obj(key) + except KeyError: + raise KeyError("'%s' is neither a property on the bundle nor does it match the id property of any of the bundle's objects" % key) diff --git a/stix2/v20/common.py b/stix2/v20/common.py index 8f6f01b..8f4cd17 100644 --- a/stix2/v20/common.py +++ b/stix2/v20/common.py @@ -6,6 +6,7 @@ import copy from ..base import _STIXBase from ..custom import _custom_marking_builder from ..markings import _MarkingsMixin +from ..markings.utils import check_tlp_marking from ..properties import ( HashesProperty, IDProperty, ListProperty, Property, ReferenceProperty, SelectorProperty, StringProperty, TimestampProperty, TypeProperty, @@ -140,6 +141,14 @@ class MarkingDefinition(_STIXBase, _MarkingsMixin): super(MarkingDefinition, self).__init__(**kwargs) + def _check_object_constraints(self): + super(MarkingDefinition, self)._check_object_constraints() + check_tlp_marking(self, '2.0') + + def serialize(self, pretty=False, include_optional_defaults=False, **kwargs): + check_tlp_marking(self, '2.0') + return super(MarkingDefinition, self).serialize(pretty, include_optional_defaults, **kwargs) + OBJ_MAP_MARKING = { 'tlp': TLPMarking, diff --git a/stix2/v21/bundle.py b/stix2/v21/bundle.py index 311e50b..334f088 100644 --- a/stix2/v21/bundle.py +++ b/stix2/v21/bundle.py @@ -38,3 +38,21 @@ class Bundle(_STIXBase): self._properties['objects'].contained.interoperability = interoperability super(Bundle, self).__init__(**kwargs) + + def get_obj(self, obj_uuid): + if "objects" in self._inner: + found_objs = [elem for elem in self.objects if elem.id == obj_uuid] + if found_objs == []: + raise KeyError("'%s' does not match the id property of any of the bundle's objects" % obj_uuid) + return found_objs + else: + raise KeyError("There are no objects in this empty bundle") + + def __getitem__(self, key): + try: + return super(Bundle, self).__getitem__(key) + except KeyError: + try: + return self.get_obj(key) + except KeyError: + raise KeyError("'%s' is neither a property on the bundle nor does it match the id property of any of the bundle's objects" % key) diff --git a/stix2/v21/common.py b/stix2/v21/common.py index 2082cc2..e6bf14b 100644 --- a/stix2/v21/common.py +++ b/stix2/v21/common.py @@ -6,6 +6,7 @@ import copy from ..base import _STIXBase from ..custom import _custom_marking_builder from ..markings import _MarkingsMixin +from ..markings.utils import check_tlp_marking from ..properties import ( BooleanProperty, DictionaryProperty, HashesProperty, IDProperty, IntegerProperty, ListProperty, Property, ReferenceProperty, @@ -180,6 +181,14 @@ class MarkingDefinition(_STIXBase, _MarkingsMixin): super(MarkingDefinition, self).__init__(**kwargs) + def _check_object_constraints(self): + super(MarkingDefinition, self)._check_object_constraints() + check_tlp_marking(self, '2.1') + + def serialize(self, pretty=False, include_optional_defaults=False, **kwargs): + check_tlp_marking(self, '2.1') + return super(MarkingDefinition, self).serialize(pretty, include_optional_defaults, **kwargs) + OBJ_MAP_MARKING = { 'tlp': TLPMarking,