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) 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([