diff --git a/docs/guide/datastore.ipynb b/docs/guide/datastore.ipynb index 2fabf88..1c88cf1 100644 --- a/docs/guide/datastore.ipynb +++ b/docs/guide/datastore.ipynb @@ -369,6 +369,7 @@ "* < \n", "* ```>=```\n", "* <=\n", + "* contains\n", "\n", "Value types of the property values must be one of these (Python) types:\n", "\n", diff --git a/stix2/datastore/filters.py b/stix2/datastore/filters.py index a32b14a..03585ad 100644 --- a/stix2/datastore/filters.py +++ b/stix2/datastore/filters.py @@ -9,7 +9,7 @@ from datetime import datetime from stix2.utils import format_datetime """Supported filter operations""" -FILTER_OPS = ['=', '!=', 'in', '>', '<', '>=', '<='] +FILTER_OPS = ['=', '!=', 'in', '>', '<', '>=', '<=', 'contains'] """Supported filter value types""" FILTER_VALUE_TYPES = [bool, dict, float, int, list, str, tuple] @@ -100,6 +100,11 @@ class Filter(collections.namedtuple("Filter", ['property', 'op', 'value'])): return stix_obj_property != self.value elif self.op == "in": return stix_obj_property in self.value + elif self.op == "contains": + if isinstance(self.value, dict): + return self.value in stix_obj_property.values() + else: + return self.value in stix_obj_property elif self.op == ">": return stix_obj_property > self.value elif self.op == "<": diff --git a/stix2/test/test_datastore_filters.py b/stix2/test/test_datastore_filters.py index 76ae6de..0f7b1ec 100644 --- a/stix2/test/test_datastore_filters.py +++ b/stix2/test/test_datastore_filters.py @@ -100,7 +100,9 @@ filters = [ Filter("object_marking_refs", "=", "marking-definition--613f2e26-0000-4000-8000-b8e91df99dc9"), Filter("granular_markings.selectors", "in", "description"), Filter("external_references.source_name", "=", "CVE"), - Filter("objects", "=", {"0": {"type": "file", "name": "HAL 9000.exe"}}) + Filter("objects", "=", {"0": {"type": "file", "name": "HAL 9000.exe"}}), + Filter("objects", "contains", {"type": "file", "name": "HAL 9000.exe"}), + Filter("labels", "contains", "heartbleed"), ] # same as above objects but converted to real Python STIX2 objects @@ -302,6 +304,28 @@ def test_apply_common_filters13(): assert len(resp) == 1 +def test_apply_common_filters14(): + # Return any object that contains a specific File Cyber Observable Object + resp = list(apply_common_filters(stix_objs, [filters[15]])) + assert resp[0]['id'] == stix_objs[4]['id'] + assert len(resp) == 1 + + resp = list(apply_common_filters(real_stix_objs, [filters[15]])) + assert resp[0].id == real_stix_objs[4].id + assert len(resp) == 1 + + +def test_apply_common_filters15(): + # Return any object that contains 'heartbleed' in "labels" + resp = list(apply_common_filters(stix_objs, [filters[16]])) + assert resp[0]['id'] == stix_objs[3]['id'] + assert len(resp) == 1 + + resp = list(apply_common_filters(real_stix_objs, [filters[16]])) + assert resp[0].id == real_stix_objs[3].id + assert len(resp) == 1 + + def test_datetime_filter_behavior(): """if a filter is initialized with its value being a datetime object OR the STIX object property being filtered on is a datetime object, all