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,