diff --git a/stix2/base.py b/stix2/base.py index 5ca0834..f9c4391 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -49,6 +49,12 @@ class _STIXBase(collections.Mapping): except ValueError as exc: raise InvalidValueError(self.__class__, prop_name, reason=str(exc)) + def _check_object_constaints(self): + if self.granular_markings: + for m in self.granular_markings: + # TODO: check selectors + pass + def __init__(self, **kwargs): cls = self.__class__ @@ -77,10 +83,7 @@ class _STIXBase(collections.Mapping): self._inner = setting_kwargs - if self.granular_markings: - for m in self.granular_markings: - # TODO: check selectors - pass + self._check_object_constaints() def __getitem__(self, key): return self._inner[key] @@ -116,6 +119,9 @@ class _STIXBase(collections.Mapping): cls = type(self) return cls(**new_inner) + def properties_populated(self): + return list(self._inner.keys()) + # Versioning API def new_version(self, **kwargs): diff --git a/stix2/exceptions.py b/stix2/exceptions.py index fdd3a46..2b0cb7a 100644 --- a/stix2/exceptions.py +++ b/stix2/exceptions.py @@ -60,7 +60,7 @@ class DictionaryKeyError(STIXError, ValueError): self.reason = reason def __str__(self): - msg = "Invliad dictionary key {0.key}: ({0.reason})." + msg = "Invalid dictionary key {0.key}: ({0.reason})." return msg.format(self) @@ -90,6 +90,20 @@ class UnmodifiablePropertyError(STIXError, ValueError): return msg.format(", ".join(self.unchangable_properties)) +class ObjectConstraintError(STIXError, TypeError): + """Violating some interproperty constraint of a STIX object type.""" + + def __init__(self, cls, fields): + super(ObjectConstraintError, self).__init__() + self.cls = cls + self.fields = sorted(list(fields)) + + def __str__(self): + msg = "The field(s) for {0}: ({1}) are not consistent." + return msg.format(self.cls.__name__, + ", ".join(x for x in self.fields)) + + class RevokeError(STIXError, ValueError): """Attempted to an operation on a revoked object""" diff --git a/stix2/observables.py b/stix2/observables.py index 73d58cb..2b245d9 100644 --- a/stix2/observables.py +++ b/stix2/observables.py @@ -6,6 +6,7 @@ and do not have a '_type' attribute. """ from .base import _STIXBase, Observable +from .exceptions import ObjectConstraintError from .properties import BinaryProperty, BooleanProperty, DictionaryProperty, \ EmbeddedObjectProperty, HashesProperty, HexProperty, IntegerProperty, \ ListProperty, ObjectReferenceProperty, Property, StringProperty, \ @@ -113,12 +114,24 @@ class File(Observable): 'accessed': TimestampProperty(), 'parent_directory_ref': ObjectReferenceProperty(), 'is_encrypted': BooleanProperty(), - 'encyption_algorithm': StringProperty(), + 'encryption_algorithm': StringProperty(), 'decryption_key': StringProperty(), 'contains_refs': ListProperty(ObjectReferenceProperty), 'content_ref': ObjectReferenceProperty(), } + def _check_object_constaints(self): + super(File, self)._check_object_constaints() + illegal_properties = [] + current_properties = self.properties_populated() + if not self.is_encrypted: + for p in ["encryption_algorithm", "decryption_key"]: + if p in current_properties: + illegal_properties.append(p) + if illegal_properties: + illegal_properties.append("is_encrypted") + raise ObjectConstraintError(self.__class__, illegal_properties) + class IPv4Address(Observable): _type = 'ipv4-addr' @@ -258,7 +271,7 @@ class WindowsRegistryKey(Observable): _properties = { 'type': TypeProperty(_type), 'key': StringProperty(required=True), - 'values': ListProperty(WindowsRegistryValueType), + 'values': ListProperty(EmbeddedObjectProperty(type=WindowsRegistryValueType)), # this is not the modified timestamps of the object itself 'modified': TimestampProperty(), 'creator_user_ref': ObjectReferenceProperty(), diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py index 52523e8..2910d3b 100644 --- a/stix2/test/test_observed_data.py +++ b/stix2/test/test_observed_data.py @@ -326,7 +326,7 @@ def test_file_example(): modified="2016-12-24T19:00:00Z", accessed="2016-12-21T20:00:00Z", is_encrypted=True, - encyption_algorithm="AES128-CBC", + encryption_algorithm="AES128-CBC", decryption_key="fred" ) @@ -339,19 +339,19 @@ def test_file_example(): assert f.modified == dt.datetime(2016, 12, 24, 19, 0, 0, tzinfo=pytz.utc) assert f.accessed == dt.datetime(2016, 12, 21, 20, 0, 0, tzinfo=pytz.utc) assert f.is_encrypted - assert f.encyption_algorithm == "AES128-CBC" + assert f.encryption_algorithm == "AES128-CBC" assert f.decryption_key == "fred" # does the key have a format we can test for? -# def test_file_example_encyption_error(): -# f = stix2.File(name="qwerty.dll", -# is_encrypted=False, -# encyption_algorithm="AES128-CBC" -# ) -# -# assert f.name == "qwerty.dll" -# assert f.is_encrypted == False -# assert f.encyption_algorithm == "AES128-CBC" +def test_file_example_encryption_error(): + with pytest.raises(stix2.exceptions.ObjectConstraintError) as excinfo: + stix2.File(name="qwerty.dll", + is_encrypted=False, + encryption_algorithm="AES128-CBC" + ) + + assert excinfo.value.cls == stix2.File + assert excinfo.value.fields == ["encryption_algorithm", "is_encrypted"] def test_ip4_address_example():