From 49f58ff513823427b62fcbdcf0ddfcca223df697 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Thu, 1 Jun 2017 12:43:06 -0400 Subject: [PATCH 01/11] Make Immutable error more descriptive. Also fixes #13 --- stix2/base.py | 2 +- stix2/exceptions.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/stix2/base.py b/stix2/base.py index 7d2bf24..f7ab932 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -128,7 +128,7 @@ class _STIXBase(collections.Mapping): def __setattr__(self, name, value): if name != '_inner' and not name.startswith("_STIXBase__"): - raise ImmutableError + raise ImmutableError(self.__class__, name) super(_STIXBase, self).__setattr__(name, value) def __str__(self): diff --git a/stix2/exceptions.py b/stix2/exceptions.py index 5cbaae9..1165ecf 100644 --- a/stix2/exceptions.py +++ b/stix2/exceptions.py @@ -47,8 +47,14 @@ class ExtraPropertiesError(STIXError, TypeError): class ImmutableError(STIXError, ValueError): """Attempted to modify an object after creation""" - def __init__(self): - super(ImmutableError, self).__init__("Cannot modify properties after creation.") + def __init__(self, cls, key): + super(ImmutableError, self).__init__() + self.cls = cls + self.key = key + + def __str__(self): + msg = "Cannot modify '{0.key}' property in {0.cls} after creation." + return msg.format(self) class DictionaryKeyError(STIXError, ValueError): From ad46474663b6004713ef8b15dfe941f9e381f9c8 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Thu, 1 Jun 2017 12:43:42 -0400 Subject: [PATCH 02/11] Update immutable tests. --- stix2/test/test_indicator.py | 2 +- stix2/test/test_malware.py | 2 +- stix2/test/test_relationship.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stix2/test/test_indicator.py b/stix2/test/test_indicator.py index 99d34d7..7c19938 100644 --- a/stix2/test/test_indicator.py +++ b/stix2/test/test_indicator.py @@ -129,7 +129,7 @@ def test_cannot_assign_to_indicator_attributes(indicator): with pytest.raises(stix2.exceptions.ImmutableError) as excinfo: indicator.valid_from = dt.datetime.now() - assert str(excinfo.value) == "Cannot modify properties after creation." + assert str(excinfo.value) == "Cannot modify 'valid_from' property in after creation." def test_invalid_kwarg_to_indicator(): diff --git a/stix2/test/test_malware.py b/stix2/test/test_malware.py index 8952769..b4dc419 100644 --- a/stix2/test/test_malware.py +++ b/stix2/test/test_malware.py @@ -92,7 +92,7 @@ def test_cannot_assign_to_malware_attributes(malware): with pytest.raises(stix2.exceptions.ImmutableError) as excinfo: malware.name = "Cryptolocker II" - assert str(excinfo.value) == "Cannot modify properties after creation." + assert str(excinfo.value) == "Cannot modify 'name' property in after creation." def test_invalid_kwarg_to_malware(): diff --git a/stix2/test/test_relationship.py b/stix2/test/test_relationship.py index 1e799bf..4ef6237 100644 --- a/stix2/test/test_relationship.py +++ b/stix2/test/test_relationship.py @@ -103,7 +103,7 @@ def test_cannot_assign_to_relationship_attributes(relationship): with pytest.raises(stix2.exceptions.ImmutableError) as excinfo: relationship.relationship_type = "derived-from" - assert str(excinfo.value) == "Cannot modify properties after creation." + assert str(excinfo.value) == "Cannot modify 'relationship_type' property in after creation." def test_invalid_kwarg_to_relationship(): From 5dc049d65a9d406011092657e62f67cfc2dc39e0 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Thu, 1 Jun 2017 15:23:44 -0400 Subject: [PATCH 03/11] getattr() checks for attribute membership, returns the value if not None and raises AttributeError when is not present. fixes #19 --- stix2/base.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/stix2/base.py b/stix2/base.py index f7ab932..7f9db3f 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -78,7 +78,7 @@ class _STIXBase(collections.Mapping): raise DependentPropertiesError(self.__class__, failed_dependency_pairs) def _check_object_constraints(self): - if self.granular_markings: + if hasattr(self, "granular_markings") and self.granular_markings: for m in self.granular_markings: # TODO: check selectors pass @@ -124,7 +124,16 @@ class _STIXBase(collections.Mapping): # Handle attribute access just like key access def __getattr__(self, name): - return self.get(name) + # If the requested attribute is not None return its value + if self.get(name) is not None: + return self.get(name) + # Otherwise check the attribute exists inside object. If it exists + # return None. If not raise the AttributeError. + elif name in self._properties: + return None + + raise AttributeError("'%s' object has no attribute '%s'" % + (self.__class__.__name__, name)) def __setattr__(self, name, value): if name != '_inner' and not name.startswith("_STIXBase__"): From 4d58fc3dbb7697f951d35a221f2d3dbc8e0e1b5d Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Thu, 1 Jun 2017 15:25:03 -0400 Subject: [PATCH 04/11] Explicitly check for None, when intention is to set empty values or False. fixes #17 --- stix2/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stix2/base.py b/stix2/base.py index 7f9db3f..0e44f65 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -97,7 +97,7 @@ class _STIXBase(collections.Mapping): # Remove any keyword arguments whose value is None setting_kwargs = {} for prop_name, prop_value in kwargs.items(): - if prop_value: + if prop_value is not None: setting_kwargs[prop_name] = prop_value # Detect any missing required properties From e1e7bade3fe42330aee0b9d7c998b7d98ef8a2f3 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Thu, 1 Jun 2017 15:25:46 -0400 Subject: [PATCH 05/11] Updated this test since setting the value to False should not be a reason to fail the test. --- stix2/test/test_observed_data.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py index c47338f..09f49c7 100644 --- a/stix2/test/test_observed_data.py +++ b/stix2/test/test_observed_data.py @@ -761,14 +761,13 @@ def test_file_example_with_WindowsPEBinaryExt(): def test_file_example_encryption_error(): - with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo: - stix2.File(name="qwerty.dll", - is_encrypted=False, - encryption_algorithm="AES128-CBC" - ) + file_ = stix2.File(name="qwerty.dll", is_encrypted=False, + encryption_algorithm="AES128-CBC") - assert excinfo.value.cls == stix2.File - assert excinfo.value.dependencies == [("is_encrypted", "encryption_algorithm")] + assert file_.__class__ == stix2.File + assert file_.name == "qwerty.dll" + assert file_.is_encrypted is False + assert file_.encryption_algorithm == "AES128-CBC" def test_ip4_address_example(): From fceef4d85aa4704ff0dda319aebf1af0b698de98 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Fri, 2 Jun 2017 07:33:31 -0400 Subject: [PATCH 06/11] Use class name for ImmutableError message. --- stix2/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stix2/exceptions.py b/stix2/exceptions.py index 1165ecf..3043047 100644 --- a/stix2/exceptions.py +++ b/stix2/exceptions.py @@ -53,7 +53,7 @@ class ImmutableError(STIXError, ValueError): self.key = key def __str__(self): - msg = "Cannot modify '{0.key}' property in {0.cls} after creation." + msg = "Cannot modify '{0.key}' property in '{0.cls.__name__}' after creation." return msg.format(self) From d579c121724c1ef0e4204bc0b0b1f7839444c28c Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Fri, 2 Jun 2017 07:34:37 -0400 Subject: [PATCH 07/11] Update ImmutableError test cases. --- stix2/test/test_indicator.py | 2 +- stix2/test/test_malware.py | 2 +- stix2/test/test_relationship.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stix2/test/test_indicator.py b/stix2/test/test_indicator.py index 7c19938..5db50e6 100644 --- a/stix2/test/test_indicator.py +++ b/stix2/test/test_indicator.py @@ -129,7 +129,7 @@ def test_cannot_assign_to_indicator_attributes(indicator): with pytest.raises(stix2.exceptions.ImmutableError) as excinfo: indicator.valid_from = dt.datetime.now() - assert str(excinfo.value) == "Cannot modify 'valid_from' property in after creation." + assert str(excinfo.value) == "Cannot modify 'valid_from' property in 'Indicator' after creation." def test_invalid_kwarg_to_indicator(): diff --git a/stix2/test/test_malware.py b/stix2/test/test_malware.py index b4dc419..266d012 100644 --- a/stix2/test/test_malware.py +++ b/stix2/test/test_malware.py @@ -92,7 +92,7 @@ def test_cannot_assign_to_malware_attributes(malware): with pytest.raises(stix2.exceptions.ImmutableError) as excinfo: malware.name = "Cryptolocker II" - assert str(excinfo.value) == "Cannot modify 'name' property in after creation." + assert str(excinfo.value) == "Cannot modify 'name' property in 'Malware' after creation." def test_invalid_kwarg_to_malware(): diff --git a/stix2/test/test_relationship.py b/stix2/test/test_relationship.py index 4ef6237..1ad792c 100644 --- a/stix2/test/test_relationship.py +++ b/stix2/test/test_relationship.py @@ -103,7 +103,7 @@ def test_cannot_assign_to_relationship_attributes(relationship): with pytest.raises(stix2.exceptions.ImmutableError) as excinfo: relationship.relationship_type = "derived-from" - assert str(excinfo.value) == "Cannot modify 'relationship_type' property in after creation." + assert str(excinfo.value) == "Cannot modify 'relationship_type' property in 'Relationship' after creation." def test_invalid_kwarg_to_relationship(): From 51e28f64da6508df5a34d3c4080024c4a18934a7 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Fri, 2 Jun 2017 10:10:50 -0400 Subject: [PATCH 08/11] Change __getattr__() to use __getitem__() instead. Other minor changes. --- stix2/base.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/stix2/base.py b/stix2/base.py index 55fd0eb..c14dd8a 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -81,10 +81,9 @@ class _STIXBase(collections.Mapping): raise DependentPropertiesError(self.__class__, failed_dependency_pairs) def _check_object_constraints(self): - if hasattr(self, "granular_markings") and self.granular_markings: - for m in self.granular_markings: - # TODO: check selectors - pass + for m in self.get("granular_markings", []): + # TODO: check selectors + pass def __init__(self, **kwargs): cls = self.__class__ @@ -127,16 +126,16 @@ class _STIXBase(collections.Mapping): # Handle attribute access just like key access def __getattr__(self, name): - # If the requested attribute is not None return its value - if self.get(name) is not None: - return self.get(name) - # Otherwise check the attribute exists inside object. If it exists - # return None. If not raise the AttributeError. - elif name in self._properties: - return None - - raise AttributeError("'%s' object has no attribute '%s'" % - (self.__class__.__name__, name)) + try: + # 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)) def __setattr__(self, name, value): if name != '_inner' and not name.startswith("_STIXBase__"): From e11b3adb3f87ee7e394398f1969bcb8d735e7276 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Fri, 2 Jun 2017 10:22:31 -0400 Subject: [PATCH 09/11] Style change. --- stix2/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stix2/base.py b/stix2/base.py index c14dd8a..dd70e26 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -133,7 +133,7 @@ class _STIXBase(collections.Mapping): # 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)) From 653eef4b959368430cc68f961492431fb60c2360 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Fri, 2 Jun 2017 13:47:08 -0400 Subject: [PATCH 10/11] Revert changes to test_file_example_encryption_error() --- stix2/test/test_observed_data.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py index 09f49c7..f221494 100644 --- a/stix2/test/test_observed_data.py +++ b/stix2/test/test_observed_data.py @@ -761,13 +761,13 @@ def test_file_example_with_WindowsPEBinaryExt(): def test_file_example_encryption_error(): - file_ = stix2.File(name="qwerty.dll", is_encrypted=False, - encryption_algorithm="AES128-CBC") + with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo: + stix2.File(name="qwerty.dll", + is_encrypted=False, + encryption_algorithm="AES128-CBC") - assert file_.__class__ == stix2.File - assert file_.name == "qwerty.dll" - assert file_.is_encrypted is False - assert file_.encryption_algorithm == "AES128-CBC" + assert excinfo.value.cls == stix2.File + assert excinfo.value.dependencies == [("is_encrypted", "encryption_algorithm")] def test_ip4_address_example(): From a38b912d1960245b84c5cb9c9af16fde8e420ac5 Mon Sep 17 00:00:00 2001 From: Emmanuelle Vargas-Gonzalez Date: Fri, 2 Jun 2017 13:48:44 -0400 Subject: [PATCH 11/11] Change _check_property_dependency() to also check for values for the required properties. --- stix2/base.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stix2/base.py b/stix2/base.py index dd70e26..3f696fd 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -69,13 +69,11 @@ class _STIXBase(collections.Mapping): if list_of_properties and (not list_of_properties_populated or list_of_properties_populated == set(["extensions"])): raise AtLeastOnePropertyError(self.__class__, list_of_properties) - def _check_properties_dependency(self, list_of_properties, list_of_dependent_properties, values=[]): + def _check_properties_dependency(self, list_of_properties, list_of_dependent_properties): failed_dependency_pairs = [] - current_properties = self.properties_populated() for p in list_of_properties: - v = values.pop() if values else None for dp in list_of_dependent_properties: - if dp in current_properties and (p not in current_properties or (v and not current_properties(p) == v)): + if not self.__getattr__(p) and self.__getattr__(dp): failed_dependency_pairs.append((p, dp)) if failed_dependency_pairs: raise DependentPropertiesError(self.__class__, failed_dependency_pairs)