Update IDProperty and ReferenceProperty to support both stix 2.0
and 2.1 rules regarding identifiers. Change relevant property tests to specify which spec version to use, and modify tests according to the specs.master
parent
5b6a0dc087
commit
ed106f23ff
|
@ -16,22 +16,29 @@ from .core import STIX2_OBJ_MAPS, parse, parse_observable
|
|||
from .exceptions import CustomContentError, DictionaryKeyError
|
||||
from .utils import _get_dict, get_class_hierarchy_names, parse_into_datetime
|
||||
|
||||
# This uses the regular expression for a RFC 4122, Version 4 UUID. In the
|
||||
# 8-4-4-4-12 hexadecimal representation, the first hex digit of the third
|
||||
# component must be a 4, and the first hex digit of the fourth component
|
||||
# must be 8, 9, a, or b (10xx bit pattern).
|
||||
ID_REGEX = re.compile(
|
||||
r"^[a-z0-9][a-z0-9-]+[a-z0-9]--" # object type
|
||||
"[0-9a-fA-F]{8}-"
|
||||
"[0-9a-fA-F]{4}-"
|
||||
"4[0-9a-fA-F]{3}-"
|
||||
"[89abAB][0-9a-fA-F]{3}-"
|
||||
"[0-9a-fA-F]{12}$",
|
||||
ERROR_INVALID_ID = (
|
||||
"not a valid STIX identifier, must match <object-type>--<UUID>: {}"
|
||||
)
|
||||
|
||||
ERROR_INVALID_ID = (
|
||||
"not a valid STIX identifier, must match <object-type>--<UUIDv4>"
|
||||
)
|
||||
|
||||
def _check_uuid(uuid_str, spec_version):
|
||||
"""
|
||||
Check whether the given UUID string is valid with respect to the given STIX
|
||||
spec version. STIX 2.0 requires UUIDv4; 2.1 only requires the RFC 4122
|
||||
variant.
|
||||
|
||||
:param uuid_str: A UUID as a string
|
||||
:param spec_version: The STIX spec version
|
||||
:return: True if the UUID is valid, False if not
|
||||
:raises ValueError: If uuid_str is malformed
|
||||
"""
|
||||
uuid_obj = uuid.UUID(uuid_str)
|
||||
|
||||
ok = uuid_obj.variant == uuid.RFC_4122
|
||||
if ok and spec_version == "2.0":
|
||||
ok = uuid_obj.version == 4
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
class Property(object):
|
||||
|
@ -185,15 +192,25 @@ class TypeProperty(Property):
|
|||
|
||||
class IDProperty(Property):
|
||||
|
||||
def __init__(self, type):
|
||||
def __init__(self, type, spec_version="2.1"):
|
||||
self.required_prefix = type + "--"
|
||||
self.spec_version = spec_version
|
||||
super(IDProperty, self).__init__()
|
||||
|
||||
def clean(self, value):
|
||||
if not value.startswith(self.required_prefix):
|
||||
raise ValueError("must start with '{}'.".format(self.required_prefix))
|
||||
if not ID_REGEX.match(value):
|
||||
raise ValueError(ERROR_INVALID_ID)
|
||||
|
||||
uuid_part = value[len(self.required_prefix):]
|
||||
try:
|
||||
result = _check_uuid(uuid_part, self.spec_version)
|
||||
except ValueError:
|
||||
# replace their ValueError with ours
|
||||
raise ValueError(ERROR_INVALID_ID.format(value))
|
||||
|
||||
if not result:
|
||||
raise ValueError(ERROR_INVALID_ID.format(value))
|
||||
|
||||
return value
|
||||
|
||||
def default(self):
|
||||
|
@ -366,22 +383,40 @@ class HexProperty(Property):
|
|||
|
||||
class ReferenceProperty(Property):
|
||||
|
||||
def __init__(self, type=None, **kwargs):
|
||||
def __init__(self, type=None, spec_version="2.1", **kwargs):
|
||||
"""
|
||||
references sometimes must be to a specific object type
|
||||
"""
|
||||
self.type = type
|
||||
self.required_prefix = type + "--" if type else None
|
||||
self.spec_version = spec_version
|
||||
super(ReferenceProperty, self).__init__(**kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
if isinstance(value, _STIXBase):
|
||||
value = value.id
|
||||
value = str(value)
|
||||
if self.type:
|
||||
if not value.startswith(self.type):
|
||||
raise ValueError("must start with '{}'.".format(self.type))
|
||||
if not ID_REGEX.match(value):
|
||||
raise ValueError(ERROR_INVALID_ID)
|
||||
|
||||
if self.required_prefix:
|
||||
if not value.startswith(self.required_prefix):
|
||||
raise ValueError(
|
||||
"must start with '{}'.".format(self.required_prefix),
|
||||
)
|
||||
|
||||
try:
|
||||
if self.required_prefix:
|
||||
uuid_part = value[len(self.required_prefix):]
|
||||
else:
|
||||
idx = value.index("--")
|
||||
uuid_part = value[idx+2:]
|
||||
|
||||
result = _check_uuid(uuid_part, self.spec_version)
|
||||
except ValueError:
|
||||
# replace their ValueError with ours
|
||||
raise ValueError(ERROR_INVALID_ID.format(value))
|
||||
|
||||
if not result:
|
||||
raise ValueError(ERROR_INVALID_ID.format(value))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import pytest
|
|||
import stix2
|
||||
from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError
|
||||
from stix2.properties import (
|
||||
ERROR_INVALID_ID, BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||
BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||
EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
|
||||
HashesProperty, HexProperty, IDProperty, IntegerProperty, ListProperty,
|
||||
Property, ReferenceProperty, STIXObjectProperty, StringProperty,
|
||||
|
@ -89,7 +89,7 @@ def test_type_property():
|
|||
assert prop.clean(prop.default())
|
||||
|
||||
|
||||
ID_PROP = IDProperty('my-type')
|
||||
ID_PROP = IDProperty('my-type', spec_version="2.0")
|
||||
MY_ID = 'my-type--232c9d3f-49fc-4440-bb01-607f638778e7'
|
||||
|
||||
|
||||
|
@ -127,7 +127,7 @@ CONSTANT_IDS.extend(constants.RELATIONSHIP_IDS)
|
|||
@pytest.mark.parametrize("value", CONSTANT_IDS)
|
||||
def test_id_property_valid_for_type(value):
|
||||
type = value.split('--', 1)[0]
|
||||
assert IDProperty(type=type).clean(value) == value
|
||||
assert IDProperty(type=type, spec_version="2.0").clean(value) == value
|
||||
|
||||
|
||||
def test_id_property_wrong_type():
|
||||
|
@ -147,9 +147,8 @@ def test_id_property_wrong_type():
|
|||
],
|
||||
)
|
||||
def test_id_property_not_a_valid_hex_uuid(value):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
ID_PROP.clean(value)
|
||||
assert str(excinfo.value) == ERROR_INVALID_ID
|
||||
|
||||
|
||||
def test_id_property_default():
|
||||
|
@ -275,7 +274,7 @@ def test_boolean_property_invalid(value):
|
|||
|
||||
|
||||
def test_reference_property():
|
||||
ref_prop = ReferenceProperty()
|
||||
ref_prop = ReferenceProperty(spec_version="2.0")
|
||||
|
||||
assert ref_prop.clean("my-type--00000000-0000-4000-8000-000000000000")
|
||||
with pytest.raises(ValueError):
|
||||
|
@ -286,6 +285,16 @@ def test_reference_property():
|
|||
ref_prop.clean("my-type--00000000-0000-0000-0000-000000000000")
|
||||
|
||||
|
||||
def test_reference_property_specific_type():
|
||||
ref_prop = ReferenceProperty("my-type", spec_version="2.0")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ref_prop.clean("not-my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf")
|
||||
|
||||
assert ref_prop.clean("my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf") == \
|
||||
"my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value", [
|
||||
'2017-01-01T12:34:56Z',
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError
|
||||
from stix2.properties import (
|
||||
ERROR_INVALID_ID, BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||
BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||
EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
|
||||
HashesProperty, HexProperty, IDProperty, IntegerProperty, ListProperty,
|
||||
Property, ReferenceProperty, StringProperty, TimestampProperty,
|
||||
|
@ -89,7 +87,7 @@ def test_type_property():
|
|||
assert prop.clean(prop.default())
|
||||
|
||||
|
||||
ID_PROP = IDProperty('my-type')
|
||||
ID_PROP = IDProperty('my-type', spec_version="2.1")
|
||||
MY_ID = 'my-type--232c9d3f-49fc-4440-bb01-607f638778e7'
|
||||
|
||||
|
||||
|
@ -127,7 +125,7 @@ CONSTANT_IDS.extend(constants.RELATIONSHIP_IDS)
|
|||
@pytest.mark.parametrize("value", CONSTANT_IDS)
|
||||
def test_id_property_valid_for_type(value):
|
||||
type = value.split('--', 1)[0]
|
||||
assert IDProperty(type=type).clean(value) == value
|
||||
assert IDProperty(type=type, spec_version="2.1").clean(value) == value
|
||||
|
||||
|
||||
def test_id_property_wrong_type():
|
||||
|
@ -139,17 +137,13 @@ def test_id_property_wrong_type():
|
|||
@pytest.mark.parametrize(
|
||||
"value", [
|
||||
'my-type--foo',
|
||||
# Not a v4 UUID
|
||||
# Not a RFC 4122 UUID
|
||||
'my-type--00000000-0000-0000-0000-000000000000',
|
||||
'my-type--' + str(uuid.uuid1()),
|
||||
'my-type--' + str(uuid.uuid3(uuid.NAMESPACE_DNS, "example.org")),
|
||||
'my-type--' + str(uuid.uuid5(uuid.NAMESPACE_DNS, "example.org")),
|
||||
],
|
||||
)
|
||||
def test_id_property_not_a_valid_hex_uuid(value):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
ID_PROP.clean(value)
|
||||
assert str(excinfo.value) == ERROR_INVALID_ID
|
||||
|
||||
|
||||
def test_id_property_default():
|
||||
|
@ -275,17 +269,27 @@ def test_boolean_property_invalid(value):
|
|||
|
||||
|
||||
def test_reference_property():
|
||||
ref_prop = ReferenceProperty()
|
||||
ref_prop = ReferenceProperty(spec_version="2.1")
|
||||
|
||||
assert ref_prop.clean("my-type--00000000-0000-4000-8000-000000000000")
|
||||
with pytest.raises(ValueError):
|
||||
ref_prop.clean("foo")
|
||||
|
||||
# This is not a valid V4 UUID
|
||||
# This is not a valid RFC 4122 UUID
|
||||
with pytest.raises(ValueError):
|
||||
ref_prop.clean("my-type--00000000-0000-0000-0000-000000000000")
|
||||
|
||||
|
||||
def test_reference_property_specific_type():
|
||||
ref_prop = ReferenceProperty("my-type", spec_version="2.1")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ref_prop.clean("not-my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf")
|
||||
|
||||
assert ref_prop.clean("my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf") == \
|
||||
"my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value", [
|
||||
'2017-01-01T12:34:56Z',
|
||||
|
|
Loading…
Reference in New Issue