Repository: oasis-open/cti-python-stix2

Issue:
Unable to creat 'Bundle' when using custom fields with SDO
Hi,

When attempting to generate a bundle, a failure message is created when passing an SDO with custom objects even with `allow_custom=True` set on the SDO object.

example:

`v = factory.create(
Vulnerability,
name="Test Vulnerability",
custom_field = "This is custom",
allow_custom=True
)`

`print Bundle(v)`

Will result in the following output:

`File "stix.py", line 142, in <module>
    print Bundle(v)
  File "/usr/local/lib/python2.7/dist-packages/stix2/core.py", line 51, in __init__
    super(Bundle, self).__init__(**kwargs)
  File "/usr/local/lib/python2.7/dist-packages/stix2/base.py", line 121, in __init__
    self._check_property(prop_name, prop_metadata, setting_kwargs)
  File "/usr/local/lib/python2.7/dist-packages/stix2/base.py", line 55, in _check_property
    kwargs[prop_name] = prop.clean(kwargs[prop_name])
  File "/usr/local/lib/python2.7/dist-packages/stix2/properties.py", line 115, in clean
    valid = self.contained.clean(item)
  File "/usr/local/lib/python2.7/dist-packages/stix2/core.py", line 28, in clean
    parsed_obj = parse(dictified)
  File "/usr/local/lib/python2.7/dist-packages/stix2/core.py", line 94, in parse
    return obj_class(allow_custom=allow_custom, **obj)
  File "/usr/local/lib/python2.7/dist-packages/stix2/base.py", line 104, in __init__
    raise ExtraPropertiesError(cls, extra_kwargs)
stix2.exceptions.ExtraPropertiesError: Unexpected properties for Vulnerability: (custom_field).`




Discussion:

Patch:
diff --git a/stix2/core.py b/stix2/core.py
index 3eaabb0..8ee11f5 100644
--- a/stix2/core.py
+++ b/stix2/core.py
@@ -15,6 +15,10 @@ from .utils import get_dict
 
 class STIXObjectProperty(Property):
 
+    def __init__(self, allow_custom=False):
+        self.allow_custom = allow_custom
+        super(STIXObjectProperty, self).__init__()
+
     def clean(self, value):
         try:
             dictified = get_dict(value)
@@ -25,7 +29,10 @@ class STIXObjectProperty(Property):
         if 'type' in dictified and dictified['type'] == 'bundle':
             raise ValueError('This property may not contain a Bundle object')
 
-        parsed_obj = parse(dictified)
+        if self.allow_custom:
+            parsed_obj = parse(dictified, allow_custom=True)
+        else:
+            parsed_obj = parse(dictified)
         return parsed_obj
 
 
@@ -48,6 +55,10 @@ class Bundle(_STIXBase):
             else:
                 kwargs['objects'] = list(args) + kwargs.get('objects', [])
 
+        allow_custom = kwargs.get('allow_custom', False)
+        if allow_custom:
+            self._properties['objects'] = ListProperty(STIXObjectProperty(True))
+
         super(Bundle, self).__init__(**kwargs)
 
 
diff --git a/stix2/properties.py b/stix2/properties.py
index afe994f..ca7f04c 100644
--- a/stix2/properties.py
+++ b/stix2/properties.py
@@ -124,7 +124,11 @@ class ListProperty(Property):
                 obj_type = self.contained.type
             elif type(self.contained).__name__ is 'STIXObjectProperty':
                 # ^ this way of checking doesn't require a circular import
-                obj_type = type(valid)
+                # valid is already an instance of a python-stix2 class; no need
+                # to turn it into a dictionary and then pass it to the class
+                # constructor again
+                result.append(valid)
+                continue
             else:
                 obj_type = self.contained
 


Test patch:
diff --git a/stix2/test/test_custom.py b/stix2/test/test_custom.py
index ff432c1..48529b9 100644
--- a/stix2/test/test_custom.py
+++ b/stix2/test/test_custom.py
@@ -81,6 +81,18 @@ def test_parse_identity_custom_property(data):
     assert identity.foo == "bar"
 
 
+def test_custom_property_in_bundled_object():
+    identity = stix2.Identity(
+        name="John Smith",
+        identity_class="individual",
+        x_foo="bar",
+        allow_custom=True,
+    )
+    bundle = stix2.Bundle(identity, allow_custom=True)
+
+    assert bundle.objects[0].x_foo == "bar"
+
+
 @stix2.sdo.CustomObject('x-new-type', [
     ('property1', stix2.properties.StringProperty(required=True)),
     ('property2', stix2.properties.IntegerProperty()),