Repository: oasis-open/cti-python-stix2
Pull request #167 (2018-04-13 18:53:49)

Disallow invalid type names in custom classes


Patch:
diff --git a/stix2/utils.py b/stix2/utils.py
index f4760ee..619652a 100644
--- a/stix2/utils.py
+++ b/stix2/utils.py
@@ -18,6 +18,8 @@ NOW = object()
 # STIX object properties that cannot be modified
 STIX_UNMOD_PROPERTIES = ["created", "created_by_ref", "id", "type"]
 
+TYPE_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$'
+
 
 class STIXdatetime(dt.datetime):
     def __new__(cls, *args, **kwargs):
diff --git a/stix2/v20/observables.py b/stix2/v20/observables.py
index 254498b..6c65e8e 100644
--- a/stix2/v20/observables.py
+++ b/stix2/v20/observables.py
@@ -7,6 +7,7 @@ Observable and do not have a ``_type`` attribute.
 
 from collections import OrderedDict
 import copy
+import re
 
 from ..base import _Extension, _Observable, _STIXBase
 from ..exceptions import (AtLeastOnePropertyError, CustomContentError,
@@ -16,7 +17,7 @@ from ..properties import (BinaryProperty, BooleanProperty, DictionaryProperty,
                           HashesProperty, HexProperty, IntegerProperty,
                           ListProperty, ObjectReferenceProperty, Property,
                           StringProperty, TimestampProperty, TypeProperty)
-from ..utils import _get_dict
+from ..utils import TYPE_REGEX, _get_dict
 
 
 class ObservableProperty(Property):
@@ -981,6 +982,12 @@ def CustomObservable(type='x-custom-observable', properties=None):
 
         class _Custom(cls, _Observable):
 
+            if not re.match(TYPE_REGEX, type):
+                raise ValueError("Invalid observable type name '%s': must only contain the "
+                                 "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type)
+            elif len(type) < 3 or len(type) > 250:
+                raise ValueError("Invalid observable type name '%s': must be between 3 and 250 characters." % type)
+
             _type = type
             _properties = OrderedDict()
             _properties.update([
@@ -1054,6 +1061,12 @@ def CustomExtension(observable=None, type='x-custom-observable', properties=None
 
         class _Custom(cls, _Extension):
 
+            if not re.match(TYPE_REGEX, type):
+                raise ValueError("Invalid extension type name '%s': must only contain the "
+                                 "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type)
+            elif len(type) < 3 or len(type) > 250:
+                raise ValueError("Invalid extension type name '%s': must be between 3 and 250 characters." % type)
+
             _type = type
             _properties = OrderedDict()
 
diff --git a/stix2/v20/sdo.py b/stix2/v20/sdo.py
index 060b9f0..ff024f5 100644
--- a/stix2/v20/sdo.py
+++ b/stix2/v20/sdo.py
@@ -2,6 +2,7 @@
 """
 
 from collections import OrderedDict
+import re
 
 import stix2
 
@@ -10,7 +11,7 @@ from ..markings import _MarkingsMixin
 from ..properties import (BooleanProperty, IDProperty, IntegerProperty,
                           ListProperty, PatternProperty, ReferenceProperty,
                           StringProperty, TimestampProperty, TypeProperty)
-from ..utils import NOW
+from ..utils import NOW, TYPE_REGEX
 from .common import ExternalReference, GranularMarking, KillChainPhase
 from .observables import ObservableProperty
 
@@ -357,6 +358,13 @@ def CustomObject(type='x-custom-type', properties=None):
     def custom_builder(cls):
 
         class _Custom(cls, STIXDomainObject):
+
+            if not re.match(TYPE_REGEX, type):
+                raise ValueError("Invalid type name '%s': must only contain the "
+                                 "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type)
+            elif len(type) < 3 or len(type) > 250:
+                raise ValueError("Invalid type name '%s': must be between 3 and 250 characters." % type)
+
             _type = type
             _properties = OrderedDict()
             _properties.update([


Test patch:
diff --git a/stix2/test/test_custom.py b/stix2/test/test_custom.py
index 56e578f..0e004a8 100644
--- a/stix2/test/test_custom.py
+++ b/stix2/test/test_custom.py
@@ -197,6 +197,24 @@ def test_custom_object_no_init_2():
     assert no2.property1 == 'something'
 
 
+def test_custom_object_invalid_type_name():
+    with pytest.raises(ValueError) as excinfo:
+        @stix2.sdo.CustomObject('x', [
+            ('property1', stix2.properties.StringProperty(required=True)),
+        ])
+        class NewObj(object):
+            pass  # pragma: no cover
+    assert "Invalid type name 'x': " in str(excinfo.value)
+
+    with pytest.raises(ValueError) as excinfo:
+        @stix2.sdo.CustomObject('x_new_object', [
+            ('property1', stix2.properties.StringProperty(required=True)),
+        ])
+        class NewObj2(object):
+            pass  # pragma: no cover
+    assert "Invalid type name 'x_new_object':" in str(excinfo.value)
+
+
 def test_parse_custom_object_type():
     nt_string = """{
         "type": "x-new-type",
@@ -295,6 +313,24 @@ def test_custom_observable_object_no_init_2():
     assert no2.property1 == 'something'
 
 
+def test_custom_observable_object_invalid_type_name():
+    with pytest.raises(ValueError) as excinfo:
+        @stix2.observables.CustomObservable('x', [
+            ('property1', stix2.properties.StringProperty()),
+        ])
+        class NewObs(object):
+            pass  # pragma: no cover
+    assert "Invalid observable type name 'x':" in str(excinfo.value)
+
+    with pytest.raises(ValueError) as excinfo:
+        @stix2.observables.CustomObservable('x_new_obs', [
+            ('property1', stix2.properties.StringProperty()),
+        ])
+        class NewObs2(object):
+            pass  # pragma: no cover
+    assert "Invalid observable type name 'x_new_obs':" in str(excinfo.value)
+
+
 def test_custom_observable_object_invalid_ref_property():
     with pytest.raises(ValueError) as excinfo:
         @stix2.observables.CustomObservable('x-new-obs', [
@@ -573,6 +609,24 @@ def test_custom_extension_invalid_observable():
     assert "Custom observables must be created with the @CustomObservable decorator." in str(excinfo.value)
 
 
+def test_custom_extension_invalid_type_name():
+    with pytest.raises(ValueError) as excinfo:
+        @stix2.observables.CustomExtension(stix2.File, 'x', {
+            'property1': stix2.properties.StringProperty(required=True),
+        })
+        class FooExtension():
+            pass  # pragma: no cover
+    assert "Invalid extension type name 'x':" in str(excinfo.value)
+
+    with pytest.raises(ValueError) as excinfo:
+        @stix2.observables.CustomExtension(stix2.File, 'x_new_ext', {
+            'property1': stix2.properties.StringProperty(required=True),
+        })
+        class BlaExtension():
+            pass  # pragma: no cover
+    assert "Invalid extension type name 'x_new_ext':" in str(excinfo.value)
+
+
 def test_custom_extension_no_properties():
     with pytest.raises(ValueError) as excinfo:
         @stix2.observables.CustomExtension(stix2.DomainName, 'x-new-ext2', None)