Repository: oasis-open/cti-stix-validator
Pull request #156 (2020-10-07 21:05:23)

Fix bug with 2.1 observed-data.objects
Warnings about SCO types were incorrectly attributed to the Observed Data object that contained them in the deprecated `objects` property.


Patch:
diff --git a/stix2validator/util.py b/stix2validator/util.py
index 665b642..ca6e039 100644
--- a/stix2validator/util.py
+++ b/stix2validator/util.py
@@ -425,8 +425,8 @@ class ValidationOptions(object):
 
 
 def has_cyber_observable_data(instance, version="2.0"):
-    """Return True only if the given instance is an observed-data object
-    containing STIX Cyber Observable objects.
+    """Return True if the given instance is an observed-data object
+    containing cyber observables or is a cyber observable itself.
     """
     if (instance['type'] == 'observed-data' and
             'objects' in instance and
@@ -451,22 +451,23 @@ def cyber_observable_check(version, requires_objects=False):
             data as a top level object or within the observed-data sdo and loops
             through objects in the latter case to keep checks consistent.
             """
+            instance = args[0]
             if version == "2.1" and not requires_objects:
-                if not has_cyber_observable_data(args[0], version="2.1"):
+                if not has_cyber_observable_data(instance, version="2.1"):
                     return
-                if 'objects' in args[0]:
-                    for obj in args[0]['objects']:
-                        func = original_function(args[0], **kwargs)
+                if 'objects' in instance:
+                    for key, obj in instance['objects'].items():
+                        func = original_function(obj, **kwargs)
                         if isinstance(func, Iterable):
-                            for x in original_function(args[0], **kwargs):
+                            for x in original_function(obj, **kwargs):
                                 yield x
                 else:
                     func = original_function(*args, **kwargs)
                     if isinstance(func, Iterable):
-                        for x in original_function(args[0], **kwargs):
+                        for x in original_function(instance, **kwargs):
                             yield x
             else:
-                if not has_cyber_observable_data(args[0]):
+                if not has_cyber_observable_data(instance):
                     return
                 func = original_function(*args, **kwargs)
                 if isinstance(func, Iterable):
diff --git a/stix2validator/v21/shoulds.py b/stix2validator/v21/shoulds.py
index 05d2484..86d42a7 100644
--- a/stix2validator/v21/shoulds.py
+++ b/stix2validator/v21/shoulds.py
@@ -156,7 +156,8 @@ def uuid_check(instance):
         return
 
     object_id = uuid.UUID(instance['id'].split("--")[-1])
-    if has_cyber_observable_data(instance, "2.1") and instance['type'] != 'process':
+    if (has_cyber_observable_data(instance, "2.1") and
+            instance['type'] not in ['observed-data', 'process']):
         if object_id.version != 5:
             yield JSONError("Cyber Observable ID value %s is not a valid UUIDv5 ID."
                             % instance['id'], instance['id'], 'uuid-check')


Test patch:
diff --git a/stix2validator/test/v21/observed_data_tests.py b/stix2validator/test/v21/observed_data_tests.py
index ca7e2c8..6a42fd2 100644
--- a/stix2validator/test/v21/observed_data_tests.py
+++ b/stix2validator/test/v21/observed_data_tests.py
@@ -732,8 +732,21 @@ class ObservedDataTestCases(ValidatorTest):
         observed_data['extensions']['x-example-com-foobar-ext']['foo_value'] = 'something else'
         self.assertTrueWithOptions(observed_data, schema_dir=self.custom_schemas)
 
+    def test_deprecated_objects_property(self):
+        observed_data = copy.deepcopy(self.valid_observed_data)
+        del observed_data['object_refs']
+        observed_data['objects'] = {
+            "windows-registry-key--ff1e0780-358c-5808-a8c7-d0fca4ef6ef4": {
+                "type": "windows-registry-key",
+                "id": "windows-registry-key--ff1e0780-358c-5808-a8c7-d0fca4ef6ef4",
+                "key": "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\WSALG2"
+            }
+        }
+        self.assertTrueWithOptions(observed_data, disabled="141,304")
+
     def test_invalid_objects_property(self):
         observed_data = copy.deepcopy(self.valid_observed_data)
+        del observed_data['object_refs']
         observed_data['objects'] = [
             {
                 "type": "windows-registry-key",