diff --git a/stix2/base.py b/stix2/base.py index a0de3b6..7926f43 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -56,9 +56,14 @@ class _STIXBase(collections.Mapping): if count > 1 or (at_least_one and count == 0): raise MutuallyExclusivePropertiesError(self.__class__, list_of_properties) - def _check_at_least_one_property(self, list_of_properties): + def _check_at_least_one_property(self, list_of_properties=None): + if not list_of_properties: + list_of_properties = sorted(list(self.__class__._properties.keys())) + if "type" in list_of_properties: + list_of_properties.remove("type") current_properties = self.properties_populated() - if not set(list_of_properties).intersection(current_properties): + list_of_properties_populated = set(list_of_properties).intersection(current_properties) + if list_of_properties and (not list_of_properties_populated or list_of_properties_populated == {"extensions"}): raise AtLeastOnePropertyError(self.__class__, list_of_properties) def _check_properties_dependency(self, list_of_properties, list_of_dependent_properties, values=[]): diff --git a/stix2/observables.py b/stix2/observables.py index a1e247f..3047556 100644 --- a/stix2/observables.py +++ b/stix2/observables.py @@ -6,9 +6,11 @@ and do not have a '_type' attribute. """ from .base import _Observable, _STIXBase +from .exceptions import AtLeastOnePropertyError from .properties import (BinaryProperty, BooleanProperty, DictionaryProperty, - EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty, - HashesProperty, HexProperty, IntegerProperty, ListProperty, + EmbeddedObjectProperty, EnumProperty, + ExtensionsProperty, FloatProperty, HashesProperty, + HexProperty, IntegerProperty, ListProperty, ObjectReferenceProperty, StringProperty, TimestampProperty, TypeProperty) @@ -133,6 +135,10 @@ class NTFSExt(_STIXBase): 'alternate_data_streams': ListProperty(EmbeddedObjectProperty(type=AlternateDataStream)), } + def _check_object_constaints(self): + super(NTFSExt, self)._check_object_constaints() + self._check_at_least_one_property() + class PDFExt(_STIXBase): _properties = { @@ -143,6 +149,10 @@ class PDFExt(_STIXBase): 'pdfid1': StringProperty(), } + def _check_object_constaints(self): + super(PDFExt, self)._check_object_constaints() + self._check_at_least_one_property() + class RasterImageExt(_STIXBase): _properties = { @@ -153,6 +163,10 @@ class RasterImageExt(_STIXBase): 'exif_tags': DictionaryProperty(), } + def _check_object_constaints(self): + super(RasterImageExt, self)._check_object_constaints() + self._check_at_least_one_property() + class WindowsPEOptionalHeaderType(_STIXBase): _properties = { @@ -189,6 +203,10 @@ class WindowsPEOptionalHeaderType(_STIXBase): 'hashes': HashesProperty(), } + def _check_object_constaints(self): + super(WindowsPEOptionalHeaderType, self)._check_object_constaints() + self._check_at_least_one_property() + class WindowsPESection(_STIXBase): _properties = { @@ -338,6 +356,10 @@ class TCPExt(_STIXBase): 'dst_flags_hex': HexProperty(), } + def _check_object_constaints(self): + super(TCPExt, self)._check_object_constaints() + self._check_at_least_one_property() + class NetworkTraffic(_Observable): _type = 'network-traffic' @@ -432,6 +454,19 @@ class Process(_Observable): 'child_refs': ListProperty(ObjectReferenceProperty), } + def _check_object_constaints(self): + super(Process, self)._check_object_constaints() + try: + self._check_at_least_one_property() + if self.extensions and "windows-process-ext" in self.extensions: + self.extensions["windows-process-ext"]._check_at_least_one_property() + except AtLeastOnePropertyError as enclosing_exc: + if not self.extensions: + raise enclosing_exc + else: + if "windows-process-ext" in self.extensions: + self.extensions["windows-process-ext"]._check_at_least_one_property() + class Software(_Observable): _type = 'software' diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py index 438ca65..a079f37 100644 --- a/stix2/test/test_observed_data.py +++ b/stix2/test/test_observed_data.py @@ -576,6 +576,35 @@ def test_file_example(): assert f.decryption_key == "fred" # does the key have a format we can test for? +def test_file_example_with_NTFSExt(): + f = stix2.File(name="abc.txt", + extensions={ + "ntfs-ext": { + "alternate_data_streams": [ + { + "name": "second.stream", + "size": 25536 + } + ] + } + }) + + assert f.name == "abc.txt" + assert f.extensions["ntfs-ext"].alternate_data_streams[0].size == 25536 + + +def test_file_example_with_empty_NTFSExt(): + with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: + stix2.File(name="abc.txt", + extensions={ + "ntfs-ext": { + } + }) + + assert excinfo.value.cls == stix2.NTFSExt + assert excinfo.value.fields == sorted(list(stix2.NTFSExt._properties.keys())) + + def test_file_example_with_PDFExt(): f = stix2.File(name="qwerty.dll", extensions={ @@ -751,20 +780,77 @@ def test_mutex_example(): assert m.name == "barney" +def test_process_example(): + p = stix2.Process(_valid_refs=["0"], + pid=1221, + name="gedit-bin", + created="2016-01-20T14:11:25.55Z", + arguments=["--new-window"], + binary_ref="0") + + assert p.name == "gedit-bin" + assert p.arguments == ["--new-window"] + + +def test_process_example_empty_error(): + with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: + stix2.Process() + + assert excinfo.value.cls == stix2.Process + properties_of_process = list(stix2.Process._properties.keys()) + properties_of_process.remove("type") + assert excinfo.value.fields == sorted(properties_of_process) + + +def test_process_example_empty_with_extensions(): + with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: + stix2.Process(extensions={ + "windows-process-ext": {} + }) + + assert excinfo.value.cls == stix2.WindowsProcessExt + properties_of_extension = list(stix2.WindowsProcessExt._properties.keys()) + assert excinfo.value.fields == sorted(properties_of_extension) + + +def test_process_example_windows_process_ext_empty(): + with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: + stix2.Process(pid=1221, + name="gedit-bin", + extensions={ + "windows-process-ext": {} + }) + + assert excinfo.value.cls == stix2.WindowsProcessExt + properties_of_extension = list(stix2.WindowsProcessExt._properties.keys()) + assert excinfo.value.fields == sorted(properties_of_extension) + + +def test_process_example_extensions_empty(): + with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: + stix2.Process(extensions={ + }) + + assert excinfo.value.cls == stix2.Process + properties_of_process = list(stix2.Process._properties.keys()) + properties_of_process.remove("type") + assert excinfo.value.fields == sorted(properties_of_process) + + def test_process_example_with_WindowsProcessExt_Object(): - f = stix2.Process(extensions={ + p = stix2.Process(extensions={ "windows-process-ext": stix2.WindowsProcessExt(aslr_enabled=True, dep_enabled=True, priority="HIGH_PRIORITY_CLASS", owner_sid="S-1-5-21-186985262-1144665072-74031268-1309") # noqa }) - assert f.extensions["windows-process-ext"].dep_enabled - assert f.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309" + assert p.extensions["windows-process-ext"].dep_enabled + assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309" def test_process_example_with_WindowsServiceExt(): - f = stix2.Process(extensions={ + p = stix2.Process(extensions={ "windows-service-ext": { "service_name": "sirvizio", "display_name": "Sirvizio", @@ -774,12 +860,12 @@ def test_process_example_with_WindowsServiceExt(): } }) - assert f.extensions["windows-service-ext"].service_name == "sirvizio" - assert f.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS" + assert p.extensions["windows-service-ext"].service_name == "sirvizio" + assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS" def test_process_example_with_WindowsProcessServiceExt(): - f = stix2.Process(extensions={ + p = stix2.Process(extensions={ "windows-service-ext": { "service_name": "sirvizio", "display_name": "Sirvizio", @@ -795,10 +881,10 @@ def test_process_example_with_WindowsProcessServiceExt(): } }) - assert f.extensions["windows-service-ext"].service_name == "sirvizio" - assert f.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS" - assert f.extensions["windows-process-ext"].dep_enabled - assert f.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309" + assert p.extensions["windows-service-ext"].service_name == "sirvizio" + assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS" + assert p.extensions["windows-process-ext"].dep_enabled + assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309" def test_software_example():