Make object attribute access act like dictionary access, raising an
error for any property (including custom or optional) not set on the object.stix2.1
parent
35e973243f
commit
860efcc230
|
@ -73,7 +73,8 @@ class _STIXBase(collections.Mapping):
|
||||||
failed_dependency_pairs = []
|
failed_dependency_pairs = []
|
||||||
for p in list_of_properties:
|
for p in list_of_properties:
|
||||||
for dp in list_of_dependent_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))
|
failed_dependency_pairs.append((p, dp))
|
||||||
if failed_dependency_pairs:
|
if failed_dependency_pairs:
|
||||||
raise DependentPropertiesError(self.__class__, failed_dependency_pairs)
|
raise DependentPropertiesError(self.__class__, failed_dependency_pairs)
|
||||||
|
@ -128,10 +129,6 @@ class _STIXBase(collections.Mapping):
|
||||||
# Return attribute value.
|
# Return attribute value.
|
||||||
return self.__getitem__(name)
|
return self.__getitem__(name)
|
||||||
except KeyError:
|
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'" %
|
raise AttributeError("'%s' object has no attribute '%s'" %
|
||||||
(self.__class__.__name__, name))
|
(self.__class__.__name__, name))
|
||||||
|
|
||||||
|
@ -163,7 +160,7 @@ class _STIXBase(collections.Mapping):
|
||||||
|
|
||||||
def new_version(self, **kwargs):
|
def new_version(self, **kwargs):
|
||||||
unchangable_properties = []
|
unchangable_properties = []
|
||||||
if self.revoked:
|
if hasattr(self, 'revoked') and self.revoked:
|
||||||
raise RevokeError("new_version")
|
raise RevokeError("new_version")
|
||||||
new_obj_inner = copy.deepcopy(self._inner)
|
new_obj_inner = copy.deepcopy(self._inner)
|
||||||
properties_to_change = kwargs.keys()
|
properties_to_change = kwargs.keys()
|
||||||
|
@ -183,7 +180,7 @@ class _STIXBase(collections.Mapping):
|
||||||
return cls(**new_obj_inner)
|
return cls(**new_obj_inner)
|
||||||
|
|
||||||
def revoke(self):
|
def revoke(self):
|
||||||
if self.revoked:
|
if hasattr(self, 'revoked') and self.revoked:
|
||||||
raise RevokeError("revoke")
|
raise RevokeError("revoke")
|
||||||
return self.new_version(revoked=True)
|
return self.new_version(revoked=True)
|
||||||
|
|
||||||
|
|
|
@ -443,10 +443,10 @@ class Process(_Observable):
|
||||||
super(Process, self)._check_object_constraints()
|
super(Process, self)._check_object_constraints()
|
||||||
try:
|
try:
|
||||||
self._check_at_least_one_property()
|
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()
|
self.extensions["windows-process-ext"]._check_at_least_one_property()
|
||||||
except AtLeastOnePropertyError as enclosing_exc:
|
except AtLeastOnePropertyError as enclosing_exc:
|
||||||
if not self.extensions:
|
if not hasattr(self, 'extensions'):
|
||||||
raise enclosing_exc
|
raise enclosing_exc
|
||||||
else:
|
else:
|
||||||
if "windows-process-ext" in self.extensions:
|
if "windows-process-ext" in self.extensions:
|
||||||
|
|
|
@ -244,6 +244,8 @@ class ObservableProperty(Property):
|
||||||
dictified = get_dict(value)
|
dictified = get_dict(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("The observable property must contain a dictionary")
|
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())
|
valid_refs = dict((k, v['type']) for (k, v) in dictified.items())
|
||||||
|
|
||||||
|
@ -265,6 +267,8 @@ class DictionaryProperty(Property):
|
||||||
dictified = get_dict(value)
|
dictified = get_dict(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("The dictionary property must contain a dictionary")
|
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():
|
for k in dictified.keys():
|
||||||
if len(k) < 3:
|
if len(k) < 3:
|
||||||
|
@ -418,6 +422,8 @@ class ExtensionsProperty(DictionaryProperty):
|
||||||
dictified = get_dict(value)
|
dictified = get_dict(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("The extensions property must contain a dictionary")
|
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
|
from .__init__ import EXT_MAP # avoid circular import
|
||||||
if self.enclosing_type in EXT_MAP:
|
if self.enclosing_type in EXT_MAP:
|
||||||
|
|
|
@ -48,7 +48,8 @@ def test_empty_bundle():
|
||||||
assert bundle.type == "bundle"
|
assert bundle.type == "bundle"
|
||||||
assert bundle.id.startswith("bundle--")
|
assert bundle.id.startswith("bundle--")
|
||||||
assert bundle.spec_version == "2.0"
|
assert bundle.spec_version == "2.0"
|
||||||
assert bundle.objects is None
|
with pytest.raises(AttributeError):
|
||||||
|
assert bundle.objects
|
||||||
|
|
||||||
|
|
||||||
def test_bundle_with_wrong_type():
|
def test_bundle_with_wrong_type():
|
||||||
|
|
|
@ -199,6 +199,13 @@ def test_parse_artifact_invalid(data):
|
||||||
stix2.parse(odata_str)
|
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", [
|
@pytest.mark.parametrize("data", [
|
||||||
""""0": {
|
""""0": {
|
||||||
"type": "autonomous-system",
|
"type": "autonomous-system",
|
||||||
|
@ -769,6 +776,10 @@ def test_file_example_encryption_error():
|
||||||
assert excinfo.value.cls == stix2.File
|
assert excinfo.value.cls == stix2.File
|
||||||
assert excinfo.value.dependencies == [("is_encrypted", "encryption_algorithm")]
|
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():
|
def test_ip4_address_example():
|
||||||
ip4 = stix2.IPv4Address(_valid_refs={"4": "mac-addr", "5": "mac-addr"},
|
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():
|
def test_process_example_extensions_empty():
|
||||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||||
stix2.Process(extensions={
|
stix2.Process(extensions={})
|
||||||
})
|
|
||||||
|
|
||||||
assert excinfo.value.cls == stix2.Process
|
assert excinfo.value.cls == stix2.Process
|
||||||
properties_of_process = list(stix2.Process._properties.keys())
|
assert excinfo.value.prop_name == 'extensions'
|
||||||
properties_of_process.remove("type")
|
assert 'non-empty dictionary' in excinfo.value.reason
|
||||||
assert excinfo.value.properties == sorted(properties_of_process)
|
|
||||||
|
|
||||||
|
|
||||||
def test_process_example_with_WindowsProcessExt_Object():
|
def test_process_example_with_WindowsProcessExt_Object():
|
||||||
|
|
|
@ -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_by_ref == campaign_v2.created_by_ref
|
||||||
assert campaign_v1.created == campaign_v2.created
|
assert campaign_v1.created == campaign_v2.created
|
||||||
assert campaign_v1.name == campaign_v2.name
|
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
|
assert campaign_v1.modified < campaign_v2.modified
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue