diff --git a/stix2/base.py b/stix2/base.py index 75bb0ab..eabff70 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -173,7 +173,7 @@ class _STIXBase(Mapping): for prop_name, prop_value in custom_props.items(): if not re.match(PREFIX_21_REGEX, prop_name): raise InvalidValueError(self.__class__, prop_name, - reason="Property names must begin with an alpha character.") + reason="Property name '%s' must begin with an alpha character." % prop_name) # Remove any keyword arguments whose value is None or [] (i.e. empty list) setting_kwargs = {} diff --git a/stix2/core.py b/stix2/core.py index cf97344..2c388f7 100644 --- a/stix2/core.py +++ b/stix2/core.py @@ -10,7 +10,7 @@ import stix2 from .base import _Observable, _STIXBase from .exceptions import ParseError from .markings import _MarkingsMixin -from .utils import _get_dict, SCO21_EXT_REGEX, TYPE_REGEX +from .utils import _get_dict, TYPE_REGEX, PREFIX_21_REGEX, TYPE_21_REGEX STIX2_OBJ_MAPS = {} @@ -230,6 +230,36 @@ def _register_marking(new_marking, version=None): None, use latest version. """ + + type = new_marking._type + + if version == "2.0": + if not re.match(TYPE_REGEX, type): + raise ValueError( + "Invalid marking type name '%s': must only contain the " + "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % + type, + ) + else: # 2.1+ + if not re.match(TYPE_21_REGEX, type): + raise ValueError( + "Invalid marking type name '%s': must only contain the " + "characters a-z (lowercase ASCII), 0-9, and hyphen (-) " + "and must begin with an a-z character" % type, + ) + + if len(type) < 3 or len(type) > 250: + raise ValueError( + "Invalid marking type name '%s': must be between 3 and 250 characters." % type, + ) + + properties = new_marking._properties + + if version == "2.1": + for prop_name, prop_value in properties.items(): + if not re.match(PREFIX_21_REGEX, prop_name): + raise ValueError("Property name '%s' must begin with an alpha character." % prop_name) + if version: v = 'v' + version.replace('.', '') else: @@ -276,6 +306,7 @@ def _register_observable_extension( obs_class = observable if isinstance(observable, type) else \ type(observable) ext_type = new_extension._type + properties = new_extension._properties if not issubclass(obs_class, _Observable): raise ValueError("'observable' must be a valid Observable class!") @@ -288,7 +319,7 @@ def _register_observable_extension( ext_type, ) else: # 2.1+ - if not re.match(SCO21_EXT_REGEX, ext_type): + if not re.match(TYPE_21_REGEX, ext_type): raise ValueError( "Invalid extension type name '%s': must only contain the " "characters a-z (lowercase ASCII), 0-9, hyphen (-), " @@ -308,6 +339,11 @@ def _register_observable_extension( ext_type, ) + if version == "2.1": + for prop_name, prop_value in properties.items(): + if not re.match(PREFIX_21_REGEX, prop_name): + raise ValueError("Property name '%s' must begin with an alpha character." % prop_name) + v = 'v' + version.replace('.', '') try: diff --git a/stix2/custom.py b/stix2/custom.py index 736d459..1b26998 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -8,7 +8,7 @@ from .core import ( STIXDomainObject, _register_marking, _register_object, _register_observable, _register_observable_extension, ) -from .utils import get_class_hierarchy_names, SCO21_TYPE_REGEX, TYPE_REGEX, PREFIX_21_REGEX +from .utils import get_class_hierarchy_names, TYPE_21_REGEX, TYPE_REGEX, PREFIX_21_REGEX def _custom_object_builder(cls, type, properties, version): @@ -22,7 +22,7 @@ def _custom_object_builder(cls, type, properties, version): type, ) else: # 2.1+ - if not re.match(SCO21_TYPE_REGEX, type): + if not re.match(TYPE_21_REGEX, type): raise ValueError( "Invalid type name '%s': must only contain the " "characters a-z (lowercase ASCII), 0-9, and hyphen (-) " @@ -40,7 +40,7 @@ def _custom_object_builder(cls, type, properties, version): if version == "2.1": for prop_name, prop in properties: if not re.match(PREFIX_21_REGEX, prop_name): - raise ValueError("Property name %s must begin with an alpha character" % prop_name) + raise ValueError("Property name '%s' must begin with an alpha character" % prop_name) _type = type _properties = OrderedDict(properties) @@ -55,48 +55,8 @@ def _custom_object_builder(cls, type, properties, version): def _custom_marking_builder(cls, type, properties, version): - try: - prop_dict = OrderedDict(properties) - except TypeError as e: - six.raise_from( - ValueError( - "Marking properties must be dict-like, e.g. a list " - "containing tuples. For example, " - "[('property1', IntegerProperty())]", - ), - e, - ) - class _CustomMarking(cls, _STIXBase): - if version == "2.0": - if not re.match(TYPE_REGEX, type): - raise ValueError( - "Invalid marking type name '%s': must only contain the " - "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % - type, - ) - else: # 2.1+ - if not re.match(SCO21_TYPE_REGEX, type): - raise ValueError( - "Invalid marking type name '%s': must only contain the " - "characters a-z (lowercase ASCII), 0-9, and hyphen (-) " - "and must begin with an a-z character" % type, - ) - - if len(type) < 3 or len(type) > 250: - raise ValueError( - "Invalid marking type name '%s': must be between 3 and 250 characters." % type, - ) - - if not properties or not isinstance(properties, list): - raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") - - if version == "2.1 ": - for prop_name, prop_value in prop_dict.items(): - if not re.match(PREFIX_21_REGEX, prop_name): - raise ValueError("Property name %s must begin with an alpha character." % prop_name) - _type = type _properties = OrderedDict(properties) @@ -122,7 +82,7 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props= type, ) else: # 2.1+ - if not re.match(SCO21_TYPE_REGEX, type): + if not re.match(TYPE_21_REGEX, type): raise ValueError( "Invalid observable type name '%s': must only contain the " "characters a-z (lowercase ASCII), 0-9, and hyphen (-) " @@ -153,7 +113,7 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props= # If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties for prop_name, prop in properties: if not re.match(PREFIX_21_REGEX, prop_name): - raise ValueError("Property name %s must begin with an alpha character." % prop_name) + raise ValueError("Property name '%s' must begin with an alpha character." % prop_name) elif prop_name.endswith('_ref') and ('ReferenceProperty' not in get_class_hierarchy_names(prop)): raise ValueError( "'%s' is named like a reference property but " @@ -195,11 +155,6 @@ def _custom_extension_builder(cls, observable, type, properties, version): class _CustomExtension(cls, _Extension): - if version == "2.1": - for prop_name, prop_value in prop_dict.items(): - if not re.match(PREFIX_21_REGEX, prop_name): - raise ValueError("Property name %s must begin with an alpha character." % prop_name) - _type = type _properties = prop_dict diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index 8b1224e..c81d914 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -9,6 +9,8 @@ import stix2.v21 from ...exceptions import InvalidValueError from .constants import FAKE_TIME, IDENTITY_ID, MARKING_DEFINITION_ID +# Custom Properties in SDOs + IDENTITY_CUSTOM_PROP = stix2.v21.Identity( name="John Smith", identity_class="individual", @@ -54,7 +56,7 @@ def test_identity_custom_property(): "7foo": "bar", }, ) - assert "Property names must begin with an alpha character." in str(excinfo.value) + assert "must begin with an alpha character." in str(excinfo.value) identity = stix2.v21.Identity( id=IDENTITY_ID, @@ -178,6 +180,7 @@ def test_custom_properties_dict_in_bundled_object(): assert bundle.objects[0].x_foo == "bar" assert '"x_foo": "bar"' in str(bundle) +# Custom properties in SCOs def test_custom_property_in_observed_data(): artifact = stix2.v21.File( @@ -206,7 +209,7 @@ def test_invalid_custom_property_in_observed_data(): x_foo='bar', ) - assert "Property names must begin with an alpha character." in str(excinfo.value) + assert "must begin with an alpha character." in str(excinfo.value) def test_custom_property_object_in_observable_extension(): @@ -270,6 +273,7 @@ def test_identity_custom_property_revoke(): identity = IDENTITY_CUSTOM_PROP.revoke() assert identity.x_foo == "bar" +# Custom markings def test_identity_custom_property_edit_markings(): marking_obj = stix2.v21.MarkingDefinition( @@ -302,7 +306,7 @@ def test_invalid_custom_property_in_marking(): class NewObj(): pass - assert "Property names must begin with an alpha character." in str(excinfo.value) + assert "must begin with an alpha character." in str(excinfo.value) def test_custom_marking_no_init_1(): @@ -362,6 +366,7 @@ def test_custom_marking_invalid_type_name(): pass # pragma: no cover assert "Invalid marking type name '7x-new-marking':" in str(excinfo.value) +# Custom Objects @stix2.v21.CustomObject( 'x-new-type', [ @@ -492,6 +497,7 @@ def test_parse_unregistered_custom_object_type_w_allow_custom(): custom_obj = stix2.parse(nt_string, version="2.1", allow_custom=True) assert custom_obj["type"] == "x-foobar-observable" +# Custom SCOs @stix2.v21.CustomObservable( 'x-new-observable', [ @@ -559,6 +565,18 @@ def test_custom_observable_object_no_init_2(): assert no2.property1 == 'something' +def test_invalid_custom_property_in_custom_observable_object(): + with pytest.raises(ValueError) as excinfo: + @stix2.v21.CustomObservable( + 'x-new-sco', [ + ('5property1', stix2.properties.StringProperty()), + ], + ) + class NewObs(object): + pass # pragma: no cover + assert "must begin with an alpha character." in str(excinfo.value) + + def test_custom_observable_object_invalid_type_name(): with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomObservable( @@ -826,6 +844,7 @@ def test_custom_observable_object_no_id_contrib_props(): assert uuid_obj.variant == uuid.RFC_4122 assert uuid_obj.version == 4 +# Custom Extensions @stix2.v21.CustomExtension( stix2.v21.DomainName, 'x-new-ext', [ @@ -1023,6 +1042,20 @@ def test_custom_extension_no_init_2(): assert ne2.property1 == "foobar" +def test_invalid_custom_property_in_extension(): + with pytest.raises(ValueError) as excinfo: + @stix2.v21.CustomExtension( + stix2.v21.DomainName, 'x-new3-ext', [ + ('6property1', stix2.properties.StringProperty(required=True)), + ], + ) + class NewExt(): + pass + + assert "must begin with an alpha character." in str(excinfo.value) + + + def test_parse_observable_with_custom_extension(): input_str = """{ "type": "domain-name", diff --git a/stix2/utils.py b/stix2/utils.py index 4d75f8e..abec9a9 100644 --- a/stix2/utils.py +++ b/stix2/utils.py @@ -27,8 +27,8 @@ NOW = object() STIX_UNMOD_PROPERTIES = ['created', 'created_by_ref', 'id', 'type'] TYPE_REGEX = re.compile(r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$') -SCO21_TYPE_REGEX = re.compile(r'^\-?([a-z][a-z0-9]*)+(-[a-z0-9]+)*\-?$') -SCO21_EXT_REGEX = re.compile(r'^\-?([a-z][a-z0-9]*)+(-[a-z0-9]+)*\-ext$') +TYPE_21_REGEX = re.compile(r'^\-?([a-z][a-z0-9]*)+(-[a-z0-9]+)*\-?$') +EXT_21_REGEX = re.compile(r'^\-?([a-z][a-z0-9]*)+(-[a-z0-9]+)*\-ext$') PREFIX_21_REGEX = re.compile(r'^[a-z].*')