2020-07-01 15:41:10 +02:00
|
|
|
import datetime as dt
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
import pytz
|
|
|
|
|
2020-06-19 02:49:25 +02:00
|
|
|
from stix2.base import _STIXBase
|
|
|
|
from stix2.exceptions import (
|
2020-07-07 04:43:25 +02:00
|
|
|
CustomContentError, ExtraPropertiesError, STIXError,
|
2020-06-19 02:49:25 +02:00
|
|
|
)
|
2020-07-01 15:41:10 +02:00
|
|
|
from stix2.properties import (
|
|
|
|
BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty,
|
2020-07-10 02:13:53 +02:00
|
|
|
FloatProperty, HashesProperty, HexProperty, IntegerProperty, ListProperty,
|
|
|
|
OpenVocabProperty, Property, StringProperty, TimestampProperty,
|
|
|
|
TypeProperty,
|
2020-07-01 15:41:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_property():
|
|
|
|
p = Property()
|
|
|
|
|
|
|
|
assert p.required is False
|
2020-06-19 02:49:25 +02:00
|
|
|
assert p.clean('foo') == ('foo', False)
|
|
|
|
assert p.clean(3) == (3, False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_basic_clean():
|
|
|
|
class Prop(Property):
|
|
|
|
|
|
|
|
def clean(self, value):
|
|
|
|
if value == 42:
|
|
|
|
return value
|
|
|
|
else:
|
|
|
|
raise ValueError("Must be 42")
|
|
|
|
|
|
|
|
p = Prop()
|
|
|
|
|
|
|
|
assert p.clean(42) == 42
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
p.clean(41)
|
|
|
|
|
|
|
|
|
|
|
|
def test_property_default():
|
|
|
|
class Prop(Property):
|
|
|
|
|
|
|
|
def default(self):
|
|
|
|
return 77
|
|
|
|
|
|
|
|
p = Prop()
|
|
|
|
|
|
|
|
assert p.default() == 77
|
|
|
|
|
|
|
|
|
2020-06-19 02:49:25 +02:00
|
|
|
def test_fixed_property():
|
2020-07-01 15:41:10 +02:00
|
|
|
p = Property(fixed="2.0")
|
|
|
|
|
|
|
|
assert p.clean("2.0")
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
assert p.clean("x") is False
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
assert p.clean(2.0) is False
|
|
|
|
|
|
|
|
assert p.default() == "2.0"
|
|
|
|
assert p.clean(p.default())
|
|
|
|
|
|
|
|
|
2020-07-01 15:54:50 +02:00
|
|
|
def test_property_fixed_and_required():
|
|
|
|
with pytest.raises(STIXError):
|
|
|
|
Property(default=lambda: 3, required=True)
|
|
|
|
|
|
|
|
|
2020-06-19 02:49:25 +02:00
|
|
|
def test_list_property_property_type():
|
2020-07-01 15:41:10 +02:00
|
|
|
p = ListProperty(StringProperty)
|
|
|
|
|
2020-06-25 01:39:47 +02:00
|
|
|
result = p.clean(['abc', 'xyz'], False)
|
|
|
|
assert result == (['abc', 'xyz'], False)
|
|
|
|
|
2020-07-01 15:41:10 +02:00
|
|
|
with pytest.raises(ValueError):
|
2020-06-19 02:49:25 +02:00
|
|
|
p.clean([], False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_list_property_property_type_custom():
|
2020-06-19 02:49:25 +02:00
|
|
|
class TestObj(_STIXBase):
|
2020-07-01 15:41:10 +02:00
|
|
|
_type = "test"
|
|
|
|
_properties = {
|
|
|
|
"foo": StringProperty(),
|
|
|
|
}
|
|
|
|
p = ListProperty(EmbeddedObjectProperty(type=TestObj))
|
|
|
|
|
|
|
|
objs_custom = [
|
|
|
|
TestObj(foo="abc", bar=123, allow_custom=True),
|
|
|
|
TestObj(foo="xyz"),
|
|
|
|
]
|
|
|
|
|
2020-06-25 01:39:47 +02:00
|
|
|
result = p.clean(objs_custom, True)
|
|
|
|
assert result == (objs_custom, True)
|
2020-06-19 02:49:25 +02:00
|
|
|
|
|
|
|
with pytest.raises(CustomContentError):
|
|
|
|
p.clean(objs_custom, False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
dicts_custom = [
|
|
|
|
{"foo": "abc", "bar": 123},
|
|
|
|
{"foo": "xyz"},
|
|
|
|
]
|
|
|
|
|
2020-06-25 01:39:47 +02:00
|
|
|
result = p.clean(dicts_custom, True)
|
|
|
|
assert result == (objs_custom, True)
|
2020-06-19 02:49:25 +02:00
|
|
|
|
2020-07-01 15:41:10 +02:00
|
|
|
with pytest.raises(ExtraPropertiesError):
|
2020-06-19 02:49:25 +02:00
|
|
|
p.clean(dicts_custom, False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_list_property_object_type():
|
2020-06-19 02:49:25 +02:00
|
|
|
class TestObj(_STIXBase):
|
2020-07-01 15:41:10 +02:00
|
|
|
_type = "test"
|
|
|
|
_properties = {
|
|
|
|
"foo": StringProperty(),
|
|
|
|
}
|
|
|
|
p = ListProperty(TestObj)
|
|
|
|
|
|
|
|
objs = [TestObj(foo="abc"), TestObj(foo="xyz")]
|
2020-06-25 01:39:47 +02:00
|
|
|
result = p.clean(objs, False)
|
|
|
|
assert result == (objs, False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
dicts = [{"foo": "abc"}, {"foo": "xyz"}]
|
2020-06-25 01:39:47 +02:00
|
|
|
result = p.clean(dicts, False)
|
|
|
|
assert result == (objs, False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_list_property_object_type_custom():
|
2020-06-19 02:49:25 +02:00
|
|
|
class TestObj(_STIXBase):
|
2020-07-01 15:41:10 +02:00
|
|
|
_type = "test"
|
|
|
|
_properties = {
|
|
|
|
"foo": StringProperty(),
|
|
|
|
}
|
|
|
|
p = ListProperty(TestObj)
|
|
|
|
|
|
|
|
objs_custom = [
|
|
|
|
TestObj(foo="abc", bar=123, allow_custom=True),
|
|
|
|
TestObj(foo="xyz"),
|
|
|
|
]
|
|
|
|
|
2020-06-25 01:39:47 +02:00
|
|
|
result = p.clean(objs_custom, True)
|
|
|
|
assert result == (objs_custom, True)
|
2020-06-19 02:49:25 +02:00
|
|
|
|
|
|
|
with pytest.raises(CustomContentError):
|
|
|
|
p.clean(objs_custom, False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
dicts_custom = [
|
|
|
|
{"foo": "abc", "bar": 123},
|
|
|
|
{"foo": "xyz"},
|
|
|
|
]
|
|
|
|
|
2020-06-25 01:39:47 +02:00
|
|
|
result = p.clean(dicts_custom, True)
|
|
|
|
assert result == (objs_custom, True)
|
2020-06-19 02:49:25 +02:00
|
|
|
|
2020-07-01 15:41:10 +02:00
|
|
|
with pytest.raises(ExtraPropertiesError):
|
2020-06-19 02:49:25 +02:00
|
|
|
p.clean(dicts_custom, False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_list_property_bad_element_type():
|
|
|
|
with pytest.raises(TypeError):
|
|
|
|
ListProperty(1)
|
|
|
|
|
|
|
|
|
|
|
|
def test_list_property_bad_value_type():
|
2020-06-19 02:49:25 +02:00
|
|
|
class TestObj(_STIXBase):
|
2020-07-01 15:41:10 +02:00
|
|
|
_type = "test"
|
|
|
|
_properties = {
|
|
|
|
"foo": StringProperty(),
|
|
|
|
}
|
|
|
|
|
|
|
|
list_prop = ListProperty(TestObj)
|
|
|
|
with pytest.raises(ValueError):
|
2020-06-19 02:49:25 +02:00
|
|
|
list_prop.clean([1], False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_string_property():
|
|
|
|
prop = StringProperty()
|
|
|
|
|
|
|
|
assert prop.clean('foobar')
|
|
|
|
assert prop.clean(1)
|
|
|
|
assert prop.clean([1, 2, 3])
|
|
|
|
|
|
|
|
|
|
|
|
def test_type_property():
|
|
|
|
prop = TypeProperty('my-type')
|
|
|
|
|
|
|
|
assert prop.clean('my-type')
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
prop.clean('not-my-type')
|
|
|
|
assert prop.clean(prop.default())
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
2,
|
|
|
|
-1,
|
|
|
|
3.14,
|
|
|
|
False,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_integer_property_valid(value):
|
|
|
|
int_prop = IntegerProperty()
|
|
|
|
assert int_prop.clean(value) is not None
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
-1,
|
|
|
|
-100,
|
|
|
|
-50 * 6,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_integer_property_invalid_min_with_constraints(value):
|
|
|
|
int_prop = IntegerProperty(min=0, max=180)
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
int_prop.clean(value)
|
|
|
|
assert "minimum value is" in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
181,
|
|
|
|
200,
|
|
|
|
50 * 6,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_integer_property_invalid_max_with_constraints(value):
|
|
|
|
int_prop = IntegerProperty(min=0, max=180)
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
int_prop.clean(value)
|
|
|
|
assert "maximum value is" in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
"something",
|
|
|
|
StringProperty(),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_integer_property_invalid(value):
|
|
|
|
int_prop = IntegerProperty()
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
int_prop.clean(value)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
2,
|
|
|
|
-1,
|
|
|
|
3.14,
|
|
|
|
False,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_float_property_valid(value):
|
|
|
|
int_prop = FloatProperty()
|
|
|
|
assert int_prop.clean(value) is not None
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
"something",
|
|
|
|
StringProperty(),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_float_property_invalid(value):
|
|
|
|
int_prop = FloatProperty()
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
int_prop.clean(value)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
True,
|
|
|
|
False,
|
|
|
|
'True',
|
|
|
|
'False',
|
|
|
|
'true',
|
|
|
|
'false',
|
|
|
|
'TRUE',
|
|
|
|
'FALSE',
|
|
|
|
'T',
|
|
|
|
'F',
|
|
|
|
't',
|
|
|
|
'f',
|
|
|
|
1,
|
|
|
|
0,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_boolean_property_valid(value):
|
|
|
|
bool_prop = BooleanProperty()
|
|
|
|
|
|
|
|
assert bool_prop.clean(value) is not None
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
'abc',
|
|
|
|
['false'],
|
|
|
|
{'true': 'true'},
|
|
|
|
2,
|
|
|
|
-1,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_boolean_property_invalid(value):
|
|
|
|
bool_prop = BooleanProperty()
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
bool_prop.clean(value)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
'2017-01-01T12:34:56Z',
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_timestamp_property_valid(value):
|
|
|
|
ts_prop = TimestampProperty()
|
2020-06-19 02:49:25 +02:00
|
|
|
assert ts_prop.clean(value) == (dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc), False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_timestamp_property_invalid():
|
|
|
|
ts_prop = TimestampProperty()
|
|
|
|
with pytest.raises(TypeError):
|
|
|
|
ts_prop.clean(1)
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
ts_prop.clean("someday sometime")
|
|
|
|
|
|
|
|
|
|
|
|
def test_binary_property():
|
|
|
|
bin_prop = BinaryProperty()
|
|
|
|
|
|
|
|
assert bin_prop.clean("TG9yZW0gSXBzdW0=")
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
bin_prop.clean("foobar")
|
|
|
|
|
|
|
|
|
|
|
|
def test_hex_property():
|
|
|
|
hex_prop = HexProperty()
|
|
|
|
|
|
|
|
assert hex_prop.clean("4c6f72656d20497073756d")
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
hex_prop.clean("foobar")
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
['a', 'b', 'c'],
|
|
|
|
('a', 'b', 'c'),
|
|
|
|
'b',
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_enum_property_valid(value):
|
|
|
|
enum_prop = EnumProperty(value)
|
2020-06-19 02:49:25 +02:00
|
|
|
assert enum_prop.clean('b', False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_enum_property_clean():
|
|
|
|
enum_prop = EnumProperty(['1'])
|
2020-06-19 02:49:25 +02:00
|
|
|
assert enum_prop.clean(1, False) == ('1', False)
|
2020-07-01 15:41:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_enum_property_invalid():
|
|
|
|
enum_prop = EnumProperty(['a', 'b', 'c'])
|
|
|
|
with pytest.raises(ValueError):
|
2020-06-19 02:49:25 +02:00
|
|
|
enum_prop.clean('z', False)
|
|
|
|
|
2020-07-10 02:13:53 +02:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
enum_prop.clean('z', True)
|
2020-06-19 02:49:25 +02:00
|
|
|
|
2020-07-10 02:13:53 +02:00
|
|
|
|
2021-04-14 18:48:32 +02:00
|
|
|
@pytest.mark.xfail(
|
|
|
|
reason="Temporarily disabled custom open vocab enforcement",
|
2021-04-14 18:58:09 +02:00
|
|
|
strict=True,
|
2021-04-14 18:48:32 +02:00
|
|
|
)
|
2020-07-10 02:13:53 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"vocab", [
|
|
|
|
['a', 'b', 'c'],
|
|
|
|
('a', 'b', 'c'),
|
|
|
|
'b',
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_openvocab_property(vocab):
|
|
|
|
ov_prop = OpenVocabProperty(vocab)
|
|
|
|
|
|
|
|
assert ov_prop.clean("b", False) == ("b", False)
|
|
|
|
assert ov_prop.clean("b", True) == ("b", False)
|
|
|
|
|
|
|
|
with pytest.raises(CustomContentError):
|
|
|
|
ov_prop.clean("d", False)
|
|
|
|
|
|
|
|
assert ov_prop.clean("d", True) == ("d", True)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
{"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"},
|
|
|
|
[('MD5', '2dfb1bcc980200c6706feee399d41b3f'), ('RIPEMD-160', 'b3a8cd8a27c90af79b3c81754f267780f443dfef')],
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_hashes_property_valid(value):
|
|
|
|
hash_prop = HashesProperty(["sha256", "md5", "ripemd160"])
|
|
|
|
_, has_custom = hash_prop.clean(value, False)
|
|
|
|
assert not has_custom
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"value", [
|
|
|
|
{"MD5": "a"},
|
|
|
|
{"SHA-256": "2dfb1bcc980200c6706feee399d41b3f"},
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_hashes_property_invalid(value):
|
|
|
|
hash_prop = HashesProperty(["sha256", "md5"])
|
|
|
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
hash_prop.clean(value, False)
|
|
|
|
|
|
|
|
|
|
|
|
def test_hashes_property_custom():
|
|
|
|
value = {
|
|
|
|
"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b",
|
|
|
|
"abc-123": "aaaaaaaaaaaaaaaaaaaaa",
|
|
|
|
}
|
|
|
|
expected_cleaned_value = {
|
|
|
|
# cleaning transforms recognized hash algorithm names to the spec-
|
|
|
|
# mandated name.
|
|
|
|
"SHA-256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b",
|
|
|
|
"abc-123": "aaaaaaaaaaaaaaaaaaaaa",
|
|
|
|
}
|
|
|
|
|
|
|
|
hash_prop = HashesProperty(["SHA-256"])
|
|
|
|
result = hash_prop.clean(value, True)
|
|
|
|
assert result == (expected_cleaned_value, True)
|
|
|
|
|
|
|
|
with pytest.raises(CustomContentError):
|
|
|
|
hash_prop.clean(value, False)
|
|
|
|
|
|
|
|
|
|
|
|
def test_hashes_no_library_support():
|
|
|
|
prop = HashesProperty(["foo"])
|
|
|
|
|
|
|
|
result = prop.clean({"foo": "bar"}, False)
|
|
|
|
assert result == ({"foo": "bar"}, False)
|
|
|
|
|
|
|
|
result = prop.clean({"foo": "bar"}, True)
|
|
|
|
assert result == ({"foo": "bar"}, False)
|
|
|
|
|
|
|
|
with pytest.raises(CustomContentError):
|
|
|
|
# require exact name match for unsupported hash algorithms
|
|
|
|
prop.clean({"FOO": "bar"}, False)
|
|
|
|
|
|
|
|
result = prop.clean({"FOO": "bar"}, True)
|
|
|
|
assert result == ({"FOO": "bar"}, True)
|