issue-365
parent
6842abb371
commit
9699c78ad8
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
|
@ -17,7 +18,7 @@ from .exceptions import (
|
||||||
from .markings.utils import validate
|
from .markings.utils import validate
|
||||||
from .utils import NOW, find_property_index, format_datetime, get_timestamp
|
from .utils import NOW, find_property_index, format_datetime, get_timestamp
|
||||||
from .utils import new_version as _new_version
|
from .utils import new_version as _new_version
|
||||||
from .utils import revoke as _revoke
|
from .utils import revoke as _revoke, PREFIX_21_REGEX
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
|
@ -76,6 +77,11 @@ def get_required_properties(properties):
|
||||||
class _STIXBase(Mapping):
|
class _STIXBase(Mapping):
|
||||||
"""Base class for STIX object types"""
|
"""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):
|
def object_properties(self):
|
||||||
props = set(self._properties.keys())
|
props = set(self._properties.keys())
|
||||||
custom_props = list(set(self._inner.keys()) - props)
|
custom_props = list(set(self._inner.keys()) - props)
|
||||||
|
@ -163,6 +169,11 @@ class _STIXBase(Mapping):
|
||||||
raise ExtraPropertiesError(cls, extra_kwargs)
|
raise ExtraPropertiesError(cls, extra_kwargs)
|
||||||
if custom_props:
|
if custom_props:
|
||||||
self._allow_custom = True
|
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)
|
# Remove any keyword arguments whose value is None or [] (i.e. empty list)
|
||||||
setting_kwargs = {}
|
setting_kwargs = {}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import stix2
|
||||||
from .base import _Observable, _STIXBase
|
from .base import _Observable, _STIXBase
|
||||||
from .exceptions import ParseError
|
from .exceptions import ParseError
|
||||||
from .markings import _MarkingsMixin
|
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 = {}
|
STIX2_OBJ_MAPS = {}
|
||||||
|
|
||||||
|
@ -210,6 +210,7 @@ def _register_object(new_type, version=None):
|
||||||
None, use latest version.
|
None, use latest version.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if version:
|
if version:
|
||||||
v = 'v' + version.replace('.', '')
|
v = 'v' + version.replace('.', '')
|
||||||
else:
|
else:
|
||||||
|
@ -248,6 +249,7 @@ def _register_observable(new_observable, version=None):
|
||||||
None, use latest version.
|
None, use latest version.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if version:
|
if version:
|
||||||
v = 'v' + version.replace('.', '')
|
v = 'v' + version.replace('.', '')
|
||||||
else:
|
else:
|
||||||
|
@ -289,8 +291,9 @@ def _register_observable_extension(
|
||||||
if not re.match(SCO21_EXT_REGEX, ext_type):
|
if not re.match(SCO21_EXT_REGEX, ext_type):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Invalid extension type name '%s': must only contain the "
|
"Invalid extension type name '%s': must only contain the "
|
||||||
"characters a-z (lowercase ASCII), 0-9, hyphen (-), and end "
|
"characters a-z (lowercase ASCII), 0-9, hyphen (-), "
|
||||||
"with '-ext'." % ext_type,
|
"must begin with an a-z character"
|
||||||
|
"and end with '-ext'." % ext_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(ext_type) < 3 or len(ext_type) > 250:
|
if len(ext_type) < 3 or len(ext_type) > 250:
|
||||||
|
|
|
@ -8,18 +8,28 @@ from .core import (
|
||||||
STIXDomainObject, _register_marking, _register_object,
|
STIXDomainObject, _register_marking, _register_object,
|
||||||
_register_observable, _register_observable_extension,
|
_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):
|
def _custom_object_builder(cls, type, properties, version):
|
||||||
class _CustomObject(cls, STIXDomainObject):
|
class _CustomObject(cls, STIXDomainObject):
|
||||||
|
|
||||||
if not re.match(TYPE_REGEX, type):
|
if version == "2.0":
|
||||||
raise ValueError(
|
if not re.match(TYPE_REGEX, type):
|
||||||
"Invalid type name '%s': must only contain the "
|
raise ValueError(
|
||||||
"characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type,
|
"Invalid type type name '%s': must only contain the "
|
||||||
)
|
"characters a-z (lowercase ASCII), 0-9, and hyphen (-)." %
|
||||||
elif len(type) < 3 or len(type) > 250:
|
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(
|
raise ValueError(
|
||||||
"Invalid type name '%s': must be between 3 and 250 characters." % type,
|
"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):
|
if not properties or not isinstance(properties, list):
|
||||||
raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
|
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
|
_type = type
|
||||||
_properties = OrderedDict(properties)
|
_properties = OrderedDict(properties)
|
||||||
|
|
||||||
|
@ -41,9 +56,34 @@ def _custom_object_builder(cls, type, properties, version):
|
||||||
def _custom_marking_builder(cls, type, properties, version):
|
def _custom_marking_builder(cls, type, properties, version):
|
||||||
class _CustomMarking(cls, _STIXBase):
|
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):
|
if not properties or not isinstance(properties, list):
|
||||||
raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
|
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
|
_type = type
|
||||||
_properties = OrderedDict(properties)
|
_properties = OrderedDict(properties)
|
||||||
|
|
||||||
|
@ -61,12 +101,22 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props=
|
||||||
|
|
||||||
class _CustomObservable(cls, _Observable):
|
class _CustomObservable(cls, _Observable):
|
||||||
|
|
||||||
if not re.match(TYPE_REGEX, type):
|
if version == "2.0":
|
||||||
raise ValueError(
|
if not re.match(TYPE_REGEX, type):
|
||||||
"Invalid observable type name '%s': must only contain the "
|
raise ValueError(
|
||||||
"characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type,
|
"Invalid type type name '%s': must only contain the "
|
||||||
)
|
"characters a-z (lowercase ASCII), 0-9, and hyphen (-)." %
|
||||||
elif len(type) < 3 or len(type) > 250:
|
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)
|
raise ValueError("Invalid observable type name '%s': must be between 3 and 250 characters." % type)
|
||||||
|
|
||||||
if not properties or not isinstance(properties, list):
|
if not properties or not isinstance(properties, list):
|
||||||
|
@ -89,7 +139,9 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props=
|
||||||
else:
|
else:
|
||||||
# If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties
|
# If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties
|
||||||
for prop_name, prop in properties:
|
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(
|
raise ValueError(
|
||||||
"'%s' is named like a reference property but "
|
"'%s' is named like a reference property but "
|
||||||
"is not a ReferenceProperty." % prop_name,
|
"is not a ReferenceProperty." % prop_name,
|
||||||
|
@ -130,6 +182,11 @@ def _custom_extension_builder(cls, observable, type, properties, version):
|
||||||
|
|
||||||
class _CustomExtension(cls, _Extension):
|
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
|
_type = type
|
||||||
_properties = prop_dict
|
_properties = prop_dict
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,19 @@ def test_identity_custom_property():
|
||||||
)
|
)
|
||||||
assert "Unexpected properties for Identity" in str(excinfo.value)
|
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(
|
identity = stix2.v21.Identity(
|
||||||
id=IDENTITY_ID,
|
id=IDENTITY_ID,
|
||||||
created="2015-12-21T19:59:11Z",
|
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)
|
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():
|
def test_custom_property_object_in_observable_extension():
|
||||||
ntfs = stix2.v21.NTFSExt(
|
ntfs = stix2.v21.NTFSExt(
|
||||||
allow_custom=True,
|
allow_custom=True,
|
||||||
|
@ -293,6 +318,38 @@ def test_custom_marking_no_init_2():
|
||||||
assert no2.property1 == 'something'
|
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(
|
@stix2.v21.CustomObject(
|
||||||
'x-new-type', [
|
'x-new-type', [
|
||||||
('property1', stix2.properties.StringProperty(required=True)),
|
('property1', stix2.properties.StringProperty(required=True)),
|
||||||
|
@ -374,6 +431,17 @@ def test_custom_object_invalid_type_name():
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
assert "Invalid type name 'x_new_object':" in str(excinfo.value)
|
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():
|
def test_parse_custom_object_type():
|
||||||
nt_string = """{
|
nt_string = """{
|
||||||
|
@ -500,6 +568,17 @@ def test_custom_observable_object_invalid_type_name():
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
assert "Invalid observable type name 'x_new_obs':" in str(excinfo.value)
|
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():
|
def test_custom_observable_object_invalid_ref_property():
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
@ -874,6 +953,17 @@ def test_custom_extension_invalid_type_name():
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
assert "Invalid extension type name 'x_new_ext':" in str(excinfo.value)
|
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():
|
def test_custom_extension_no_properties():
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
|
|
@ -7,6 +7,7 @@ except ImportError:
|
||||||
import copy
|
import copy
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
import pytz
|
import pytz
|
||||||
|
@ -25,8 +26,10 @@ NOW = object()
|
||||||
# STIX object properties that cannot be modified
|
# STIX object properties that cannot be modified
|
||||||
STIX_UNMOD_PROPERTIES = ['created', 'created_by_ref', 'id', 'type']
|
STIX_UNMOD_PROPERTIES = ['created', 'created_by_ref', 'id', 'type']
|
||||||
|
|
||||||
TYPE_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$'
|
TYPE_REGEX = re.compile(r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$')
|
||||||
SCO21_EXT_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-ext$'
|
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):
|
class STIXdatetime(dt.datetime):
|
||||||
|
|
Loading…
Reference in New Issue