From 9699c78ad82ccd20932af6adb91b005de86ffbae Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Thu, 19 Mar 2020 10:40:35 -0400 Subject: [PATCH 01/17] issue-365 --- stix2/base.py | 13 ++++- stix2/core.py | 9 ++-- stix2/custom.py | 85 +++++++++++++++++++++++++++------ stix2/test/v21/test_custom.py | 90 +++++++++++++++++++++++++++++++++++ stix2/utils.py | 7 ++- 5 files changed, 184 insertions(+), 20 deletions(-) diff --git a/stix2/base.py b/stix2/base.py index 4248075..75bb0ab 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -2,6 +2,7 @@ import copy import datetime as dt +import re import uuid import simplejson as json @@ -17,7 +18,7 @@ from .exceptions import ( from .markings.utils import validate from .utils import NOW, find_property_index, format_datetime, get_timestamp from .utils import new_version as _new_version -from .utils import revoke as _revoke +from .utils import revoke as _revoke, PREFIX_21_REGEX try: from collections.abc import Mapping @@ -76,6 +77,11 @@ def get_required_properties(properties): class _STIXBase(Mapping): """Base class for STIX object types""" + def get_class_version(self): + module_name = self.__class__.__module__ + module_parts = module_name.split(".") + return module_parts[1] + def object_properties(self): props = set(self._properties.keys()) custom_props = list(set(self._inner.keys()) - props) @@ -163,6 +169,11 @@ class _STIXBase(Mapping): raise ExtraPropertiesError(cls, extra_kwargs) if custom_props: self._allow_custom = True + if self.get_class_version() == "v21": + 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.") # 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 b03e3d7..cf97344 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 SCO21_EXT_REGEX, TYPE_REGEX, _get_dict +from .utils import _get_dict, SCO21_EXT_REGEX, TYPE_REGEX STIX2_OBJ_MAPS = {} @@ -210,6 +210,7 @@ def _register_object(new_type, version=None): None, use latest version. """ + if version: v = 'v' + version.replace('.', '') else: @@ -248,6 +249,7 @@ def _register_observable(new_observable, version=None): None, use latest version. """ + if version: v = 'v' + version.replace('.', '') else: @@ -289,8 +291,9 @@ def _register_observable_extension( if not re.match(SCO21_EXT_REGEX, ext_type): raise ValueError( "Invalid extension type name '%s': must only contain the " - "characters a-z (lowercase ASCII), 0-9, hyphen (-), and end " - "with '-ext'." % ext_type, + "characters a-z (lowercase ASCII), 0-9, hyphen (-), " + "must begin with an a-z character" + "and end with '-ext'." % ext_type, ) if len(ext_type) < 3 or len(ext_type) > 250: diff --git a/stix2/custom.py b/stix2/custom.py index f3c89cf..e73a16a 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -8,18 +8,28 @@ from .core import ( STIXDomainObject, _register_marking, _register_object, _register_observable, _register_observable_extension, ) -from .utils import TYPE_REGEX, get_class_hierarchy_names +from .utils import get_class_hierarchy_names, SCO21_TYPE_REGEX, TYPE_REGEX, PREFIX_21_REGEX def _custom_object_builder(cls, type, properties, version): class _CustomObject(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: + if version == "2.0": + if not re.match(TYPE_REGEX, type): + raise ValueError( + "Invalid type 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 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 type name '%s': must be between 3 and 250 characters." % type, ) @@ -27,6 +37,11 @@ def _custom_object_builder(cls, type, properties, version): 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 in properties: + if not re.match(r'^[a-z]', prop_name): + raise ValueError("Property name %s must begin with an alpha character" % prop_name) + _type = type _properties = OrderedDict(properties) @@ -41,9 +56,34 @@ def _custom_object_builder(cls, type, properties, version): def _custom_marking_builder(cls, type, properties, version): class _CustomMarking(cls, _STIXBase): + if version == "2.0": + if not re.match(TYPE_REGEX, type): + raise ValueError( + "Invalid type 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 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 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 in properties: + 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) @@ -61,12 +101,22 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props= class _CustomObservable(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: + if version == "2.0": + if not re.match(TYPE_REGEX, type): + raise ValueError( + "Invalid type 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 observable 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 observable type name '%s': must be between 3 and 250 characters." % type) if not properties or not isinstance(properties, list): @@ -89,7 +139,9 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props= else: # If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties for prop_name, prop in properties: - if prop_name.endswith('_ref') and ('ReferenceProperty' not in get_class_hierarchy_names(prop)): + if not re.match(PREFIX_21_REGEX, 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 " "is not a ReferenceProperty." % prop_name, @@ -130,6 +182,11 @@ 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 1e6f629..33cffd5 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -43,6 +43,19 @@ def test_identity_custom_property(): ) assert "Unexpected properties for Identity" in str(excinfo.value) + with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: + stix2.v21.Identity( + id=IDENTITY_ID, + created="2015-12-21T19:59:11Z", + modified="2015-12-21T19:59:11Z", + name="John Smith", + identity_class="individual", + custom_properties={ + "7foo": "bar", + }, + ) + assert "Property names must begin with an alpha character." in str(excinfo.value) + identity = stix2.v21.Identity( id=IDENTITY_ID, created="2015-12-21T19:59:11Z", @@ -184,6 +197,18 @@ def test_custom_property_in_observed_data(): assert '"x_foo": "bar"' in str(observed_data) +def test_invalid_custom_property_in_observed_data(): + with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: + stix2.v21.File( + custom_properties={"8foo": 1}, + allow_custom=True, + name='test', + x_foo='bar', + ) + + assert "Property names must begin with an alpha character." in str(excinfo.value) + + def test_custom_property_object_in_observable_extension(): ntfs = stix2.v21.NTFSExt( allow_custom=True, @@ -293,6 +318,38 @@ def test_custom_marking_no_init_2(): assert no2.property1 == 'something' +def test_custom_marking_invalid_type_name(): + with pytest.raises(ValueError) as excinfo: + @stix2.v21.CustomMarking( + '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.v21.CustomMarking( + 'x_new_marking', [ + ('property1', stix2.properties.StringProperty(required=True)), + ], + ) + class NewObj2(object): + pass # pragma: no cover + assert "Invalid type name 'x_new_marking':" in str(excinfo.value) + + with pytest.raises(ValueError) as excinfo: + @stix2.v21.CustomMarking( + '7x-new-marking', [ + ('property1', stix2.properties.StringProperty(required=True)), + ], + ) + class NewObj3(object): + pass # pragma: no cover + assert "Invalid type name '7x-new-marking':" in str(excinfo.value) + + @stix2.v21.CustomObject( 'x-new-type', [ ('property1', stix2.properties.StringProperty(required=True)), @@ -374,6 +431,17 @@ def test_custom_object_invalid_type_name(): pass # pragma: no cover assert "Invalid type name 'x_new_object':" in str(excinfo.value) + with pytest.raises(ValueError) as excinfo: + @stix2.v21.CustomObject( + '7x-new-object', [ + ('property1', stix2.properties.StringProperty(required=True)), + ], + ) + class NewObj3(object): + pass # pragma: no cover + assert "Invalid type name '7x-new-object':" in str(excinfo.value) + + def test_parse_custom_object_type(): nt_string = """{ @@ -500,6 +568,17 @@ def test_custom_observable_object_invalid_type_name(): pass # pragma: no cover assert "Invalid observable type name 'x_new_obs':" in str(excinfo.value) + with pytest.raises(ValueError) as excinfo: + @stix2.v21.CustomObservable( + '7x-new-obs', [ + ('property1', stix2.properties.StringProperty()), + ], + ) + class NewObs3(object): + pass # pragma: no cover + assert "Invalid observable type name '7x-new-obs':" in str(excinfo.value) + + def test_custom_observable_object_invalid_ref_property(): with pytest.raises(ValueError) as excinfo: @@ -874,6 +953,17 @@ def test_custom_extension_invalid_type_name(): pass # pragma: no cover assert "Invalid extension type name 'x_new_ext':" in str(excinfo.value) + with pytest.raises(ValueError) as excinfo: + @stix2.v21.CustomExtension( + stix2.v21.File, '7x-new-ext', { + 'property1': stix2.properties.StringProperty(required=True), + }, + ) + class Bla2Extension(): + pass # pragma: no cover + assert "Invalid extension type name '7x-new-ext':" in str(excinfo.value) + + def test_custom_extension_no_properties(): with pytest.raises(ValueError): diff --git a/stix2/utils.py b/stix2/utils.py index 7b3b6cf..4d75f8e 100644 --- a/stix2/utils.py +++ b/stix2/utils.py @@ -7,6 +7,7 @@ except ImportError: import copy import datetime as dt import json +import re from dateutil import parser import pytz @@ -25,8 +26,10 @@ 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]+)*\-?$' -SCO21_EXT_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-ext$' +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$') +PREFIX_21_REGEX = re.compile(r'^[a-z].*') class STIXdatetime(dt.datetime): From 844ec2c3bf8ffb24033e116676c5bb1ca330667b Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Thu, 19 Mar 2020 14:16:48 -0400 Subject: [PATCH 02/17] more on issue 365 --- stix2/custom.py | 29 +++++++++++++++++++++-------- stix2/test/v21/test_custom.py | 21 ++++++++++++++++----- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/stix2/custom.py b/stix2/custom.py index e73a16a..736d459 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -17,7 +17,7 @@ def _custom_object_builder(cls, type, properties, version): if version == "2.0": if not re.match(TYPE_REGEX, type): raise ValueError( - "Invalid type type name '%s': must only contain the " + "Invalid type name '%s': must only contain the " "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type, ) @@ -39,7 +39,7 @@ def _custom_object_builder(cls, type, properties, version): if version == "2.1": for prop_name, prop in properties: - if not re.match(r'^[a-z]', prop_name): + if not re.match(PREFIX_21_REGEX, prop_name): raise ValueError("Property name %s must begin with an alpha character" % prop_name) _type = type @@ -54,33 +54,46 @@ 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 type type name '%s': must only contain the " + "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 type name '%s': must only contain the " + "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 type name '%s': must be between 3 and 250 characters." % type, + "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 in properties: + 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) @@ -104,7 +117,7 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props= if version == "2.0": if not re.match(TYPE_REGEX, type): raise ValueError( - "Invalid type type name '%s': must only contain the " + "Invalid observable type name '%s': must only contain the " "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type, ) diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index 33cffd5..8b1224e 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -292,6 +292,19 @@ def test_identity_custom_property_edit_markings(): identity2.clear_markings('x_foo') +def test_invalid_custom_property_in_marking(): + with pytest.raises(ValueError) as excinfo: + @stix2.v21.CustomMarking( + 'x-new-obj', [ + ('9property1', stix2.properties.StringProperty(required=True)), + ], + ) + class NewObj(): + pass + + assert "Property names must begin with an alpha character." in str(excinfo.value) + + def test_custom_marking_no_init_1(): @stix2.v21.CustomMarking( 'x-new-obj', [ @@ -327,7 +340,7 @@ def test_custom_marking_invalid_type_name(): ) class NewObj(object): pass # pragma: no cover - assert "Invalid type name 'x': " in str(excinfo.value) + assert "Invalid marking type name 'x': " in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomMarking( @@ -337,7 +350,7 @@ def test_custom_marking_invalid_type_name(): ) class NewObj2(object): pass # pragma: no cover - assert "Invalid type name 'x_new_marking':" in str(excinfo.value) + assert "Invalid marking type name 'x_new_marking':" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomMarking( @@ -347,7 +360,7 @@ def test_custom_marking_invalid_type_name(): ) class NewObj3(object): pass # pragma: no cover - assert "Invalid type name '7x-new-marking':" in str(excinfo.value) + assert "Invalid marking type name '7x-new-marking':" in str(excinfo.value) @stix2.v21.CustomObject( @@ -442,7 +455,6 @@ def test_custom_object_invalid_type_name(): assert "Invalid type name '7x-new-object':" in str(excinfo.value) - def test_parse_custom_object_type(): nt_string = """{ "type": "x-new-type", @@ -579,7 +591,6 @@ def test_custom_observable_object_invalid_type_name(): assert "Invalid observable type name '7x-new-obs':" in str(excinfo.value) - def test_custom_observable_object_invalid_ref_property(): with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomObservable( From f60e4170fdc8a75262d78a80f411929657489414 Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Thu, 19 Mar 2020 16:11:52 -0400 Subject: [PATCH 03/17] finish 365 --- stix2/base.py | 2 +- stix2/core.py | 40 +++++++++++++++++++++++-- stix2/custom.py | 55 ++++------------------------------- stix2/test/v21/test_custom.py | 39 +++++++++++++++++++++++-- stix2/utils.py | 4 +-- 5 files changed, 82 insertions(+), 58 deletions(-) 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].*') From fe919049b8b59a5e5a5a734cd573d2e383125f18 Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Thu, 19 Mar 2020 16:43:37 -0400 Subject: [PATCH 04/17] fix marking test --- stix2/custom.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stix2/custom.py b/stix2/custom.py index 1b26998..8a08f0e 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -57,6 +57,9 @@ def _custom_marking_builder(cls, type, properties, version): class _CustomMarking(cls, _STIXBase): + if not properties or not isinstance(properties, list): + raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") + _type = type _properties = OrderedDict(properties) From 6e4151aeebba03c112e6062c253c135d77364688 Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Thu, 19 Mar 2020 16:49:46 -0400 Subject: [PATCH 05/17] flaky --- stix2/base.py | 12 ++++++++---- stix2/core.py | 2 +- stix2/custom.py | 4 +++- stix2/test/v21/test_custom.py | 7 +++++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/stix2/base.py b/stix2/base.py index eabff70..8bffa3a 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -16,9 +16,11 @@ from .exceptions import ( MissingPropertiesError, MutuallyExclusivePropertiesError, ) from .markings.utils import validate -from .utils import NOW, find_property_index, format_datetime, get_timestamp +from .utils import ( + NOW, PREFIX_21_REGEX, find_property_index, format_datetime, get_timestamp, +) from .utils import new_version as _new_version -from .utils import revoke as _revoke, PREFIX_21_REGEX +from .utils import revoke as _revoke try: from collections.abc import Mapping @@ -172,8 +174,10 @@ class _STIXBase(Mapping): if self.get_class_version() == "v21": 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 name '%s' must begin with an alpha character." % prop_name) + raise InvalidValueError( + self.__class__, prop_name, + 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 2c388f7..bd2b524 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, TYPE_REGEX, PREFIX_21_REGEX, TYPE_21_REGEX +from .utils import PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, _get_dict STIX2_OBJ_MAPS = {} diff --git a/stix2/custom.py b/stix2/custom.py index 8a08f0e..bd2fe1d 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -8,7 +8,9 @@ from .core import ( STIXDomainObject, _register_marking, _register_object, _register_observable, _register_observable_extension, ) -from .utils import get_class_hierarchy_names, TYPE_21_REGEX, TYPE_REGEX, PREFIX_21_REGEX +from .utils import ( + PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, get_class_hierarchy_names, +) def _custom_object_builder(cls, type, properties, version): diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index c81d914..168c7fe 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -182,6 +182,7 @@ def test_custom_properties_dict_in_bundled_object(): # Custom properties in SCOs + def test_custom_property_in_observed_data(): artifact = stix2.v21.File( allow_custom=True, @@ -275,6 +276,7 @@ def test_identity_custom_property_revoke(): # Custom markings + def test_identity_custom_property_edit_markings(): marking_obj = stix2.v21.MarkingDefinition( id=MARKING_DEFINITION_ID, @@ -368,6 +370,7 @@ def test_custom_marking_invalid_type_name(): # Custom Objects + @stix2.v21.CustomObject( 'x-new-type', [ ('property1', stix2.properties.StringProperty(required=True)), @@ -499,6 +502,7 @@ def test_parse_unregistered_custom_object_type_w_allow_custom(): # Custom SCOs + @stix2.v21.CustomObservable( 'x-new-observable', [ ('property1', stix2.properties.StringProperty(required=True)), @@ -846,6 +850,7 @@ def test_custom_observable_object_no_id_contrib_props(): # Custom Extensions + @stix2.v21.CustomExtension( stix2.v21.DomainName, 'x-new-ext', [ ('property1', stix2.properties.StringProperty(required=True)), @@ -994,7 +999,6 @@ def test_custom_extension_invalid_type_name(): assert "Invalid extension type name '7x-new-ext':" in str(excinfo.value) - def test_custom_extension_no_properties(): with pytest.raises(ValueError): @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', None) @@ -1055,7 +1059,6 @@ def test_invalid_custom_property_in_extension(): assert "must begin with an alpha character." in str(excinfo.value) - def test_parse_observable_with_custom_extension(): input_str = """{ "type": "domain-name", From 2c4e47de562a5b8b7df93267819e2d09e8276ac4 Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Fri, 20 Mar 2020 11:56:09 -0400 Subject: [PATCH 06/17] remove leading - from type name re --- stix2/core.py | 4 ++-- stix2/test/v21/test_custom.py | 15 ++++++++++++++- stix2/utils.py | 4 ++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/stix2/core.py b/stix2/core.py index bd2b524..6f05bf9 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 PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, _get_dict +from .utils import PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, EXT_21_REGEX, _get_dict STIX2_OBJ_MAPS = {} @@ -319,7 +319,7 @@ def _register_observable_extension( ext_type, ) else: # 2.1+ - if not re.match(TYPE_21_REGEX, ext_type): + if not re.match(EXT_21_REGEX, ext_type): raise ValueError( "Invalid extension type name '%s': must only contain the " "characters a-z (lowercase ASCII), 0-9, hyphen (-), " diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index 168c7fe..2bdca05 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -58,6 +58,19 @@ def test_identity_custom_property(): ) assert "must begin with an alpha character." in str(excinfo.value) + with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: + stix2.v21.Identity( + id=IDENTITY_ID, + created="2015-12-21T19:59:11Z", + modified="2015-12-21T19:59:11Z", + name="John Smith", + identity_class="individual", + custom_properties={ + "_foo": "bar", + }, + ) + assert "must begin with an alpha character." in str(excinfo.value) + identity = stix2.v21.Identity( id=IDENTITY_ID, created="2015-12-21T19:59:11Z", @@ -210,7 +223,7 @@ def test_invalid_custom_property_in_observed_data(): x_foo='bar', ) - assert "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(): diff --git a/stix2/utils.py b/stix2/utils.py index abec9a9..f2bac52 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]+)*\-?$') -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$') +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].*') From 9e5e998c3d83c0706eb7f7b8097cbc78f4772e25 Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Fri, 20 Mar 2020 12:49:20 -0400 Subject: [PATCH 07/17] don't allow leading '_' on custom properties, whenever allow_custom is true --- stix2/base.py | 10 +++++++-- stix2/test/v21/test_custom.py | 38 +++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/stix2/base.py b/stix2/base.py index 8bffa3a..e018059 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -169,10 +169,16 @@ class _STIXBase(Mapping): extra_kwargs = list(set(kwargs) - set(self._properties)) if extra_kwargs: raise ExtraPropertiesError(cls, extra_kwargs) - if custom_props: + else: + # because allow_custom is true, any extra kwargs are custom + extra_kwargs = list(set(kwargs) - set(self._properties)) + + if custom_props or extra_kwargs: self._allow_custom = True if self.get_class_version() == "v21": - for prop_name, prop_value in custom_props.items(): + all_custom_prop_names = extra_kwargs + all_custom_prop_names.extend(list(custom_props.keys())) + for prop_name in all_custom_prop_names: if not re.match(PREFIX_21_REGEX, prop_name): raise InvalidValueError( self.__class__, prop_name, diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index 2bdca05..ef57a9b 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -20,6 +20,18 @@ IDENTITY_CUSTOM_PROP = stix2.v21.Identity( def test_identity_custom_property(): + identity = stix2.v21.Identity( + id=IDENTITY_ID, + created="2015-12-21T19:59:11Z", + modified="2015-12-21T19:59:11Z", + name="John Smith", + identity_class="individual", + custom_properties={ + "foo": "bar", + }, + ) + assert identity.foo == "bar" + with pytest.raises(ValueError) as excinfo: stix2.v21.Identity( id=IDENTITY_ID, @@ -45,6 +57,8 @@ def test_identity_custom_property(): ) assert "Unexpected properties for Identity" in str(excinfo.value) + # leading numeric character is illegal in 2.1 + with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Identity( id=IDENTITY_ID, @@ -58,6 +72,8 @@ def test_identity_custom_property(): ) assert "must begin with an alpha character." in str(excinfo.value) + # leading "_" is illegal in 2.1 + with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: stix2.v21.Identity( id=IDENTITY_ID, @@ -71,17 +87,17 @@ def test_identity_custom_property(): ) assert "must begin with an alpha character." in str(excinfo.value) - identity = stix2.v21.Identity( - id=IDENTITY_ID, - created="2015-12-21T19:59:11Z", - modified="2015-12-21T19:59:11Z", - name="John Smith", - identity_class="individual", - custom_properties={ - "foo": "bar", - }, - ) - assert identity.foo == "bar" + with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: + identity = stix2.v21.Identity( + id=IDENTITY_ID, + created="2015-12-21T19:59:11Z", + modified="2015-12-21T19:59:11Z", + name="John Smith", + identity_class="individual", + _x_foo="bar", + allow_custom=True, + ) + assert "must begin with an alpha character." in str(excinfo.value) def test_identity_custom_property_invalid(): From d8a9fc23065336bad2e75de0a154889162e1525e Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Fri, 20 Mar 2020 13:15:42 -0400 Subject: [PATCH 08/17] flaky --- stix2/core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stix2/core.py b/stix2/core.py index 6f05bf9..055a4a6 100644 --- a/stix2/core.py +++ b/stix2/core.py @@ -10,7 +10,9 @@ import stix2 from .base import _Observable, _STIXBase from .exceptions import ParseError from .markings import _MarkingsMixin -from .utils import PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, EXT_21_REGEX, _get_dict +from .utils import ( + EXT_21_REGEX, PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, _get_dict, +) STIX2_OBJ_MAPS = {} From 2dea4caf00e5ad3706cd26f67734285a64dc7a81 Mon Sep 17 00:00:00 2001 From: Rich Piazza Date: Fri, 20 Mar 2020 14:24:16 -0400 Subject: [PATCH 09/17] fix re so they begin with ^ --- stix2/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stix2/utils.py b/stix2/utils.py index f2bac52..da04a3e 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]+)*\-?$') -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$') +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].*') From e31634c32b0fee2d4344fe454efe1215773e26a5 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Sat, 21 Mar 2020 22:22:36 -0400 Subject: [PATCH 10/17] Rework spec version detection for _STIXBase objs --- stix2/base.py | 7 +------ stix2/custom.py | 30 +++++++++++++++--------------- stix2/v20/base.py | 24 ++++++++++++++++++++++++ stix2/v20/bundle.py | 4 ++-- stix2/v20/common.py | 16 ++++++++-------- stix2/v20/observables.py | 22 +++++++++++----------- stix2/v20/sdo.py | 4 ++-- stix2/v20/sro.py | 2 +- stix2/v21/base.py | 24 ++++++++++++++++++++++++ stix2/v21/bundle.py | 4 ++-- stix2/v21/common.py | 18 +++++++++--------- stix2/v21/observables.py | 22 +++++++++++----------- stix2/v21/sdo.py | 4 ++-- stix2/v21/sro.py | 2 +- 14 files changed, 113 insertions(+), 70 deletions(-) create mode 100644 stix2/v20/base.py create mode 100644 stix2/v21/base.py diff --git a/stix2/base.py b/stix2/base.py index e018059..5af3607 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -79,11 +79,6 @@ def get_required_properties(properties): class _STIXBase(Mapping): """Base class for STIX object types""" - def get_class_version(self): - module_name = self.__class__.__module__ - module_parts = module_name.split(".") - return module_parts[1] - def object_properties(self): props = set(self._properties.keys()) custom_props = list(set(self._inner.keys()) - props) @@ -175,7 +170,7 @@ class _STIXBase(Mapping): if custom_props or extra_kwargs: self._allow_custom = True - if self.get_class_version() == "v21": + if self._spec_version == "2.1": all_custom_prop_names = extra_kwargs all_custom_prop_names.extend(list(custom_props.keys())) for prop_name in all_custom_prop_names: diff --git a/stix2/custom.py b/stix2/custom.py index bd2fe1d..687618d 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -3,18 +3,18 @@ import re import six -from .base import _cls_init, _Extension, _Observable, _STIXBase +from .base import _cls_init from .core import ( - STIXDomainObject, _register_marking, _register_object, - _register_observable, _register_observable_extension, + _register_marking, _register_object, _register_observable, + _register_observable_extension, ) from .utils import ( PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, get_class_hierarchy_names, ) -def _custom_object_builder(cls, type, properties, version): - class _CustomObject(cls, STIXDomainObject): +def _custom_object_builder(cls, type, properties, version, base_class): + class _CustomObject(cls, base_class): if version == "2.0": if not re.match(TYPE_REGEX, type): @@ -48,16 +48,16 @@ def _custom_object_builder(cls, type, properties, version): _properties = OrderedDict(properties) def __init__(self, **kwargs): - _STIXBase.__init__(self, **kwargs) + base_class.__init__(self, **kwargs) _cls_init(cls, self, kwargs) _register_object(_CustomObject, version=version) return _CustomObject -def _custom_marking_builder(cls, type, properties, version): +def _custom_marking_builder(cls, type, properties, version, base_class): - class _CustomMarking(cls, _STIXBase): + class _CustomMarking(cls, base_class): if not properties or not isinstance(properties, list): raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") @@ -66,18 +66,18 @@ def _custom_marking_builder(cls, type, properties, version): _properties = OrderedDict(properties) def __init__(self, **kwargs): - _STIXBase.__init__(self, **kwargs) + base_class.__init__(self, **kwargs) _cls_init(cls, self, kwargs) _register_marking(_CustomMarking, version=version) return _CustomMarking -def _custom_observable_builder(cls, type, properties, version, id_contrib_props=None): +def _custom_observable_builder(cls, type, properties, version, base_class, id_contrib_props=None): if id_contrib_props is None: id_contrib_props = [] - class _CustomObservable(cls, _Observable): + class _CustomObservable(cls, base_class): if version == "2.0": if not re.match(TYPE_REGEX, type): @@ -137,14 +137,14 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props= _id_contributing_properties = id_contrib_props def __init__(self, **kwargs): - _Observable.__init__(self, **kwargs) + base_class.__init__(self, **kwargs) _cls_init(cls, self, kwargs) _register_observable(_CustomObservable, version=version) return _CustomObservable -def _custom_extension_builder(cls, observable, type, properties, version): +def _custom_extension_builder(cls, observable, type, properties, version, base_class): try: prop_dict = OrderedDict(properties) @@ -158,13 +158,13 @@ def _custom_extension_builder(cls, observable, type, properties, version): e, ) - class _CustomExtension(cls, _Extension): + class _CustomExtension(cls, base_class): _type = type _properties = prop_dict def __init__(self, **kwargs): - _Extension.__init__(self, **kwargs) + base_class.__init__(self, **kwargs) _cls_init(cls, self, kwargs) _register_observable_extension(observable, _CustomExtension, version=version) diff --git a/stix2/v20/base.py b/stix2/v20/base.py new file mode 100644 index 0000000..e32069a --- /dev/null +++ b/stix2/v20/base.py @@ -0,0 +1,24 @@ +"""Base classes for STIX 2.0 type definitions.""" + +from ..base import _Extension, _Observable, _STIXBase +from ..core import STIXDomainObject, STIXRelationshipObject + + +class _STIXBase20(_STIXBase): + _spec_version = "2.0" + + +class _Observable(_Observable, _STIXBase20): + pass + + +class _Extension(_Extension, _STIXBase20): + pass + + +class STIXDomainObject(STIXDomainObject, _STIXBase20): + pass + + +class STIXRelationshipObject(STIXRelationshipObject, _STIXBase20): + pass diff --git a/stix2/v20/bundle.py b/stix2/v20/bundle.py index ffd70ea..b811a79 100644 --- a/stix2/v20/bundle.py +++ b/stix2/v20/bundle.py @@ -2,13 +2,13 @@ from collections import OrderedDict -from ..base import _STIXBase from ..properties import ( IDProperty, ListProperty, STIXObjectProperty, StringProperty, TypeProperty, ) +from .base import _STIXBase20 -class Bundle(_STIXBase): +class Bundle(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ diff --git a/stix2/v20/common.py b/stix2/v20/common.py index a1ffa60..55f2c3e 100644 --- a/stix2/v20/common.py +++ b/stix2/v20/common.py @@ -5,7 +5,6 @@ import copy import six -from ..base import _STIXBase from ..custom import _custom_marking_builder from ..markings import _MarkingsMixin from ..markings.utils import check_tlp_marking @@ -14,6 +13,7 @@ from ..properties import ( SelectorProperty, StringProperty, TimestampProperty, TypeProperty, ) from ..utils import NOW, _get_dict +from .base import _STIXBase20 def _should_set_millisecond(cr, marking_type): @@ -31,7 +31,7 @@ def _should_set_millisecond(cr, marking_type): return False -class ExternalReference(_STIXBase): +class ExternalReference(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -49,7 +49,7 @@ class ExternalReference(_STIXBase): self._check_at_least_one_property(['description', 'external_id', 'url']) -class KillChainPhase(_STIXBase): +class KillChainPhase(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -60,7 +60,7 @@ class KillChainPhase(_STIXBase): ]) -class GranularMarking(_STIXBase): +class GranularMarking(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -71,7 +71,7 @@ class GranularMarking(_STIXBase): ]) -class TLPMarking(_STIXBase): +class TLPMarking(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -83,7 +83,7 @@ class TLPMarking(_STIXBase): ]) -class StatementMarking(_STIXBase): +class StatementMarking(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -113,7 +113,7 @@ class MarkingProperty(Property): raise ValueError("must be a Statement, TLP Marking or a registered marking.") -class MarkingDefinition(_STIXBase, _MarkingsMixin): +class MarkingDefinition(_STIXBase20, _MarkingsMixin): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -182,7 +182,7 @@ def CustomMarking(type='x-custom-marking', properties=None): """ def wrapper(cls): - return _custom_marking_builder(cls, type, properties, '2.0') + return _custom_marking_builder(cls, type, properties, '2.0', _STIXBase20) return wrapper diff --git a/stix2/v20/observables.py b/stix2/v20/observables.py index 81e2c48..eb9bfde 100644 --- a/stix2/v20/observables.py +++ b/stix2/v20/observables.py @@ -1,14 +1,13 @@ """STIX 2.0 Cyber Observable Objects. Embedded observable object types, such as Email MIME Component, which is -embedded in Email Message objects, inherit from ``_STIXBase`` instead of -Observable and do not have a ``_type`` attribute. +embedded in Email Message objects, inherit from ``_STIXBase20`` instead of +_Observable and do not have a ``_type`` attribute. """ from collections import OrderedDict import itertools -from ..base import _Extension, _Observable, _STIXBase from ..custom import _custom_extension_builder, _custom_observable_builder from ..exceptions import AtLeastOnePropertyError, DependentPropertiesError from ..properties import ( @@ -17,6 +16,7 @@ from ..properties import ( HashesProperty, HexProperty, IntegerProperty, ListProperty, ObjectReferenceProperty, StringProperty, TimestampProperty, TypeProperty, ) +from .base import _Extension, _Observable, _STIXBase20 class Artifact(_Observable): @@ -103,7 +103,7 @@ class EmailAddress(_Observable): ]) -class EmailMIMEComponent(_STIXBase): +class EmailMIMEComponent(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ # noqa @@ -166,7 +166,7 @@ class ArchiveExt(_Extension): ]) -class AlternateDataStream(_STIXBase): +class AlternateDataStream(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ # noqa @@ -220,7 +220,7 @@ class RasterImageExt(_Extension): ]) -class WindowsPEOptionalHeaderType(_STIXBase): +class WindowsPEOptionalHeaderType(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ # noqa @@ -264,7 +264,7 @@ class WindowsPEOptionalHeaderType(_STIXBase): self._check_at_least_one_property() -class WindowsPESection(_STIXBase): +class WindowsPESection(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ # noqa @@ -677,7 +677,7 @@ class UserAccount(_Observable): ]) -class WindowsRegistryValueType(_STIXBase): +class WindowsRegistryValueType(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ # noqa @@ -724,7 +724,7 @@ class WindowsRegistryKey(_Observable): ]) -class X509V3ExtenstionsType(_STIXBase): +class X509V3ExtenstionsType(_STIXBase20): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ # noqa @@ -795,7 +795,7 @@ def CustomObservable(type='x-custom-observable', properties=None): properties, [('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=type))], ])) - return _custom_observable_builder(cls, type, _properties, '2.0') + return _custom_observable_builder(cls, type, _properties, '2.0', _Observable) return wrapper @@ -803,5 +803,5 @@ def CustomExtension(observable=None, type='x-custom-observable-ext', properties= """Decorator for custom extensions to STIX Cyber Observables. """ def wrapper(cls): - return _custom_extension_builder(cls, observable, type, properties, '2.0') + return _custom_extension_builder(cls, observable, type, properties, '2.0', _Extension) return wrapper diff --git a/stix2/v20/sdo.py b/stix2/v20/sdo.py index 430bfc0..b083e42 100644 --- a/stix2/v20/sdo.py +++ b/stix2/v20/sdo.py @@ -5,7 +5,6 @@ import itertools from stix2patterns.validator import run_validator -from ..core import STIXDomainObject from ..custom import _custom_object_builder from ..exceptions import InvalidValueError from ..properties import ( @@ -14,6 +13,7 @@ from ..properties import ( TimestampProperty, TypeProperty, ) from ..utils import NOW +from .base import STIXDomainObject from .common import ExternalReference, GranularMarking, KillChainPhase @@ -374,5 +374,5 @@ def CustomObject(type='x-custom-type', properties=None): ], sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]), ])) - return _custom_object_builder(cls, type, _properties, '2.0') + return _custom_object_builder(cls, type, _properties, '2.0', STIXDomainObject) return wrapper diff --git a/stix2/v20/sro.py b/stix2/v20/sro.py index b85eb68..0a07969 100644 --- a/stix2/v20/sro.py +++ b/stix2/v20/sro.py @@ -2,12 +2,12 @@ from collections import OrderedDict -from ..core import STIXRelationshipObject from ..properties import ( BooleanProperty, IDProperty, IntegerProperty, ListProperty, ReferenceProperty, StringProperty, TimestampProperty, TypeProperty, ) from ..utils import NOW +from .base import STIXRelationshipObject from .common import ExternalReference, GranularMarking diff --git a/stix2/v21/base.py b/stix2/v21/base.py new file mode 100644 index 0000000..bfe1d5e --- /dev/null +++ b/stix2/v21/base.py @@ -0,0 +1,24 @@ +"""Base classes for STIX 2.1 type definitions.""" + +from ..base import _Extension, _Observable, _STIXBase +from ..core import STIXDomainObject, STIXRelationshipObject + + +class _STIXBase21(_STIXBase): + _spec_version = "2.1" + + +class _Observable(_Observable, _STIXBase21): + pass + + +class _Extension(_Extension, _STIXBase21): + pass + + +class STIXDomainObject(STIXDomainObject, _STIXBase21): + pass + + +class STIXRelationshipObject(STIXRelationshipObject, _STIXBase21): + pass diff --git a/stix2/v21/bundle.py b/stix2/v21/bundle.py index 168771f..ca60fe0 100644 --- a/stix2/v21/bundle.py +++ b/stix2/v21/bundle.py @@ -2,13 +2,13 @@ from collections import OrderedDict -from ..base import _STIXBase from ..properties import ( IDProperty, ListProperty, STIXObjectProperty, TypeProperty, ) +from .base import _STIXBase21 -class Bundle(_STIXBase): +class Bundle(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. diff --git a/stix2/v21/common.py b/stix2/v21/common.py index ac8daf1..4254767 100644 --- a/stix2/v21/common.py +++ b/stix2/v21/common.py @@ -2,7 +2,6 @@ from collections import OrderedDict -from ..base import _STIXBase from ..custom import _custom_marking_builder from ..exceptions import InvalidValueError from ..markings import _MarkingsMixin @@ -13,9 +12,10 @@ from ..properties import ( SelectorProperty, StringProperty, TimestampProperty, TypeProperty, ) from ..utils import NOW, _get_dict +from .base import _STIXBase21 -class ExternalReference(_STIXBase): +class ExternalReference(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -50,7 +50,7 @@ class ExternalReference(_STIXBase): ) -class KillChainPhase(_STIXBase): +class KillChainPhase(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -62,7 +62,7 @@ class KillChainPhase(_STIXBase): ]) -class GranularMarking(_STIXBase): +class GranularMarking(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -79,7 +79,7 @@ class GranularMarking(_STIXBase): self._check_at_least_one_property(['lang', 'marking_ref']) -class LanguageContent(_STIXBase): +class LanguageContent(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -107,7 +107,7 @@ class LanguageContent(_STIXBase): ]) -class TLPMarking(_STIXBase): +class TLPMarking(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -119,7 +119,7 @@ class TLPMarking(_STIXBase): ]) -class StatementMarking(_STIXBase): +class StatementMarking(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -150,7 +150,7 @@ class MarkingProperty(Property): raise ValueError("must be a Statement, TLP Marking or a registered marking.") -class MarkingDefinition(_STIXBase, _MarkingsMixin): +class MarkingDefinition(_STIXBase21, _MarkingsMixin): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -215,7 +215,7 @@ def CustomMarking(type='x-custom-marking', properties=None): """ def wrapper(cls): - return _custom_marking_builder(cls, type, properties, '2.1') + return _custom_marking_builder(cls, type, properties, '2.1', _STIXBase21) return wrapper diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py index 8c9a2a1..e71e445 100644 --- a/stix2/v21/observables.py +++ b/stix2/v21/observables.py @@ -1,14 +1,13 @@ """STIX 2.1 Cyber Observable Objects. Embedded observable object types, such as Email MIME Component, which is -embedded in Email Message objects, inherit from ``_STIXBase`` instead of -Observable and do not have a ``_type`` attribute. +embedded in Email Message objects, inherit from ``_STIXBase21`` instead of +_Observable and do not have a ``_type`` attribute. """ from collections import OrderedDict import itertools -from ..base import _Extension, _Observable, _STIXBase from ..custom import _custom_extension_builder, _custom_observable_builder from ..exceptions import AtLeastOnePropertyError, DependentPropertiesError from ..properties import ( @@ -18,6 +17,7 @@ from ..properties import ( ObjectReferenceProperty, ReferenceProperty, StringProperty, TimestampProperty, TypeProperty, ) +from .base import _Extension, _Observable, _STIXBase21 from .common import GranularMarking @@ -142,7 +142,7 @@ class EmailAddress(_Observable): _id_contributing_properties = ["value"] -class EmailMIMEComponent(_STIXBase): +class EmailMIMEComponent(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -214,7 +214,7 @@ class ArchiveExt(_Extension): ]) -class AlternateDataStream(_STIXBase): +class AlternateDataStream(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -271,7 +271,7 @@ class RasterImageExt(_Extension): ]) -class WindowsPEOptionalHeaderType(_STIXBase): +class WindowsPEOptionalHeaderType(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -316,7 +316,7 @@ class WindowsPEOptionalHeaderType(_STIXBase): self._check_at_least_one_property() -class WindowsPESection(_STIXBase): +class WindowsPESection(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -841,7 +841,7 @@ class UserAccount(_Observable): _id_contributing_properties = ["account_type", "user_id", "account_login"] -class WindowsRegistryValueType(_STIXBase): +class WindowsRegistryValueType(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -896,7 +896,7 @@ class WindowsRegistryKey(_Observable): _id_contributing_properties = ["key", "values"] -class X509V3ExtenstionsType(_STIXBase): +class X509V3ExtenstionsType(_STIXBase21): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -988,7 +988,7 @@ def CustomObservable(type='x-custom-observable', properties=None, id_contrib_pro properties, [('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=type))], ])) - return _custom_observable_builder(cls, type, _properties, '2.1', id_contrib_props) + return _custom_observable_builder(cls, type, _properties, '2.1', _Observable, id_contrib_props) return wrapper @@ -996,5 +996,5 @@ def CustomExtension(observable=None, type='x-custom-observable-ext', properties= """Decorator for custom extensions to STIX Cyber Observables. """ def wrapper(cls): - return _custom_extension_builder(cls, observable, type, properties, '2.1') + return _custom_extension_builder(cls, observable, type, properties, '2.1', _Extension) return wrapper diff --git a/stix2/v21/sdo.py b/stix2/v21/sdo.py index f2dc0ea..42c58e4 100644 --- a/stix2/v21/sdo.py +++ b/stix2/v21/sdo.py @@ -7,7 +7,6 @@ import warnings from six.moves.urllib.parse import quote_plus from stix2patterns.validator import run_validator -from ..core import STIXDomainObject from ..custom import _custom_object_builder from ..exceptions import ( InvalidValueError, PropertyPresenceError, STIXDeprecationWarning, @@ -18,6 +17,7 @@ from ..properties import ( StringProperty, TimestampProperty, TypeProperty, ) from ..utils import NOW +from .base import STIXDomainObject from .common import ExternalReference, GranularMarking, KillChainPhase @@ -828,6 +828,6 @@ def CustomObject(type='x-custom-type', properties=None): ], sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]), ])) - return _custom_object_builder(cls, type, _properties, '2.1') + return _custom_object_builder(cls, type, _properties, '2.1', STIXDomainObject) return wrapper diff --git a/stix2/v21/sro.py b/stix2/v21/sro.py index 059bb66..91b8b80 100644 --- a/stix2/v21/sro.py +++ b/stix2/v21/sro.py @@ -2,12 +2,12 @@ from collections import OrderedDict -from ..core import STIXRelationshipObject from ..properties import ( BooleanProperty, IDProperty, IntegerProperty, ListProperty, ReferenceProperty, StringProperty, TimestampProperty, TypeProperty, ) from ..utils import NOW +from .base import STIXRelationshipObject from .common import ExternalReference, GranularMarking From 01ba190525639791389a1698fab3e478f233d84b Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Fri, 27 Mar 2020 02:40:42 -0400 Subject: [PATCH 11/17] Reorganize bases, use isinstance to check version Renamed STIXDomainObject -> _DomainObject. Renamed STIXRelationshipObject -> _RelationshipObject. --- stix2/base.py | 24 ++++++++++++++++-------- stix2/core.py | 11 +---------- stix2/properties.py | 2 +- stix2/v20/__init__.py | 3 +++ stix2/v20/base.py | 11 ++++++----- stix2/v20/sdo.py | 28 ++++++++++++++-------------- stix2/v20/sro.py | 6 +++--- stix2/v21/__init__.py | 3 +++ stix2/v21/base.py | 11 ++++++----- stix2/v21/sdo.py | 42 +++++++++++++++++++++--------------------- stix2/v21/sro.py | 6 +++--- 11 files changed, 77 insertions(+), 70 deletions(-) diff --git a/stix2/base.py b/stix2/base.py index 5af3607..a1229ca 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -8,6 +8,7 @@ import uuid import simplejson as json import six +import stix2 from stix2.canonicalization.Canonicalize import canonicalize from .exceptions import ( @@ -15,6 +16,7 @@ from .exceptions import ( ImmutableError, InvalidObjRefError, InvalidValueError, MissingPropertiesError, MutuallyExclusivePropertiesError, ) +from .markings import _MarkingsMixin from .markings.utils import validate from .utils import ( NOW, PREFIX_21_REGEX, find_property_index, format_datetime, get_timestamp, @@ -160,17 +162,15 @@ class _STIXBase(Mapping): custom_props = kwargs.pop('custom_properties', {}) if custom_props and not isinstance(custom_props, dict): raise ValueError("'custom_properties' must be a dictionary") - if not self._allow_custom: - extra_kwargs = list(set(kwargs) - set(self._properties)) - if extra_kwargs: - raise ExtraPropertiesError(cls, extra_kwargs) - else: - # because allow_custom is true, any extra kwargs are custom - extra_kwargs = list(set(kwargs) - set(self._properties)) + extra_kwargs = list(set(kwargs) - set(self._properties)) + if extra_kwargs and not self._allow_custom: + raise ExtraPropertiesError(cls, extra_kwargs) + + # because allow_custom is true, any extra kwargs are custom if custom_props or extra_kwargs: self._allow_custom = True - if self._spec_version == "2.1": + if isinstance(self, stix2.v21._STIXBase21): all_custom_prop_names = extra_kwargs all_custom_prop_names.extend(list(custom_props.keys())) for prop_name in all_custom_prop_names: @@ -321,6 +321,14 @@ class _STIXBase(Mapping): return json.dumps(self, cls=STIXJSONEncoder, **kwargs) +class _DomainObject(_STIXBase, _MarkingsMixin): + pass + + +class _RelationshipObject(_STIXBase, _MarkingsMixin): + pass + + class _Observable(_STIXBase): def __init__(self, **kwargs): diff --git a/stix2/core.py b/stix2/core.py index 055a4a6..7dedc15 100644 --- a/stix2/core.py +++ b/stix2/core.py @@ -7,9 +7,8 @@ import re import stix2 -from .base import _Observable, _STIXBase +from .base import _Observable from .exceptions import ParseError -from .markings import _MarkingsMixin from .utils import ( EXT_21_REGEX, PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, _get_dict, ) @@ -17,14 +16,6 @@ from .utils import ( STIX2_OBJ_MAPS = {} -class STIXDomainObject(_STIXBase, _MarkingsMixin): - pass - - -class STIXRelationshipObject(_STIXBase, _MarkingsMixin): - pass - - def parse(data, allow_custom=False, version=None): """Convert a string, dict or file-like object into a STIX object. diff --git a/stix2/properties.py b/stix2/properties.py index b013b7e..e02f96b 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -639,7 +639,7 @@ class STIXObjectProperty(Property): def clean(self, value): # Any STIX Object (SDO, SRO, or Marking Definition) can be added to # a bundle with no further checks. - if any(x in ('STIXDomainObject', 'STIXRelationshipObject', 'MarkingDefinition') + if any(x in ('_DomainObject', '_RelationshipObject', 'MarkingDefinition') for x in get_class_hierarchy_names(value)): # A simple "is this a spec version 2.1+ object" test. For now, # limit 2.0 bundles to 2.0 objects. It's not possible yet to diff --git a/stix2/v20/__init__.py b/stix2/v20/__init__.py index 4d0a98f..b77d46c 100644 --- a/stix2/v20/__init__.py +++ b/stix2/v20/__init__.py @@ -14,6 +14,9 @@ # flake8: noqa +from .base import ( + _DomainObject, _Extension, _Observable, _RelationshipObject, _STIXBase20, +) from .bundle import Bundle from .common import ( TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, CustomMarking, ExternalReference, diff --git a/stix2/v20/base.py b/stix2/v20/base.py index e32069a..b5437ca 100644 --- a/stix2/v20/base.py +++ b/stix2/v20/base.py @@ -1,11 +1,12 @@ """Base classes for STIX 2.0 type definitions.""" -from ..base import _Extension, _Observable, _STIXBase -from ..core import STIXDomainObject, STIXRelationshipObject +from ..base import ( + _DomainObject, _Extension, _Observable, _RelationshipObject, _STIXBase, +) class _STIXBase20(_STIXBase): - _spec_version = "2.0" + pass class _Observable(_Observable, _STIXBase20): @@ -16,9 +17,9 @@ class _Extension(_Extension, _STIXBase20): pass -class STIXDomainObject(STIXDomainObject, _STIXBase20): +class _DomainObject(_DomainObject, _STIXBase20): pass -class STIXRelationshipObject(STIXRelationshipObject, _STIXBase20): +class _RelationshipObject(_RelationshipObject, _STIXBase20): pass diff --git a/stix2/v20/sdo.py b/stix2/v20/sdo.py index b083e42..cb5ba04 100644 --- a/stix2/v20/sdo.py +++ b/stix2/v20/sdo.py @@ -13,11 +13,11 @@ from ..properties import ( TimestampProperty, TypeProperty, ) from ..utils import NOW -from .base import STIXDomainObject +from .base import _DomainObject from .common import ExternalReference, GranularMarking, KillChainPhase -class AttackPattern(STIXDomainObject): +class AttackPattern(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -40,7 +40,7 @@ class AttackPattern(STIXDomainObject): ]) -class Campaign(STIXDomainObject): +class Campaign(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -66,7 +66,7 @@ class Campaign(STIXDomainObject): ]) -class CourseOfAction(STIXDomainObject): +class CourseOfAction(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -88,7 +88,7 @@ class CourseOfAction(STIXDomainObject): ]) -class Identity(STIXDomainObject): +class Identity(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -113,7 +113,7 @@ class Identity(STIXDomainObject): ]) -class Indicator(STIXDomainObject): +class Indicator(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -144,7 +144,7 @@ class Indicator(STIXDomainObject): raise InvalidValueError(self.__class__, 'pattern', str(errors[0])) -class IntrusionSet(STIXDomainObject): +class IntrusionSet(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -173,7 +173,7 @@ class IntrusionSet(STIXDomainObject): ]) -class Malware(STIXDomainObject): +class Malware(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -196,7 +196,7 @@ class Malware(STIXDomainObject): ]) -class ObservedData(STIXDomainObject): +class ObservedData(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -226,7 +226,7 @@ class ObservedData(STIXDomainObject): super(ObservedData, self).__init__(*args, **kwargs) -class Report(STIXDomainObject): +class Report(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -250,7 +250,7 @@ class Report(STIXDomainObject): ]) -class ThreatActor(STIXDomainObject): +class ThreatActor(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -280,7 +280,7 @@ class ThreatActor(STIXDomainObject): ]) -class Tool(STIXDomainObject): +class Tool(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -304,7 +304,7 @@ class Tool(STIXDomainObject): ]) -class Vulnerability(STIXDomainObject): +class Vulnerability(_DomainObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -374,5 +374,5 @@ def CustomObject(type='x-custom-type', properties=None): ], sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]), ])) - return _custom_object_builder(cls, type, _properties, '2.0', STIXDomainObject) + return _custom_object_builder(cls, type, _properties, '2.0', _DomainObject) return wrapper diff --git a/stix2/v20/sro.py b/stix2/v20/sro.py index 0a07969..b7d1ad3 100644 --- a/stix2/v20/sro.py +++ b/stix2/v20/sro.py @@ -7,11 +7,11 @@ from ..properties import ( ReferenceProperty, StringProperty, TimestampProperty, TypeProperty, ) from ..utils import NOW -from .base import STIXRelationshipObject +from .base import _RelationshipObject from .common import ExternalReference, GranularMarking -class Relationship(STIXRelationshipObject): +class Relationship(_RelationshipObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ @@ -52,7 +52,7 @@ class Relationship(STIXRelationshipObject): super(Relationship, self).__init__(**kwargs) -class Sighting(STIXRelationshipObject): +class Sighting(_RelationshipObject): """For more detailed information on this object's properties, see `the STIX 2.0 specification `__. """ diff --git a/stix2/v21/__init__.py b/stix2/v21/__init__.py index b2451d2..eea61dd 100644 --- a/stix2/v21/__init__.py +++ b/stix2/v21/__init__.py @@ -14,6 +14,9 @@ # flake8: noqa +from .base import ( + _DomainObject, _Extension, _Observable, _RelationshipObject, _STIXBase21, +) from .bundle import Bundle from .common import ( TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, CustomMarking, ExternalReference, diff --git a/stix2/v21/base.py b/stix2/v21/base.py index bfe1d5e..8b5a5f1 100644 --- a/stix2/v21/base.py +++ b/stix2/v21/base.py @@ -1,11 +1,12 @@ """Base classes for STIX 2.1 type definitions.""" -from ..base import _Extension, _Observable, _STIXBase -from ..core import STIXDomainObject, STIXRelationshipObject +from ..base import ( + _DomainObject, _Extension, _Observable, _RelationshipObject, _STIXBase, +) class _STIXBase21(_STIXBase): - _spec_version = "2.1" + pass class _Observable(_Observable, _STIXBase21): @@ -16,9 +17,9 @@ class _Extension(_Extension, _STIXBase21): pass -class STIXDomainObject(STIXDomainObject, _STIXBase21): +class _DomainObject(_DomainObject, _STIXBase21): pass -class STIXRelationshipObject(STIXRelationshipObject, _STIXBase21): +class _RelationshipObject(_RelationshipObject, _STIXBase21): pass diff --git a/stix2/v21/sdo.py b/stix2/v21/sdo.py index 42c58e4..1e9633e 100644 --- a/stix2/v21/sdo.py +++ b/stix2/v21/sdo.py @@ -17,11 +17,11 @@ from ..properties import ( StringProperty, TimestampProperty, TypeProperty, ) from ..utils import NOW -from .base import STIXDomainObject +from .base import _DomainObject from .common import ExternalReference, GranularMarking, KillChainPhase -class AttackPattern(STIXDomainObject): +class AttackPattern(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -49,7 +49,7 @@ class AttackPattern(STIXDomainObject): ]) -class Campaign(STIXDomainObject): +class Campaign(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -89,7 +89,7 @@ class Campaign(STIXDomainObject): raise ValueError(msg.format(self)) -class CourseOfAction(STIXDomainObject): +class CourseOfAction(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -115,7 +115,7 @@ class CourseOfAction(STIXDomainObject): ]) -class Grouping(STIXDomainObject): +class Grouping(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -143,7 +143,7 @@ class Grouping(STIXDomainObject): ]) -class Identity(STIXDomainObject): +class Identity(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -173,7 +173,7 @@ class Identity(STIXDomainObject): ]) -class Indicator(STIXDomainObject): +class Indicator(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -210,7 +210,7 @@ class Indicator(STIXDomainObject): if kwargs.get('pattern') and kwargs.get('pattern_type') == 'stix' and not kwargs.get('pattern_version'): kwargs['pattern_version'] = '2.1' - super(STIXDomainObject, self).__init__(*args, **kwargs) + super(_DomainObject, self).__init__(*args, **kwargs) def _check_object_constraints(self): super(Indicator, self)._check_object_constraints() @@ -233,7 +233,7 @@ class Indicator(STIXDomainObject): raise InvalidValueError(self.__class__, 'pattern', str(errors[0])) -class Infrastructure(STIXDomainObject): +class Infrastructure(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -274,7 +274,7 @@ class Infrastructure(STIXDomainObject): raise ValueError(msg.format(self)) -class IntrusionSet(STIXDomainObject): +class IntrusionSet(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -317,7 +317,7 @@ class IntrusionSet(STIXDomainObject): raise ValueError(msg.format(self)) -class Location(STIXDomainObject): +class Location(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -425,7 +425,7 @@ class Location(STIXDomainObject): return final_url -class Malware(STIXDomainObject): +class Malware(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -478,7 +478,7 @@ class Malware(STIXDomainObject): ) -class MalwareAnalysis(STIXDomainObject): +class MalwareAnalysis(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -523,7 +523,7 @@ class MalwareAnalysis(STIXDomainObject): self._check_at_least_one_property(["result", "analysis_sco_refs"]) -class Note(STIXDomainObject): +class Note(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -551,7 +551,7 @@ class Note(STIXDomainObject): ]) -class ObservedData(STIXDomainObject): +class ObservedData(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -607,7 +607,7 @@ class ObservedData(STIXDomainObject): ) -class Opinion(STIXDomainObject): +class Opinion(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -645,7 +645,7 @@ class Opinion(STIXDomainObject): ]) -class Report(STIXDomainObject): +class Report(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -674,7 +674,7 @@ class Report(STIXDomainObject): ]) -class ThreatActor(STIXDomainObject): +class ThreatActor(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -721,7 +721,7 @@ class ThreatActor(STIXDomainObject): raise ValueError(msg.format(self)) -class Tool(STIXDomainObject): +class Tool(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -751,7 +751,7 @@ class Tool(STIXDomainObject): ]) -class Vulnerability(STIXDomainObject): +class Vulnerability(_DomainObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -828,6 +828,6 @@ def CustomObject(type='x-custom-type', properties=None): ], sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]), ])) - return _custom_object_builder(cls, type, _properties, '2.1', STIXDomainObject) + return _custom_object_builder(cls, type, _properties, '2.1', _DomainObject) return wrapper diff --git a/stix2/v21/sro.py b/stix2/v21/sro.py index 91b8b80..3ffdd77 100644 --- a/stix2/v21/sro.py +++ b/stix2/v21/sro.py @@ -7,11 +7,11 @@ from ..properties import ( ReferenceProperty, StringProperty, TimestampProperty, TypeProperty, ) from ..utils import NOW -from .base import STIXRelationshipObject +from .base import _RelationshipObject from .common import ExternalReference, GranularMarking -class Relationship(STIXRelationshipObject): +class Relationship(_RelationshipObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. @@ -68,7 +68,7 @@ class Relationship(STIXRelationshipObject): raise ValueError(msg.format(self)) -class Sighting(STIXRelationshipObject): +class Sighting(_RelationshipObject): # TODO: Add link """For more detailed information on this object's properties, see `the STIX 2.1 specification `__. From 50df6f14744697fdbdbad04bebf8516e4c09aea3 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Fri, 27 Mar 2020 05:53:39 -0400 Subject: [PATCH 12/17] Rename core.py -> parsing.py --- stix2/__init__.py | 2 +- stix2/custom.py | 2 +- stix2/datastore/filesystem.py | 2 +- stix2/datastore/memory.py | 2 +- stix2/datastore/taxii.py | 2 +- stix2/environment.py | 2 +- stix2/{core.py => parsing.py} | 0 stix2/properties.py | 2 +- stix2/test/test_spec_version_detect.py | 2 +- stix2/test/v20/test_core.py | 32 +++++++++++++------------- stix2/test/v20/test_custom.py | 2 +- stix2/test/v21/test_core.py | 32 +++++++++++++------------- stix2/test/v21/test_custom.py | 2 +- 13 files changed, 42 insertions(+), 42 deletions(-) rename stix2/{core.py => parsing.py} (100%) diff --git a/stix2/__init__.py b/stix2/__init__.py index c9384a0..f3e02b4 100644 --- a/stix2/__init__.py +++ b/stix2/__init__.py @@ -23,7 +23,6 @@ DEFAULT_VERSION = '2.0' # Default version will always be the latest STIX 2.X version from .confidence import scales -from .core import _collect_stix2_mappings, parse, parse_observable from .datastore import CompositeDataSource from .datastore.filesystem import ( FileSystemSink, FileSystemSource, FileSystemStore, @@ -38,6 +37,7 @@ from .markings import ( add_markings, clear_markings, get_markings, is_marked, remove_markings, set_markings, ) +from .parsing import _collect_stix2_mappings, parse, parse_observable from .patterns import ( AndBooleanExpression, AndObservationExpression, BasicObjectPathComponent, BinaryConstant, BooleanConstant, EqualityComparisonExpression, diff --git a/stix2/custom.py b/stix2/custom.py index 687618d..6d1abc5 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -4,7 +4,7 @@ import re import six from .base import _cls_init -from .core import ( +from .parsing import ( _register_marking, _register_object, _register_observable, _register_observable_extension, ) diff --git a/stix2/datastore/filesystem.py b/stix2/datastore/filesystem.py index d5acc24..5a5844a 100644 --- a/stix2/datastore/filesystem.py +++ b/stix2/datastore/filesystem.py @@ -10,11 +10,11 @@ import six from stix2 import v20, v21 from stix2.base import _STIXBase -from stix2.core import parse from stix2.datastore import ( DataSink, DataSource, DataSourceError, DataStoreMixin, ) from stix2.datastore.filters import Filter, FilterSet, apply_common_filters +from stix2.parsing import parse from stix2.utils import format_datetime, get_type_from_id diff --git a/stix2/datastore/memory.py b/stix2/datastore/memory.py index 52da168..f71b763 100644 --- a/stix2/datastore/memory.py +++ b/stix2/datastore/memory.py @@ -7,9 +7,9 @@ import os from stix2 import v20, v21 from stix2.base import _STIXBase -from stix2.core import parse from stix2.datastore import DataSink, DataSource, DataStoreMixin from stix2.datastore.filters import FilterSet, apply_common_filters +from stix2.parsing import parse def _add(store, stix_data, allow_custom=True, version=None): diff --git a/stix2/datastore/taxii.py b/stix2/datastore/taxii.py index 41c968f..58dcdbc 100644 --- a/stix2/datastore/taxii.py +++ b/stix2/datastore/taxii.py @@ -4,11 +4,11 @@ from requests.exceptions import HTTPError from stix2 import v20, v21 from stix2.base import _STIXBase -from stix2.core import parse from stix2.datastore import ( DataSink, DataSource, DataSourceError, DataStoreMixin, ) from stix2.datastore.filters import Filter, FilterSet, apply_common_filters +from stix2.parsing import parse from stix2.utils import deduplicate try: diff --git a/stix2/environment.py b/stix2/environment.py index ada5f33..6e3c76a 100644 --- a/stix2/environment.py +++ b/stix2/environment.py @@ -4,8 +4,8 @@ import copy import logging import time -from .core import parse as _parse from .datastore import CompositeDataSource, DataStoreMixin +from .parsing import parse as _parse from .utils import STIXdatetime, parse_into_datetime logger = logging.getLogger(__name__) diff --git a/stix2/core.py b/stix2/parsing.py similarity index 100% rename from stix2/core.py rename to stix2/parsing.py diff --git a/stix2/properties.py b/stix2/properties.py index e02f96b..be66533 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -12,11 +12,11 @@ from six import string_types, text_type import stix2 from .base import _STIXBase -from .core import STIX2_OBJ_MAPS, parse, parse_observable from .exceptions import ( CustomContentError, DictionaryKeyError, MissingPropertiesError, MutuallyExclusivePropertiesError, ) +from .parsing import STIX2_OBJ_MAPS, parse, parse_observable from .utils import _get_dict, get_class_hierarchy_names, parse_into_datetime try: diff --git a/stix2/test/test_spec_version_detect.py b/stix2/test/test_spec_version_detect.py index d2a9da8..7039024 100644 --- a/stix2/test/test_spec_version_detect.py +++ b/stix2/test/test_spec_version_detect.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import pytest -from stix2.core import _detect_spec_version +from stix2.parsing import _detect_spec_version @pytest.mark.parametrize( diff --git a/stix2/test/v20/test_core.py b/stix2/test/v20/test_core.py index d2efa22..ca701fb 100644 --- a/stix2/test/v20/test_core.py +++ b/stix2/test/v20/test_core.py @@ -1,7 +1,7 @@ import pytest import stix2 -from stix2 import core, exceptions +from stix2 import exceptions, parsing from .constants import IDENTITY_ID @@ -46,14 +46,14 @@ BUNDLE = { def test_dict_to_stix2_bundle_with_version(): with pytest.raises(exceptions.ExtraPropertiesError) as excinfo: - core.dict_to_stix2(BUNDLE, version='2.1') + parsing.dict_to_stix2(BUNDLE, version='2.1') assert str(excinfo.value) == "Unexpected properties for Bundle: (spec_version)." def test_parse_observable_with_version(): observable = {"type": "file", "name": "foo.exe"} - obs_obj = core.parse_observable(observable, version='2.0') + obs_obj = parsing.parse_observable(observable, version='2.0') v = 'v20' assert v in str(obs_obj.__class__) @@ -62,38 +62,38 @@ def test_parse_observable_with_version(): @pytest.mark.xfail(reason="The default version is no longer 2.0", condition=stix2.DEFAULT_VERSION != "2.0") def test_parse_observable_with_no_version(): observable = {"type": "file", "name": "foo.exe"} - obs_obj = core.parse_observable(observable) + obs_obj = parsing.parse_observable(observable) v = 'v20' assert v in str(obs_obj.__class__) def test_register_object_with_version(): - bundle = core.dict_to_stix2(BUNDLE, version='2.0') - core._register_object(bundle.objects[0].__class__, version='2.0') + bundle = parsing.dict_to_stix2(BUNDLE, version='2.0') + parsing._register_object(bundle.objects[0].__class__, version='2.0') v = 'v20' - assert bundle.objects[0].type in core.STIX2_OBJ_MAPS[v]['objects'] + assert bundle.objects[0].type in parsing.STIX2_OBJ_MAPS[v]['objects'] # spec_version is not in STIX 2.0, and is required in 2.1, so this # suffices as a test for a STIX 2.0 object. assert "spec_version" not in bundle.objects[0] def test_register_marking_with_version(): - core._register_marking(stix2.v20.TLP_WHITE.__class__, version='2.0') + parsing._register_marking(stix2.v20.TLP_WHITE.__class__, version='2.0') v = 'v20' - assert stix2.v20.TLP_WHITE.definition._type in core.STIX2_OBJ_MAPS[v]['markings'] + assert stix2.v20.TLP_WHITE.definition._type in parsing.STIX2_OBJ_MAPS[v]['markings'] assert v in str(stix2.v20.TLP_WHITE.__class__) @pytest.mark.xfail(reason="The default version is no longer 2.0", condition=stix2.DEFAULT_VERSION != "2.0") def test_register_marking_with_no_version(): # Uses default version (2.0 in this case) - core._register_marking(stix2.v20.TLP_WHITE.__class__) + parsing._register_marking(stix2.v20.TLP_WHITE.__class__) v = 'v20' - assert stix2.v20.TLP_WHITE.definition._type in core.STIX2_OBJ_MAPS[v]['markings'] + assert stix2.v20.TLP_WHITE.definition._type in parsing.STIX2_OBJ_MAPS[v]['markings'] assert v in str(stix2.v20.TLP_WHITE.__class__) @@ -128,10 +128,10 @@ def test_register_observable_with_version(): }, }, ) - core._register_observable(observed_data.objects['0'].__class__, version='2.0') + parsing._register_observable(observed_data.objects['0'].__class__, version='2.0') v = 'v20' - assert observed_data.objects['0'].type in core.STIX2_OBJ_MAPS[v]['observables'] + assert observed_data.objects['0'].type in parsing.STIX2_OBJ_MAPS[v]['observables'] assert v in str(observed_data.objects['0'].__class__) @@ -166,11 +166,11 @@ def test_register_observable_extension_with_version(): }, }, ) - core._register_observable_extension(observed_data.objects['0'], observed_data.objects['0'].extensions['ntfs-ext'].__class__, version='2.0') + parsing._register_observable_extension(observed_data.objects['0'], observed_data.objects['0'].extensions['ntfs-ext'].__class__, version='2.0') v = 'v20' - assert observed_data.objects['0'].type in core.STIX2_OBJ_MAPS[v]['observables'] + assert observed_data.objects['0'].type in parsing.STIX2_OBJ_MAPS[v]['observables'] assert v in str(observed_data.objects['0'].__class__) - assert observed_data.objects['0'].extensions['ntfs-ext']._type in core.STIX2_OBJ_MAPS[v]['observable-extensions']['file'] + assert observed_data.objects['0'].extensions['ntfs-ext']._type in parsing.STIX2_OBJ_MAPS[v]['observable-extensions']['file'] assert v in str(observed_data.objects['0'].extensions['ntfs-ext'].__class__) diff --git a/stix2/test/v20/test_custom.py b/stix2/test/v20/test_custom.py index b986777..e24393c 100644 --- a/stix2/test/v20/test_custom.py +++ b/stix2/test/v20/test_custom.py @@ -967,7 +967,7 @@ def test_register_custom_object(): class CustomObject2(object): _type = 'awesome-object' - stix2.core._register_object(CustomObject2, version="2.0") + stix2.parsing._register_object(CustomObject2, version="2.0") # Note that we will always check against newest OBJ_MAP. assert (CustomObject2._type, CustomObject2) in stix2.v20.OBJ_MAP.items() diff --git a/stix2/test/v21/test_core.py b/stix2/test/v21/test_core.py index 2018395..48f0055 100644 --- a/stix2/test/v21/test_core.py +++ b/stix2/test/v21/test_core.py @@ -1,7 +1,7 @@ import pytest import stix2 -from stix2 import core, exceptions +from stix2 import exceptions, parsing from .constants import IDENTITY_ID, OBSERVED_DATA_ID @@ -50,7 +50,7 @@ BUNDLE = { def test_dict_to_stix2_bundle_with_version(): with pytest.raises(exceptions.InvalidValueError) as excinfo: - core.dict_to_stix2(BUNDLE, version='2.0') + parsing.dict_to_stix2(BUNDLE, version='2.0') msg = "Invalid value for Bundle 'objects': Spec version 2.0 bundles don't yet support containing objects of a different spec version." assert str(excinfo.value) == msg @@ -58,7 +58,7 @@ def test_dict_to_stix2_bundle_with_version(): def test_parse_observable_with_version(): observable = {"type": "file", "name": "foo.exe"} - obs_obj = core.parse_observable(observable, version='2.1') + obs_obj = parsing.parse_observable(observable, version='2.1') v = 'v21' assert v in str(obs_obj.__class__) @@ -67,36 +67,36 @@ def test_parse_observable_with_version(): @pytest.mark.xfail(reason="The default version is not 2.1", condition=stix2.DEFAULT_VERSION != "2.1") def test_parse_observable_with_no_version(): observable = {"type": "file", "name": "foo.exe"} - obs_obj = core.parse_observable(observable) + obs_obj = parsing.parse_observable(observable) v = 'v21' assert v in str(obs_obj.__class__) def test_register_object_with_version(): - bundle = core.dict_to_stix2(BUNDLE, version='2.1') - core._register_object(bundle.objects[0].__class__) + bundle = parsing.dict_to_stix2(BUNDLE, version='2.1') + parsing._register_object(bundle.objects[0].__class__) v = 'v21' - assert bundle.objects[0].type in core.STIX2_OBJ_MAPS[v]['objects'] + assert bundle.objects[0].type in parsing.STIX2_OBJ_MAPS[v]['objects'] assert bundle.objects[0].spec_version == "2.1" def test_register_marking_with_version(): - core._register_marking(stix2.v21.TLP_WHITE.__class__, version='2.1') + parsing._register_marking(stix2.v21.TLP_WHITE.__class__, version='2.1') v = 'v21' - assert stix2.v21.TLP_WHITE.definition._type in core.STIX2_OBJ_MAPS[v]['markings'] + assert stix2.v21.TLP_WHITE.definition._type in parsing.STIX2_OBJ_MAPS[v]['markings'] assert v in str(stix2.v21.TLP_WHITE.__class__) @pytest.mark.xfail(reason="The default version is not 2.1", condition=stix2.DEFAULT_VERSION != "2.1") def test_register_marking_with_no_version(): # Uses default version (2.0 in this case) - core._register_marking(stix2.v21.TLP_WHITE.__class__) + parsing._register_marking(stix2.v21.TLP_WHITE.__class__) v = 'v21' - assert stix2.v21.TLP_WHITE.definition._type in core.STIX2_OBJ_MAPS[v]['markings'] + assert stix2.v21.TLP_WHITE.definition._type in parsing.STIX2_OBJ_MAPS[v]['markings'] assert v in str(stix2.v21.TLP_WHITE.__class__) @@ -131,10 +131,10 @@ def test_register_observable_with_default_version(): }, }, ) - core._register_observable(observed_data.objects['0'].__class__) + parsing._register_observable(observed_data.objects['0'].__class__) v = 'v21' - assert observed_data.objects['0'].type in core.STIX2_OBJ_MAPS[v]['observables'] + assert observed_data.objects['0'].type in parsing.STIX2_OBJ_MAPS[v]['observables'] assert v in str(observed_data.objects['0'].__class__) @@ -169,11 +169,11 @@ def test_register_observable_extension_with_default_version(): }, }, ) - core._register_observable_extension(observed_data.objects['0'], observed_data.objects['0'].extensions['ntfs-ext'].__class__) + parsing._register_observable_extension(observed_data.objects['0'], observed_data.objects['0'].extensions['ntfs-ext'].__class__) v = 'v21' - assert observed_data.objects['0'].type in core.STIX2_OBJ_MAPS[v]['observables'] + assert observed_data.objects['0'].type in parsing.STIX2_OBJ_MAPS[v]['observables'] assert v in str(observed_data.objects['0'].__class__) - assert observed_data.objects['0'].extensions['ntfs-ext']._type in core.STIX2_OBJ_MAPS[v]['observable-extensions']['file'] + assert observed_data.objects['0'].extensions['ntfs-ext']._type in parsing.STIX2_OBJ_MAPS[v]['observable-extensions']['file'] assert v in str(observed_data.objects['0'].extensions['ntfs-ext'].__class__) diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index ef57a9b..a9969b8 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -1186,7 +1186,7 @@ def test_register_custom_object(): class CustomObject2(object): _type = 'awesome-object' - stix2.core._register_object(CustomObject2, version="2.1") + stix2.parsing._register_object(CustomObject2, version="2.1") # Note that we will always check against newest OBJ_MAP. assert (CustomObject2._type, CustomObject2) in stix2.v21.OBJ_MAP.items() From b4700e6d00d1c46e91bede4b6e1cf128334d8369 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Fri, 27 Mar 2020 06:33:29 -0400 Subject: [PATCH 13/17] Fix import errors And pin medallion version for testing. --- stix2/utils.py | 6 ++---- tox.ini | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/stix2/utils.py b/stix2/utils.py index da04a3e..69489d9 100644 --- a/stix2/utils.py +++ b/stix2/utils.py @@ -12,7 +12,7 @@ import re from dateutil import parser import pytz -import stix2.base +import stix2 from .exceptions import ( InvalidValueError, RevokeError, UnmodifiablePropertyError, @@ -233,14 +233,12 @@ def find_property_index(obj, search_key, search_value): Returns: int: An index; -1 if the key and value aren't found """ - from .base import _STIXBase - # Special-case keys which are numbers-as-strings, e.g. for cyber-observable # mappings. Use the int value of the key as the index. if search_key.isdigit(): return int(search_key) - if isinstance(obj, _STIXBase): + if isinstance(obj, stix2.base._STIXBase): if search_key in obj and obj[search_key] == search_value: idx = _find(obj.object_properties(), search_key) else: diff --git a/tox.ini b/tox.ini index d34aac1..38ecb61 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ deps = fuzzywuzzy haversine python-Levenshtein - medallion + medallion<2.0.0 commands = python -m pytest --cov=stix2 stix2/test/ --cov-report term-missing -W ignore::stix2.exceptions.STIXDeprecationWarning From c494a2e47798f9df0e189158f9022da88bca9378 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Wed, 1 Apr 2020 21:52:04 -0400 Subject: [PATCH 14/17] Use TypeProperty.clean() to verify type format --- stix2/custom.py | 42 +----------------------- stix2/parsing.py | 62 ++++++++++------------------------- stix2/properties.py | 31 ++++++++++++++++-- stix2/test/v21/test_custom.py | 6 ++-- stix2/utils.py | 1 - stix2/v20/bundle.py | 2 +- stix2/v20/common.py | 2 +- stix2/v20/observables.py | 38 ++++++++++----------- stix2/v20/sdo.py | 26 +++++++-------- stix2/v20/sro.py | 4 +-- stix2/v21/bundle.py | 2 +- stix2/v21/common.py | 4 +-- stix2/v21/observables.py | 38 ++++++++++----------- stix2/v21/sdo.py | 38 ++++++++++----------- stix2/v21/sro.py | 4 +-- 15 files changed, 129 insertions(+), 171 deletions(-) diff --git a/stix2/custom.py b/stix2/custom.py index 6d1abc5..387906b 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -8,34 +8,12 @@ from .parsing import ( _register_marking, _register_object, _register_observable, _register_observable_extension, ) -from .utils import ( - PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, get_class_hierarchy_names, -) +from .utils import PREFIX_21_REGEX, get_class_hierarchy_names def _custom_object_builder(cls, type, properties, version, base_class): class _CustomObject(cls, base_class): - if version == "2.0": - 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, - ) - else: # 2.1+ - 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 (-) " - "and must begin with an a-z character" % type, - ) - - if len(type) < 3 or len(type) > 250: - raise ValueError( - "Invalid 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())]") @@ -79,24 +57,6 @@ def _custom_observable_builder(cls, type, properties, version, base_class, id_co class _CustomObservable(cls, base_class): - if version == "2.0": - 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, - ) - else: # 2.1+ - 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 (-) " - "and must begin with an a-z character" % type, - ) - - if len(type) < 3 or len(type) > 250: - raise ValueError("Invalid observable 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())]") diff --git a/stix2/parsing.py b/stix2/parsing.py index 7dedc15..f355f41 100644 --- a/stix2/parsing.py +++ b/stix2/parsing.py @@ -7,11 +7,9 @@ import re import stix2 -from .base import _Observable +from .base import _DomainObject, _Observable from .exceptions import ParseError -from .utils import ( - EXT_21_REGEX, PREFIX_21_REGEX, TYPE_21_REGEX, TYPE_REGEX, _get_dict, -) +from .utils import PREFIX_21_REGEX, _get_dict STIX2_OBJ_MAPS = {} @@ -204,6 +202,14 @@ def _register_object(new_type, version=None): """ + if not issubclass(new_type, _DomainObject): + raise ValueError( + "'%s' must be created with the @CustomObject decorator." % + new_type.__name__, + ) + + new_type._properties['type'].clean(new_type._type) + if version: v = 'v' + version.replace('.', '') else: @@ -224,28 +230,6 @@ def _register_marking(new_marking, version=None): """ - 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": @@ -273,6 +257,8 @@ def _register_observable(new_observable, version=None): """ + new_observable._properties['type'].clean(new_observable._type) + if version: v = 'v' + version.replace('.', '') else: @@ -304,26 +290,12 @@ def _register_observable_extension( if not issubclass(obs_class, _Observable): raise ValueError("'observable' must be a valid Observable class!") - if version == "2.0": - if not re.match(TYPE_REGEX, ext_type): - raise ValueError( - "Invalid extension type name '%s': must only contain the " - "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % - ext_type, - ) - else: # 2.1+ - if not re.match(EXT_21_REGEX, ext_type): - raise ValueError( - "Invalid extension type name '%s': must only contain the " - "characters a-z (lowercase ASCII), 0-9, hyphen (-), " - "must begin with an a-z character" - "and end with '-ext'." % ext_type, - ) - - if len(ext_type) < 3 or len(ext_type) > 250: + temp_prop = stix2.properties.TypeProperty(ext_type, spec_version=version) + temp_prop.clean(ext_type) + if not ext_type.endswith('-ext'): raise ValueError( - "Invalid extension type name '%s': must be between 3 and 250" - " characters." % ext_type, + "Invalid extension type name '%s': must end with '-ext'." % + ext_type, ) if not new_extension._properties: diff --git a/stix2/properties.py b/stix2/properties.py index be66533..4ccfa89 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -17,7 +17,10 @@ from .exceptions import ( MutuallyExclusivePropertiesError, ) from .parsing import STIX2_OBJ_MAPS, parse, parse_observable -from .utils import _get_dict, get_class_hierarchy_names, parse_into_datetime +from .utils import ( + TYPE_21_REGEX, TYPE_REGEX, _get_dict, get_class_hierarchy_names, + parse_into_datetime, +) try: from collections.abc import Mapping @@ -232,9 +235,33 @@ class StringProperty(Property): class TypeProperty(Property): - def __init__(self, type): + def __init__(self, type, spec_version=stix2.DEFAULT_VERSION): + self.spec_version = spec_version super(TypeProperty, self).__init__(fixed=type) + def clean(self, value): + if self.spec_version == "2.0": + 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, + ) + else: # 2.1+ + 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 (-) " + "and must begin with an a-z character" % type, + ) + + if len(type) < 3 or len(type) > 250: + raise ValueError( + "Invalid type name '%s': must be between 3 and 250 characters." % type, + ) + + return value + class IDProperty(Property): diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index a9969b8..2183d4f 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -1186,9 +1186,9 @@ def test_register_custom_object(): class CustomObject2(object): _type = 'awesome-object' - stix2.parsing._register_object(CustomObject2, version="2.1") - # Note that we will always check against newest OBJ_MAP. - assert (CustomObject2._type, CustomObject2) in stix2.v21.OBJ_MAP.items() + with pytest.raises(ValueError) as excinfo: + stix2.parsing._register_object(CustomObject2, version="2.1") + assert '@CustomObject decorator' in str(excinfo) def test_extension_property_location(): diff --git a/stix2/utils.py b/stix2/utils.py index 69489d9..7acf523 100644 --- a/stix2/utils.py +++ b/stix2/utils.py @@ -28,7 +28,6 @@ STIX_UNMOD_PROPERTIES = ['created', 'created_by_ref', 'id', 'type'] TYPE_REGEX = re.compile(r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$') 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].*') diff --git a/stix2/v20/bundle.py b/stix2/v20/bundle.py index b811a79..25b948d 100644 --- a/stix2/v20/bundle.py +++ b/stix2/v20/bundle.py @@ -15,7 +15,7 @@ class Bundle(_STIXBase20): _type = 'bundle' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), # Not technically correct: STIX 2.0 spec doesn't say spec_version must # have this value, but it's all we support for now. diff --git a/stix2/v20/common.py b/stix2/v20/common.py index 55f2c3e..a92f81d 100644 --- a/stix2/v20/common.py +++ b/stix2/v20/common.py @@ -120,7 +120,7 @@ class MarkingDefinition(_STIXBase20, _MarkingsMixin): _type = 'marking-definition' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW)), diff --git a/stix2/v20/observables.py b/stix2/v20/observables.py index eb9bfde..3491c47 100644 --- a/stix2/v20/observables.py +++ b/stix2/v20/observables.py @@ -26,7 +26,7 @@ class Artifact(_Observable): _type = 'artifact' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('mime_type', StringProperty()), ('payload_bin', BinaryProperty()), ('url', StringProperty()), @@ -47,7 +47,7 @@ class AutonomousSystem(_Observable): _type = 'autonomous-system' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('number', IntegerProperty(required=True)), ('name', StringProperty()), ('rir', StringProperty()), @@ -62,7 +62,7 @@ class Directory(_Observable): _type = 'directory' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('path', StringProperty(required=True)), ('path_enc', StringProperty()), # these are not the created/modified timestamps of the object itself @@ -81,7 +81,7 @@ class DomainName(_Observable): _type = 'domain-name' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('value', StringProperty(required=True)), ('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'domain-name']))), ('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)), @@ -95,7 +95,7 @@ class EmailAddress(_Observable): _type = 'email-addr' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('value', StringProperty(required=True)), ('display_name', StringProperty()), ('belongs_to_ref', ObjectReferenceProperty(valid_types='user-account')), @@ -127,7 +127,7 @@ class EmailMessage(_Observable): _type = 'email-message' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('is_multipart', BooleanProperty(required=True)), ('date', TimestampProperty()), ('content_type', StringProperty()), @@ -306,7 +306,7 @@ class File(_Observable): _type = 'file' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('hashes', HashesProperty()), ('size', IntegerProperty()), ('name', StringProperty()), @@ -339,7 +339,7 @@ class IPv4Address(_Observable): _type = 'ipv4-addr' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('value', StringProperty(required=True)), ('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types='mac-addr'))), ('belongs_to_refs', ListProperty(ObjectReferenceProperty(valid_types='autonomous-system'))), @@ -354,7 +354,7 @@ class IPv6Address(_Observable): _type = 'ipv6-addr' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('value', StringProperty(required=True)), ('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types='mac-addr'))), ('belongs_to_refs', ListProperty(ObjectReferenceProperty(valid_types='autonomous-system'))), @@ -369,7 +369,7 @@ class MACAddress(_Observable): _type = 'mac-addr' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('value', StringProperty(required=True)), ('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)), ]) @@ -382,7 +382,7 @@ class Mutex(_Observable): _type = 'mutex' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('name', StringProperty(required=True)), ('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)), ]) @@ -483,7 +483,7 @@ class NetworkTraffic(_Observable): _type = 'network-traffic' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('start', TimestampProperty()), ('end', TimestampProperty()), ('is_active', BooleanProperty()), @@ -575,7 +575,7 @@ class Process(_Observable): _type = 'process' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('is_hidden', BooleanProperty()), ('pid', IntegerProperty()), ('name', StringProperty()), @@ -615,7 +615,7 @@ class Software(_Observable): _type = 'software' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('name', StringProperty(required=True)), ('cpe', StringProperty()), ('languages', ListProperty(StringProperty)), @@ -632,7 +632,7 @@ class URL(_Observable): _type = 'url' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('value', StringProperty(required=True)), ('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)), ]) @@ -659,7 +659,7 @@ class UserAccount(_Observable): _type = 'user-account' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('user_id', StringProperty(required=True)), ('account_login', StringProperty()), ('account_type', StringProperty()), # open vocab @@ -713,7 +713,7 @@ class WindowsRegistryKey(_Observable): _type = 'windows-registry-key' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('key', StringProperty(required=True)), ('values', ListProperty(EmbeddedObjectProperty(type=WindowsRegistryValueType))), # this is not the modified timestamps of the object itself @@ -757,7 +757,7 @@ class X509Certificate(_Observable): _type = 'x509-certificate' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('is_self_signed', BooleanProperty()), ('hashes', HashesProperty()), ('version', StringProperty()), @@ -791,7 +791,7 @@ def CustomObservable(type='x-custom-observable', properties=None): """ def wrapper(cls): _properties = list(itertools.chain.from_iterable([ - [('type', TypeProperty(type))], + [('type', TypeProperty(type, spec_version='2.0'))], properties, [('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=type))], ])) diff --git a/stix2/v20/sdo.py b/stix2/v20/sdo.py index cb5ba04..8cbd94b 100644 --- a/stix2/v20/sdo.py +++ b/stix2/v20/sdo.py @@ -24,7 +24,7 @@ class AttackPattern(_DomainObject): _type = 'attack-pattern' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -47,7 +47,7 @@ class Campaign(_DomainObject): _type = 'campaign' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -73,7 +73,7 @@ class CourseOfAction(_DomainObject): _type = 'course-of-action' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -95,7 +95,7 @@ class Identity(_DomainObject): _type = 'identity' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -120,7 +120,7 @@ class Indicator(_DomainObject): _type = 'indicator' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -151,7 +151,7 @@ class IntrusionSet(_DomainObject): _type = 'intrusion-set' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -180,7 +180,7 @@ class Malware(_DomainObject): _type = 'malware' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -203,7 +203,7 @@ class ObservedData(_DomainObject): _type = 'observed-data' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -233,7 +233,7 @@ class Report(_DomainObject): _type = 'report' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -257,7 +257,7 @@ class ThreatActor(_DomainObject): _type = 'threat-actor' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -287,7 +287,7 @@ class Tool(_DomainObject): _type = 'tool' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -311,7 +311,7 @@ class Vulnerability(_DomainObject): _type = 'vulnerability' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -358,7 +358,7 @@ def CustomObject(type='x-custom-type', properties=None): def wrapper(cls): _properties = list(itertools.chain.from_iterable([ [ - ('type', TypeProperty(type)), + ('type', TypeProperty(type, spec_version='2.0')), ('id', IDProperty(type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), diff --git a/stix2/v20/sro.py b/stix2/v20/sro.py index b7d1ad3..1372a5e 100644 --- a/stix2/v20/sro.py +++ b/stix2/v20/sro.py @@ -20,7 +20,7 @@ class Relationship(_RelationshipObject): _type = 'relationship' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -59,7 +59,7 @@ class Sighting(_RelationshipObject): _type = 'sighting' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), diff --git a/stix2/v21/bundle.py b/stix2/v21/bundle.py index ca60fe0..1b8c652 100644 --- a/stix2/v21/bundle.py +++ b/stix2/v21/bundle.py @@ -16,7 +16,7 @@ class Bundle(_STIXBase21): _type = 'bundle' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('objects', ListProperty(STIXObjectProperty(spec_version='2.1'))), ]) diff --git a/stix2/v21/common.py b/stix2/v21/common.py index 4254767..115bdb5 100644 --- a/stix2/v21/common.py +++ b/stix2/v21/common.py @@ -87,7 +87,7 @@ class LanguageContent(_STIXBase21): _type = 'language-content' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -158,7 +158,7 @@ class MarkingDefinition(_STIXBase21, _MarkingsMixin): _type = 'marking-definition' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type)), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py index e71e445..275bbbe 100644 --- a/stix2/v21/observables.py +++ b/stix2/v21/observables.py @@ -29,7 +29,7 @@ class Artifact(_Observable): _type = 'artifact' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('mime_type', StringProperty()), ('payload_bin', BinaryProperty()), @@ -59,7 +59,7 @@ class AutonomousSystem(_Observable): _type = 'autonomous-system' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('number', IntegerProperty(required=True)), ('name', StringProperty()), @@ -81,7 +81,7 @@ class Directory(_Observable): _type = 'directory' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('path', StringProperty(required=True)), ('path_enc', StringProperty()), @@ -107,7 +107,7 @@ class DomainName(_Observable): _type = 'domain-name' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('value', StringProperty(required=True)), ('resolves_to_refs', ListProperty(ReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'domain-name'], spec_version='2.1'))), @@ -128,7 +128,7 @@ class EmailAddress(_Observable): _type = 'email-addr' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('value', StringProperty(required=True)), ('display_name', StringProperty()), @@ -168,7 +168,7 @@ class EmailMessage(_Observable): _type = 'email-message' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('is_multipart', BooleanProperty(required=True)), ('date', TimestampProperty()), @@ -361,7 +361,7 @@ class File(_Observable): _type = 'file' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('hashes', HashesProperty(spec_version='2.1')), ('size', IntegerProperty(min=0)), @@ -397,7 +397,7 @@ class IPv4Address(_Observable): _type = 'ipv4-addr' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('value', StringProperty(required=True)), ('resolves_to_refs', ListProperty(ReferenceProperty(valid_types='mac-addr', spec_version='2.1'))), @@ -419,7 +419,7 @@ class IPv6Address(_Observable): _type = 'ipv6-addr' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('value', StringProperty(required=True)), ('resolves_to_refs', ListProperty(ReferenceProperty(valid_types='mac-addr', spec_version='2.1'))), @@ -441,7 +441,7 @@ class MACAddress(_Observable): _type = 'mac-addr' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('value', StringProperty(required=True)), ('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)), @@ -461,7 +461,7 @@ class Mutex(_Observable): _type = 'mutex' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('name', StringProperty(required=True)), ('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)), @@ -586,7 +586,7 @@ class NetworkTraffic(_Observable): _type = 'network-traffic' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('start', TimestampProperty()), ('end', TimestampProperty()), @@ -711,7 +711,7 @@ class Process(_Observable): _type = 'process' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('is_hidden', BooleanProperty()), ('pid', IntegerProperty()), @@ -756,7 +756,7 @@ class Software(_Observable): _type = 'software' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('name', StringProperty(required=True)), ('cpe', StringProperty()), @@ -781,7 +781,7 @@ class URL(_Observable): _type = 'url' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('value', StringProperty(required=True)), ('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)), @@ -816,7 +816,7 @@ class UserAccount(_Observable): _type = 'user-account' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('user_id', StringProperty()), ('credential', StringProperty()), @@ -879,7 +879,7 @@ class WindowsRegistryKey(_Observable): _type = 'windows-registry-key' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('key', StringProperty()), ('values', ListProperty(EmbeddedObjectProperty(type=WindowsRegistryValueType))), @@ -931,7 +931,7 @@ class X509Certificate(_Observable): _type = 'x509-certificate' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('is_self_signed', BooleanProperty()), ('hashes', HashesProperty(spec_version='2.1')), @@ -983,7 +983,7 @@ def CustomObservable(type='x-custom-observable', properties=None, id_contrib_pro """ def wrapper(cls): _properties = list(itertools.chain.from_iterable([ - [('type', TypeProperty(type))], + [('type', TypeProperty(type, spec_version='2.1'))], [('id', IDProperty(type, spec_version='2.1'))], properties, [('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=type))], diff --git a/stix2/v21/sdo.py b/stix2/v21/sdo.py index 1e9633e..396b030 100644 --- a/stix2/v21/sdo.py +++ b/stix2/v21/sdo.py @@ -29,7 +29,7 @@ class AttackPattern(_DomainObject): _type = 'attack-pattern' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -57,7 +57,7 @@ class Campaign(_DomainObject): _type = 'campaign' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -97,7 +97,7 @@ class CourseOfAction(_DomainObject): _type = 'course-of-action' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -123,7 +123,7 @@ class Grouping(_DomainObject): _type = 'grouping' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -151,7 +151,7 @@ class Identity(_DomainObject): _type = 'identity' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -181,7 +181,7 @@ class Indicator(_DomainObject): _type = 'indicator' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -241,7 +241,7 @@ class Infrastructure(_DomainObject): _type = 'infrastructure' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -282,7 +282,7 @@ class IntrusionSet(_DomainObject): _type = 'intrusion-set' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -325,7 +325,7 @@ class Location(_DomainObject): _type = 'location' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -433,7 +433,7 @@ class Malware(_DomainObject): _type = 'malware' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -486,7 +486,7 @@ class MalwareAnalysis(_DomainObject): _type = 'malware-analysis' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), @@ -531,7 +531,7 @@ class Note(_DomainObject): _type = 'note' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -559,7 +559,7 @@ class ObservedData(_DomainObject): _type = 'observed-data' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -615,7 +615,7 @@ class Opinion(_DomainObject): _type = 'opinion' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -653,7 +653,7 @@ class Report(_DomainObject): _type = 'report' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -682,7 +682,7 @@ class ThreatActor(_DomainObject): _type = 'threat-actor' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -729,7 +729,7 @@ class Tool(_DomainObject): _type = 'tool' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -759,7 +759,7 @@ class Vulnerability(_DomainObject): _type = 'vulnerability' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -809,7 +809,7 @@ def CustomObject(type='x-custom-type', properties=None): def wrapper(cls): _properties = list(itertools.chain.from_iterable([ [ - ('type', TypeProperty(type)), + ('type', TypeProperty(type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), diff --git a/stix2/v21/sro.py b/stix2/v21/sro.py index 3ffdd77..9f85004 100644 --- a/stix2/v21/sro.py +++ b/stix2/v21/sro.py @@ -21,7 +21,7 @@ class Relationship(_RelationshipObject): _type = 'relationship' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), @@ -76,7 +76,7 @@ class Sighting(_RelationshipObject): _type = 'sighting' _properties = OrderedDict([ - ('type', TypeProperty(_type)), + ('type', TypeProperty(_type, spec_version='2.1')), ('spec_version', StringProperty(fixed='2.1')), ('id', IDProperty(_type, spec_version='2.1')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), From 13cddf9d6d4e2f97b7e8f4db20022f553369e747 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Thu, 2 Apr 2020 08:17:34 -0400 Subject: [PATCH 15/17] Move TypeProperty format checks to __init__ TypeProperty uses a fixed value, so check() was never called. This way also runs the check at object registration time because the wrapper creates an instance of TypeProperty and doesn't have to wait for the object to be instantiated so clean() can be called. Also fix some tests. --- stix2/parsing.py | 21 ++++++++------ stix2/properties.py | 54 ++++++++++++++++++++--------------- stix2/test/v20/test_custom.py | 13 ++++----- stix2/test/v21/test_custom.py | 18 ++++++------ 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/stix2/parsing.py b/stix2/parsing.py index b81a07d..db0c693 100644 --- a/stix2/parsing.py +++ b/stix2/parsing.py @@ -200,6 +200,11 @@ def _register_object(new_type, version=None): version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If None, use latest version. + Raises: + ValueError: If the class being registered wasn't created with the + @CustomObject decorator. + DuplicateRegistrationError: If the class has already been registered. + """ if not issubclass(new_type, _DomainObject): @@ -208,8 +213,6 @@ def _register_object(new_type, version=None): new_type.__name__, ) - new_type._properties['type'].clean(new_type._type) - if version: v = 'v' + version.replace('.', '') else: @@ -232,8 +235,11 @@ def _register_marking(new_marking, version=None): """ + mark_type = new_marking._type properties = new_marking._properties + stix2.properties._validate_type(mark_type, version) + if version == "2.1": for prop_name, prop_value in properties.items(): if not re.match(PREFIX_21_REGEX, prop_name): @@ -246,9 +252,9 @@ def _register_marking(new_marking, version=None): v = 'v' + stix2.DEFAULT_VERSION.replace('.', '') OBJ_MAP_MARKING = STIX2_OBJ_MAPS[v]['markings'] - if new_marking._type in OBJ_MAP_MARKING.keys(): - raise DuplicateRegistrationError("STIX Marking", new_marking._type) - OBJ_MAP_MARKING[new_marking._type] = new_marking + if mark_type in OBJ_MAP_MARKING.keys(): + raise DuplicateRegistrationError("STIX Marking", mark_type) + OBJ_MAP_MARKING[mark_type] = new_marking def _register_observable(new_observable, version=None): @@ -261,8 +267,6 @@ def _register_observable(new_observable, version=None): """ - new_observable._properties['type'].clean(new_observable._type) - if version: v = 'v' + version.replace('.', '') else: @@ -296,8 +300,7 @@ def _register_observable_extension( if not issubclass(obs_class, _Observable): raise ValueError("'observable' must be a valid Observable class!") - temp_prop = stix2.properties.TypeProperty(ext_type, spec_version=version) - temp_prop.clean(ext_type) + stix2.properties._validate_type(ext_type, version) if not new_extension._properties: raise ValueError( diff --git a/stix2/properties.py b/stix2/properties.py index 4ccfa89..39c409c 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -84,6 +84,36 @@ def _validate_id(id_, spec_version, required_prefix): raise ValueError(ERROR_INVALID_ID.format(id_)) +def _validate_type(type_, spec_version): + """ + Check the STIX type name for correctness, raise an exception if there are + errors. + + :param type_: The STIX type name + :param spec_version: The STIX specification version to use + :raises ValueError: If there are any errors with the identifier + """ + if spec_version == "2.0": + 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_, + ) + else: # 2.1+ + 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 (-) " + "and must begin with an a-z character" % type_, + ) + + if len(type_) < 3 or len(type_) > 250: + raise ValueError( + "Invalid type name '%s': must be between 3 and 250 characters." % type_, + ) + + class Property(object): """Represent a property of STIX data type. @@ -236,32 +266,10 @@ class StringProperty(Property): class TypeProperty(Property): def __init__(self, type, spec_version=stix2.DEFAULT_VERSION): + _validate_type(type, spec_version) self.spec_version = spec_version super(TypeProperty, self).__init__(fixed=type) - def clean(self, value): - if self.spec_version == "2.0": - 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, - ) - else: # 2.1+ - 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 (-) " - "and must begin with an a-z character" % type, - ) - - if len(type) < 3 or len(type) > 250: - raise ValueError( - "Invalid type name '%s': must be between 3 and 250 characters." % type, - ) - - return value - class IDProperty(Property): diff --git a/stix2/test/v20/test_custom.py b/stix2/test/v20/test_custom.py index 8342fec..57f8aac 100644 --- a/stix2/test/v20/test_custom.py +++ b/stix2/test/v20/test_custom.py @@ -483,7 +483,7 @@ def test_custom_observable_object_invalid_type_name(): ) class NewObs(object): pass # pragma: no cover - assert "Invalid observable type name 'x':" in str(excinfo.value) + assert "Invalid type name 'x':" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v20.CustomObservable( @@ -493,7 +493,7 @@ def test_custom_observable_object_invalid_type_name(): ) class NewObs2(object): pass # pragma: no cover - assert "Invalid observable type name 'x_new_obs':" in str(excinfo.value) + assert "Invalid type name 'x_new_obs':" in str(excinfo.value) def test_custom_observable_object_invalid_ref_property(): @@ -808,7 +808,7 @@ def test_custom_extension_invalid_type_name(): ) class FooExtension(): pass # pragma: no cover - assert "Invalid extension type name 'x':" in str(excinfo.value) + assert "Invalid type name 'x':" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v20.CustomExtension( @@ -818,7 +818,7 @@ def test_custom_extension_invalid_type_name(): ) class BlaExtension(): pass # pragma: no cover - assert "Invalid extension type name 'x_new_ext':" in str(excinfo.value) + assert "Invalid type name 'x_new_ext':" in str(excinfo.value) def test_custom_extension_no_properties(): @@ -968,9 +968,8 @@ def test_register_custom_object(): class CustomObject2(object): _type = 'awesome-object' - stix2.parsing._register_object(CustomObject2, version="2.0") - # Note that we will always check against newest OBJ_MAP. - assert (CustomObject2._type, CustomObject2) in stix2.v20.OBJ_MAP.items() + with pytest.raises(ValueError): + stix2.parsing._register_object(CustomObject2, version="2.0") def test_extension_property_location(): diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index 59eb53e..3646f11 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -375,7 +375,7 @@ def test_custom_marking_invalid_type_name(): ) class NewObj(object): pass # pragma: no cover - assert "Invalid marking type name 'x': " in str(excinfo.value) + assert "Invalid type name 'x': " in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomMarking( @@ -385,7 +385,7 @@ def test_custom_marking_invalid_type_name(): ) class NewObj2(object): pass # pragma: no cover - assert "Invalid marking type name 'x_new_marking':" in str(excinfo.value) + assert "Invalid type name 'x_new_marking':" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomMarking( @@ -395,7 +395,7 @@ def test_custom_marking_invalid_type_name(): ) class NewObj3(object): pass # pragma: no cover - assert "Invalid marking type name '7x-new-marking':" in str(excinfo.value) + assert "Invalid type name '7x-new-marking':" in str(excinfo.value) # Custom Objects @@ -619,7 +619,7 @@ def test_custom_observable_object_invalid_type_name(): ) class NewObs(object): pass # pragma: no cover - assert "Invalid observable type name 'x':" in str(excinfo.value) + assert "Invalid type name 'x':" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomObservable( @@ -629,7 +629,7 @@ def test_custom_observable_object_invalid_type_name(): ) class NewObs2(object): pass # pragma: no cover - assert "Invalid observable type name 'x_new_obs':" in str(excinfo.value) + assert "Invalid type name 'x_new_obs':" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomObservable( @@ -639,7 +639,7 @@ def test_custom_observable_object_invalid_type_name(): ) class NewObs3(object): pass # pragma: no cover - assert "Invalid observable type name '7x-new-obs':" in str(excinfo.value) + assert "Invalid type name '7x-new-obs':" in str(excinfo.value) def test_custom_observable_object_invalid_ref_property(): @@ -1005,7 +1005,7 @@ def test_custom_extension_invalid_type_name(): ) class FooExtension(): pass # pragma: no cover - assert "Invalid extension type name 'x':" in str(excinfo.value) + assert "Invalid type name 'x':" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomExtension( @@ -1015,7 +1015,7 @@ def test_custom_extension_invalid_type_name(): ) class BlaExtension(): pass # pragma: no cover - assert "Invalid extension type name 'x_new_ext':" in str(excinfo.value) + assert "Invalid type name 'x_new_ext':" in str(excinfo.value) with pytest.raises(ValueError) as excinfo: @stix2.v21.CustomExtension( @@ -1025,7 +1025,7 @@ def test_custom_extension_invalid_type_name(): ) class Bla2Extension(): pass # pragma: no cover - assert "Invalid extension type name '7x-new-ext':" in str(excinfo.value) + assert "Invalid type name '7x-new-ext':" in str(excinfo.value) def test_custom_extension_no_properties(): From d33adbcc716d53c12f8d4672e62ac9f41d748728 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Thu, 2 Apr 2020 08:22:49 -0400 Subject: [PATCH 16/17] Rename test files to align with module renaming --- stix2/test/v20/{test_core.py => test_parsing.py} | 0 stix2/test/v21/{test_core.py => test_parsing.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename stix2/test/v20/{test_core.py => test_parsing.py} (100%) rename stix2/test/v21/{test_core.py => test_parsing.py} (100%) diff --git a/stix2/test/v20/test_core.py b/stix2/test/v20/test_parsing.py similarity index 100% rename from stix2/test/v20/test_core.py rename to stix2/test/v20/test_parsing.py diff --git a/stix2/test/v21/test_core.py b/stix2/test/v21/test_parsing.py similarity index 100% rename from stix2/test/v21/test_core.py rename to stix2/test/v21/test_parsing.py From 14540c0ea16d9ec34851623c15f4471501e84cd5 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Thu, 2 Apr 2020 14:15:45 -0400 Subject: [PATCH 17/17] Clean up _register_* functions Made them consistent with _register_observable_extension, by: - moving validation logic there from _custom_*_builder functions - using a new function for ensuring properties are dict-like - using the library default spec version instead of None Fix #371, fix #372, fix #373. --- stix2/custom.py | 85 +++++++++------------------------ stix2/parsing.py | 47 ++++++++++++++++-- stix2/test/v20/test_markings.py | 4 +- stix2/test/v21/test_markings.py | 4 +- 4 files changed, 68 insertions(+), 72 deletions(-) diff --git a/stix2/custom.py b/stix2/custom.py index 387906b..f749b04 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -1,5 +1,4 @@ from collections import OrderedDict -import re import six @@ -8,22 +7,29 @@ from .parsing import ( _register_marking, _register_object, _register_observable, _register_observable_extension, ) -from .utils import PREFIX_21_REGEX, get_class_hierarchy_names + + +def _get_properties_dict(properties): + try: + return OrderedDict(properties) + except TypeError as e: + six.raise_from( + ValueError( + "properties must be dict-like, e.g. a list " + "containing tuples. For example, " + "[('property1', IntegerProperty())]", + ), + e, + ) def _custom_object_builder(cls, type, properties, version, base_class): + prop_dict = _get_properties_dict(properties) + class _CustomObject(cls, base_class): - 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 in properties: - 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) + _properties = prop_dict def __init__(self, **kwargs): base_class.__init__(self, **kwargs) @@ -34,14 +40,12 @@ def _custom_object_builder(cls, type, properties, version, base_class): def _custom_marking_builder(cls, type, properties, version, base_class): + prop_dict = _get_properties_dict(properties) class _CustomMarking(cls, base_class): - if not properties or not isinstance(properties, list): - raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") - _type = type - _properties = OrderedDict(properties) + _properties = prop_dict def __init__(self, **kwargs): base_class.__init__(self, **kwargs) @@ -55,44 +59,12 @@ def _custom_observable_builder(cls, type, properties, version, base_class, id_co if id_contrib_props is None: id_contrib_props = [] + prop_dict = _get_properties_dict(properties) + class _CustomObservable(cls, base_class): - if not properties or not isinstance(properties, list): - raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") - - if version == "2.0": - # If using STIX2.0, check properties ending in "_ref/s" are ObjectReferenceProperties - for prop_name, prop in properties: - if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)): - raise ValueError( - "'%s' is named like an object reference property but " - "is not an ObjectReferenceProperty." % prop_name, - ) - elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or - 'ObjectReferenceProperty' not in get_class_hierarchy_names(prop.contained))): - raise ValueError( - "'%s' is named like an object reference list property but " - "is not a ListProperty containing ObjectReferenceProperty." % prop_name, - ) - else: - # 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) - elif prop_name.endswith('_ref') and ('ReferenceProperty' not in get_class_hierarchy_names(prop)): - raise ValueError( - "'%s' is named like a reference property but " - "is not a ReferenceProperty." % prop_name, - ) - elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or - 'ReferenceProperty' not in get_class_hierarchy_names(prop.contained))): - raise ValueError( - "'%s' is named like a reference list property but " - "is not a ListProperty containing ReferenceProperty." % prop_name, - ) - _type = type - _properties = OrderedDict(properties) + _properties = prop_dict if version != '2.0': _id_contributing_properties = id_contrib_props @@ -105,18 +77,7 @@ def _custom_observable_builder(cls, type, properties, version, base_class, id_co def _custom_extension_builder(cls, observable, type, properties, version, base_class): - - try: - prop_dict = OrderedDict(properties) - except TypeError as e: - six.raise_from( - ValueError( - "Extension properties must be dict-like, e.g. a list " - "containing tuples. For example, " - "[('property1', IntegerProperty())]", - ), - e, - ) + prop_dict = _get_properties_dict(properties) class _CustomExtension(cls, base_class): diff --git a/stix2/parsing.py b/stix2/parsing.py index db0c693..baa3b7b 100644 --- a/stix2/parsing.py +++ b/stix2/parsing.py @@ -9,7 +9,7 @@ import stix2 from .base import _DomainObject, _Observable from .exceptions import DuplicateRegistrationError, ParseError -from .utils import PREFIX_21_REGEX, _get_dict +from .utils import PREFIX_21_REGEX, _get_dict, get_class_hierarchy_names STIX2_OBJ_MAPS = {} @@ -192,7 +192,7 @@ def parse_observable(data, _valid_refs=None, allow_custom=False, version=None): return obj_class(allow_custom=allow_custom, **obj) -def _register_object(new_type, version=None): +def _register_object(new_type, version=stix2.DEFAULT_VERSION): """Register a custom STIX Object type. Args: @@ -213,6 +213,13 @@ def _register_object(new_type, version=None): new_type.__name__, ) + properties = new_type._properties + + if version == "2.1": + for prop_name, prop 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: @@ -225,7 +232,7 @@ def _register_object(new_type, version=None): OBJ_MAP[new_type._type] = new_type -def _register_marking(new_marking, version=None): +def _register_marking(new_marking, version=stix2.DEFAULT_VERSION): """Register a custom STIX Marking Definition type. Args: @@ -257,7 +264,7 @@ def _register_marking(new_marking, version=None): OBJ_MAP_MARKING[mark_type] = new_marking -def _register_observable(new_observable, version=None): +def _register_observable(new_observable, version=stix2.DEFAULT_VERSION): """Register a custom STIX Cyber Observable type. Args: @@ -266,6 +273,38 @@ def _register_observable(new_observable, version=None): None, use latest version. """ + properties = new_observable._properties + + if version == "2.0": + # If using STIX2.0, check properties ending in "_ref/s" are ObjectReferenceProperties + for prop_name, prop in properties.items(): + if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)): + raise ValueError( + "'%s' is named like an object reference property but " + "is not an ObjectReferenceProperty." % prop_name, + ) + elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or + 'ObjectReferenceProperty' not in get_class_hierarchy_names(prop.contained))): + raise ValueError( + "'%s' is named like an object reference list property but " + "is not a ListProperty containing ObjectReferenceProperty." % prop_name, + ) + else: + # If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties + for prop_name, prop 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) + elif prop_name.endswith('_ref') and ('ReferenceProperty' not in get_class_hierarchy_names(prop)): + raise ValueError( + "'%s' is named like a reference property but " + "is not a ReferenceProperty." % prop_name, + ) + elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or + 'ReferenceProperty' not in get_class_hierarchy_names(prop.contained))): + raise ValueError( + "'%s' is named like a reference list property but " + "is not a ListProperty containing ReferenceProperty." % prop_name, + ) if version: v = 'v' + version.replace('.', '') diff --git a/stix2/test/v20/test_markings.py b/stix2/test/v20/test_markings.py index b34ef43..0753e86 100644 --- a/stix2/test/v20/test_markings.py +++ b/stix2/test/v20/test_markings.py @@ -249,14 +249,12 @@ def test_not_registered_marking_raises_exception(): def test_marking_wrong_type_construction(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): # Test passing wrong type for properties. @stix2.v20.CustomMarking('x-new-marking-type2', ("a", "b")) class NewObject3(object): pass - assert str(excinfo.value) == "Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]" - def test_campaign_add_markings(): campaign = stix2.v20.Campaign( diff --git a/stix2/test/v21/test_markings.py b/stix2/test/v21/test_markings.py index a2fca51..b0a0d09 100644 --- a/stix2/test/v21/test_markings.py +++ b/stix2/test/v21/test_markings.py @@ -277,14 +277,12 @@ def test_not_registered_marking_raises_exception(): def test_marking_wrong_type_construction(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): # Test passing wrong type for properties. @stix2.v21.CustomMarking('x-new-marking-type2', ("a", "b")) class NewObject3(object): pass - assert str(excinfo.value) == "Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]" - def test_campaign_add_markings(): campaign = stix2.v21.Campaign(