Repository: oasis-open/cti-python-stix2
Pull request #12 (2017-05-16 16:40:11)

Cyber observables


Patch:
diff --git a/stix2/__init__.py b/stix2/__init__.py
index f3dfd20..7ab1f9c 100644
--- a/stix2/__init__.py
+++ b/stix2/__init__.py
@@ -123,7 +123,7 @@ def parse_observable(data, _valid_refs):
     obj['_valid_refs'] = _valid_refs
 
     if 'type' not in obj:
-        raise ValueError("'type' is a required property!")
+        raise ValueError("'type' is a required field!")
     try:
         obj_class = OBJ_MAP_OBSERVABLE[obj['type']]
     except KeyError:
diff --git a/stix2/base.py b/stix2/base.py
index ba10d04..3ff8e72 100644
--- a/stix2/base.py
+++ b/stix2/base.py
@@ -12,7 +12,7 @@ from .utils import NOW, format_datetime, get_timestamp, parse_into_datetime
 
 __all__ = ['STIXJSONEncoder', '_STIXBase']
 
-DEFAULT_ERROR = "{type} must have {property}='{expected}'."
+DEFAULT_ERROR = "{type} must have {field}='{expected}'."
 
 
 class STIXJSONEncoder(json.JSONEncoder):
@@ -56,9 +56,14 @@ class _STIXBase(collections.Mapping):
         if count > 1 or (at_least_one and count == 0):
             raise MutuallyExclusivePropertiesError(self.__class__, list_of_properties)
 
-    def _check_at_least_one_property(self, list_of_properties):
+    def _check_at_least_one_property(self, list_of_properties=None):
+        if not list_of_properties:
+            list_of_properties = sorted(list(self.__class__._properties.keys()))
+            if "type" in list_of_properties:
+                list_of_properties.remove("type")
         current_properties = self.properties_populated()
-        if not set(list_of_properties).intersection(current_properties):
+        list_of_properties_populated = set(list_of_properties).intersection(current_properties)
+        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=[]):
@@ -72,7 +77,7 @@ class _STIXBase(collections.Mapping):
         if failed_dependency_pairs:
             raise DependentPropertiestError(self.__class__, failed_dependency_pairs)
 
-    def _check_object_constaints(self):
+    def _check_object_constraints(self):
         if self.granular_markings:
             for m in self.granular_markings:
                 # TODO: check selectors
@@ -95,9 +100,9 @@ class _STIXBase(collections.Mapping):
             if prop_value:
                 setting_kwargs[prop_name] = prop_value
 
-        # Detect any missing required properties
-        required_properties = get_required_properties(cls._properties)
-        missing_kwargs = set(required_properties) - set(setting_kwargs)
+        # Detect any missing required fields
+        required_fields = get_required_properties(cls._properties)
+        missing_kwargs = set(required_fields) - set(setting_kwargs)
         if missing_kwargs:
             raise MissingFieldsError(cls, missing_kwargs)
 
@@ -106,7 +111,7 @@ class _STIXBase(collections.Mapping):
 
         self._inner = setting_kwargs
 
-        self._check_object_constaints()
+        self._check_object_constraints()
 
     def __getitem__(self, key):
         return self._inner[key]
diff --git a/stix2/exceptions.py b/stix2/exceptions.py
index 7c92dda..fcc8048 100644
--- a/stix2/exceptions.py
+++ b/stix2/exceptions.py
@@ -17,31 +17,31 @@ class InvalidValueError(STIXError, ValueError):
 
 
 class MissingFieldsError(STIXError, ValueError):
-    """Missing one or more required properties when constructing STIX object."""
+    """Missing required field(s) when constructing STIX object."""
 
-    def __init__(self, cls, properties):
+    def __init__(self, cls, fields):
         super(MissingFieldsError, self).__init__()
         self.cls = cls
-        self.properties = sorted(list(properties))
+        self.fields = sorted(list(fields))
 
     def __str__(self):
-        msg = "No values for required properties for {0}: ({1})."
+        msg = "No values for required field(s) for {0}: ({1})."
         return msg.format(self.cls.__name__,
-                          ", ".join(x for x in self.properties))
+                          ", ".join(x for x in self.fields))
 
 
 class ExtraFieldsError(STIXError, TypeError):
-    """One or more extra properties were provided when constructing STIX object."""
+    """Extra field(s) were provided when constructing STIX object."""
 
-    def __init__(self, cls, properties):
+    def __init__(self, cls, fields):
         super(ExtraFieldsError, self).__init__()
         self.cls = cls
-        self.properties = sorted(list(properties))
+        self.fields = sorted(list(fields))
 
     def __str__(self):
-        msg = "Unexpected properties for {0}: ({1})."
+        msg = "Unexpected field(s) for {0}: ({1})."
         return msg.format(self.cls.__name__,
-                          ", ".join(x for x in self.properties))
+                          ", ".join(x for x in self.fields))
 
 
 class ImmutableError(STIXError, ValueError):
@@ -93,15 +93,15 @@ class UnmodifiablePropertyError(STIXError, ValueError):
 class MutuallyExclusivePropertiesError(STIXError, TypeError):
     """Violating interproperty mutually exclusive constraint of a STIX object type."""
 
-    def __init__(self, cls, properties):
+    def __init__(self, cls, fields):
         super(MutuallyExclusivePropertiesError, self).__init__()
         self.cls = cls
-        self.properties = sorted(list(properties))
+        self.fields = sorted(list(fields))
 
     def __str__(self):
         msg = "The field(s) for {0}: ({1}) are mutually exclusive."
         return msg.format(self.cls.__name__,
-                          ", ".join(x for x in self.properties))
+                          ", ".join(x for x in self.fields))
 
 
 class DependentPropertiestError(STIXError, TypeError):
@@ -121,15 +121,15 @@ class DependentPropertiestError(STIXError, TypeError):
 class AtLeastOnePropertyError(STIXError, TypeError):
     """Violating a constraint of a STIX object type that at least one of the given properties must be populated."""
 
-    def __init__(self, cls, properties):
+    def __init__(self, cls, fields):
         super(AtLeastOnePropertyError, self).__init__()
         self.cls = cls
-        self.properties = sorted(list(properties))
+        self.fields = sorted(list(fields))
 
     def __str__(self):
         msg = "At least one of the field(s) for {0}: ({1}) must be populated."
         return msg.format(self.cls.__name__,
-                          ", ".join(x for x in self.properties))
+                          ", ".join(x for x in self.fields))
 
 
 class RevokeError(STIXError, ValueError):
diff --git a/stix2/observables.py b/stix2/observables.py
index 597508b..1c60db5 100644
--- a/stix2/observables.py
+++ b/stix2/observables.py
@@ -6,9 +6,11 @@ and do not have a '_type' attribute.
 """
 
 from .base import _Observable, _STIXBase
+from .exceptions import AtLeastOnePropertyError
 from .properties import (BinaryProperty, BooleanProperty, DictionaryProperty,
-                         EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
-                         HashesProperty, HexProperty, IntegerProperty, ListProperty,
+                         EmbeddedObjectProperty, EnumProperty,
+                         ExtensionsProperty, FloatProperty, HashesProperty,
+                         HexProperty, IntegerProperty, ListProperty,
                          ObjectReferenceProperty, StringProperty,
                          TimestampProperty, TypeProperty)
 
@@ -23,8 +25,8 @@ class Artifact(_Observable):
         'hashes': HashesProperty(),
     }
 
-    def _check_object_constaints(self):
-        super(Artifact, self)._check_object_constaints()
+    def _check_object_constraints(self):
+        super(Artifact, self)._check_object_constraints()
         self._check_mutually_exclusive_properties(["payload_bin", "url"])
         self._check_properties_dependency(["hashes"], ["url"])
 
@@ -80,8 +82,8 @@ class EmailMIMEComponent(_STIXBase):
         'content_disposition': StringProperty(),
     }
 
-    def _check_object_constaints(self):
-        super(EmailMIMEComponent, self)._check_object_constaints()
+    def _check_object_constraints(self):
+        super(EmailMIMEComponent, self)._check_object_constraints()
         self._check_at_least_one_property(["body", "body_raw_ref"])
 
 
@@ -105,8 +107,8 @@ class EmailMessage(_Observable):
         'raw_email_ref': ObjectReferenceProperty(),
     }
 
-    def _check_object_constaints(self):
-        super(EmailMessage, self)._check_object_constaints()
+    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])
 
@@ -133,6 +135,10 @@ class NTFSExt(_STIXBase):
         'alternate_data_streams': ListProperty(EmbeddedObjectProperty(type=AlternateDataStream)),
     }
 
+    def _check_object_constraints(self):
+        super(NTFSExt, self)._check_object_constraints()
+        self._check_at_least_one_property()
+
 
 class PDFExt(_STIXBase):
     _properties = {
@@ -143,6 +149,10 @@ class PDFExt(_STIXBase):
         'pdfid1': StringProperty(),
     }
 
+    def _check_object_constraints(self):
+        super(PDFExt, self)._check_object_constraints()
+        self._check_at_least_one_property()
+
 
 class RasterImageExt(_STIXBase):
     _properties = {
@@ -153,6 +163,10 @@ class RasterImageExt(_STIXBase):
         'exif_tags': DictionaryProperty(),
     }
 
+    def _check_object_constraints(self):
+        super(RasterImageExt, self)._check_object_constraints()
+        self._check_at_least_one_property()
+
 
 class WindowsPEOptionalHeaderType(_STIXBase):
     _properties = {
@@ -184,11 +198,15 @@ class WindowsPEOptionalHeaderType(_STIXBase):
         'size_of_stack_commit': IntegerProperty(),
         'size_of_heap_reserve': IntegerProperty(),
         'size_of_heap_commit': IntegerProperty(),
-        'loader_fkags_hex': HexProperty(),
+        'loader_flags_hex': HexProperty(),
         'number_of_rva_and_sizes': IntegerProperty(),
         'hashes': HashesProperty(),
     }
 
+    def _check_object_constraints(self):
+        super(WindowsPEOptionalHeaderType, self)._check_object_constraints()
+        self._check_at_least_one_property()
+
 
 class WindowsPESection(_STIXBase):
     _properties = {
@@ -239,8 +257,8 @@ class File(_Observable):
         'content_ref': ObjectReferenceProperty(),
     }
 
-    def _check_object_constaints(self):
-        super(File, self)._check_object_constaints()
+    def _check_object_constraints(self):
+        super(File, self)._check_object_constraints()
         self._check_properties_dependency(["is_encrypted"], ["encryption_algorithm", "decryption_key"])
         self._check_at_least_one_property(["hashes", "name"])
 
@@ -338,6 +356,10 @@ class TCPExt(_STIXBase):
         'dst_flags_hex': HexProperty(),
     }
 
+    def _check_object_constraints(self):
+        super(TCPExt, self)._check_object_constraints()
+        self._check_at_least_one_property()
+
 
 class NetworkTraffic(_Observable):
     _type = 'network-traffic'
@@ -363,8 +385,8 @@ class NetworkTraffic(_Observable):
         'encapsulates_by_ref': ObjectReferenceProperty(),
     }
 
-    def _check_object_constaints(self):
-        super(NetworkTraffic, self)._check_object_constaints()
+    def _check_object_constraints(self):
+        super(NetworkTraffic, self)._check_object_constraints()
         self._check_at_least_one_property(["src_ref", "dst_ref"])
 
 
@@ -432,6 +454,20 @@ class Process(_Observable):
         'child_refs': ListProperty(ObjectReferenceProperty),
     }
 
+    def _check_object_constraints(self):
+        # no need to check windows-service-ext, since it has a required property
+        super(Process, self)._check_object_constraints()
+        try:
+            self._check_at_least_one_property()
+            if 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:
+                raise enclosing_exc
+            else:
+                if "windows-process-ext" in self.extensions:
+                    self.extensions["windows-process-ext"]._check_at_least_one_property()
+
 
 class Software(_Observable):
     _type = 'software'
diff --git a/stix2/other.py b/stix2/other.py
index 9ae3128..51663b3 100644
--- a/stix2/other.py
+++ b/stix2/other.py
@@ -15,8 +15,8 @@ class ExternalReference(_STIXBase):
         'external_id': StringProperty(),
     }
 
-    def _check_object_constaints(self):
-        super(ExternalReference, self)._check_object_constaints()
+    def _check_object_constraints(self):
+        super(ExternalReference, self)._check_object_constraints()
         self._check_at_least_one_property(["description", "external_id", "url"])
 
 
diff --git a/stix2/properties.py b/stix2/properties.py
index 6d217f4..ede41db 100644
--- a/stix2/properties.py
+++ b/stix2/properties.py
@@ -12,7 +12,6 @@ from six import text_type
 
 from .base import _Observable, _STIXBase
 from .exceptions import DictionaryKeyError
-from .utils import get_dict
 
 
 class Property(object):
@@ -45,7 +44,7 @@ class Property(object):
         - provide a default value for this property.
         - `default()` can return the special value `NOW` to use the current
             time. This is useful when several timestamps in the same object need
-            to use the same default value, so calling now() for each property--
+            to use the same default value, so calling now() for each field--
             likely several microseconds apart-- does not work.
 
     Subclasses can instead provide a lambda function for `default` as a keyword
@@ -53,7 +52,7 @@ class Property(object):
     raise their own exceptions.
 
     When instantiating Properties, `required` and `default` should not be used
-    together. `default` implies that the property is required in the specification
+    together. `default` implies that the field is required in the specification
     so this function will be used to supply a value if none is provided.
     `required` means that the user must provide this; it is required in the
     specification and we can't or don't want to create a default value.
@@ -104,12 +103,6 @@ class ListProperty(Property):
             iter(value)
         except TypeError:
             raise ValueError("must be an iterable.")
-        try:
-            if isinstance(value, basestring):
-                value = [value]
-        except NameError:
-            if isinstance(value, str):
-                value = [value]
 
         result = []
         for item in value:
@@ -188,7 +181,7 @@ class FloatProperty(Property):
         try:
             return float(value)
         except Exception:
-            raise ValueError("must be a float.")
+            raise ValueError("must be an float.")
 
 
 class BooleanProperty(Property):
@@ -240,11 +233,7 @@ class TimestampProperty(Property):
 class ObservableProperty(Property):
 
     def clean(self, value):
-        try:
-            dictified = get_dict(value)
-        except ValueError:
-            raise ValueError("The observable property must contain a dictionary")
-
+        dictified = dict(value)
         from .__init__ import parse_observable  # avoid circular import
         for key, obj in dictified.items():
             parsed_obj = parse_observable(obj, dictified.keys())
@@ -259,11 +248,7 @@ class ObservableProperty(Property):
 class DictionaryProperty(Property):
 
     def clean(self, value):
-        try:
-            dictified = get_dict(value)
-        except ValueError:
-            raise ValueError("The dictionary property must contain a dictionary")
-
+        dictified = dict(value)
         for k in dictified.keys():
             if len(k) < 3:
                 raise DictionaryKeyError(k, "shorter than 3 characters")
@@ -407,25 +392,23 @@ class ExtensionsProperty(DictionaryProperty):
         super(ExtensionsProperty, self).__init__(required)
 
     def clean(self, value):
-        try:
-            dictified = get_dict(value)
-        except ValueError:
-            raise ValueError("The extensions property must contain a dictionary")
-
-        from .__init__ import EXT_MAP  # avoid circular import
-        if self.enclosing_type in EXT_MAP:
-            specific_type_map = EXT_MAP[self.enclosing_type]
-            for key, subvalue in dictified.items():
-                if key in specific_type_map:
-                    cls = specific_type_map[key]
-                    if type(subvalue) is dict:
-                        dictified[key] = cls(**subvalue)
-                    elif type(subvalue) is cls:
-                        dictified[key] = subvalue
+        if type(value) is dict:
+            from .__init__ import EXT_MAP  # avoid circular import
+            if self.enclosing_type in EXT_MAP:
+                specific_type_map = EXT_MAP[self.enclosing_type]
+                for key, subvalue in value.items():
+                    if key in specific_type_map:
+                        cls = specific_type_map[key]
+                        if type(subvalue) is dict:
+                            value[key] = cls(**subvalue)
+                        elif type(subvalue) is cls:
+                            value[key] = subvalue
+                        else:
+                            raise ValueError("Cannot determine extension type.")
                     else:
-                        raise ValueError("Cannot determine extension type.")
-                else:
-                    raise ValueError("The key used in the extensions dictionary is not an extension type name")
+                        raise ValueError("The key used in the extensions dictionary is not an extension type name")
+            else:
+                raise ValueError("The enclosing type has no extensions defined")
         else:
-            raise ValueError("The enclosing type has no extensions defined")
-        return dictified
+            raise ValueError("The extensions property must contain a dictionary")
+        return value
diff --git a/stix2/utils.py b/stix2/utils.py
index ed12cdf..a2493e2 100644
--- a/stix2/utils.py
+++ b/stix2/utils.py
@@ -63,17 +63,11 @@ def get_dict(data):
     """
 
     if type(data) is dict:
-        return data
+        obj = data
     else:
         try:
-            return json.loads(data)
+            obj = json.loads(data)
         except TypeError:
-            pass
-        try:
-            return json.load(data)
-        except AttributeError:
-            pass
-        try:
-            return dict(data)
-        except (ValueError, TypeError):
-            raise ValueError("Cannot convert '%s' to dictionary." % str(data))
+            obj = json.load(data)
+
+    return obj


Test patch:
diff --git a/stix2/test/test_external_reference.py b/stix2/test/test_external_reference.py
index 61cfea3..3470bd4 100644
--- a/stix2/test/test_external_reference.py
+++ b/stix2/test/test_external_reference.py
@@ -114,4 +114,4 @@ def test_external_reference_source_required():
         stix2.ExternalReference()
 
     assert excinfo.value.cls == stix2.ExternalReference
-    assert excinfo.value.properties == ["source_name"]
+    assert excinfo.value.fields == ["source_name"]
diff --git a/stix2/test/test_indicator.py b/stix2/test/test_indicator.py
index 2afb473..3e865b2 100644
--- a/stix2/test/test_indicator.py
+++ b/stix2/test/test_indicator.py
@@ -32,7 +32,7 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
 """.split()) + ")"
 
 
-def test_indicator_with_all_required_properties():
+def test_indicator_with_all_required_fields():
     now = dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
     epoch = dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
 
@@ -51,7 +51,7 @@ def test_indicator_with_all_required_properties():
     assert rep == EXPECTED_INDICATOR_REPR
 
 
-def test_indicator_autogenerated_properties(indicator):
+def test_indicator_autogenerated_fields(indicator):
     assert indicator.type == 'indicator'
     assert indicator.id == 'indicator--00000000-0000-0000-0000-000000000001'
     assert indicator.created == FAKE_TIME
@@ -89,21 +89,21 @@ def test_indicator_id_must_start_with_indicator():
     assert str(excinfo.value) == "Invalid value for Indicator 'id': must start with 'indicator--'."
 
 
-def test_indicator_required_properties():
+def test_indicator_required_fields():
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.Indicator()
 
     assert excinfo.value.cls == stix2.Indicator
-    assert excinfo.value.properties == ["labels", "pattern"]
-    assert str(excinfo.value) == "No values for required properties for Indicator: (labels, pattern)."
+    assert excinfo.value.fields == ["labels", "pattern"]
+    assert str(excinfo.value) == "No values for required field(s) for Indicator: (labels, pattern)."
 
 
-def test_indicator_required_property_pattern():
+def test_indicator_required_field_pattern():
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.Indicator(labels=['malicious-activity'])
 
     assert excinfo.value.cls == stix2.Indicator
-    assert excinfo.value.properties == ["pattern"]
+    assert excinfo.value.fields == ["pattern"]
 
 
 def test_indicator_created_ref_invalid_format():
@@ -137,8 +137,8 @@ def test_invalid_kwarg_to_indicator():
         stix2.Indicator(my_custom_property="foo", **INDICATOR_KWARGS)
 
     assert excinfo.value.cls == stix2.Indicator
-    assert excinfo.value.properties == ['my_custom_property']
-    assert str(excinfo.value) == "Unexpected properties for Indicator: (my_custom_property)."
+    assert excinfo.value.fields == ['my_custom_property']
+    assert str(excinfo.value) == "Unexpected field(s) for Indicator: (my_custom_property)."
 
 
 def test_created_modified_time_are_identical_by_default():
diff --git a/stix2/test/test_kill_chain_phases.py b/stix2/test/test_kill_chain_phases.py
index 2596095..92dfd66 100644
--- a/stix2/test/test_kill_chain_phases.py
+++ b/stix2/test/test_kill_chain_phases.py
@@ -35,28 +35,28 @@ def test_kill_chain_example():
     assert str(preattack) == FOO_PRE_ATTACK
 
 
-def test_kill_chain_required_properties():
+def test_kill_chain_required_fields():
 
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.KillChainPhase()
 
     assert excinfo.value.cls == stix2.KillChainPhase
-    assert excinfo.value.properties == ["kill_chain_name", "phase_name"]
+    assert excinfo.value.fields == ["kill_chain_name", "phase_name"]
 
 
-def test_kill_chain_required_property_chain_name():
+def test_kill_chain_required_field_chain_name():
 
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.KillChainPhase(phase_name="weaponization")
 
     assert excinfo.value.cls == stix2.KillChainPhase
-    assert excinfo.value.properties == ["kill_chain_name"]
+    assert excinfo.value.fields == ["kill_chain_name"]
 
 
-def test_kill_chain_required_property_phase_name():
+def test_kill_chain_required_field_phase_name():
 
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.KillChainPhase(kill_chain_name="lockheed-martin-cyber-kill-chain")
 
     assert excinfo.value.cls == stix2.KillChainPhase
-    assert excinfo.value.properties == ["phase_name"]
+    assert excinfo.value.fields == ["phase_name"]
diff --git a/stix2/test/test_malware.py b/stix2/test/test_malware.py
index 23dc68e..9874a3c 100644
--- a/stix2/test/test_malware.py
+++ b/stix2/test/test_malware.py
@@ -21,7 +21,7 @@ EXPECTED_MALWARE = """{
 }"""
 
 
-def test_malware_with_all_required_properties():
+def test_malware_with_all_required_fields():
     now = dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
 
     mal = stix2.Malware(
@@ -36,7 +36,7 @@ def test_malware_with_all_required_properties():
     assert str(mal) == EXPECTED_MALWARE
 
 
-def test_malware_autogenerated_properties(malware):
+def test_malware_autogenerated_fields(malware):
     assert malware.type == 'malware'
     assert malware.id == 'malware--00000000-0000-0000-0000-000000000001'
     assert malware.created == FAKE_TIME
@@ -72,20 +72,20 @@ def test_malware_id_must_start_with_malware():
     assert str(excinfo.value) == "Invalid value for Malware 'id': must start with 'malware--'."
 
 
-def test_malware_required_properties():
+def test_malware_required_fields():
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.Malware()
 
     assert excinfo.value.cls == stix2.Malware
-    assert excinfo.value.properties == ["labels", "name"]
+    assert excinfo.value.fields == ["labels", "name"]
 
 
-def test_malware_required_property_name():
+def test_malware_required_field_name():
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.Malware(labels=['ransomware'])
 
     assert excinfo.value.cls == stix2.Malware
-    assert excinfo.value.properties == ["name"]
+    assert excinfo.value.fields == ["name"]
 
 
 def test_cannot_assign_to_malware_attributes(malware):
@@ -100,8 +100,8 @@ def test_invalid_kwarg_to_malware():
         stix2.Malware(my_custom_property="foo", **MALWARE_KWARGS)
 
     assert excinfo.value.cls == stix2.Malware
-    assert excinfo.value.properties == ['my_custom_property']
-    assert str(excinfo.value) == "Unexpected properties for Malware: (my_custom_property)."
+    assert excinfo.value.fields == ['my_custom_property']
+    assert str(excinfo.value) == "Unexpected field(s) for Malware: (my_custom_property)."
 
 
 @pytest.mark.parametrize("data", [
diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py
index 5c69fb9..a079f37 100644
--- a/stix2/test/test_observed_data.py
+++ b/stix2/test/test_observed_data.py
@@ -369,7 +369,7 @@ def test_parse_email_message_with_at_least_one_error(data):
         stix2.parse_observable(data, [str(i) for i in range(1, 6)])
 
     assert excinfo.value.cls == stix2.EmailMIMEComponent
-    assert excinfo.value.properties == ["body", "body_raw_ref"]
+    assert excinfo.value.fields == ["body", "body_raw_ref"]
 
 
 @pytest.mark.parametrize("data", [
@@ -416,7 +416,7 @@ def test_parse_basic_tcp_traffic_with_error(data):
         stix2.parse_observable(data, ["4"])
 
     assert excinfo.value.cls == stix2.NetworkTraffic
-    assert excinfo.value.properties == ["dst_ref", "src_ref"]
+    assert excinfo.value.fields == ["dst_ref", "src_ref"]
 
 
 EXPECTED_PROCESS_OD = """{
@@ -508,7 +508,7 @@ def test_artifact_mutual_exclusion_error():
                        payload_bin="VBORw0KGgoAAAANSUhEUgAAADI==")
 
     assert excinfo.value.cls == stix2.Artifact
-    assert excinfo.value.properties == ["payload_bin", "url"]
+    assert excinfo.value.fields == ["payload_bin", "url"]
 
 
 def test_directory_example():
@@ -576,6 +576,35 @@ def test_file_example():
     assert f.decryption_key == "fred"   # does the key have a format we can test for?
 
 
+def test_file_example_with_NTFSExt():
+    f = stix2.File(name="abc.txt",
+                   extensions={
+                       "ntfs-ext": {
+                           "alternate_data_streams": [
+                                {
+                                    "name": "second.stream",
+                                    "size": 25536
+                                }
+                            ]
+                        }
+                   })
+
+    assert f.name == "abc.txt"
+    assert f.extensions["ntfs-ext"].alternate_data_streams[0].size == 25536
+
+
+def test_file_example_with_empty_NTFSExt():
+    with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
+        stix2.File(name="abc.txt",
+                   extensions={
+                       "ntfs-ext": {
+                        }
+                   })
+
+    assert excinfo.value.cls == stix2.NTFSExt
+    assert excinfo.value.fields == sorted(list(stix2.NTFSExt._properties.keys()))
+
+
 def test_file_example_with_PDFExt():
     f = stix2.File(name="qwerty.dll",
                    extensions={
@@ -598,6 +627,115 @@ def test_file_example_with_PDFExt():
     assert f.extensions["pdf-ext"].document_info_dict["Title"] == "Sample document"
 
 
+def test_file_example_with_PDFExt_Object():
+    f = stix2.File(name="qwerty.dll",
+                   extensions={
+                       "pdf-ext":
+                           stix2.PDFExt(version="1.7",
+                                        document_info_dict={
+                                                    "Title": "Sample document",
+                                                    "Author": "Adobe Systems Incorporated",
+                                                    "Creator": "Adobe FrameMaker 5.5.3 for Power Macintosh",
+                                                    "Producer": "Acrobat Distiller 3.01 for Power Macintosh",
+                                                    "CreationDate": "20070412090123-02"
+                                        },
+                                        pdfid0="DFCE52BD827ECF765649852119D",
+                                        pdfid1="57A1E0F9ED2AE523E313C")
+
+                   })
+
+    assert f.name == "qwerty.dll"
+    assert f.extensions["pdf-ext"].version == "1.7"
+    assert f.extensions["pdf-ext"].document_info_dict["Title"] == "Sample document"
+
+
+def test_file_example_with_RasterImageExt_Object():
+    f = stix2.File(name="qwerty.jpeg",
+                   extensions={
+                        "raster-image-ext": {
+                                "bits_per_pixel": 123,
+                                "exif_tags": {
+                                    "Make": "Nikon",
+                                    "Model": "D7000",
+                                    "XResolution": 4928,
+                                    "YResolution": 3264
+                                }
+                        }
+                   })
+    assert f.name == "qwerty.jpeg"
+    assert f.extensions["raster-image-ext"].bits_per_pixel == 123
+    assert f.extensions["raster-image-ext"].exif_tags["XResolution"] == 4928
+
+
+def test_file_example_with_WindowsPEBinaryExt():
+    f = stix2.File(name="qwerty.dll",
+                   extensions={
+                       "windows-pebinary-ext": {
+                            "pe_type": "exe",
+                            "machine_hex": "014c",
+                            "number_of_sections": 4,
+                            "time_date_stamp": "2016-01-22T12:31:12Z",
+                            "pointer_to_symbol_table_hex": "74726144",
+                            "number_of_symbols": 4542568,
+                            "size_of_optional_header": 224,
+                            "characteristics_hex": "818f",
+                            "optional_header": {
+                              "magic_hex": "010b",
+                              "major_linker_version": 2,
+                              "minor_linker_version": 25,
+                              "size_of_code": 512,
+                              "size_of_initialized_data": 283648,
+                              "size_of_uninitialized_data": 0,
+                              "address_of_entry_point": 4096,
+                              "base_of_code": 4096,
+                              "base_of_data": 8192,
+                              "image_base": 14548992,
+                              "section_alignment": 4096,
+                              "file_alignment": 4096,
+                              "major_os_version": 1,
+                              "minor_os_version": 0,
+                              "major_image_version": 0,
+                              "minor_image_version": 0,
+                              "major_subsystem_version": 4,
+                              "minor_subsystem_version": 0,
+                              "win32_version_value_hex": "00",
+                              "size_of_image": 299008,
+                              "size_of_headers": 4096,
+                              "checksum_hex": "00",
+                              "subsystem_hex": "03",
+                              "dll_characteristics_hex": "00",
+                              "size_of_stack_reserve": 100000,
+                              "size_of_stack_commit": 8192,
+                              "size_of_heap_reserve": 100000,
+                              "size_of_heap_commit": 4096,
+                              "loader_flags_hex": "abdbffde",
+                              "number_of_rva_and_sizes": 3758087646
+                            },
+                            "sections": [
+                              {
+                                "name": "CODE",
+                                "entropy": 0.061089
+                              },
+                              {
+                                "name": "DATA",
+                                "entropy": 7.980693
+                              },
+                              {
+                                "name": "NicolasB",
+                                "entropy": 0.607433
+                              },
+                              {
+                                "name": ".idata",
+                                "entropy": 0.607433
+                              }
+                            ]
+                        }
+
+                   })
+    assert f.name == "qwerty.dll"
+    assert f.extensions["windows-pebinary-ext"].sections[2].entropy == 0.607433
+
+
 def test_file_example_encryption_error():
     with pytest.raises(stix2.exceptions.DependentPropertiestError) as excinfo:
         stix2.File(name="qwerty.dll",
@@ -636,78 +774,119 @@ def test_mac_address_example():
     assert ip6.value == "d2:fb:49:24:37:18"
 
 
-def test_network_traffic_example():
-    nt = stix2.NetworkTraffic(_valid_refs=["0", "1"],
-                              protocols="tcp",
-                              src_ref="0",
-                              dst_ref="1")
-    assert nt.protocols == ["tcp"]
-    assert nt.src_ref == "0"
-    assert nt.dst_ref == "1"
-
-
-def test_network_traffic_http_request_example():
-    h = stix2.HTTPRequestExt(request_method="get",
-                             request_value="/download.html",
-                             request_version="http/1.1",
-                             request_header={
-                                 "Accept-Encoding": "gzip,deflate",
-                                 "User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113",
-                                 "Host": "www.example.com"
-                             })
-    nt = stix2.NetworkTraffic(_valid_refs=["0", "1"],
-                              protocols="tcp",
-                              src_ref="0",
-                              extensions={'http-request-ext': h})
-    assert nt.extensions['http-request-ext'].request_method == "get"
-    assert nt.extensions['http-request-ext'].request_value == "/download.html"
-    assert nt.extensions['http-request-ext'].request_version == "http/1.1"
-    assert nt.extensions['http-request-ext'].request_header['Accept-Encoding'] == "gzip,deflate"
-    assert nt.extensions['http-request-ext'].request_header['User-Agent'] == "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113"
-    assert nt.extensions['http-request-ext'].request_header['Host'] == "www.example.com"
-
-
-def test_network_traffic_icmp_example():
-    h = stix2.ICMPExt(icmp_type_hex="08",
-                      icmp_code_hex="00")
-    nt = stix2.NetworkTraffic(_valid_refs=["0", "1"],
-                              protocols="tcp",
-                              src_ref="0",
-                              extensions={'icmp-ext': h})
-    assert nt.extensions['icmp-ext'].icmp_type_hex == "08"
-    assert nt.extensions['icmp-ext'].icmp_code_hex == "00"
-
-
-def test_network_traffic_socket_example():
-    h = stix2.SocketExt(is_listening=True,
-                        address_family="AF_INET",
-                        protocol_family="PF_INET",
-                        socket_type="SOCK_STREAM")
-    nt = stix2.NetworkTraffic(_valid_refs=["0", "1"],
-                              protocols="tcp",
-                              src_ref="0",
-                              extensions={'socket-ext': h})
-    assert nt.extensions['socket-ext'].is_listening
-    assert nt.extensions['socket-ext'].address_family == "AF_INET"
-    assert nt.extensions['socket-ext'].protocol_family == "PF_INET"
-    assert nt.extensions['socket-ext'].socket_type == "SOCK_STREAM"
-
-
-def test_network_traffic_tcp_example():
-    h = stix2.TCPExt(src_flags_hex="00000002")
-    nt = stix2.NetworkTraffic(_valid_refs=["0", "1"],
-                              protocols="tcp",
-                              src_ref="0",
-                              extensions={'tcp-ext': h})
-    assert nt.extensions['tcp-ext'].src_flags_hex == "00000002"
-
-
 def test_mutex_example():
     m = stix2.Mutex(name="barney")
 
     assert m.name == "barney"
 
 
+def test_process_example():
+    p = stix2.Process(_valid_refs=["0"],
+                      pid=1221,
+                      name="gedit-bin",
+                      created="2016-01-20T14:11:25.55Z",
+                      arguments=["--new-window"],
+                      binary_ref="0")
+
+    assert p.name == "gedit-bin"
+    assert p.arguments == ["--new-window"]
+
+
+def test_process_example_empty_error():
+    with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
+        stix2.Process()
+
+    assert excinfo.value.cls == stix2.Process
+    properties_of_process = list(stix2.Process._properties.keys())
+    properties_of_process.remove("type")
+    assert excinfo.value.fields == sorted(properties_of_process)
+
+
+def test_process_example_empty_with_extensions():
+    with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
+            stix2.Process(extensions={
+                            "windows-process-ext": {}
+                        })
+
+    assert excinfo.value.cls == stix2.WindowsProcessExt
+    properties_of_extension = list(stix2.WindowsProcessExt._properties.keys())
+    assert excinfo.value.fields == sorted(properties_of_extension)
+
+
+def test_process_example_windows_process_ext_empty():
+    with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
+            stix2.Process(pid=1221,
+                          name="gedit-bin",
+                          extensions={
+                            "windows-process-ext": {}
+                          })
+
+    assert excinfo.value.cls == stix2.WindowsProcessExt
+    properties_of_extension = list(stix2.WindowsProcessExt._properties.keys())
+    assert excinfo.value.fields == sorted(properties_of_extension)
+
+
+def test_process_example_extensions_empty():
+    with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) 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.fields == sorted(properties_of_process)
+
+
+def test_process_example_with_WindowsProcessExt_Object():
+    p = stix2.Process(extensions={
+                        "windows-process-ext": stix2.WindowsProcessExt(aslr_enabled=True,
+                                                                       dep_enabled=True,
+                                                                       priority="HIGH_PRIORITY_CLASS",
+                                                                       owner_sid="S-1-5-21-186985262-1144665072-74031268-1309")   # noqa
+                   })
+
+    assert p.extensions["windows-process-ext"].dep_enabled
+    assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309"
+
+
+def test_process_example_with_WindowsServiceExt():
+    p = stix2.Process(extensions={
+                        "windows-service-ext": {
+                            "service_name": "sirvizio",
+                            "display_name": "Sirvizio",
+                            "start_type": "SERVICE_AUTO_START",
+                            "service_type": "SERVICE_WIN32_OWN_PROCESS",
+                            "service_status": "SERVICE_RUNNING"
+                        }
+    })
+
+    assert p.extensions["windows-service-ext"].service_name == "sirvizio"
+    assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS"
+
+
+def test_process_example_with_WindowsProcessServiceExt():
+    p = stix2.Process(extensions={
+        "windows-service-ext": {
+            "service_name": "sirvizio",
+            "display_name": "Sirvizio",
+            "start_type": "SERVICE_AUTO_START",
+            "service_type": "SERVICE_WIN32_OWN_PROCESS",
+            "service_status": "SERVICE_RUNNING"
+        },
+        "windows-process-ext": {
+            "aslr_enabled": True,
+            "dep_enabled": True,
+            "priority": "HIGH_PRIORITY_CLASS",
+            "owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309"
+        }
+    })
+
+    assert p.extensions["windows-service-ext"].service_name == "sirvizio"
+    assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS"
+    assert p.extensions["windows-process-ext"].dep_enabled
+    assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309"
+
+
 def test_software_example():
     s = stix2.Software(name="Word",
                        cpe="cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*",
@@ -753,21 +932,6 @@ def test_user_account_example():
     assert a.account_last_login == dt.datetime(2016, 7, 22, 16, 8, 28, tzinfo=pytz.utc)
 
 
-def test_user_account_unix_account_ext_example():
-    u = stix2.UNIXAccountExt(gid=1001,
-                             groups=["wheel"],
-                             home_dir="/home/jdoe",
-                             shell="/bin/bash")
-    a = stix2.UserAccount(user_id="1001",
-                          account_login="jdoe",
-                          account_type="unix",
-                          extensions={'unix-account-ext': u})
-    assert a.extensions['unix-account-ext'].gid == 1001
-    assert a.extensions['unix-account-ext'].groups == ["wheel"]
-    assert a.extensions['unix-account-ext'].home_dir == "/home/jdoe"
-    assert a.extensions['unix-account-ext'].shell == "/bin/bash"
-
-
 def test_windows_registry_key_example():
     with pytest.raises(ValueError):
         v = stix2.WindowsRegistryValueType(name="Foo",
diff --git a/stix2/test/test_properties.py b/stix2/test/test_properties.py
index 5dc0084..4aa4973 100644
--- a/stix2/test/test_properties.py
+++ b/stix2/test/test_properties.py
@@ -1,14 +1,13 @@
 import pytest
 
-from stix2 import TCPExt
 from stix2.exceptions import DictionaryKeyError
 from stix2.observables import EmailMIMEComponent
 from stix2.properties import (BinaryProperty, BooleanProperty,
                               DictionaryProperty, EmbeddedObjectProperty,
-                              EnumProperty, ExtensionsProperty, HashesProperty,
-                              HexProperty, IDProperty, IntegerProperty,
-                              ListProperty, Property, ReferenceProperty,
-                              StringProperty, TimestampProperty, TypeProperty)
+                              EnumProperty, HashesProperty, HexProperty,
+                              IDProperty, IntegerProperty, ListProperty,
+                              Property, ReferenceProperty, StringProperty,
+                              TimestampProperty, TypeProperty)
 
 from .constants import FAKE_TIME
 
@@ -256,36 +255,3 @@ def test_enum_property():
 
     with pytest.raises(ValueError):
         enum_prop.clean('z')
-
-
-def test_extension_property_valid():
-    ext_prop = ExtensionsProperty(enclosing_type='file')
-    assert ext_prop({
-        'windows-pebinary-ext': {
-            'pe_type': 'exe'
-        },
-    })
-
-
-@pytest.mark.parametrize("data", [
-    1,
-    {'foobar-ext': {
-        'pe_type': 'exe'
-    }},
-    {'windows-pebinary-ext': TCPExt()},
-])
-def test_extension_property_invalid(data):
-    ext_prop = ExtensionsProperty(enclosing_type='file')
-    with pytest.raises(ValueError):
-        ext_prop.clean(data)
-
-
-def test_extension_property_invalid_type():
-    ext_prop = ExtensionsProperty(enclosing_type='indicator')
-    with pytest.raises(ValueError) as excinfo:
-        ext_prop.clean({
-            'windows-pebinary-ext': {
-                'pe_type': 'exe'
-            }}
-        )
-    assert 'no extensions defined' in str(excinfo.value)
diff --git a/stix2/test/test_relationship.py b/stix2/test/test_relationship.py
index c4f9ebe..e05c262 100644
--- a/stix2/test/test_relationship.py
+++ b/stix2/test/test_relationship.py
@@ -20,7 +20,7 @@ EXPECTED_RELATIONSHIP = """{
 }"""
 
 
-def test_relationship_all_required_properties():
+def test_relationship_all_required_fields():
     now = dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
 
     rel = stix2.Relationship(
@@ -35,7 +35,7 @@ def test_relationship_all_required_properties():
     assert str(rel) == EXPECTED_RELATIONSHIP
 
 
-def test_relationship_autogenerated_properties(relationship):
+def test_relationship_autogenerated_fields(relationship):
     assert relationship.type == 'relationship'
     assert relationship.id == 'relationship--00000000-0000-0000-0000-000000000001'
     assert relationship.created == FAKE_TIME
@@ -73,22 +73,22 @@ def test_relationship_id_must_start_with_relationship():
     assert str(excinfo.value) == "Invalid value for Relationship 'id': must start with 'relationship--'."
 
 
-def test_relationship_required_property_relationship_type():
+def test_relationship_required_field_relationship_type():
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.Relationship()
     assert excinfo.value.cls == stix2.Relationship
-    assert excinfo.value.properties == ["relationship_type", "source_ref", "target_ref"]
+    assert excinfo.value.fields == ["relationship_type", "source_ref", "target_ref"]
 
 
-def test_relationship_missing_some_required_properties():
+def test_relationship_missing_some_required_fields():
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.Relationship(relationship_type='indicates')
 
     assert excinfo.value.cls == stix2.Relationship
-    assert excinfo.value.properties == ["source_ref", "target_ref"]
+    assert excinfo.value.fields == ["source_ref", "target_ref"]
 
 
-def test_relationship_required_properties_target_ref():
+def test_relationship_required_field_target_ref():
     with pytest.raises(stix2.exceptions.MissingFieldsError) as excinfo:
         stix2.Relationship(
             relationship_type='indicates',
@@ -96,7 +96,7 @@ def test_relationship_required_properties_target_ref():
         )
 
     assert excinfo.value.cls == stix2.Relationship
-    assert excinfo.value.properties == ["target_ref"]
+    assert excinfo.value.fields == ["target_ref"]
 
 
 def test_cannot_assign_to_relationship_attributes(relationship):
@@ -111,8 +111,8 @@ def test_invalid_kwarg_to_relationship():
         stix2.Relationship(my_custom_property="foo", **RELATIONSHIP_KWARGS)
 
     assert excinfo.value.cls == stix2.Relationship
-    assert excinfo.value.properties == ['my_custom_property']
-    assert str(excinfo.value) == "Unexpected properties for Relationship: (my_custom_property)."
+    assert excinfo.value.fields == ['my_custom_property']
+    assert str(excinfo.value) == "Unexpected field(s) for Relationship: (my_custom_property)."
 
 
 def test_create_relationship_from_objects_rather_than_ids(indicator, malware):
diff --git a/stix2/test/test_sighting.py b/stix2/test/test_sighting.py
index d8db35e..9da6d4e 100644
--- a/stix2/test/test_sighting.py
+++ b/stix2/test/test_sighting.py
@@ -31,7 +31,7 @@ BAD_SIGHTING = """{
 }"""
 
 
-def test_sighting_all_required_properties():
+def test_sighting_all_required_fields():
     now = dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
 
     s = stix2.Sighting(
@@ -79,8 +79,8 @@ def test_invalid_kwarg_to_sighting():
         stix2.Sighting(my_custom_property="foo", **SIGHTING_KWARGS)
 
     assert excinfo.value.cls == stix2.Sighting
-    assert excinfo.value.properties == ['my_custom_property']
-    assert str(excinfo.value) == "Unexpected properties for Sighting: (my_custom_property)."
+    assert excinfo.value.fields == ['my_custom_property']
+    assert str(excinfo.value) == "Unexpected field(s) for Sighting: (my_custom_property)."
 
 
 def test_create_sighting_from_objects_rather_than_ids(malware):  # noqa: F811
diff --git a/stix2/test/test_utils.py b/stix2/test/test_utils.py
index a70853c..3eee491 100644
--- a/stix2/test/test_utils.py
+++ b/stix2/test/test_utils.py
@@ -1,5 +1,4 @@
 import datetime as dt
-from io import StringIO
 
 import pytest
 import pytz
@@ -18,24 +17,3 @@ eastern = pytz.timezone('US/Eastern')
 ])
 def test_timestamp_formatting(dttm, timestamp):
     assert stix2.utils.format_datetime(dttm) == timestamp
-
-
-@pytest.mark.parametrize('data', [
-    {"a": 1},
-    '{"a": 1}',
-    StringIO(u'{"a": 1}'),
-    [("a", 1,)],
-])
-def test_get_dict(data):
-    assert stix2.utils.get_dict(data)
-
-
-@pytest.mark.parametrize('data', [
-    1,
-    [1],
-    ['a', 1],
-    "foobar",
-])
-def test_get_dict_invalid(data):
-    with pytest.raises(ValueError):
-        stix2.utils.get_dict(data)
diff --git a/stix2/test/test_versioning.py b/stix2/test/test_versioning.py
index fc9ae8a..34f1c56 100644
--- a/stix2/test/test_versioning.py
+++ b/stix2/test/test_versioning.py
@@ -142,7 +142,7 @@ def test_versioning_error_usetting_required_property():
         campaign_v1.new_version(name=None)
 
     assert excinfo.value.cls == stix2.Campaign
-    assert excinfo.value.properties == ["name"]
+    assert excinfo.value.fields == ["name"]
 
 
 def test_versioning_error_new_version_of_revoked():