Repository: oasis-open/cti-python-stix2

Issue:
Fail to create new version of ObservedData if it has related objects
If you try to generate a new version of an ObservedData which contains related objects (like domain-name -> ipv4) you obtain a fatal error
Example:
`from stix2 import ObservedData`
`from datetime import datetime`
`data = ObservedData(first_observed=datetime.now(),last_observed=datetime.now(),number_observed=1,objects={'src_ip': {'type':'ipv4-addr','value':'127.0.0.1/32'}, 'domain':{'type':'domain-name','value':'example.com','resolves_to_refs':['src_ip']}})`
`new_version = data.new_version(last_observed=datetime.now())`

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/site-packages/stix2/base.py", line 175, in new_version
    return _new_version(self, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/stix2/utils.py", line 212, in new_version
    new_obj_inner = copy.deepcopy(data._inner)
  File "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/copy.py", line 150, in deepcopy
    y = copier(x, memo)
  File "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/copy.py", line 240, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/copy.py", line 150, in deepcopy
    y = copier(x, memo)
  File "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/copy.py", line 240, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/copy.py", line 161, in deepcopy
    y = copier(memo)
  File "/usr/local/lib/python3.6/site-packages/stix2/base.py", line 167, in __deepcopy__
    return cls(**new_inner)
  File "/usr/local/lib/python3.6/site-packages/stix2/base.py", line 221, in __init__
    super(_Observable, self).__init__(**kwargs)
  File "/usr/local/lib/python3.6/site-packages/stix2/base.py", line 128, in __init__
    self._check_property(prop_name, prop_metadata, setting_kwargs)
  File "/usr/local/lib/python3.6/site-packages/stix2/base.py", line 251, in _check_property
    self._check_ref(ref, prop, prop_name)
  File "/usr/local/lib/python3.6/site-packages/stix2/base.py", line 225, in _check_ref
    raise InvalidObjRefError(self.__class__, prop_name, "'%s' is not a valid object in local scope" % ref)`

The error appears also if you use an incremental value as index in objects dictionary

Comments:

Patch:
diff --git a/stix2/base.py b/stix2/base.py
index 76b07b8..fc13094 100644
--- a/stix2/base.py
+++ b/stix2/base.py
@@ -161,9 +161,12 @@ class _STIXBase(collections.Mapping):
                                  ", ".join(["{0!s}={1!r}".format(k, v) for k, v in props]))
 
     def __deepcopy__(self, memo):
-        # Assumption: we can ignore the memo argument, because no object will ever contain the same sub-object multiple times.
+        # Assume: we can ignore the memo argument, because no object will ever contain the same sub-object multiple times.
         new_inner = copy.deepcopy(self._inner, memo)
         cls = type(self)
+        if isinstance(self, _Observable):
+            # Assume: valid references in the original object are still valid in the new version
+            new_inner['_valid_refs'] = {'*': '*'}
         return cls(**new_inner)
 
     def properties_populated(self):
@@ -221,6 +224,9 @@ class _Observable(_STIXBase):
         super(_Observable, self).__init__(**kwargs)
 
     def _check_ref(self, ref, prop, prop_name):
+        if '*' in self._STIXBase__valid_refs:
+            return  # don't check if refs are valid
+
         if ref not in self._STIXBase__valid_refs:
             raise InvalidObjRefError(self.__class__, prop_name, "'%s' is not a valid object in local scope" % ref)
 


Test patch:
diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py
index 3029b68..30c3cab 100644
--- a/stix2/test/test_observed_data.py
+++ b/stix2/test/test_observed_data.py
@@ -1162,3 +1162,25 @@ def test_x509_certificate_example():
     assert x509.type == "x509-certificate"
     assert x509.issuer == "C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com"  # noqa
     assert x509.subject == "C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org"  # noqa
+
+
+def test_new_version_with_related_objects():
+    data = stix2.ObservedData(
+        first_observed="2016-03-12T12:00:00Z",
+        last_observed="2016-03-12T12:00:00Z",
+        number_observed=1,
+        objects={
+            'src_ip': {
+                'type': 'ipv4-addr',
+                'value': '127.0.0.1/32'
+            },
+            'domain': {
+                'type': 'domain-name',
+                'value': 'example.com',
+                'resolves_to_refs': ['src_ip']
+            }
+        }
+    )
+    new_version = data.new_version(last_observed="2017-12-12T12:00:00Z")
+    assert new_version.last_observed.year == 2017
+    assert new_version.objects['domain'].resolves_to_refs[0] == 'src_ip'