2020-02-27 21:15:37 +01:00
|
|
|
import uuid
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
import pytest
|
|
|
|
|
|
|
|
import stix2
|
|
|
|
import stix2.base
|
2019-08-26 23:10:54 +02:00
|
|
|
import stix2.v21
|
2018-07-03 13:00:18 +02:00
|
|
|
|
Improved the exception class hierarchy:
- Removed all plain python base classes (e.g. ValueError, TypeError)
- Renamed InvalidPropertyConfigurationError -> PropertyPresenceError,
since incorrect values could be considered a property config error, and
I really just wanted this class to apply to presence (co-)constraint
violations.
- Added ObjectConfigurationError as a superclass of InvalidValueError,
PropertyPresenceError, and any other exception that could be raised
during _STIXBase object init, which is when the spec compliance
checks happen. This class is intended to represent general spec
violations.
- Did some class reordering in exceptions.py, so all the
ObjectConfigurationError subclasses were together.
Changed how property "cleaning" errors were handled:
- Previous docs said they should all be ValueErrors, but that would require
extra exception check-and-replace complexity in the property
implementations, so that requirement is removed. Doc is changed to just
say that cleaning problems should cause exceptions to be raised.
_STIXBase._check_property() now handles most exception types, not just
ValueError.
- Decided to try chaining the original clean error to the InvalidValueError,
in case the extra diagnostics would be helpful in the future. This is
done via 'six' adapter function and only works on python3.
- A small amount of testing was removed, since it was looking at custom
exception properties which became unavailable once the exception was
replaced with InvalidValueError.
Did another pass through unit tests to fix breakage caused by the changed
exception class hierarchy.
Removed unnecessary observable extension handling code from
parse_observable(), since it was all duplicated in ExtensionsProperty.
The redundant code in parse_observable() had different exception behavior
than ExtensionsProperty, which makes the API inconsistent and unit tests
more complicated. (Problems in ExtensionsProperty get replaced with
InvalidValueError, but extensions problems handled directly in
parse_observable() don't get the same replacement, and so the exception
type is different.)
Redid the workbench monkeypatching. The old way was impossible to make
work, and had caused ugly ripple effect hackage in other parts of the
codebase. Now, it replaces the global object maps with factory functions
which behave the same way when called, as real classes. Had to fix up a
few unit tests to get them all passing with this monkeypatching in place.
Also remove all the xfail markings in the workbench test suite, since all
tests now pass.
Since workbench monkeypatching isn't currently affecting any unit tests,
tox.ini was simplified to remove the special-casing for running the
workbench tests.
Removed the v20 workbench test suite, since the workbench currently only
works with the latest stix object version.
2019-07-19 20:50:11 +02:00
|
|
|
from ...exceptions import InvalidValueError
|
2019-01-23 16:56:20 +01:00
|
|
|
from .constants import FAKE_TIME, IDENTITY_ID, MARKING_DEFINITION_ID
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
# Custom Properties in SDOs
|
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
IDENTITY_CUSTOM_PROP = stix2.v21.Identity(
|
2018-07-03 13:00:18 +02:00
|
|
|
name="John Smith",
|
|
|
|
identity_class="individual",
|
|
|
|
x_foo="bar",
|
|
|
|
allow_custom=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_identity_custom_property():
|
2020-03-20 17:49:20 +01:00
|
|
|
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"
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.Identity(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
created="2015-12-21T19:59:11Z",
|
|
|
|
modified="2015-12-21T19:59:11Z",
|
|
|
|
name="John Smith",
|
|
|
|
identity_class="individual",
|
|
|
|
custom_properties="foobar",
|
|
|
|
)
|
|
|
|
assert str(excinfo.value) == "'custom_properties' must be a dictionary"
|
|
|
|
|
|
|
|
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.Identity(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
created="2015-12-21T19:59:11Z",
|
|
|
|
modified="2015-12-21T19:59:11Z",
|
|
|
|
name="John Smith",
|
|
|
|
identity_class="individual",
|
|
|
|
custom_properties={
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
foo="bar",
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert "Unexpected properties for Identity" in str(excinfo.value)
|
|
|
|
|
2020-03-20 17:49:20 +01:00
|
|
|
# leading numeric character is illegal in 2.1
|
|
|
|
|
2020-03-19 15:40:35 +01:00
|
|
|
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",
|
|
|
|
},
|
|
|
|
)
|
2020-03-19 21:11:52 +01:00
|
|
|
assert "must begin with an alpha character." in str(excinfo.value)
|
2020-03-19 15:40:35 +01:00
|
|
|
|
2020-03-20 17:49:20 +01:00
|
|
|
# leading "_" is illegal in 2.1
|
|
|
|
|
2020-03-20 16:56:09 +01:00
|
|
|
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)
|
|
|
|
|
2020-03-20 17:49:20 +01:00
|
|
|
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)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_identity_custom_property_invalid():
|
|
|
|
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.Identity(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
created="2015-12-21T19:59:11Z",
|
|
|
|
modified="2015-12-21T19:59:11Z",
|
|
|
|
name="John Smith",
|
|
|
|
identity_class="individual",
|
|
|
|
x_foo="bar",
|
|
|
|
)
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.Identity
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.properties == ['x_foo']
|
|
|
|
assert "Unexpected properties for" in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
def test_identity_custom_property_allowed():
|
2018-07-03 15:40:51 +02:00
|
|
|
identity = stix2.v21.Identity(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
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 identity.x_foo == "bar"
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
"""{
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "identity",
|
2018-07-03 15:40:51 +02:00
|
|
|
"spec_version": "2.1",
|
2018-07-11 15:43:37 +02:00
|
|
|
"id": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c",
|
2018-07-03 13:00:18 +02:00
|
|
|
"created": "2015-12-21T19:59:11Z",
|
|
|
|
"modified": "2015-12-21T19:59:11Z",
|
|
|
|
"name": "John Smith",
|
|
|
|
"identity_class": "individual",
|
|
|
|
"foo": "bar"
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_identity_custom_property(data):
|
|
|
|
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.parse(data, version="2.1")
|
Improved the exception class hierarchy:
- Removed all plain python base classes (e.g. ValueError, TypeError)
- Renamed InvalidPropertyConfigurationError -> PropertyPresenceError,
since incorrect values could be considered a property config error, and
I really just wanted this class to apply to presence (co-)constraint
violations.
- Added ObjectConfigurationError as a superclass of InvalidValueError,
PropertyPresenceError, and any other exception that could be raised
during _STIXBase object init, which is when the spec compliance
checks happen. This class is intended to represent general spec
violations.
- Did some class reordering in exceptions.py, so all the
ObjectConfigurationError subclasses were together.
Changed how property "cleaning" errors were handled:
- Previous docs said they should all be ValueErrors, but that would require
extra exception check-and-replace complexity in the property
implementations, so that requirement is removed. Doc is changed to just
say that cleaning problems should cause exceptions to be raised.
_STIXBase._check_property() now handles most exception types, not just
ValueError.
- Decided to try chaining the original clean error to the InvalidValueError,
in case the extra diagnostics would be helpful in the future. This is
done via 'six' adapter function and only works on python3.
- A small amount of testing was removed, since it was looking at custom
exception properties which became unavailable once the exception was
replaced with InvalidValueError.
Did another pass through unit tests to fix breakage caused by the changed
exception class hierarchy.
Removed unnecessary observable extension handling code from
parse_observable(), since it was all duplicated in ExtensionsProperty.
The redundant code in parse_observable() had different exception behavior
than ExtensionsProperty, which makes the API inconsistent and unit tests
more complicated. (Problems in ExtensionsProperty get replaced with
InvalidValueError, but extensions problems handled directly in
parse_observable() don't get the same replacement, and so the exception
type is different.)
Redid the workbench monkeypatching. The old way was impossible to make
work, and had caused ugly ripple effect hackage in other parts of the
codebase. Now, it replaces the global object maps with factory functions
which behave the same way when called, as real classes. Had to fix up a
few unit tests to get them all passing with this monkeypatching in place.
Also remove all the xfail markings in the workbench test suite, since all
tests now pass.
Since workbench monkeypatching isn't currently affecting any unit tests,
tox.ini was simplified to remove the special-casing for running the
workbench tests.
Removed the v20 workbench test suite, since the workbench currently only
works with the latest stix object version.
2019-07-19 20:50:11 +02:00
|
|
|
assert issubclass(excinfo.value.cls, stix2.v21.Identity)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.properties == ['foo']
|
|
|
|
assert "Unexpected properties for" in str(excinfo.value)
|
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
identity = stix2.parse(data, version="2.1", allow_custom=True)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert identity.foo == "bar"
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_property_object_in_bundled_object():
|
2018-07-03 15:40:51 +02:00
|
|
|
bundle = stix2.v21.Bundle(IDENTITY_CUSTOM_PROP, allow_custom=True)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert bundle.objects[0].x_foo == "bar"
|
|
|
|
assert '"x_foo": "bar"' in str(bundle)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_properties_object_in_bundled_object():
|
2018-07-03 15:40:51 +02:00
|
|
|
obj = stix2.v21.Identity(
|
2018-07-03 13:00:18 +02:00
|
|
|
name="John Smith",
|
|
|
|
identity_class="individual",
|
|
|
|
custom_properties={
|
|
|
|
"x_foo": "bar",
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
2018-07-03 13:00:18 +02:00
|
|
|
)
|
2018-07-03 15:40:51 +02:00
|
|
|
bundle = stix2.v21.Bundle(obj, allow_custom=True)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert bundle.objects[0].x_foo == "bar"
|
|
|
|
assert '"x_foo": "bar"' in str(bundle)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_property_dict_in_bundled_object():
|
|
|
|
custom_identity = {
|
|
|
|
'type': 'identity',
|
2018-07-03 15:40:51 +02:00
|
|
|
'spec_version': '2.1',
|
2019-01-23 16:56:20 +01:00
|
|
|
'id': IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
'created': '2015-12-21T19:59:11Z',
|
|
|
|
'name': 'John Smith',
|
|
|
|
'identity_class': 'individual',
|
|
|
|
'x_foo': 'bar',
|
|
|
|
}
|
Improved the exception class hierarchy:
- Removed all plain python base classes (e.g. ValueError, TypeError)
- Renamed InvalidPropertyConfigurationError -> PropertyPresenceError,
since incorrect values could be considered a property config error, and
I really just wanted this class to apply to presence (co-)constraint
violations.
- Added ObjectConfigurationError as a superclass of InvalidValueError,
PropertyPresenceError, and any other exception that could be raised
during _STIXBase object init, which is when the spec compliance
checks happen. This class is intended to represent general spec
violations.
- Did some class reordering in exceptions.py, so all the
ObjectConfigurationError subclasses were together.
Changed how property "cleaning" errors were handled:
- Previous docs said they should all be ValueErrors, but that would require
extra exception check-and-replace complexity in the property
implementations, so that requirement is removed. Doc is changed to just
say that cleaning problems should cause exceptions to be raised.
_STIXBase._check_property() now handles most exception types, not just
ValueError.
- Decided to try chaining the original clean error to the InvalidValueError,
in case the extra diagnostics would be helpful in the future. This is
done via 'six' adapter function and only works on python3.
- A small amount of testing was removed, since it was looking at custom
exception properties which became unavailable once the exception was
replaced with InvalidValueError.
Did another pass through unit tests to fix breakage caused by the changed
exception class hierarchy.
Removed unnecessary observable extension handling code from
parse_observable(), since it was all duplicated in ExtensionsProperty.
The redundant code in parse_observable() had different exception behavior
than ExtensionsProperty, which makes the API inconsistent and unit tests
more complicated. (Problems in ExtensionsProperty get replaced with
InvalidValueError, but extensions problems handled directly in
parse_observable() don't get the same replacement, and so the exception
type is different.)
Redid the workbench monkeypatching. The old way was impossible to make
work, and had caused ugly ripple effect hackage in other parts of the
codebase. Now, it replaces the global object maps with factory functions
which behave the same way when called, as real classes. Had to fix up a
few unit tests to get them all passing with this monkeypatching in place.
Also remove all the xfail markings in the workbench test suite, since all
tests now pass.
Since workbench monkeypatching isn't currently affecting any unit tests,
tox.ini was simplified to remove the special-casing for running the
workbench tests.
Removed the v20 workbench test suite, since the workbench currently only
works with the latest stix object version.
2019-07-19 20:50:11 +02:00
|
|
|
with pytest.raises(InvalidValueError):
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.Bundle(custom_identity)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
bundle = stix2.v21.Bundle(custom_identity, allow_custom=True)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert bundle.objects[0].x_foo == "bar"
|
|
|
|
assert '"x_foo": "bar"' in str(bundle)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_properties_dict_in_bundled_object():
|
|
|
|
custom_identity = {
|
|
|
|
'type': 'identity',
|
2018-07-03 15:40:51 +02:00
|
|
|
'spec_version': '2.1',
|
2019-01-23 16:56:20 +01:00
|
|
|
'id': IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
'created': '2015-12-21T19:59:11Z',
|
|
|
|
'name': 'John Smith',
|
|
|
|
'identity_class': 'individual',
|
|
|
|
'custom_properties': {
|
|
|
|
'x_foo': 'bar',
|
|
|
|
},
|
|
|
|
}
|
2018-07-03 15:40:51 +02:00
|
|
|
bundle = stix2.v21.Bundle(custom_identity)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert bundle.objects[0].x_foo == "bar"
|
|
|
|
assert '"x_foo": "bar"' in str(bundle)
|
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
# Custom properties in SCOs
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2020-03-19 21:49:46 +01:00
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_custom_property_in_observed_data():
|
2018-07-03 15:40:51 +02:00
|
|
|
artifact = stix2.v21.File(
|
2018-07-03 13:00:18 +02:00
|
|
|
allow_custom=True,
|
|
|
|
name='test',
|
2018-07-13 17:10:05 +02:00
|
|
|
x_foo='bar',
|
2018-07-03 13:00:18 +02:00
|
|
|
)
|
2018-07-03 15:40:51 +02:00
|
|
|
observed_data = stix2.v21.ObservedData(
|
2018-07-03 13:00:18 +02:00
|
|
|
allow_custom=True,
|
|
|
|
first_observed="2015-12-21T19:00:00Z",
|
|
|
|
last_observed="2015-12-21T19:00:00Z",
|
2018-10-15 20:48:52 +02:00
|
|
|
number_observed=1,
|
2018-07-03 13:00:18 +02:00
|
|
|
objects={"0": artifact},
|
|
|
|
)
|
|
|
|
|
|
|
|
assert observed_data.objects['0'].x_foo == "bar"
|
|
|
|
assert '"x_foo": "bar"' in str(observed_data)
|
|
|
|
|
|
|
|
|
2020-03-19 15:40:35 +01:00
|
|
|
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',
|
|
|
|
)
|
|
|
|
|
2020-03-20 16:56:09 +01:00
|
|
|
assert "must begin with an alpha character." in str(excinfo.value)
|
2020-03-19 15:40:35 +01:00
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_custom_property_object_in_observable_extension():
|
2018-07-03 15:40:51 +02:00
|
|
|
ntfs = stix2.v21.NTFSExt(
|
2018-07-03 13:00:18 +02:00
|
|
|
allow_custom=True,
|
|
|
|
sid=1,
|
|
|
|
x_foo='bar',
|
|
|
|
)
|
2018-07-03 15:40:51 +02:00
|
|
|
artifact = stix2.v21.File(
|
2018-07-03 13:00:18 +02:00
|
|
|
name='test',
|
|
|
|
extensions={'ntfs-ext': ntfs},
|
|
|
|
)
|
2018-07-03 15:40:51 +02:00
|
|
|
observed_data = stix2.v21.ObservedData(
|
2018-07-03 13:00:18 +02:00
|
|
|
allow_custom=True,
|
|
|
|
first_observed="2015-12-21T19:00:00Z",
|
|
|
|
last_observed="2015-12-21T19:00:00Z",
|
2018-10-15 20:48:52 +02:00
|
|
|
number_observed=1,
|
2018-07-03 13:00:18 +02:00
|
|
|
objects={"0": artifact},
|
|
|
|
)
|
|
|
|
|
|
|
|
assert observed_data.objects['0'].extensions['ntfs-ext'].x_foo == "bar"
|
|
|
|
assert '"x_foo": "bar"' in str(observed_data)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_property_dict_in_observable_extension():
|
Improved the exception class hierarchy:
- Removed all plain python base classes (e.g. ValueError, TypeError)
- Renamed InvalidPropertyConfigurationError -> PropertyPresenceError,
since incorrect values could be considered a property config error, and
I really just wanted this class to apply to presence (co-)constraint
violations.
- Added ObjectConfigurationError as a superclass of InvalidValueError,
PropertyPresenceError, and any other exception that could be raised
during _STIXBase object init, which is when the spec compliance
checks happen. This class is intended to represent general spec
violations.
- Did some class reordering in exceptions.py, so all the
ObjectConfigurationError subclasses were together.
Changed how property "cleaning" errors were handled:
- Previous docs said they should all be ValueErrors, but that would require
extra exception check-and-replace complexity in the property
implementations, so that requirement is removed. Doc is changed to just
say that cleaning problems should cause exceptions to be raised.
_STIXBase._check_property() now handles most exception types, not just
ValueError.
- Decided to try chaining the original clean error to the InvalidValueError,
in case the extra diagnostics would be helpful in the future. This is
done via 'six' adapter function and only works on python3.
- A small amount of testing was removed, since it was looking at custom
exception properties which became unavailable once the exception was
replaced with InvalidValueError.
Did another pass through unit tests to fix breakage caused by the changed
exception class hierarchy.
Removed unnecessary observable extension handling code from
parse_observable(), since it was all duplicated in ExtensionsProperty.
The redundant code in parse_observable() had different exception behavior
than ExtensionsProperty, which makes the API inconsistent and unit tests
more complicated. (Problems in ExtensionsProperty get replaced with
InvalidValueError, but extensions problems handled directly in
parse_observable() don't get the same replacement, and so the exception
type is different.)
Redid the workbench monkeypatching. The old way was impossible to make
work, and had caused ugly ripple effect hackage in other parts of the
codebase. Now, it replaces the global object maps with factory functions
which behave the same way when called, as real classes. Had to fix up a
few unit tests to get them all passing with this monkeypatching in place.
Also remove all the xfail markings in the workbench test suite, since all
tests now pass.
Since workbench monkeypatching isn't currently affecting any unit tests,
tox.ini was simplified to remove the special-casing for running the
workbench tests.
Removed the v20 workbench test suite, since the workbench currently only
works with the latest stix object version.
2019-07-19 20:50:11 +02:00
|
|
|
with pytest.raises(InvalidValueError):
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.File(
|
2018-07-03 13:00:18 +02:00
|
|
|
name='test',
|
|
|
|
extensions={
|
|
|
|
'ntfs-ext': {
|
|
|
|
'sid': 1,
|
|
|
|
'x_foo': 'bar',
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
artifact = stix2.v21.File(
|
2018-07-03 13:00:18 +02:00
|
|
|
allow_custom=True,
|
|
|
|
name='test',
|
|
|
|
extensions={
|
|
|
|
'ntfs-ext': {
|
|
|
|
'allow_custom': True,
|
|
|
|
'sid': 1,
|
|
|
|
'x_foo': 'bar',
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
)
|
2018-07-03 15:40:51 +02:00
|
|
|
observed_data = stix2.v21.ObservedData(
|
2018-07-03 13:00:18 +02:00
|
|
|
allow_custom=True,
|
|
|
|
first_observed="2015-12-21T19:00:00Z",
|
|
|
|
last_observed="2015-12-21T19:00:00Z",
|
2018-10-15 20:48:52 +02:00
|
|
|
number_observed=1,
|
2018-07-03 13:00:18 +02:00
|
|
|
objects={"0": artifact},
|
|
|
|
)
|
|
|
|
|
|
|
|
assert observed_data.objects['0'].extensions['ntfs-ext'].x_foo == "bar"
|
|
|
|
assert '"x_foo": "bar"' in str(observed_data)
|
|
|
|
|
|
|
|
|
|
|
|
def test_identity_custom_property_revoke():
|
|
|
|
identity = IDENTITY_CUSTOM_PROP.revoke()
|
|
|
|
assert identity.x_foo == "bar"
|
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
# Custom markings
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2020-03-19 21:49:46 +01:00
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_identity_custom_property_edit_markings():
|
2018-07-03 15:40:51 +02:00
|
|
|
marking_obj = stix2.v21.MarkingDefinition(
|
2018-07-03 13:00:18 +02:00
|
|
|
id=MARKING_DEFINITION_ID,
|
|
|
|
definition_type="statement",
|
2018-07-13 17:10:05 +02:00
|
|
|
definition=stix2.v21.StatementMarking(statement="Copyright 2016, Example Corp"),
|
2018-07-03 13:00:18 +02:00
|
|
|
)
|
2018-07-03 15:40:51 +02:00
|
|
|
marking_obj2 = stix2.v21.MarkingDefinition(
|
2018-07-03 13:00:18 +02:00
|
|
|
id=MARKING_DEFINITION_ID,
|
|
|
|
definition_type="statement",
|
2018-07-13 17:10:05 +02:00
|
|
|
definition=stix2.v21.StatementMarking(statement="Another one"),
|
2018-07-03 13:00:18 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
# None of the following should throw exceptions
|
|
|
|
identity = IDENTITY_CUSTOM_PROP.add_markings(marking_obj)
|
|
|
|
identity2 = identity.add_markings(marking_obj2, ['x_foo'])
|
|
|
|
identity2.remove_markings(marking_obj.id)
|
|
|
|
identity2.remove_markings(marking_obj2.id, ['x_foo'])
|
|
|
|
identity2.clear_markings()
|
|
|
|
identity2.clear_markings('x_foo')
|
|
|
|
|
|
|
|
|
2020-03-19 19:16:48 +01:00
|
|
|
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
|
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
assert "must begin with an alpha character." in str(excinfo.value)
|
2020-03-19 19:16:48 +01:00
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_custom_marking_no_init_1():
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomMarking(
|
|
|
|
'x-new-obj', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObj():
|
|
|
|
pass
|
|
|
|
|
|
|
|
no = NewObj(property1='something')
|
|
|
|
assert no.property1 == 'something'
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_marking_no_init_2():
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomMarking(
|
|
|
|
'x-new-obj2', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObj2(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
no2 = NewObj2(property1='something')
|
|
|
|
assert no2.property1 == 'something'
|
|
|
|
|
|
|
|
|
2020-03-19 15:40:35 +01:00
|
|
|
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
|
2020-03-19 19:16:48 +01:00
|
|
|
assert "Invalid marking type name 'x': " in str(excinfo.value)
|
2020-03-19 15:40:35 +01:00
|
|
|
|
|
|
|
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
|
2020-03-19 19:16:48 +01:00
|
|
|
assert "Invalid marking type name 'x_new_marking':" in str(excinfo.value)
|
2020-03-19 15:40:35 +01:00
|
|
|
|
|
|
|
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
|
2020-03-19 19:16:48 +01:00
|
|
|
assert "Invalid marking type name '7x-new-marking':" in str(excinfo.value)
|
2020-03-19 15:40:35 +01:00
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
# Custom Objects
|
2020-03-19 15:40:35 +01:00
|
|
|
|
2020-03-19 21:49:46 +01:00
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObject(
|
|
|
|
'x-new-type', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
('property2', stix2.properties.IntegerProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewType(object):
|
|
|
|
def __init__(self, property2=None, **kwargs):
|
|
|
|
if property2 and property2 < 10:
|
|
|
|
raise ValueError("'property2' is too small.")
|
|
|
|
if "property3" in kwargs and not isinstance(kwargs.get("property3"), int):
|
|
|
|
raise TypeError("Must be integer!")
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_object_raises_exception():
|
|
|
|
with pytest.raises(TypeError) as excinfo:
|
|
|
|
NewType(property1='something', property3='something', allow_custom=True)
|
|
|
|
|
|
|
|
assert str(excinfo.value) == "Must be integer!"
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_object_type():
|
|
|
|
nt = NewType(property1='something')
|
|
|
|
assert nt.property1 == 'something'
|
|
|
|
|
|
|
|
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
|
|
|
NewType(property2=42)
|
|
|
|
assert "No values for required properties" in str(excinfo.value)
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
NewType(property1='something', property2=4)
|
|
|
|
assert "'property2' is too small." in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_object_no_init_1():
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObject(
|
|
|
|
'x-new-obj', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObj():
|
|
|
|
pass
|
|
|
|
|
|
|
|
no = NewObj(property1='something')
|
|
|
|
assert no.property1 == 'something'
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_object_no_init_2():
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObject(
|
|
|
|
'x-new-obj2', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObj2(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
no2 = NewObj2(property1='something')
|
|
|
|
assert no2.property1 == 'something'
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_object_invalid_type_name():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObject(
|
|
|
|
'x', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObj(object):
|
|
|
|
pass # pragma: no cover
|
|
|
|
assert "Invalid type name 'x': " in str(excinfo.value)
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObject(
|
|
|
|
'x_new_object', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObj2(object):
|
|
|
|
pass # pragma: no cover
|
|
|
|
assert "Invalid type name 'x_new_object':" in str(excinfo.value)
|
|
|
|
|
2020-03-19 15:40:35 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_custom_object_type():
|
|
|
|
nt_string = """{
|
|
|
|
"type": "x-new-type",
|
|
|
|
"created": "2015-12-21T19:59:11Z",
|
|
|
|
"property1": "something"
|
|
|
|
}"""
|
|
|
|
|
|
|
|
nt = stix2.parse(nt_string, allow_custom=True)
|
|
|
|
assert nt["property1"] == 'something'
|
|
|
|
|
|
|
|
|
|
|
|
def test_parse_unregistered_custom_object_type():
|
|
|
|
nt_string = """{
|
|
|
|
"type": "x-foobar-observable",
|
|
|
|
"created": "2015-12-21T19:59:11Z",
|
|
|
|
"property1": "something"
|
|
|
|
}"""
|
|
|
|
|
|
|
|
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
2018-07-05 21:21:09 +02:00
|
|
|
stix2.parse(nt_string, version="2.1")
|
2018-07-03 13:00:18 +02:00
|
|
|
assert "Can't parse unknown object type" in str(excinfo.value)
|
|
|
|
assert "use the CustomObject decorator." in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
def test_parse_unregistered_custom_object_type_w_allow_custom():
|
|
|
|
"""parse an unknown custom object, allowed by passing
|
|
|
|
'allow_custom' flag
|
|
|
|
"""
|
|
|
|
nt_string = """{
|
|
|
|
"type": "x-foobar-observable",
|
|
|
|
"created": "2015-12-21T19:59:11Z",
|
|
|
|
"property1": "something"
|
|
|
|
}"""
|
|
|
|
|
2018-07-05 21:21:09 +02:00
|
|
|
custom_obj = stix2.parse(nt_string, version="2.1", allow_custom=True)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert custom_obj["type"] == "x-foobar-observable"
|
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
# Custom SCOs
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2020-03-19 21:49:46 +01:00
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-new-observable', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
('property2', stix2.properties.IntegerProperty()),
|
|
|
|
('x_property3', stix2.properties.BooleanProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObservable():
|
|
|
|
def __init__(self, property2=None, **kwargs):
|
|
|
|
if property2 and property2 < 10:
|
|
|
|
raise ValueError("'property2' is too small.")
|
|
|
|
if "property3" in kwargs and not isinstance(kwargs.get("property3"), int):
|
|
|
|
raise TypeError("Must be integer!")
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_1():
|
|
|
|
no = NewObservable(property1='something')
|
|
|
|
assert no.property1 == 'something'
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_2():
|
|
|
|
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
|
|
|
NewObservable(property2=42)
|
|
|
|
assert excinfo.value.properties == ['property1']
|
|
|
|
assert "No values for required properties" in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_3():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
NewObservable(property1='something', property2=4)
|
|
|
|
assert "'property2' is too small." in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_raises_exception():
|
|
|
|
with pytest.raises(TypeError) as excinfo:
|
|
|
|
NewObservable(property1='something', property3='something', allow_custom=True)
|
|
|
|
|
|
|
|
assert str(excinfo.value) == "Must be integer!"
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_no_init_1():
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-new-observable', [
|
|
|
|
('property1', stix2.properties.StringProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObs():
|
|
|
|
pass
|
|
|
|
|
|
|
|
no = NewObs(property1='something')
|
|
|
|
assert no.property1 == 'something'
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_no_init_2():
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-new-obs2', [
|
|
|
|
('property1', stix2.properties.StringProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObs2(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
no2 = NewObs2(property1='something')
|
|
|
|
assert no2.property1 == 'something'
|
|
|
|
|
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_custom_observable_object_invalid_type_name():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x', [
|
|
|
|
('property1', stix2.properties.StringProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObs(object):
|
|
|
|
pass # pragma: no cover
|
|
|
|
assert "Invalid observable type name 'x':" in str(excinfo.value)
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x_new_obs', [
|
|
|
|
('property1', stix2.properties.StringProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObs2(object):
|
|
|
|
pass # pragma: no cover
|
|
|
|
assert "Invalid observable type name 'x_new_obs':" in str(excinfo.value)
|
|
|
|
|
2020-03-19 15:40:35 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_custom_observable_object_invalid_ref_property():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-new-obs', [
|
|
|
|
('property_ref', stix2.properties.StringProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObs():
|
|
|
|
pass
|
2019-11-06 16:11:12 +01:00
|
|
|
assert "is named like a reference property but is not a ReferenceProperty" in str(excinfo.value)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_invalid_refs_property():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-new-obs', [
|
|
|
|
('property_refs', stix2.properties.StringProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObs():
|
|
|
|
pass
|
2019-11-06 16:11:12 +01:00
|
|
|
assert "is named like a reference list property but is not a ListProperty containing ReferenceProperty" in str(excinfo.value)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_invalid_refs_list_property():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-new-obs', [
|
|
|
|
('property_refs', stix2.properties.ListProperty(stix2.properties.StringProperty)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObs():
|
|
|
|
pass
|
2019-11-06 16:11:12 +01:00
|
|
|
assert "is named like a reference list property but is not a ListProperty containing ReferenceProperty" in str(excinfo.value)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_custom_no_properties_raises_exception():
|
2018-07-10 21:56:22 +02:00
|
|
|
with pytest.raises(TypeError):
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-05 21:21:09 +02:00
|
|
|
@stix2.v21.CustomObject('x-new-object-type')
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObject1(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_wrong_properties_arg_raises_exception():
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
2018-07-10 21:56:22 +02:00
|
|
|
@stix2.v21.CustomObservable('x-new-object-type', (("prop", stix2.properties.BooleanProperty())))
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewObject2(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def test_parse_custom_observable_object():
|
|
|
|
nt_string = """{
|
|
|
|
"type": "x-new-observable",
|
|
|
|
"property1": "something"
|
|
|
|
}"""
|
2019-11-06 16:11:12 +01:00
|
|
|
nt = stix2.parse(nt_string, [], version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
assert isinstance(nt, stix2.base._STIXBase)
|
|
|
|
assert nt.property1 == 'something'
|
|
|
|
|
|
|
|
|
|
|
|
def test_parse_unregistered_custom_observable_object():
|
|
|
|
nt_string = """{
|
|
|
|
"type": "x-foobar-observable",
|
|
|
|
"property1": "something"
|
|
|
|
}"""
|
|
|
|
|
2019-11-06 16:11:12 +01:00
|
|
|
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
|
|
|
stix2.parse(nt_string, version='2.1')
|
|
|
|
assert "Can't parse unknown object type" in str(excinfo.value)
|
|
|
|
parsed_custom = stix2.parse(nt_string, allow_custom=True, version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
assert parsed_custom['property1'] == 'something'
|
|
|
|
with pytest.raises(AttributeError) as excinfo:
|
|
|
|
assert parsed_custom.property1 == 'something'
|
|
|
|
assert not isinstance(parsed_custom, stix2.base._STIXBase)
|
|
|
|
|
|
|
|
|
|
|
|
def test_parse_unregistered_custom_observable_object_with_no_type():
|
|
|
|
nt_string = """{
|
|
|
|
"property1": "something"
|
|
|
|
}"""
|
|
|
|
|
|
|
|
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
2019-11-06 16:11:12 +01:00
|
|
|
stix2.parse(nt_string, allow_custom=True, version='2.1')
|
|
|
|
assert "Can't parse object with no 'type' property" in str(excinfo.value)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_observed_data_with_custom_observable():
|
|
|
|
input_str = """{
|
|
|
|
"type": "observed-data",
|
|
|
|
"id": "observed-data--dc20c4ca-a2a3-4090-a5d5-9558c3af4758",
|
|
|
|
"created": "2016-04-06T19:58:16.000Z",
|
|
|
|
"modified": "2016-04-06T19:58:16.000Z",
|
|
|
|
"first_observed": "2015-12-21T19:00:00Z",
|
|
|
|
"last_observed": "2015-12-21T19:00:00Z",
|
|
|
|
"number_observed": 1,
|
|
|
|
"objects": {
|
|
|
|
"0": {
|
|
|
|
"type": "x-foobar-observable",
|
|
|
|
"property1": "something"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}"""
|
2018-07-05 21:21:09 +02:00
|
|
|
parsed = stix2.parse(input_str, version="2.1", allow_custom=True)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert parsed.objects['0']['property1'] == 'something'
|
|
|
|
|
|
|
|
|
|
|
|
def test_parse_invalid_custom_observable_object():
|
|
|
|
nt_string = """{
|
|
|
|
"property1": "something"
|
|
|
|
}"""
|
|
|
|
|
|
|
|
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
2019-11-06 16:11:12 +01:00
|
|
|
stix2.parse(nt_string, version='2.1')
|
|
|
|
assert "Can't parse object with no 'type' property" in str(excinfo.value)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_observable_custom_property():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
NewObservable(
|
|
|
|
property1='something',
|
|
|
|
custom_properties="foobar",
|
|
|
|
)
|
|
|
|
assert "'custom_properties' must be a dictionary" in str(excinfo.value)
|
|
|
|
|
|
|
|
no = NewObservable(
|
|
|
|
property1='something',
|
|
|
|
custom_properties={
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
assert no.foo == "bar"
|
|
|
|
|
|
|
|
|
|
|
|
def test_observable_custom_property_invalid():
|
|
|
|
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
|
|
|
NewObservable(
|
|
|
|
property1='something',
|
|
|
|
x_foo="bar",
|
|
|
|
)
|
|
|
|
assert excinfo.value.properties == ['x_foo']
|
|
|
|
assert "Unexpected properties for" in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
def test_observable_custom_property_allowed():
|
|
|
|
no = NewObservable(
|
|
|
|
property1='something',
|
|
|
|
x_foo="bar",
|
|
|
|
allow_custom=True,
|
|
|
|
)
|
|
|
|
assert no.x_foo == "bar"
|
|
|
|
|
|
|
|
|
|
|
|
def test_observed_data_with_custom_observable_object():
|
|
|
|
no = NewObservable(property1='something')
|
2018-07-05 21:21:09 +02:00
|
|
|
ob_data = stix2.v21.ObservedData(
|
2018-07-03 13:00:18 +02:00
|
|
|
first_observed=FAKE_TIME,
|
|
|
|
last_observed=FAKE_TIME,
|
|
|
|
number_observed=1,
|
|
|
|
objects={'0': no},
|
|
|
|
allow_custom=True,
|
|
|
|
)
|
|
|
|
assert ob_data.objects['0'].property1 == 'something'
|
|
|
|
|
|
|
|
|
2020-02-27 21:15:37 +01:00
|
|
|
def test_custom_observable_object_det_id_1():
|
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-det-id-observable-1', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
('property2', stix2.properties.IntegerProperty()),
|
|
|
|
], [
|
|
|
|
'property1',
|
|
|
|
],
|
|
|
|
)
|
|
|
|
class DetIdObs1():
|
|
|
|
pass
|
|
|
|
|
|
|
|
dio_1 = DetIdObs1(property1='I am property1!', property2=42)
|
|
|
|
dio_2 = DetIdObs1(property1='I am property1!', property2=24)
|
|
|
|
assert dio_1.property1 == dio_2.property1 == 'I am property1!'
|
|
|
|
assert dio_1.id == dio_2.id
|
|
|
|
|
|
|
|
uuid_obj = uuid.UUID(dio_1.id[-36:])
|
|
|
|
assert uuid_obj.variant == uuid.RFC_4122
|
|
|
|
assert uuid_obj.version == 5
|
|
|
|
|
|
|
|
dio_3 = DetIdObs1(property1='I am property1!', property2=42)
|
|
|
|
dio_4 = DetIdObs1(property1='I am also property1!', property2=24)
|
|
|
|
assert dio_3.property1 == 'I am property1!'
|
|
|
|
assert dio_4.property1 == 'I am also property1!'
|
|
|
|
assert dio_3.id != dio_4.id
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_det_id_2():
|
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-det-id-observable-2', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
('property2', stix2.properties.IntegerProperty()),
|
|
|
|
], [
|
|
|
|
'property1', 'property2',
|
|
|
|
],
|
|
|
|
)
|
|
|
|
class DetIdObs2():
|
|
|
|
pass
|
|
|
|
|
|
|
|
dio_1 = DetIdObs2(property1='I am property1!', property2=42)
|
|
|
|
dio_2 = DetIdObs2(property1='I am property1!', property2=42)
|
|
|
|
assert dio_1.property1 == dio_2.property1 == 'I am property1!'
|
|
|
|
assert dio_1.property2 == dio_2.property2 == 42
|
|
|
|
assert dio_1.id == dio_2.id
|
|
|
|
|
|
|
|
dio_3 = DetIdObs2(property1='I am property1!', property2=42)
|
|
|
|
dio_4 = DetIdObs2(property1='I am also property1!', property2=42)
|
|
|
|
assert dio_3.property1 == 'I am property1!'
|
|
|
|
assert dio_4.property1 == 'I am also property1!'
|
|
|
|
assert dio_3.property2 == dio_4.property2 == 42
|
|
|
|
assert dio_3.id != dio_4.id
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_observable_object_no_id_contrib_props():
|
|
|
|
@stix2.v21.CustomObservable(
|
|
|
|
'x-det-id-observable-3', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
class DetIdObs3():
|
|
|
|
pass
|
|
|
|
|
|
|
|
dio = DetIdObs3(property1="I am property1!")
|
|
|
|
|
|
|
|
uuid_obj = uuid.UUID(dio.id[-36:])
|
|
|
|
assert uuid_obj.variant == uuid.RFC_4122
|
|
|
|
assert uuid_obj.version == 4
|
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
# Custom Extensions
|
2020-02-27 21:15:37 +01:00
|
|
|
|
2020-03-19 21:49:46 +01:00
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
|
|
|
stix2.v21.DomainName, 'x-new-ext', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
('property2', stix2.properties.IntegerProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewExtension():
|
|
|
|
def __init__(self, property2=None, **kwargs):
|
|
|
|
if property2 and property2 < 10:
|
|
|
|
raise ValueError("'property2' is too small.")
|
|
|
|
if "property3" in kwargs and not isinstance(kwargs.get("property3"), int):
|
|
|
|
raise TypeError("Must be integer!")
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension_raises_exception():
|
|
|
|
with pytest.raises(TypeError) as excinfo:
|
|
|
|
NewExtension(property1='something', property3='something', allow_custom=True)
|
|
|
|
|
|
|
|
assert str(excinfo.value) == "Must be integer!"
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension():
|
|
|
|
ext = NewExtension(property1='something')
|
|
|
|
assert ext.property1 == 'something'
|
|
|
|
|
|
|
|
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
|
|
|
NewExtension(property2=42)
|
|
|
|
assert excinfo.value.properties == ['property1']
|
2018-07-09 21:20:04 +02:00
|
|
|
assert str(excinfo.value) == "No values for required properties for _CustomExtension: (property1)."
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
NewExtension(property1='something', property2=4)
|
|
|
|
assert str(excinfo.value) == "'property2' is too small."
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension_wrong_observable_type():
|
|
|
|
# NewExtension is an extension of DomainName, not File
|
|
|
|
ext = NewExtension(property1='something')
|
Improved the exception class hierarchy:
- Removed all plain python base classes (e.g. ValueError, TypeError)
- Renamed InvalidPropertyConfigurationError -> PropertyPresenceError,
since incorrect values could be considered a property config error, and
I really just wanted this class to apply to presence (co-)constraint
violations.
- Added ObjectConfigurationError as a superclass of InvalidValueError,
PropertyPresenceError, and any other exception that could be raised
during _STIXBase object init, which is when the spec compliance
checks happen. This class is intended to represent general spec
violations.
- Did some class reordering in exceptions.py, so all the
ObjectConfigurationError subclasses were together.
Changed how property "cleaning" errors were handled:
- Previous docs said they should all be ValueErrors, but that would require
extra exception check-and-replace complexity in the property
implementations, so that requirement is removed. Doc is changed to just
say that cleaning problems should cause exceptions to be raised.
_STIXBase._check_property() now handles most exception types, not just
ValueError.
- Decided to try chaining the original clean error to the InvalidValueError,
in case the extra diagnostics would be helpful in the future. This is
done via 'six' adapter function and only works on python3.
- A small amount of testing was removed, since it was looking at custom
exception properties which became unavailable once the exception was
replaced with InvalidValueError.
Did another pass through unit tests to fix breakage caused by the changed
exception class hierarchy.
Removed unnecessary observable extension handling code from
parse_observable(), since it was all duplicated in ExtensionsProperty.
The redundant code in parse_observable() had different exception behavior
than ExtensionsProperty, which makes the API inconsistent and unit tests
more complicated. (Problems in ExtensionsProperty get replaced with
InvalidValueError, but extensions problems handled directly in
parse_observable() don't get the same replacement, and so the exception
type is different.)
Redid the workbench monkeypatching. The old way was impossible to make
work, and had caused ugly ripple effect hackage in other parts of the
codebase. Now, it replaces the global object maps with factory functions
which behave the same way when called, as real classes. Had to fix up a
few unit tests to get them all passing with this monkeypatching in place.
Also remove all the xfail markings in the workbench test suite, since all
tests now pass.
Since workbench monkeypatching isn't currently affecting any unit tests,
tox.ini was simplified to remove the special-casing for running the
workbench tests.
Removed the v20 workbench test suite, since the workbench currently only
works with the latest stix object version.
2019-07-19 20:50:11 +02:00
|
|
|
with pytest.raises(InvalidValueError) as excinfo:
|
2018-07-05 21:21:09 +02:00
|
|
|
stix2.v21.File(
|
|
|
|
name="abc.txt",
|
|
|
|
extensions={
|
|
|
|
"ntfs-ext": ext,
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert 'Cannot determine extension type' in excinfo.value.reason
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
"""{
|
2018-07-03 13:00:18 +02:00
|
|
|
"keys": [
|
|
|
|
{
|
|
|
|
"test123": 123,
|
|
|
|
"test345": "aaaa"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_custom_extension_with_list_and_dict_properties_observable_type(data):
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
2020-03-12 21:20:32 +01:00
|
|
|
stix2.v21.UserAccount, 'x-some-extension-ext', [
|
2018-07-13 17:10:05 +02:00
|
|
|
('keys', stix2.properties.ListProperty(stix2.properties.DictionaryProperty, required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class SomeCustomExtension:
|
|
|
|
pass
|
|
|
|
|
|
|
|
example = SomeCustomExtension(keys=[{'test123': 123, 'test345': 'aaaa'}])
|
|
|
|
assert data == str(example)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension_invalid_observable():
|
|
|
|
# These extensions are being applied to improperly-created Observables.
|
|
|
|
# The Observable classes should have been created with the CustomObservable decorator.
|
|
|
|
class Foo(object):
|
|
|
|
pass
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
|
|
|
Foo, 'x-new-ext', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class FooExtension():
|
|
|
|
pass # pragma: no cover
|
|
|
|
assert str(excinfo.value) == "'observable' must be a valid Observable class!"
|
|
|
|
|
2018-07-05 21:21:09 +02:00
|
|
|
class Bar(stix2.v21.observables._Observable):
|
2018-07-03 13:00:18 +02:00
|
|
|
pass
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
|
|
|
Bar, 'x-new-ext', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class BarExtension():
|
|
|
|
pass
|
|
|
|
assert "Unknown observable type" in str(excinfo.value)
|
|
|
|
assert "Custom observables must be created with the @CustomObservable decorator." in str(excinfo.value)
|
|
|
|
|
2018-07-05 21:21:09 +02:00
|
|
|
class Baz(stix2.v21.observables._Observable):
|
2018-07-03 13:00:18 +02:00
|
|
|
_type = 'Baz'
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
|
|
|
Baz, 'x-new-ext', [
|
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class BazExtension():
|
|
|
|
pass
|
|
|
|
assert "Unknown observable type" in str(excinfo.value)
|
|
|
|
assert "Custom observables must be created with the @CustomObservable decorator." in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension_invalid_type_name():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
|
|
|
stix2.v21.File, 'x', {
|
|
|
|
'property1': stix2.properties.StringProperty(required=True),
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class FooExtension():
|
|
|
|
pass # pragma: no cover
|
|
|
|
assert "Invalid extension type name 'x':" in str(excinfo.value)
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
|
|
|
stix2.v21.File, 'x_new_ext', {
|
|
|
|
'property1': stix2.properties.StringProperty(required=True),
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class BlaExtension():
|
|
|
|
pass # pragma: no cover
|
|
|
|
assert "Invalid extension type name 'x_new_ext':" in str(excinfo.value)
|
|
|
|
|
2020-03-19 15:40:35 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_custom_extension_no_properties():
|
2020-03-11 01:24:53 +01:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', None)
|
2018-07-03 13:00:18 +02:00
|
|
|
class BarExtension():
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension_empty_properties():
|
2020-03-11 01:24:53 +01:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', [])
|
2018-07-03 13:00:18 +02:00
|
|
|
class BarExtension():
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension_dict_properties():
|
2020-03-11 01:24:53 +01:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
@stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', {})
|
2018-07-03 13:00:18 +02:00
|
|
|
class BarExtension():
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension_no_init_1():
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
2020-03-11 01:24:53 +01:00
|
|
|
stix2.v21.DomainName, 'x-new-extension-ext', [
|
2018-07-13 17:10:05 +02:00
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewExt():
|
|
|
|
pass
|
|
|
|
|
|
|
|
ne = NewExt(property1="foobar")
|
|
|
|
assert ne.property1 == "foobar"
|
|
|
|
|
|
|
|
|
|
|
|
def test_custom_extension_no_init_2():
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomExtension(
|
2020-03-11 01:24:53 +01:00
|
|
|
stix2.v21.DomainName, 'x-new2-ext', [
|
2018-07-13 17:10:05 +02:00
|
|
|
('property1', stix2.properties.StringProperty(required=True)),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class NewExt2(object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
ne2 = NewExt2(property1="foobar")
|
|
|
|
assert ne2.property1 == "foobar"
|
|
|
|
|
|
|
|
|
2020-03-19 21:11:52 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_observable_with_custom_extension():
|
|
|
|
input_str = """{
|
|
|
|
"type": "domain-name",
|
|
|
|
"value": "example.com",
|
|
|
|
"extensions": {
|
|
|
|
"x-new-ext": {
|
|
|
|
"property1": "foo",
|
|
|
|
"property2": 12
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}"""
|
2019-11-06 16:11:12 +01:00
|
|
|
parsed = stix2.parse(input_str, version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
assert parsed.extensions['x-new-ext'].property2 == 12
|
|
|
|
|
|
|
|
|
2019-08-26 23:10:54 +02:00
|
|
|
def test_custom_and_spec_extension_mix():
|
|
|
|
"""
|
|
|
|
Try to make sure that when allow_custom=True, encountering a custom
|
2019-08-26 23:49:55 +02:00
|
|
|
extension doesn't result in a completely uncleaned extensions property.
|
2019-08-26 23:10:54 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
file_obs = stix2.v21.File(
|
|
|
|
name="my_file.dat",
|
|
|
|
extensions={
|
2020-03-12 21:20:32 +01:00
|
|
|
"x-custom1-ext": {
|
2019-08-26 23:10:54 +02:00
|
|
|
"a": 1,
|
|
|
|
"b": 2,
|
|
|
|
},
|
|
|
|
"ntfs-ext": {
|
|
|
|
"sid": "S-1-whatever",
|
|
|
|
},
|
2020-03-12 21:20:32 +01:00
|
|
|
"x-custom2-ext": {
|
2019-08-26 23:10:54 +02:00
|
|
|
"z": 99.9,
|
|
|
|
"y": False,
|
|
|
|
},
|
|
|
|
"raster-image-ext": {
|
|
|
|
"image_height": 1024,
|
|
|
|
"image_width": 768,
|
|
|
|
"bits_per_pixel": 32,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
allow_custom=True,
|
|
|
|
)
|
|
|
|
|
2020-03-12 21:20:32 +01:00
|
|
|
assert file_obs.extensions["x-custom1-ext"] == {"a": 1, "b": 2}
|
|
|
|
assert file_obs.extensions["x-custom2-ext"] == {"y": False, "z": 99.9}
|
2019-08-26 23:10:54 +02:00
|
|
|
assert file_obs.extensions["ntfs-ext"].sid == "S-1-whatever"
|
|
|
|
assert file_obs.extensions["raster-image-ext"].image_height == 1024
|
|
|
|
|
|
|
|
# Both of these should have been converted to objects, not left as dicts.
|
|
|
|
assert isinstance(
|
|
|
|
file_obs.extensions["raster-image-ext"], stix2.v21.RasterImageExt,
|
|
|
|
)
|
|
|
|
assert isinstance(
|
|
|
|
file_obs.extensions["ntfs-ext"], stix2.v21.NTFSExt,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-11-29 17:06:27 +01:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
# URL is not in EXT_MAP
|
|
|
|
"""{
|
2018-11-01 22:23:55 +01:00
|
|
|
"type": "url",
|
2018-07-03 13:00:18 +02:00
|
|
|
"value": "example.com",
|
|
|
|
"extensions": {
|
|
|
|
"x-foobar-ext": {
|
|
|
|
"property1": "foo",
|
|
|
|
"property2": 12
|
|
|
|
}
|
|
|
|
}
|
2018-11-01 22:23:55 +01:00
|
|
|
}""",
|
2018-11-29 17:06:27 +01:00
|
|
|
# File is in EXT_MAP
|
|
|
|
"""{
|
2018-11-01 22:23:55 +01:00
|
|
|
"type": "file",
|
|
|
|
"name": "foo.txt",
|
|
|
|
"extensions": {
|
|
|
|
"x-foobar-ext": {
|
|
|
|
"property1": "foo",
|
|
|
|
"property2": 12
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}""",
|
2018-11-29 17:06:27 +01:00
|
|
|
],
|
|
|
|
)
|
2018-11-01 22:23:55 +01:00
|
|
|
def test_parse_observable_with_unregistered_custom_extension(data):
|
Improved the exception class hierarchy:
- Removed all plain python base classes (e.g. ValueError, TypeError)
- Renamed InvalidPropertyConfigurationError -> PropertyPresenceError,
since incorrect values could be considered a property config error, and
I really just wanted this class to apply to presence (co-)constraint
violations.
- Added ObjectConfigurationError as a superclass of InvalidValueError,
PropertyPresenceError, and any other exception that could be raised
during _STIXBase object init, which is when the spec compliance
checks happen. This class is intended to represent general spec
violations.
- Did some class reordering in exceptions.py, so all the
ObjectConfigurationError subclasses were together.
Changed how property "cleaning" errors were handled:
- Previous docs said they should all be ValueErrors, but that would require
extra exception check-and-replace complexity in the property
implementations, so that requirement is removed. Doc is changed to just
say that cleaning problems should cause exceptions to be raised.
_STIXBase._check_property() now handles most exception types, not just
ValueError.
- Decided to try chaining the original clean error to the InvalidValueError,
in case the extra diagnostics would be helpful in the future. This is
done via 'six' adapter function and only works on python3.
- A small amount of testing was removed, since it was looking at custom
exception properties which became unavailable once the exception was
replaced with InvalidValueError.
Did another pass through unit tests to fix breakage caused by the changed
exception class hierarchy.
Removed unnecessary observable extension handling code from
parse_observable(), since it was all duplicated in ExtensionsProperty.
The redundant code in parse_observable() had different exception behavior
than ExtensionsProperty, which makes the API inconsistent and unit tests
more complicated. (Problems in ExtensionsProperty get replaced with
InvalidValueError, but extensions problems handled directly in
parse_observable() don't get the same replacement, and so the exception
type is different.)
Redid the workbench monkeypatching. The old way was impossible to make
work, and had caused ugly ripple effect hackage in other parts of the
codebase. Now, it replaces the global object maps with factory functions
which behave the same way when called, as real classes. Had to fix up a
few unit tests to get them all passing with this monkeypatching in place.
Also remove all the xfail markings in the workbench test suite, since all
tests now pass.
Since workbench monkeypatching isn't currently affecting any unit tests,
tox.ini was simplified to remove the special-casing for running the
workbench tests.
Removed the v20 workbench test suite, since the workbench currently only
works with the latest stix object version.
2019-07-19 20:50:11 +02:00
|
|
|
with pytest.raises(InvalidValueError) as excinfo:
|
2019-11-06 16:11:12 +01:00
|
|
|
stix2.parse(data, version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
assert "Can't parse unknown extension type" in str(excinfo.value)
|
2019-11-06 16:11:12 +01:00
|
|
|
parsed_ob = stix2.parse(data, allow_custom=True, version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
assert parsed_ob['extensions']['x-foobar-ext']['property1'] == 'foo'
|
|
|
|
assert not isinstance(parsed_ob['extensions']['x-foobar-ext'], stix2.base._STIXBase)
|
|
|
|
|
|
|
|
|
|
|
|
def test_register_custom_object():
|
|
|
|
# Not the way to register custom object.
|
|
|
|
class CustomObject2(object):
|
|
|
|
_type = 'awesome-object'
|
|
|
|
|
2020-04-02 03:52:04 +02:00
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
stix2.parsing._register_object(CustomObject2, version="2.1")
|
|
|
|
assert '@CustomObject decorator' in str(excinfo)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_extension_property_location():
|
2018-07-05 21:21:09 +02:00
|
|
|
assert 'extensions' in stix2.v21.OBJ_MAP_OBSERVABLE['x-new-observable']._properties
|
|
|
|
assert 'extensions' not in stix2.v21.EXT_MAP['domain-name']['x-new-ext']._properties
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
"""{
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "x-example",
|
2018-07-03 15:40:51 +02:00
|
|
|
"spec_version": "2.1",
|
2018-07-03 13:00:18 +02:00
|
|
|
"id": "x-example--336d8a9f-91f1-46c5-b142-6441bb9f8b8d",
|
|
|
|
"created": "2018-06-12T16:20:58.059Z",
|
|
|
|
"modified": "2018-06-12T16:20:58.059Z",
|
|
|
|
"dictionary": {
|
|
|
|
"key": {
|
|
|
|
"key_a": "value",
|
|
|
|
"key_b": "value"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_custom_object_nested_dictionary(data):
|
2018-07-13 17:10:05 +02:00
|
|
|
@stix2.v21.CustomObject(
|
|
|
|
'x-example', [
|
|
|
|
('dictionary', stix2.properties.DictionaryProperty()),
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
class Example(object):
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
pass
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
example = Example(
|
|
|
|
id='x-example--336d8a9f-91f1-46c5-b142-6441bb9f8b8d',
|
|
|
|
created='2018-06-12T16:20:58.059Z',
|
|
|
|
modified='2018-06-12T16:20:58.059Z',
|
|
|
|
dictionary={'key': {'key_b': 'value', 'key_a': 'value'}},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert data == str(example)
|