Repository: oasis-open/cti-python-stix2
Pull request #21 (2017-06-08 12:43:58)

Minor Code Changes.
Make use of the `get()` method instead. Added an extra check when `'is_multipart'` is `False` and `'body'` is not.


Patch:
diff --git a/stix2/base.py b/stix2/base.py
index c5b52aa..c40c9e6 100644
--- a/stix2/base.py
+++ b/stix2/base.py
@@ -73,8 +73,7 @@ class _STIXBase(collections.Mapping):
         failed_dependency_pairs = []
         for p in list_of_properties:
             for dp in list_of_dependent_properties:
-                if ((not hasattr(self, p) or (hasattr(self, p) and not self.__getattr__(p))) and
-                        hasattr(self, dp) and self.__getattr__(dp)):
+                if not self.get(p) and self.get(dp):
                     failed_dependency_pairs.append((p, dp))
         if failed_dependency_pairs:
             raise DependentPropertiesError(self.__class__, failed_dependency_pairs)
@@ -125,12 +124,10 @@ class _STIXBase(collections.Mapping):
 
     # Handle attribute access just like key access
     def __getattr__(self, name):
-        try:
-            # Return attribute value.
+        if name in self:
             return self.__getitem__(name)
-        except KeyError:
-            raise AttributeError("'%s' object has no attribute '%s'" %
-                                 (self.__class__.__name__, name))
+        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__"):
@@ -160,7 +157,7 @@ class _STIXBase(collections.Mapping):
 
     def new_version(self, **kwargs):
         unchangable_properties = []
-        if hasattr(self, 'revoked') and self.revoked:
+        if self.get("revoked"):
             raise RevokeError("new_version")
         new_obj_inner = copy.deepcopy(self._inner)
         properties_to_change = kwargs.keys()
@@ -180,7 +177,7 @@ class _STIXBase(collections.Mapping):
         return cls(**new_obj_inner)
 
     def revoke(self):
-        if hasattr(self, 'revoked') and self.revoked:
+        if self.get("revoked"):
             raise RevokeError("revoke")
         return self.new_version(revoked=True)
 
diff --git a/stix2/observables.py b/stix2/observables.py
index 89d35c6..9c1b368 100644
--- a/stix2/observables.py
+++ b/stix2/observables.py
@@ -6,7 +6,7 @@ and do not have a '_type' attribute.
 """
 
 from .base import _Extension, _Observable, _STIXBase
-from .exceptions import AtLeastOnePropertyError
+from .exceptions import AtLeastOnePropertyError, DependentPropertiesError
 from .properties import (BinaryProperty, BooleanProperty, DictionaryProperty,
                          EmbeddedObjectProperty, EnumProperty,
                          ExtensionsProperty, FloatProperty, HashesProperty,
@@ -110,7 +110,9 @@ class EmailMessage(_Observable):
     def _check_object_constraints(self):
         super(EmailMessage, self)._check_object_constraints()
         self._check_properties_dependency(["is_multipart"], ["body_multipart"])
-        # self._dependency(["is_multipart"], ["body"], [False])
+        if self.get("is_multipart") is True and self.get("body"):
+            # 'body' MAY only be used if is_multipart is false.
+            raise DependentPropertiesError(self.__class__, [("is_multipart", "body")])
 
 
 class ArchiveExt(_Extension):
@@ -443,13 +445,13 @@ class Process(_Observable):
         super(Process, self)._check_object_constraints()
         try:
             self._check_at_least_one_property()
-            if hasattr(self, 'extensions') and "windows-process-ext" in self.extensions:
+            if "windows-process-ext" in self.get('extensions', {}):
                 self.extensions["windows-process-ext"]._check_at_least_one_property()
         except AtLeastOnePropertyError as enclosing_exc:
-            if not hasattr(self, 'extensions'):
+            if 'extensions' not in self:
                 raise enclosing_exc
             else:
-                if "windows-process-ext" in self.extensions:
+                if "windows-process-ext" in self.get('extensions', {}):
                     self.extensions["windows-process-ext"]._check_at_least_one_property()
 
 


Test patch:
diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py
index b934b31..0ed9954 100644
--- a/stix2/test/test_observed_data.py
+++ b/stix2/test/test_observed_data.py
@@ -294,6 +294,31 @@ def test_parse_email_message(data):
     assert odata.body_multipart[0].content_disposition == "inline"
 
 
+@pytest.mark.parametrize("data", [
+    """
+    {
+         "type": "email-message",
+         "from_ref": "0",
+         "to_refs": ["1"],
+         "is_multipart": true,
+         "date": "1997-11-21T15:55:06.000Z",
+         "subject": "Saying Hello",
+         "body": "Cats are funny!"
+    }
+    """
+])
+def test_parse_email_message_not_multipart(data):
+    valid_refs = {
+        "0": "email-addr",
+        "1": "email-addr",
+    }
+    with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
+        stix2.parse_observable(data, valid_refs)
+
+    assert excinfo.value.cls == stix2.EmailMessage
+    assert excinfo.value.dependencies == [("is_multipart", "body")]
+
+
 @pytest.mark.parametrize("data", [
     """"0": {
             "type": "file",
@@ -913,6 +938,23 @@ def test_process_example_empty_with_extensions():
     assert excinfo.value.properties == sorted(properties_of_extension)
 
 
+def test_process_example_windows_process_ext():
+    proc = stix2.Process(pid=314,
+                         name="foobar.exe",
+                         extensions={
+                             "windows-process-ext": {
+                                 "aslr_enabled": True,
+                                 "dep_enabled": True,
+                                 "priority": "HIGH_PRIORITY_CLASS",
+                                 "owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309"
+                             }
+                         })
+    assert proc.extensions["windows-process-ext"].aslr_enabled
+    assert proc.extensions["windows-process-ext"].dep_enabled
+    assert proc.extensions["windows-process-ext"].priority == "HIGH_PRIORITY_CLASS"
+    assert proc.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309"
+
+
 def test_process_example_windows_process_ext_empty():
     with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
             stix2.Process(pid=1221,