diff --git a/stix2/base.py b/stix2/base.py index 3f696fd..c5b52aa 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -73,7 +73,8 @@ class _STIXBase(collections.Mapping): failed_dependency_pairs = [] for p in list_of_properties: for dp in list_of_dependent_properties: - if not self.__getattr__(p) and self.__getattr__(dp): + if ((not hasattr(self, p) or (hasattr(self, p) and not self.__getattr__(p))) and + hasattr(self, dp) and self.__getattr__(dp)): failed_dependency_pairs.append((p, dp)) if failed_dependency_pairs: raise DependentPropertiesError(self.__class__, failed_dependency_pairs) @@ -128,10 +129,6 @@ class _STIXBase(collections.Mapping): # Return attribute value. return self.__getitem__(name) except KeyError: - # If attribute not found, check if its a property of the object. - if name in self._properties: - return None - raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) @@ -163,7 +160,7 @@ class _STIXBase(collections.Mapping): def new_version(self, **kwargs): unchangable_properties = [] - if self.revoked: + if hasattr(self, 'revoked') and self.revoked: raise RevokeError("new_version") new_obj_inner = copy.deepcopy(self._inner) properties_to_change = kwargs.keys() @@ -183,7 +180,7 @@ class _STIXBase(collections.Mapping): return cls(**new_obj_inner) def revoke(self): - if self.revoked: + if hasattr(self, 'revoked') and self.revoked: raise RevokeError("revoke") return self.new_version(revoked=True) diff --git a/stix2/observables.py b/stix2/observables.py index ec936fd..89d35c6 100644 --- a/stix2/observables.py +++ b/stix2/observables.py @@ -443,10 +443,10 @@ class Process(_Observable): super(Process, self)._check_object_constraints() try: self._check_at_least_one_property() - if self.extensions and "windows-process-ext" in self.extensions: + if hasattr(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: + if not hasattr(self, 'extensions'): raise enclosing_exc else: if "windows-process-ext" in self.extensions: diff --git a/stix2/properties.py b/stix2/properties.py index bd1e3a2..9d54cf0 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -244,6 +244,8 @@ class ObservableProperty(Property): dictified = get_dict(value) except ValueError: raise ValueError("The observable property must contain a dictionary") + if dictified == {}: + raise ValueError("The dictionary property must contain a non-empty dictionary") valid_refs = dict((k, v['type']) for (k, v) in dictified.items()) @@ -265,6 +267,8 @@ class DictionaryProperty(Property): dictified = get_dict(value) except ValueError: raise ValueError("The dictionary property must contain a dictionary") + if dictified == {}: + raise ValueError("The dictionary property must contain a non-empty dictionary") for k in dictified.keys(): if len(k) < 3: @@ -418,6 +422,8 @@ class ExtensionsProperty(DictionaryProperty): dictified = get_dict(value) except ValueError: raise ValueError("The extensions property must contain a dictionary") + if dictified == {}: + raise ValueError("The dictionary property must contain a non-empty dictionary") from .__init__ import EXT_MAP # avoid circular import if self.enclosing_type in EXT_MAP: diff --git a/stix2/test/test_bundle.py b/stix2/test/test_bundle.py index a849f3e..d52a955 100644 --- a/stix2/test/test_bundle.py +++ b/stix2/test/test_bundle.py @@ -48,7 +48,8 @@ def test_empty_bundle(): assert bundle.type == "bundle" assert bundle.id.startswith("bundle--") assert bundle.spec_version == "2.0" - assert bundle.objects is None + with pytest.raises(AttributeError): + assert bundle.objects def test_bundle_with_wrong_type(): diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py index f221494..b934b31 100644 --- a/stix2/test/test_observed_data.py +++ b/stix2/test/test_observed_data.py @@ -199,6 +199,13 @@ def test_parse_artifact_invalid(data): stix2.parse(odata_str) +def test_artifact_example_dependency_error(): + with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo: + stix2.Artifact(url="http://example.com/sirvizio.exe") + + assert excinfo.value.dependencies == [("hashes", "url")] + + @pytest.mark.parametrize("data", [ """"0": { "type": "autonomous-system", @@ -769,6 +776,10 @@ def test_file_example_encryption_error(): assert excinfo.value.cls == stix2.File assert excinfo.value.dependencies == [("is_encrypted", "encryption_algorithm")] + with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo: + stix2.File(name="qwerty.dll", + encryption_algorithm="AES128-CBC") + def test_ip4_address_example(): ip4 = stix2.IPv4Address(_valid_refs={"4": "mac-addr", "5": "mac-addr"}, @@ -916,14 +927,12 @@ def test_process_example_windows_process_ext_empty(): def test_process_example_extensions_empty(): - with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo: - stix2.Process(extensions={ - }) + with pytest.raises(stix2.exceptions.InvalidValueError) 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.properties == sorted(properties_of_process) + assert excinfo.value.prop_name == 'extensions' + assert 'non-empty dictionary' in excinfo.value.reason def test_process_example_with_WindowsProcessExt_Object(): diff --git a/stix2/test/test_versioning.py b/stix2/test/test_versioning.py index 76a13bf..281ae71 100644 --- a/stix2/test/test_versioning.py +++ b/stix2/test/test_versioning.py @@ -40,7 +40,8 @@ def test_making_new_version_with_unset(): assert campaign_v1.created_by_ref == campaign_v2.created_by_ref assert campaign_v1.created == campaign_v2.created assert campaign_v1.name == campaign_v2.name - assert campaign_v2.description is None + with pytest.raises(AttributeError): + assert campaign_v2.description assert campaign_v1.modified < campaign_v2.modified