2018-07-03 13:00:18 +02:00
|
|
|
import datetime as dt
|
|
|
|
import re
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
import pytz
|
|
|
|
|
|
|
|
import stix2
|
2019-06-13 03:19:50 +02:00
|
|
|
import stix2.exceptions
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2019-01-29 16:52:59 +01:00
|
|
|
from .constants import IDENTITY_ID, OBSERVED_DATA_ID
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
OBJECTS_REGEX = re.compile('\"objects\": {(?:.*?)(?:(?:[^{]*?)|(?:{[^{]*?}))*}', re.DOTALL)
|
|
|
|
|
|
|
|
|
|
|
|
EXPECTED = """{
|
|
|
|
"type": "observed-data",
|
|
|
|
"spec_version": "2.1",
|
|
|
|
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
2019-01-29 16:52:59 +01:00
|
|
|
"created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c",
|
2018-07-03 13:00:18 +02:00
|
|
|
"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": 50,
|
|
|
|
"objects": {
|
|
|
|
"0": {
|
|
|
|
"type": "file",
|
2019-09-05 01:08:34 +02:00
|
|
|
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
2019-09-06 06:25:42 +02:00
|
|
|
"spec_version": "2.1",
|
2018-07-03 13:00:18 +02:00
|
|
|
"name": "foo.exe"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_observed_data_example():
|
2018-07-03 15:40:51 +02:00
|
|
|
observed_data = stix2.v21.ObservedData(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=OBSERVED_DATA_ID,
|
2019-01-29 16:52:59 +01:00
|
|
|
created_by_ref=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
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=50,
|
|
|
|
objects={
|
|
|
|
"0": {
|
2018-07-13 17:10:05 +02:00
|
|
|
"type": "file",
|
2020-05-13 19:45:16 +02:00
|
|
|
"id": "file--7af1312c-4402-5d2f-b169-b118d73b85c4",
|
2019-09-06 06:25:42 +02:00
|
|
|
"name": "foo.exe",
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2019-09-06 06:25:42 +02:00
|
|
|
assert observed_data.id == "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf"
|
|
|
|
assert observed_data.created_by_ref == "identity--311b2d2d-f010-4473-83ec-1edf84858f4c"
|
|
|
|
assert observed_data.created == observed_data.modified == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc)
|
|
|
|
assert observed_data.first_observed == observed_data.last_observed == dt.datetime(2015, 12, 21, 19, 00, 00, tzinfo=pytz.utc)
|
|
|
|
assert observed_data.number_observed == 50
|
|
|
|
assert observed_data.objects['0'] == stix2.v21.File(name="foo.exe")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
EXPECTED_WITH_REF = """{
|
|
|
|
"type": "observed-data",
|
|
|
|
"spec_version": "2.1",
|
|
|
|
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
2019-01-29 16:52:59 +01:00
|
|
|
"created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c",
|
2018-07-03 13:00:18 +02:00
|
|
|
"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": 50,
|
|
|
|
"objects": {
|
|
|
|
"0": {
|
|
|
|
"type": "file",
|
2019-09-05 01:08:34 +02:00
|
|
|
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
2019-09-06 06:25:42 +02:00
|
|
|
"spec_version": "2.1",
|
2018-07-03 13:00:18 +02:00
|
|
|
"name": "foo.exe"
|
|
|
|
},
|
|
|
|
"1": {
|
|
|
|
"type": "directory",
|
2019-09-05 01:08:34 +02:00
|
|
|
"id": "directory--536a61a4-0934-516b-9aad-fcbb75e0583a",
|
2019-09-06 06:25:42 +02:00
|
|
|
"spec_version": "2.1",
|
2018-07-03 13:00:18 +02:00
|
|
|
"path": "/usr/home",
|
|
|
|
"contains_refs": [
|
2019-09-05 01:08:34 +02:00
|
|
|
"file--5956efbb-a7b0-566d-a7f9-a202eb05c70f"
|
2018-07-03 13:00:18 +02:00
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_observed_data_example_with_refs():
|
2018-07-03 15:40:51 +02:00
|
|
|
observed_data = stix2.v21.ObservedData(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=OBSERVED_DATA_ID,
|
2019-01-29 16:52:59 +01:00
|
|
|
created_by_ref=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
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=50,
|
|
|
|
objects={
|
|
|
|
"0": {
|
2018-07-13 17:10:05 +02:00
|
|
|
"type": "file",
|
2020-05-13 19:45:16 +02:00
|
|
|
"id": "file--7af1312c-4402-5d2f-b169-b118d73b85c4",
|
2019-09-06 06:25:42 +02:00
|
|
|
"name": "foo.exe",
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
"1": {
|
|
|
|
"type": "directory",
|
2020-05-13 19:45:16 +02:00
|
|
|
"id": "directory--ee97f78e-7e2b-5b3d-bcbd-5692968cacea",
|
2018-07-03 13:00:18 +02:00
|
|
|
"path": "/usr/home",
|
2019-09-05 01:08:34 +02:00
|
|
|
"contains_refs": ["file--5956efbb-a7b0-566d-a7f9-a202eb05c70f"],
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
)
|
2019-09-06 06:25:42 +02:00
|
|
|
assert observed_data.id == "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf"
|
|
|
|
assert observed_data.created_by_ref == "identity--311b2d2d-f010-4473-83ec-1edf84858f4c"
|
|
|
|
assert observed_data.created == observed_data.modified == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc)
|
|
|
|
assert observed_data.first_observed == observed_data.last_observed == dt.datetime(2015, 12, 21, 19, 00, 00, tzinfo=pytz.utc)
|
|
|
|
assert observed_data.number_observed == 50
|
|
|
|
assert observed_data.objects['0'] == stix2.v21.File(name="foo.exe")
|
|
|
|
assert observed_data.objects['1'] == stix2.v21.Directory(path="/usr/home", contains_refs=["file--5956efbb-a7b0-566d-a7f9-a202eb05c70f"])
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
2019-06-13 03:19:50 +02:00
|
|
|
EXPECTED_OBJECT_REFS = """{
|
|
|
|
"type": "observed-data",
|
|
|
|
"spec_version": "2.1",
|
|
|
|
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
|
|
|
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
|
|
|
"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": 50,
|
|
|
|
"object_refs": [
|
2019-09-05 01:08:34 +02:00
|
|
|
"file--758bf2c0-a6f1-56d1-872e-6b727467739a",
|
|
|
|
"url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457",
|
|
|
|
"mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c"
|
2019-06-13 03:19:50 +02:00
|
|
|
]
|
|
|
|
}"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_observed_data_example_with_object_refs():
|
|
|
|
observed_data = stix2.v21.ObservedData(
|
|
|
|
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
|
|
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
|
|
|
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=50,
|
|
|
|
object_refs=[
|
2019-09-05 01:08:34 +02:00
|
|
|
"file--758bf2c0-a6f1-56d1-872e-6b727467739a",
|
|
|
|
"url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457",
|
|
|
|
"mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c",
|
2019-06-13 03:19:50 +02:00
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
assert str(observed_data) == EXPECTED_OBJECT_REFS
|
|
|
|
|
|
|
|
|
|
|
|
def test_observed_data_object_constraint():
|
|
|
|
with pytest.raises(stix2.exceptions.MutuallyExclusivePropertiesError):
|
|
|
|
stix2.v21.ObservedData(
|
|
|
|
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
|
|
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
|
|
|
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=50,
|
|
|
|
objects={
|
|
|
|
"0": {
|
|
|
|
"name": "foo.exe",
|
|
|
|
"type": "file",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
object_refs=[
|
2019-09-05 01:08:34 +02:00
|
|
|
"file--758bf2c0-a6f1-56d1-872e-6b727467739a",
|
|
|
|
"url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457",
|
|
|
|
"mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c",
|
2019-06-13 03:19:50 +02:00
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_observed_data_example_with_bad_refs():
|
2019-09-06 06:25:42 +02:00
|
|
|
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.ObservedData(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=OBSERVED_DATA_ID,
|
2019-01-29 16:52:59 +01:00
|
|
|
created_by_ref=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
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=50,
|
|
|
|
objects={
|
|
|
|
"0": {
|
|
|
|
"type": "file",
|
2019-09-05 01:08:34 +02:00
|
|
|
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
2018-07-13 17:10:05 +02:00
|
|
|
"name": "foo.exe",
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
"1": {
|
|
|
|
"type": "directory",
|
|
|
|
"path": "/usr/home",
|
2019-09-05 01:08:34 +02:00
|
|
|
"contains_refs": ["monkey--5956efbb-a7b0-566d-a7f9-a202eb05c70f"],
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2019-09-05 01:08:34 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.Directory
|
|
|
|
assert excinfo.value.prop_name == "contains_refs"
|
2019-11-06 16:11:12 +01:00
|
|
|
assert "The type-specifying prefix 'monkey' for this property is not valid" in excinfo.value.reason
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_observed_data_example_with_non_dictionary():
|
|
|
|
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.ObservedData(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=OBSERVED_DATA_ID,
|
2019-01-29 16:52:59 +01:00
|
|
|
created_by_ref=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
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=50,
|
|
|
|
objects="file: foo.exe",
|
|
|
|
)
|
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.ObservedData
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.prop_name == "objects"
|
|
|
|
assert 'must contain a dictionary' in excinfo.value.reason
|
|
|
|
|
|
|
|
|
|
|
|
def test_observed_data_example_with_empty_dictionary():
|
|
|
|
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.ObservedData(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=OBSERVED_DATA_ID,
|
2019-01-29 16:52:59 +01:00
|
|
|
created_by_ref=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
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=50,
|
|
|
|
objects={},
|
|
|
|
)
|
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.ObservedData
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.prop_name == "objects"
|
|
|
|
assert 'must contain a non-empty dictionary' in excinfo.value.reason
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
EXPECTED,
|
|
|
|
{
|
|
|
|
"type": "observed-data",
|
|
|
|
"spec_version": "2.1",
|
2019-01-23 16:56:20 +01:00
|
|
|
"id": OBSERVED_DATA_ID,
|
2018-07-13 17:10:05 +02:00
|
|
|
"created": "2016-04-06T19:58:16.000Z",
|
2019-01-29 16:52:59 +01:00
|
|
|
"created_by_ref": IDENTITY_ID,
|
2018-07-13 17:10:05 +02:00
|
|
|
"first_observed": "2015-12-21T19:00:00Z",
|
|
|
|
"last_observed": "2015-12-21T19:00:00Z",
|
|
|
|
"modified": "2016-04-06T19:58:16.000Z",
|
|
|
|
"number_observed": 50,
|
|
|
|
"objects": {
|
|
|
|
"0": {
|
|
|
|
"name": "foo.exe",
|
|
|
|
"type": "file",
|
2019-09-05 01:08:34 +02:00
|
|
|
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_observed_data(data):
|
2018-07-03 15:40:51 +02:00
|
|
|
odata = stix2.parse(data, version="2.1")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert odata.type == 'observed-data'
|
2018-07-03 15:40:51 +02:00
|
|
|
assert odata.spec_version == '2.1'
|
2018-07-03 13:00:18 +02:00
|
|
|
assert odata.id == OBSERVED_DATA_ID
|
|
|
|
assert odata.created == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc)
|
|
|
|
assert odata.modified == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc)
|
|
|
|
assert odata.first_observed == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
|
|
|
|
assert odata.last_observed == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
|
2019-01-29 16:52:59 +01:00
|
|
|
assert odata.created_by_ref == IDENTITY_ID
|
2018-07-03 13:00:18 +02:00
|
|
|
assert odata.objects["0"].type == "file"
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
""""0": {
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "artifact",
|
|
|
|
"mime_type": "image/jpeg",
|
|
|
|
"payload_bin": "VBORw0KGgoAAAANSUhEUgAAADI=="
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
""""0": {
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "artifact",
|
|
|
|
"mime_type": "image/jpeg",
|
|
|
|
"url": "https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg",
|
|
|
|
"hashes": {
|
|
|
|
"MD5": "6826f9a05da08134006557758bb3afbb"
|
|
|
|
}
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_artifact_valid(data):
|
|
|
|
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
|
2018-07-03 15:40:51 +02:00
|
|
|
odata = stix2.parse(odata_str, version="2.1")
|
2018-07-03 13:00:18 +02:00
|
|
|
assert odata.objects["0"].type == "artifact"
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
""""0": {
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "artifact",
|
|
|
|
"mime_type": "image/jpeg",
|
|
|
|
"payload_bin": "abcVBORw0KGgoAAAANSUhEUgAAADI=="
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
""""0": {
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "artifact",
|
|
|
|
"mime_type": "image/jpeg",
|
|
|
|
"url": "https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg",
|
|
|
|
"hashes": {
|
|
|
|
"MD5": "a"
|
|
|
|
}
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_artifact_invalid(data):
|
|
|
|
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
|
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(stix2.exceptions.InvalidValueError):
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.parse(odata_str, version="2.1")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_artifact_example_dependency_error():
|
|
|
|
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.Artifact(url="http://example.com/sirvizio.exe")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert excinfo.value.dependencies == [("hashes", "url")]
|
|
|
|
assert str(excinfo.value) == "The property dependencies for Artifact: (hashes, url) are not met."
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
""""0": {
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "autonomous-system",
|
|
|
|
"number": 15139,
|
|
|
|
"name": "Slime Industries",
|
|
|
|
"rir": "ARIN"
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_autonomous_system_valid(data):
|
|
|
|
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
|
2018-07-03 15:40:51 +02:00
|
|
|
odata = stix2.parse(odata_str, version="2.1")
|
2018-07-03 13:00:18 +02:00
|
|
|
assert odata.objects["0"].type == "autonomous-system"
|
|
|
|
assert odata.objects["0"].number == 15139
|
|
|
|
assert odata.objects["0"].name == "Slime Industries"
|
|
|
|
assert odata.objects["0"].rir == "ARIN"
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
"""{
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "email-addr",
|
|
|
|
"value": "john@example.com",
|
|
|
|
"display_name": "John Doe",
|
2019-09-07 00:08:27 +02:00
|
|
|
"belongs_to_ref": "user-account--fc07c1af-6b11-41f8-97a4-47920d866a91"
|
2018-07-03 13:00:18 +02:00
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_email_address(data):
|
2019-11-06 16:11:12 +01:00
|
|
|
odata = stix2.parse(data, version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
assert odata.type == "email-addr"
|
|
|
|
|
2019-09-07 00:08:27 +02:00
|
|
|
odata_str = re.compile(
|
|
|
|
'"belongs_to_ref": "user-account--fc07c1af-6b11-41f8-97a4-47920d866a91"', re.DOTALL,
|
|
|
|
).sub(
|
|
|
|
'"belongs_to_ref": "mutex--9be6365f-b89c-48c0-9340-6953f6595718"', data,
|
|
|
|
)
|
|
|
|
with pytest.raises(stix2.exceptions.InvalidValueError):
|
2019-11-06 16:11:12 +01:00
|
|
|
stix2.parse(odata_str, version='2.1')
|
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": "email-message",
|
|
|
|
"is_multipart": true,
|
|
|
|
"content_type": "multipart/mixed",
|
|
|
|
"date": "2016-06-19T14:20:40.000Z",
|
2019-09-07 00:08:27 +02:00
|
|
|
"from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76",
|
2018-07-03 13:00:18 +02:00
|
|
|
"to_refs": [
|
2019-09-07 00:08:27 +02:00
|
|
|
"email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b"
|
2018-07-03 13:00:18 +02:00
|
|
|
],
|
|
|
|
"cc_refs": [
|
2019-09-07 00:08:27 +02:00
|
|
|
"email-addr--1766f860-5cf3-5697-8789-35f1242663d5"
|
2018-07-03 13:00:18 +02:00
|
|
|
],
|
|
|
|
"subject": "Check out this picture of a cat!",
|
|
|
|
"additional_header_fields": {
|
|
|
|
"Content-Disposition": "inline",
|
|
|
|
"X-Mailer": "Mutt/1.5.23",
|
|
|
|
"X-Originating-IP": "198.51.100.3"
|
|
|
|
},
|
|
|
|
"body_multipart": [
|
|
|
|
{
|
|
|
|
"content_type": "text/plain; charset=utf-8",
|
|
|
|
"content_disposition": "inline",
|
|
|
|
"body": "Cats are funny!"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"content_type": "image/png",
|
|
|
|
"content_disposition": "attachment; filename=\\"tabby.png\\"",
|
2019-09-07 00:08:27 +02:00
|
|
|
"body_raw_ref": "artifact--80b04ad8-db52-464b-a85a-a44a5f3a60c5"
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"content_type": "application/zip",
|
|
|
|
"content_disposition": "attachment; filename=\\"tabby_pics.zip\\"",
|
2019-09-07 00:08:27 +02:00
|
|
|
"body_raw_ref": "file--e63474fc-b386-5630-a003-1b555e22f99b"
|
2018-07-03 13:00:18 +02:00
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
2018-07-13 17:10:05 +02:00
|
|
|
""",
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_email_message(data):
|
2019-11-06 16:11:12 +01:00
|
|
|
odata = stix2.parse(data, version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
assert odata.type == "email-message"
|
|
|
|
assert odata.body_multipart[0].content_disposition == "inline"
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
"""
|
2018-07-03 13:00:18 +02:00
|
|
|
{
|
|
|
|
"type": "email-message",
|
2019-09-07 00:08:27 +02:00
|
|
|
"from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76",
|
|
|
|
"to_refs": ["email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b"],
|
2018-07-03 13:00:18 +02:00
|
|
|
"is_multipart": true,
|
|
|
|
"date": "1997-11-21T15:55:06.000Z",
|
|
|
|
"subject": "Saying Hello",
|
|
|
|
"body": "Cats are funny!"
|
|
|
|
}
|
2018-07-13 17:10:05 +02:00
|
|
|
""",
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_email_message_not_multipart(data):
|
|
|
|
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
|
2019-11-06 16:11:12 +01:00
|
|
|
stix2.parse(data, version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.EmailMessage
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.dependencies == [("is_multipart", "body")]
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
""""0": {
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "file",
|
2019-09-07 00:08:27 +02:00
|
|
|
"id": "file--ecd47d73-15e4-5250-afda-ef8897b22340",
|
2018-07-03 13:00:18 +02:00
|
|
|
"hashes": {
|
|
|
|
"SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"1": {
|
|
|
|
"type": "file",
|
2019-09-07 00:08:27 +02:00
|
|
|
"id": "file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3",
|
2018-07-03 13:00:18 +02:00
|
|
|
"hashes": {
|
2019-09-11 20:21:41 +02:00
|
|
|
"SHA-1": "6e71b3cac15d32fe2d36c270887df9479c25c640"
|
2018-07-03 13:00:18 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
"2": {
|
|
|
|
"type": "file",
|
2019-09-07 00:08:27 +02:00
|
|
|
"id": "file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac",
|
2018-07-03 13:00:18 +02:00
|
|
|
"hashes": {
|
2019-09-11 20:21:41 +02:00
|
|
|
"SHA-512": "b7e98c78c24fb4c2c7b175e90474b21eae0ccf1b5ea4708b4e0f2d2940004419edc7161c18a1e71b2565df099ba017bcaa67a248e2989b6268ce078b88f2e210"
|
2018-07-03 13:00:18 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
"3": {
|
|
|
|
"type": "file",
|
|
|
|
"name": "foo.zip",
|
|
|
|
"hashes": {
|
2019-09-11 20:21:41 +02:00
|
|
|
"SHA3-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
"mime_type": "application/zip",
|
|
|
|
"extensions": {
|
|
|
|
"archive-ext": {
|
|
|
|
"contains_refs": [
|
2019-09-07 00:08:27 +02:00
|
|
|
"file--ecd47d73-15e4-5250-afda-ef8897b22340",
|
|
|
|
"file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3",
|
|
|
|
"file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac"
|
2018-10-15 20:48:52 +02:00
|
|
|
]
|
2018-07-03 13:00:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}""",
|
2018-07-13 17:10:05 +02:00
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_file_archive(data):
|
|
|
|
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
|
2018-07-03 15:40:51 +02:00
|
|
|
odata = stix2.parse(odata_str, version="2.1")
|
2018-10-15 20:48:52 +02:00
|
|
|
assert all(x in odata.objects["3"].extensions['archive-ext'].contains_refs
|
2019-09-07 00:08:27 +02:00
|
|
|
for x in [
|
|
|
|
"file--ecd47d73-15e4-5250-afda-ef8897b22340",
|
|
|
|
"file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3",
|
|
|
|
"file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac",
|
|
|
|
])
|
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": "email-message",
|
|
|
|
"is_multipart": true,
|
|
|
|
"content_type": "multipart/mixed",
|
|
|
|
"date": "2016-06-19T14:20:40.000Z",
|
2019-09-07 00:08:27 +02:00
|
|
|
"from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76",
|
2018-07-03 13:00:18 +02:00
|
|
|
"to_refs": [
|
2019-09-07 00:08:27 +02:00
|
|
|
"email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b"
|
2018-07-03 13:00:18 +02:00
|
|
|
],
|
|
|
|
"cc_refs": [
|
2019-09-07 00:08:27 +02:00
|
|
|
"email-addr--1766f860-5cf3-5697-8789-35f1242663d5"
|
2018-07-03 13:00:18 +02:00
|
|
|
],
|
|
|
|
"subject": "Check out this picture of a cat!",
|
|
|
|
"additional_header_fields": {
|
|
|
|
"Content-Disposition": "inline",
|
|
|
|
"X-Mailer": "Mutt/1.5.23",
|
|
|
|
"X-Originating-IP": "198.51.100.3"
|
|
|
|
},
|
|
|
|
"body_multipart": [
|
|
|
|
{
|
|
|
|
"content_type": "text/plain; charset=utf-8",
|
|
|
|
"content_disposition": "inline",
|
|
|
|
"body": "Cats are funny!"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"content_type": "image/png",
|
|
|
|
"content_disposition": "attachment; filename=\\"tabby.png\\""
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"content_type": "application/zip",
|
|
|
|
"content_disposition": "attachment; filename=\\"tabby_pics.zip\\"",
|
2019-09-07 00:08:27 +02:00
|
|
|
"body_raw_ref": "file--e63474fc-b386-5630-a003-1b555e22f99b"
|
2018-07-03 13:00:18 +02:00
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
2018-07-13 17:10:05 +02:00
|
|
|
""",
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_email_message_with_at_least_one_error(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(stix2.exceptions.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
|
|
|
|
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 excinfo.value.cls == stix2.v21.EmailMessage
|
2018-07-03 13:00:18 +02:00
|
|
|
assert "At least one of the" in str(excinfo.value)
|
|
|
|
assert "must be populated" in str(excinfo.value)
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
"""
|
2018-07-03 13:00:18 +02:00
|
|
|
{
|
|
|
|
"type": "network-traffic",
|
2019-09-07 00:08:27 +02:00
|
|
|
"src_ref": "ipv4-addr--e535b017-cc1c-566b-a3e2-f69f92ed9c4c",
|
|
|
|
"dst_ref": "ipv4-addr--78327430-9ad9-5632-ae3d-8e2fce8f5483",
|
2018-07-03 13:00:18 +02:00
|
|
|
"protocols": [
|
|
|
|
"tcp"
|
|
|
|
]
|
|
|
|
}
|
2018-07-13 17:10:05 +02:00
|
|
|
""",
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_basic_tcp_traffic(data):
|
2019-11-06 16:11:12 +01:00
|
|
|
odata = stix2.parse(
|
2019-09-07 00:08:27 +02:00
|
|
|
data, version='2.1',
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert odata.type == "network-traffic"
|
2019-09-07 00:08:27 +02:00
|
|
|
assert odata.src_ref == "ipv4-addr--e535b017-cc1c-566b-a3e2-f69f92ed9c4c"
|
|
|
|
assert odata.dst_ref == "ipv4-addr--78327430-9ad9-5632-ae3d-8e2fce8f5483"
|
2018-07-03 13:00:18 +02:00
|
|
|
assert odata.protocols == ["tcp"]
|
|
|
|
|
|
|
|
|
2018-07-13 17:10:05 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"data", [
|
|
|
|
"""
|
2018-07-03 13:00:18 +02:00
|
|
|
{
|
|
|
|
"type": "network-traffic",
|
|
|
|
"src_port": 2487,
|
|
|
|
"dst_port": 1723,
|
|
|
|
"protocols": [
|
|
|
|
"ipv4",
|
|
|
|
"pptp"
|
|
|
|
],
|
|
|
|
"src_byte_count": 35779,
|
|
|
|
"dst_byte_count": 935750,
|
|
|
|
"encapsulates_refs": [
|
2019-09-07 00:08:27 +02:00
|
|
|
"network-traffic--016914c3-b680-5df2-81c4-bb9ccf8dc8b0"
|
2018-07-03 13:00:18 +02:00
|
|
|
]
|
|
|
|
}
|
2018-07-13 17:10:05 +02:00
|
|
|
""",
|
|
|
|
],
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_parse_basic_tcp_traffic_with_error(data):
|
|
|
|
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
2019-11-06 16:11:12 +01:00
|
|
|
stix2.parse(data, version='2.1')
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.NetworkTraffic
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.properties == ["dst_ref", "src_ref"]
|
|
|
|
|
|
|
|
|
|
|
|
EXPECTED_PROCESS_OD = """{
|
|
|
|
"created": "2016-04-06T19:58:16.000Z",
|
2019-01-29 16:52:59 +01:00
|
|
|
"created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c",
|
2018-07-03 13:00:18 +02:00
|
|
|
"first_observed": "2015-12-21T19:00:00Z",
|
|
|
|
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
|
|
|
"last_observed": "2015-12-21T19:00:00Z",
|
|
|
|
"modified": "2016-04-06T19:58:16.000Z",
|
|
|
|
"number_observed": 50,
|
|
|
|
"objects": {
|
|
|
|
"0": {
|
|
|
|
"type": "file",
|
|
|
|
"hashes": {
|
|
|
|
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100fSHA"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"1": {
|
|
|
|
"type": "process",
|
|
|
|
"pid": 1221,
|
|
|
|
"created": "2016-01-20T14:11:25.55Z",
|
2018-10-15 20:48:52 +02:00
|
|
|
"command_line": "./gedit-bin --new-window",
|
2018-07-03 13:00:18 +02:00
|
|
|
"binary_ref": "0"
|
|
|
|
}
|
|
|
|
},
|
2018-07-03 15:40:51 +02:00
|
|
|
"spec_version": "2.1",
|
2018-07-03 13:00:18 +02:00
|
|
|
"type": "observed-data"
|
|
|
|
}"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_observed_data_with_process_example():
|
2018-07-03 15:40:51 +02:00
|
|
|
observed_data = stix2.v21.ObservedData(
|
2019-01-23 16:56:20 +01:00
|
|
|
id=OBSERVED_DATA_ID,
|
2019-01-29 16:52:59 +01:00
|
|
|
created_by_ref=IDENTITY_ID,
|
2018-07-03 13:00:18 +02:00
|
|
|
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=50,
|
|
|
|
objects={
|
|
|
|
"0": {
|
|
|
|
"type": "file",
|
2019-09-05 01:08:34 +02:00
|
|
|
"id": "file--0d16c8d3-c177-5f5d-a022-b1bdac329bea",
|
2018-07-03 13:00:18 +02:00
|
|
|
"hashes": {
|
2018-07-13 17:10:05 +02:00
|
|
|
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f",
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"1": {
|
|
|
|
"type": "process",
|
2019-09-05 01:08:34 +02:00
|
|
|
"id": "process--f6c4a02c-23e1-4a6d-a0d7-d862e893817a",
|
2018-07-03 13:00:18 +02:00
|
|
|
"pid": 1221,
|
2019-09-05 01:08:34 +02:00
|
|
|
"created_time": "2016-01-20T14:11:25.55Z",
|
2018-10-15 20:48:52 +02:00
|
|
|
"command_line": "./gedit-bin --new-window",
|
2019-09-05 01:08:34 +02:00
|
|
|
"image_ref": "file--0d16c8d3-c177-5f5d-a022-b1bdac329bea",
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert observed_data.objects["0"].type == "file"
|
|
|
|
assert observed_data.objects["0"].hashes["SHA-256"] == "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
|
|
|
|
assert observed_data.objects["1"].type == "process"
|
|
|
|
assert observed_data.objects["1"].pid == 1221
|
2018-10-15 20:48:52 +02:00
|
|
|
assert observed_data.objects["1"].command_line == "./gedit-bin --new-window"
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
# creating cyber observables directly
|
|
|
|
|
|
|
|
def test_artifact_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
art = stix2.v21.Artifact(
|
|
|
|
mime_type="image/jpeg",
|
|
|
|
url="https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg",
|
|
|
|
hashes={
|
2018-07-13 17:10:05 +02:00
|
|
|
"MD5": "6826f9a05da08134006557758bb3afbb",
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert art.mime_type == "image/jpeg"
|
|
|
|
assert art.url == "https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg"
|
|
|
|
assert art.hashes["MD5"] == "6826f9a05da08134006557758bb3afbb"
|
|
|
|
|
|
|
|
|
|
|
|
def test_artifact_mutual_exclusion_error():
|
|
|
|
with pytest.raises(stix2.exceptions.MutuallyExclusivePropertiesError) as excinfo:
|
2018-07-03 16:32:04 +02:00
|
|
|
stix2.v21.Artifact(
|
|
|
|
mime_type="image/jpeg",
|
|
|
|
url="https://upload.wikimedia.org/wikipedia/commons/b/b4/JPEG_example_JPG_RIP_100.jpg",
|
|
|
|
hashes={
|
2018-07-13 17:10:05 +02:00
|
|
|
"MD5": "6826f9a05da08134006557758bb3afbb",
|
2018-07-03 16:32:04 +02:00
|
|
|
},
|
2018-07-13 17:10:05 +02:00
|
|
|
payload_bin="VBORw0KGgoAAAANSUhEUgAAADI==",
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.Artifact
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.properties == ["payload_bin", "url"]
|
|
|
|
assert 'are mutually exclusive' in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
def test_directory_example():
|
2019-09-05 01:08:34 +02:00
|
|
|
f = stix2.v21.File(
|
|
|
|
name="penguin.exe",
|
|
|
|
)
|
|
|
|
|
|
|
|
dir1 = stix2.v21.Directory(
|
2018-07-03 16:32:04 +02:00
|
|
|
path='/usr/lib',
|
2019-07-17 21:48:09 +02:00
|
|
|
ctime="2015-12-21T19:00:00Z",
|
|
|
|
mtime="2015-12-24T19:00:00Z",
|
|
|
|
atime="2015-12-21T20:00:00Z",
|
2019-09-05 01:08:34 +02:00
|
|
|
contains_refs=[str(f.id)],
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2019-09-05 01:08:34 +02:00
|
|
|
assert dir1.path == '/usr/lib'
|
|
|
|
assert dir1.ctime == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
|
|
|
|
assert dir1.mtime == dt.datetime(2015, 12, 24, 19, 0, 0, tzinfo=pytz.utc)
|
|
|
|
assert dir1.atime == dt.datetime(2015, 12, 21, 20, 0, 0, tzinfo=pytz.utc)
|
2020-05-13 19:45:16 +02:00
|
|
|
assert dir1.contains_refs == ["file--c6ae2cf8-92d3-56d0-a25f-713efad643a7"]
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_directory_example_ref_error():
|
2019-09-05 01:08:34 +02:00
|
|
|
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
2018-07-03 16:32:04 +02:00
|
|
|
stix2.v21.Directory(
|
|
|
|
path='/usr/lib',
|
2019-07-17 21:48:09 +02:00
|
|
|
ctime="2015-12-21T19:00:00Z",
|
|
|
|
mtime="2015-12-24T19:00:00Z",
|
|
|
|
atime="2015-12-21T20:00:00Z",
|
2019-09-05 01:08:34 +02:00
|
|
|
contains_refs=["domain-name--02af94ea-7e38-5718-87c3-5cc023e3d49d"],
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 15:40:51 +02:00
|
|
|
|
|
|
|
assert excinfo.value.cls == stix2.v21.Directory
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.prop_name == "contains_refs"
|
|
|
|
|
|
|
|
|
|
|
|
def test_domain_name_example():
|
2019-09-05 01:08:34 +02:00
|
|
|
dn1 = stix2.v21.DomainName(
|
|
|
|
value="mitre.org",
|
|
|
|
)
|
|
|
|
|
|
|
|
dn2 = stix2.v21.DomainName(
|
2018-07-03 16:32:04 +02:00
|
|
|
value="example.com",
|
2019-09-05 01:08:34 +02:00
|
|
|
resolves_to_refs=[str(dn1.id)],
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2019-09-05 01:08:34 +02:00
|
|
|
assert dn2.value == "example.com"
|
2020-05-13 19:45:16 +02:00
|
|
|
assert dn2.resolves_to_refs == ["domain-name--5b5803bf-a7eb-5076-b799-96aa574c44eb"]
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_domain_name_example_invalid_ref_type():
|
2019-09-05 01:08:34 +02:00
|
|
|
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
2018-07-03 16:32:04 +02:00
|
|
|
stix2.v21.DomainName(
|
|
|
|
value="example.com",
|
2019-09-05 01:08:34 +02:00
|
|
|
resolves_to_refs=["file--44a431e6-764b-5556-a3f5-bf655930a581"],
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.DomainName
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.prop_name == "resolves_to_refs"
|
|
|
|
|
|
|
|
|
|
|
|
def test_file_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
f = stix2.v21.File(
|
|
|
|
name="qwerty.dll",
|
|
|
|
hashes={
|
2018-07-13 17:10:05 +02:00
|
|
|
"SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a",
|
2018-07-12 20:33:00 +02:00
|
|
|
},
|
2018-07-03 16:32:04 +02:00
|
|
|
size=100,
|
|
|
|
magic_number_hex="1C",
|
|
|
|
mime_type="application/msword",
|
2019-07-17 21:48:09 +02:00
|
|
|
ctime="2016-12-21T19:00:00Z",
|
|
|
|
mtime="2016-12-24T19:00:00Z",
|
|
|
|
atime="2016-12-21T20:00:00Z",
|
2018-07-12 20:33:00 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert f.name == "qwerty.dll"
|
|
|
|
assert f.size == 100
|
|
|
|
assert f.magic_number_hex == "1C"
|
|
|
|
assert f.hashes["SHA-256"] == "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a"
|
|
|
|
assert f.mime_type == "application/msword"
|
2019-07-17 21:48:09 +02:00
|
|
|
assert f.ctime == dt.datetime(2016, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
|
|
|
|
assert f.mtime == dt.datetime(2016, 12, 24, 19, 0, 0, tzinfo=pytz.utc)
|
|
|
|
assert f.atime == dt.datetime(2016, 12, 21, 20, 0, 0, tzinfo=pytz.utc)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
2020-05-14 00:17:17 +02:00
|
|
|
def test_file_ssdeep_example():
|
|
|
|
f = stix2.v21.File(
|
|
|
|
name="example.dll",
|
|
|
|
hashes={
|
|
|
|
"SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a",
|
|
|
|
"SSDEEP": "96:gS/mFkCpXTWLr/PbKQHbr/S/mFkCpXTWLr/PbKQHbrB:Tu6SXTWGQHbeu6SXTWGQHbV",
|
|
|
|
},
|
|
|
|
size=1024,
|
|
|
|
)
|
|
|
|
|
|
|
|
assert f.name == "example.dll"
|
|
|
|
assert f.size == 1024
|
|
|
|
assert f.hashes["SHA-256"] == "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a"
|
|
|
|
assert f.hashes["SSDEEP"] == "96:gS/mFkCpXTWLr/PbKQHbr/S/mFkCpXTWLr/PbKQHbrB:Tu6SXTWGQHbeu6SXTWGQHbV"
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_file_example_with_NTFSExt():
|
2018-07-03 16:32:04 +02:00
|
|
|
f = stix2.v21.File(
|
|
|
|
name="abc.txt",
|
|
|
|
extensions={
|
|
|
|
"ntfs-ext": {
|
|
|
|
"alternate_data_streams": [
|
|
|
|
{
|
|
|
|
"name": "second.stream",
|
2018-07-13 17:10:05 +02:00
|
|
|
"size": 25536,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert f.name == "abc.txt"
|
|
|
|
assert f.extensions["ntfs-ext"].alternate_data_streams[0].size == 25536
|
|
|
|
|
|
|
|
|
|
|
|
def test_file_example_with_empty_NTFSExt():
|
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(stix2.exceptions.InvalidValueError) as excinfo:
|
2018-07-03 16:32:04 +02:00
|
|
|
stix2.v21.File(
|
|
|
|
name="abc.txt",
|
|
|
|
extensions={
|
2018-07-13 17:10:05 +02:00
|
|
|
"ntfs-ext": {},
|
|
|
|
},
|
|
|
|
)
|
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
|
|
|
assert excinfo.value.cls == stix2.v21.File
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_file_example_with_PDFExt():
|
2018-07-03 16:32:04 +02:00
|
|
|
f = stix2.v21.File(
|
|
|
|
name="qwerty.dll",
|
|
|
|
extensions={
|
|
|
|
"pdf-ext": {
|
|
|
|
"version": "1.7",
|
|
|
|
"document_info_dict": {
|
|
|
|
"Title": "Sample document",
|
|
|
|
"Author": "Adobe Systems Incorporated",
|
|
|
|
"Creator": "Adobe FrameMaker 5.5.3 for Power Macintosh",
|
|
|
|
"Producer": "Acrobat Distiller 3.01 for Power Macintosh",
|
2018-07-13 17:10:05 +02:00
|
|
|
"CreationDate": "20070412090123-02",
|
2018-07-03 16:32:04 +02:00
|
|
|
},
|
|
|
|
"pdfid0": "DFCE52BD827ECF765649852119D",
|
2018-07-13 17:10:05 +02:00
|
|
|
"pdfid1": "57A1E0F9ED2AE523E313C",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert f.name == "qwerty.dll"
|
|
|
|
assert f.extensions["pdf-ext"].version == "1.7"
|
|
|
|
assert f.extensions["pdf-ext"].document_info_dict["Title"] == "Sample document"
|
|
|
|
|
|
|
|
|
|
|
|
def test_file_example_with_PDFExt_Object():
|
2018-07-03 16:32:04 +02:00
|
|
|
f = stix2.v21.File(
|
|
|
|
name="qwerty.dll",
|
|
|
|
extensions={
|
|
|
|
"pdf-ext": stix2.v21.PDFExt(
|
|
|
|
version="1.7",
|
|
|
|
document_info_dict={
|
|
|
|
"Title": "Sample document",
|
|
|
|
"Author": "Adobe Systems Incorporated",
|
|
|
|
"Creator": "Adobe FrameMaker 5.5.3 for Power Macintosh",
|
|
|
|
"Producer": "Acrobat Distiller 3.01 for Power Macintosh",
|
2018-07-13 17:10:05 +02:00
|
|
|
"CreationDate": "20070412090123-02",
|
2018-07-03 16:32:04 +02:00
|
|
|
},
|
|
|
|
pdfid0="DFCE52BD827ECF765649852119D",
|
2018-07-13 17:10:05 +02:00
|
|
|
pdfid1="57A1E0F9ED2AE523E313C",
|
|
|
|
),
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert f.name == "qwerty.dll"
|
|
|
|
assert f.extensions["pdf-ext"].version == "1.7"
|
|
|
|
assert f.extensions["pdf-ext"].document_info_dict["Title"] == "Sample document"
|
|
|
|
|
|
|
|
|
|
|
|
def test_file_example_with_RasterImageExt_Object():
|
2018-07-03 16:32:04 +02:00
|
|
|
f = stix2.v21.File(
|
|
|
|
name="qwerty.jpeg",
|
|
|
|
extensions={
|
|
|
|
"raster-image-ext": {
|
|
|
|
"bits_per_pixel": 123,
|
|
|
|
"exif_tags": {
|
|
|
|
"Make": "Nikon",
|
|
|
|
"Model": "D7000",
|
|
|
|
"XResolution": 4928,
|
2018-07-13 17:10:05 +02:00
|
|
|
"YResolution": 3264,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert f.name == "qwerty.jpeg"
|
|
|
|
assert f.extensions["raster-image-ext"].bits_per_pixel == 123
|
|
|
|
assert f.extensions["raster-image-ext"].exif_tags["XResolution"] == 4928
|
|
|
|
|
|
|
|
|
2020-06-05 22:55:41 +02:00
|
|
|
def test_file_with_archive_ext_object():
|
|
|
|
ad = stix2.v21.Directory(path="archived/path")
|
|
|
|
f_obj = stix2.v21.File(
|
|
|
|
name="foo", extensions={
|
|
|
|
"archive-ext": {
|
|
|
|
"contains_refs": [ad, ],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
f_ref = stix2.v21.File(
|
|
|
|
name="foo", extensions={
|
|
|
|
"archive-ext": {
|
|
|
|
"contains_refs": [ad.id, ],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
assert f_obj["id"] == f_ref["id"]
|
|
|
|
assert f_obj["extensions"]["archive-ext"]["contains_refs"][0] == ad["id"]
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
RASTER_IMAGE_EXT = """{
|
|
|
|
"type": "observed-data",
|
2018-07-03 15:40:51 +02:00
|
|
|
"spec_version": "2.1",
|
2018-07-03 13:00:18 +02:00
|
|
|
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
|
|
|
"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": "file",
|
2019-09-05 01:08:34 +02:00
|
|
|
"id": "file--44a431e6-764b-5556-a3f5-bf655930a581",
|
2018-07-03 13:00:18 +02:00
|
|
|
"name": "picture.jpg",
|
|
|
|
"hashes": {
|
|
|
|
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
|
|
|
|
},
|
|
|
|
"extensions": {
|
|
|
|
"raster-image-ext": {
|
|
|
|
"image_height": 768,
|
|
|
|
"image_width": 1024,
|
|
|
|
"bits_per_pixel": 72,
|
|
|
|
"exif_tags": {
|
|
|
|
"Make": "Nikon",
|
|
|
|
"Model": "D7000",
|
|
|
|
"XResolution": 4928,
|
|
|
|
"YResolution": 3264
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_raster_image_ext_parse():
|
2018-07-03 15:40:51 +02:00
|
|
|
obj = stix2.parse(RASTER_IMAGE_EXT, version="2.1")
|
2018-07-03 13:00:18 +02:00
|
|
|
assert obj.objects["0"].extensions['raster-image-ext'].image_width == 1024
|
|
|
|
|
|
|
|
|
|
|
|
def test_raster_images_ext_create():
|
2018-07-03 15:40:51 +02:00
|
|
|
ext = stix2.v21.RasterImageExt(image_width=1024)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert "image_width" in str(ext)
|
|
|
|
|
|
|
|
|
|
|
|
def test_file_example_with_WindowsPEBinaryExt():
|
2018-07-03 16:32:04 +02:00
|
|
|
f = stix2.v21.File(
|
|
|
|
name="qwerty.dll",
|
|
|
|
extensions={
|
|
|
|
"windows-pebinary-ext": {
|
|
|
|
"pe_type": "exe",
|
|
|
|
"machine_hex": "014c",
|
|
|
|
"number_of_sections": 4,
|
|
|
|
"time_date_stamp": "2016-01-22T12:31:12Z",
|
|
|
|
"pointer_to_symbol_table_hex": "74726144",
|
|
|
|
"number_of_symbols": 4542568,
|
|
|
|
"size_of_optional_header": 224,
|
|
|
|
"characteristics_hex": "818f",
|
|
|
|
"optional_header": {
|
|
|
|
"magic_hex": "010b",
|
|
|
|
"major_linker_version": 2,
|
|
|
|
"minor_linker_version": 25,
|
|
|
|
"size_of_code": 512,
|
|
|
|
"size_of_initialized_data": 283648,
|
|
|
|
"size_of_uninitialized_data": 0,
|
|
|
|
"address_of_entry_point": 4096,
|
|
|
|
"base_of_code": 4096,
|
|
|
|
"base_of_data": 8192,
|
|
|
|
"image_base": 14548992,
|
|
|
|
"section_alignment": 4096,
|
|
|
|
"file_alignment": 4096,
|
|
|
|
"major_os_version": 1,
|
|
|
|
"minor_os_version": 0,
|
|
|
|
"major_image_version": 0,
|
|
|
|
"minor_image_version": 0,
|
|
|
|
"major_subsystem_version": 4,
|
|
|
|
"minor_subsystem_version": 0,
|
|
|
|
"win32_version_value_hex": "00",
|
|
|
|
"size_of_image": 299008,
|
|
|
|
"size_of_headers": 4096,
|
|
|
|
"checksum_hex": "00",
|
|
|
|
"subsystem_hex": "03",
|
|
|
|
"dll_characteristics_hex": "00",
|
|
|
|
"size_of_stack_reserve": 100000,
|
|
|
|
"size_of_stack_commit": 8192,
|
|
|
|
"size_of_heap_reserve": 100000,
|
|
|
|
"size_of_heap_commit": 4096,
|
|
|
|
"loader_flags_hex": "abdbffde",
|
2018-07-13 17:10:05 +02:00
|
|
|
"number_of_rva_and_sizes": 3758087646,
|
2018-07-03 16:32:04 +02:00
|
|
|
},
|
|
|
|
"sections": [
|
|
|
|
{
|
|
|
|
"name": "CODE",
|
2018-07-13 17:10:05 +02:00
|
|
|
"entropy": 0.061089,
|
2018-07-03 16:32:04 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"name": "DATA",
|
2018-07-13 17:10:05 +02:00
|
|
|
"entropy": 7.980693,
|
2018-07-03 16:32:04 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"name": "NicolasB",
|
2018-07-13 17:10:05 +02:00
|
|
|
"entropy": 0.607433,
|
2018-07-03 16:32:04 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"name": ".idata",
|
2018-07-13 17:10:05 +02:00
|
|
|
"entropy": 0.607433,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert f.name == "qwerty.dll"
|
|
|
|
assert f.extensions["windows-pebinary-ext"].sections[2].entropy == 0.607433
|
|
|
|
|
|
|
|
|
|
|
|
def test_file_example_encryption_error():
|
2018-07-12 20:33:00 +02:00
|
|
|
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
|
|
|
stix2.v21.File(magic_number_hex="010b")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.File
|
2018-07-12 20:33:00 +02:00
|
|
|
assert "At least one of the (hashes, name)" in str(excinfo.value)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
2019-09-05 01:08:34 +02:00
|
|
|
def test_ipv4_address_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
ip4 = stix2.v21.IPv4Address(
|
|
|
|
value="198.51.100.3",
|
2019-09-05 01:08:34 +02:00
|
|
|
resolves_to_refs=["mac-addr--a85820f7-d9b7-567a-a3a6-dedc34139342", "mac-addr--9a59b496-fdeb-510f-97b5-7137210bc699"],
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert ip4.value == "198.51.100.3"
|
2019-09-05 01:08:34 +02:00
|
|
|
assert ip4.resolves_to_refs == ["mac-addr--a85820f7-d9b7-567a-a3a6-dedc34139342", "mac-addr--9a59b496-fdeb-510f-97b5-7137210bc699"]
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
2019-09-05 01:08:34 +02:00
|
|
|
def test_ipv4_address_valid_refs():
|
2019-05-10 16:22:45 +02:00
|
|
|
mac1 = stix2.v21.MACAddress(
|
|
|
|
value="a1:b2:c3:d4:e5:f6",
|
|
|
|
)
|
|
|
|
mac2 = stix2.v21.MACAddress(
|
|
|
|
value="a7:b8:c9:d0:e1:f2",
|
|
|
|
)
|
|
|
|
|
|
|
|
ip4 = stix2.v21.IPv4Address(
|
|
|
|
value="177.60.40.7",
|
2019-09-05 01:08:34 +02:00
|
|
|
resolves_to_refs=[str(mac1.id), str(mac2.id)],
|
2019-05-10 16:22:45 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
assert ip4.value == "177.60.40.7"
|
2020-05-13 19:45:16 +02:00
|
|
|
assert ip4.resolves_to_refs == ["mac-addr--f72d7d00-86bd-5cd2-8c86-52f7a83bef62", "mac-addr--875ad625-177b-5c2a-9101-d44b0ad55938"]
|
2019-05-10 16:22:45 +02:00
|
|
|
|
|
|
|
|
2019-09-05 01:08:34 +02:00
|
|
|
def test_ipv4_address_example_cidr():
|
2018-07-03 15:40:51 +02:00
|
|
|
ip4 = stix2.v21.IPv4Address(value="198.51.100.0/24")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert ip4.value == "198.51.100.0/24"
|
|
|
|
|
|
|
|
|
2019-09-05 01:08:34 +02:00
|
|
|
def test_ipv6_address_example():
|
2018-07-03 15:40:51 +02:00
|
|
|
ip6 = stix2.v21.IPv6Address(value="2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert ip6.value == "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
|
|
|
|
|
|
|
|
|
|
|
|
def test_mac_address_example():
|
2018-07-03 15:40:51 +02:00
|
|
|
ip6 = stix2.v21.MACAddress(value="d2:fb:49:24:37:18")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert ip6.value == "d2:fb:49:24:37:18"
|
|
|
|
|
|
|
|
|
|
|
|
def test_network_traffic_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
nt = stix2.v21.NetworkTraffic(
|
2019-09-05 01:08:34 +02:00
|
|
|
protocols=["tcp"],
|
|
|
|
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
|
|
|
dst_ref="ipv4-addr--6d39dd0b-1f74-5faf-8d76-d8762c2a57cb",
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert nt.protocols == ["tcp"]
|
2019-09-05 01:08:34 +02:00
|
|
|
assert nt.src_ref == "ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88"
|
|
|
|
assert nt.dst_ref == "ipv4-addr--6d39dd0b-1f74-5faf-8d76-d8762c2a57cb"
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_network_traffic_http_request_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
h = stix2.v21.HTTPRequestExt(
|
|
|
|
request_method="get",
|
|
|
|
request_value="/download.html",
|
|
|
|
request_version="http/1.1",
|
|
|
|
request_header={
|
|
|
|
"Accept-Encoding": "gzip,deflate",
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113",
|
2018-07-13 17:10:05 +02:00
|
|
|
"Host": "www.example.com",
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 16:32:04 +02:00
|
|
|
nt = stix2.v21.NetworkTraffic(
|
2019-09-05 01:08:34 +02:00
|
|
|
protocols=["tcp"],
|
|
|
|
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
2018-07-13 17:10:05 +02:00
|
|
|
extensions={'http-request-ext': h},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert nt.extensions['http-request-ext'].request_method == "get"
|
|
|
|
assert nt.extensions['http-request-ext'].request_value == "/download.html"
|
|
|
|
assert nt.extensions['http-request-ext'].request_version == "http/1.1"
|
|
|
|
assert nt.extensions['http-request-ext'].request_header['Accept-Encoding'] == "gzip,deflate"
|
|
|
|
assert nt.extensions['http-request-ext'].request_header['User-Agent'] == "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113"
|
|
|
|
assert nt.extensions['http-request-ext'].request_header['Host'] == "www.example.com"
|
|
|
|
|
|
|
|
|
|
|
|
def test_network_traffic_icmp_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
h = stix2.v21.ICMPExt(icmp_type_hex="08", icmp_code_hex="00")
|
|
|
|
nt = stix2.v21.NetworkTraffic(
|
2019-09-05 01:08:34 +02:00
|
|
|
protocols=["tcp"],
|
|
|
|
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
2018-07-13 17:10:05 +02:00
|
|
|
extensions={'icmp-ext': h},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert nt.extensions['icmp-ext'].icmp_type_hex == "08"
|
|
|
|
assert nt.extensions['icmp-ext'].icmp_code_hex == "00"
|
|
|
|
|
|
|
|
|
|
|
|
def test_network_traffic_socket_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
h = stix2.v21.SocketExt(
|
|
|
|
is_listening=True,
|
|
|
|
address_family="AF_INET",
|
2018-07-13 17:10:05 +02:00
|
|
|
socket_type="SOCK_STREAM",
|
|
|
|
)
|
2018-07-03 16:32:04 +02:00
|
|
|
nt = stix2.v21.NetworkTraffic(
|
2019-09-05 01:08:34 +02:00
|
|
|
protocols=["tcp"],
|
|
|
|
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
2018-07-13 17:10:05 +02:00
|
|
|
extensions={'socket-ext': h},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert nt.extensions['socket-ext'].is_listening
|
|
|
|
assert nt.extensions['socket-ext'].address_family == "AF_INET"
|
|
|
|
assert nt.extensions['socket-ext'].socket_type == "SOCK_STREAM"
|
|
|
|
|
|
|
|
|
2019-12-18 17:09:13 +01:00
|
|
|
def test_correct_socket_options():
|
|
|
|
se1 = stix2.v21.SocketExt(
|
|
|
|
is_listening=True,
|
|
|
|
address_family="AF_INET",
|
|
|
|
socket_type="SOCK_STREAM",
|
|
|
|
options={"ICMP6_RCVTIMEO": 100},
|
|
|
|
)
|
|
|
|
|
|
|
|
assert se1.address_family == "AF_INET"
|
|
|
|
assert se1.socket_type == "SOCK_STREAM"
|
|
|
|
assert se1.options == {"ICMP6_RCVTIMEO": 100}
|
|
|
|
|
|
|
|
|
2019-11-25 21:52:50 +01:00
|
|
|
def test_incorrect_socket_options():
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
stix2.v21.SocketExt(
|
|
|
|
is_listening=True,
|
|
|
|
address_family="AF_INET",
|
|
|
|
socket_type="SOCK_STREAM",
|
|
|
|
options={"RCVTIMEO": 100},
|
|
|
|
)
|
|
|
|
assert "Incorrect options key" == str(excinfo.value)
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
|
|
|
stix2.v21.SocketExt(
|
|
|
|
is_listening=True,
|
|
|
|
address_family="AF_INET",
|
|
|
|
socket_type="SOCK_STREAM",
|
|
|
|
options={"SO_RCVTIMEO": '100'},
|
|
|
|
)
|
|
|
|
assert "Options value must be an integer" == str(excinfo.value)
|
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_network_traffic_tcp_example():
|
2018-07-03 15:40:51 +02:00
|
|
|
h = stix2.v21.TCPExt(src_flags_hex="00000002")
|
2018-07-03 16:32:04 +02:00
|
|
|
nt = stix2.v21.NetworkTraffic(
|
2019-09-05 01:08:34 +02:00
|
|
|
protocols=["tcp"],
|
|
|
|
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
2018-07-13 17:10:05 +02:00
|
|
|
extensions={'tcp-ext': h},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert nt.extensions['tcp-ext'].src_flags_hex == "00000002"
|
|
|
|
|
|
|
|
|
|
|
|
def test_mutex_example():
|
2018-07-03 15:40:51 +02:00
|
|
|
m = stix2.v21.Mutex(name="barney")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert m.name == "barney"
|
|
|
|
|
|
|
|
|
|
|
|
def test_process_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
p = stix2.v21.Process(
|
|
|
|
pid=1221,
|
2019-09-05 01:08:34 +02:00
|
|
|
created_time="2016-01-20T14:11:25.55Z",
|
2018-10-15 20:48:52 +02:00
|
|
|
command_line="./gedit-bin --new-window",
|
2019-09-05 01:08:34 +02:00
|
|
|
image_ref="file--ea587d87-5ed2-5625-a9ac-01fd64161fd8",
|
2018-07-13 17:10:05 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-10-15 20:48:52 +02:00
|
|
|
assert p.command_line == "./gedit-bin --new-window"
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_process_example_empty_error():
|
|
|
|
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
2018-07-03 15:40:51 +02:00
|
|
|
stix2.v21.Process()
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2018-07-03 15:40:51 +02:00
|
|
|
assert excinfo.value.cls == stix2.v21.Process
|
|
|
|
properties_of_process = list(stix2.v21.Process._properties.keys())
|
2019-09-05 01:08:34 +02:00
|
|
|
properties_of_process = [prop for prop in properties_of_process if prop not in ["type", "id", "defanged", "spec_version"]]
|
2018-07-03 13:00:18 +02:00
|
|
|
assert excinfo.value.properties == sorted(properties_of_process)
|
|
|
|
msg = "At least one of the ({1}) properties for {0} must be populated."
|
2018-07-13 17:10:05 +02:00
|
|
|
msg = msg.format(
|
|
|
|
stix2.v21.Process.__name__,
|
|
|
|
", ".join(sorted(properties_of_process)),
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert str(excinfo.value) == msg
|
|
|
|
|
|
|
|
|
|
|
|
def test_process_example_empty_with_extensions():
|
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(stix2.exceptions.InvalidValueError) as excinfo:
|
2019-06-15 00:10:38 +02:00
|
|
|
stix2.v21.Process(extensions={
|
|
|
|
"windows-process-ext": {},
|
|
|
|
})
|
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
|
|
|
assert excinfo.value.cls == stix2.v21.Process
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_process_example_windows_process_ext():
|
2018-07-03 16:32:04 +02:00
|
|
|
proc = stix2.v21.Process(
|
|
|
|
pid=314,
|
|
|
|
extensions={
|
|
|
|
"windows-process-ext": {
|
|
|
|
"aslr_enabled": True,
|
|
|
|
"dep_enabled": True,
|
|
|
|
"priority": "HIGH_PRIORITY_CLASS",
|
2018-07-13 17:10:05 +02:00
|
|
|
"owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert proc.extensions["windows-process-ext"].aslr_enabled
|
|
|
|
assert proc.extensions["windows-process-ext"].dep_enabled
|
|
|
|
assert proc.extensions["windows-process-ext"].priority == "HIGH_PRIORITY_CLASS"
|
|
|
|
assert proc.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309"
|
|
|
|
|
|
|
|
|
|
|
|
def test_process_example_windows_process_ext_empty():
|
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(stix2.exceptions.InvalidValueError) as excinfo:
|
2019-06-15 00:10:38 +02:00
|
|
|
stix2.v21.Process(
|
|
|
|
pid=1221,
|
|
|
|
extensions={
|
|
|
|
"windows-process-ext": {},
|
|
|
|
},
|
|
|
|
)
|
2018-07-03 15:40:51 +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
|
|
|
assert excinfo.value.cls == stix2.v21.Process
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_process_example_extensions_empty():
|
2019-01-08 15:35:01 +01:00
|
|
|
proc = stix2.v21.Process(
|
2019-01-07 21:22:08 +01:00
|
|
|
pid=314,
|
|
|
|
extensions={},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
2019-01-07 21:22:08 +01:00
|
|
|
assert '{}' in str(proc)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_process_example_with_WindowsProcessExt_Object():
|
2018-07-03 15:40:51 +02:00
|
|
|
p = stix2.v21.Process(extensions={
|
2018-07-03 16:32:04 +02:00
|
|
|
"windows-process-ext": stix2.v21.WindowsProcessExt(
|
|
|
|
aslr_enabled=True,
|
|
|
|
dep_enabled=True,
|
|
|
|
priority="HIGH_PRIORITY_CLASS",
|
2018-07-13 17:10:05 +02:00
|
|
|
owner_sid="S-1-5-21-186985262-1144665072-74031268-1309",
|
|
|
|
), # noqa
|
2018-07-03 15:40:51 +02:00
|
|
|
})
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert p.extensions["windows-process-ext"].dep_enabled
|
|
|
|
assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309"
|
|
|
|
|
|
|
|
|
|
|
|
def test_process_example_with_WindowsServiceExt():
|
2018-07-03 15:40:51 +02:00
|
|
|
p = stix2.v21.Process(extensions={
|
2018-07-03 16:32:04 +02:00
|
|
|
"windows-service-ext": {
|
|
|
|
"service_name": "sirvizio",
|
|
|
|
"display_name": "Sirvizio",
|
|
|
|
"start_type": "SERVICE_AUTO_START",
|
|
|
|
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
2018-07-13 17:10:05 +02:00
|
|
|
"service_status": "SERVICE_RUNNING",
|
|
|
|
},
|
2018-07-03 13:00:18 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
assert p.extensions["windows-service-ext"].service_name == "sirvizio"
|
|
|
|
assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS"
|
|
|
|
|
|
|
|
|
|
|
|
def test_process_example_with_WindowsProcessServiceExt():
|
2018-07-03 15:40:51 +02:00
|
|
|
p = stix2.v21.Process(extensions={
|
2018-07-03 13:00:18 +02:00
|
|
|
"windows-service-ext": {
|
|
|
|
"service_name": "sirvizio",
|
|
|
|
"display_name": "Sirvizio",
|
|
|
|
"start_type": "SERVICE_AUTO_START",
|
|
|
|
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
2018-07-13 17:10:05 +02:00
|
|
|
"service_status": "SERVICE_RUNNING",
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
"windows-process-ext": {
|
|
|
|
"aslr_enabled": True,
|
|
|
|
"dep_enabled": True,
|
|
|
|
"priority": "HIGH_PRIORITY_CLASS",
|
2018-07-13 17:10:05 +02:00
|
|
|
"owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309",
|
|
|
|
},
|
2018-07-03 13:00:18 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
assert p.extensions["windows-service-ext"].service_name == "sirvizio"
|
|
|
|
assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS"
|
|
|
|
assert p.extensions["windows-process-ext"].dep_enabled
|
|
|
|
assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309"
|
|
|
|
|
|
|
|
|
|
|
|
def test_software_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
s = stix2.v21.Software(
|
|
|
|
name="Word",
|
|
|
|
cpe="cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*",
|
2020-03-11 20:21:34 +01:00
|
|
|
swid="com.acme.rms-ce-v4-1-5-0",
|
2018-07-03 16:32:04 +02:00
|
|
|
version="2002",
|
2018-07-13 17:10:05 +02:00
|
|
|
vendor="Microsoft",
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert s.name == "Word"
|
|
|
|
assert s.cpe == "cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*"
|
|
|
|
assert s.version == "2002"
|
|
|
|
assert s.vendor == "Microsoft"
|
|
|
|
|
|
|
|
|
|
|
|
def test_url_example():
|
2018-07-03 15:40:51 +02:00
|
|
|
s = stix2.v21.URL(value="https://example.com/research/index.html")
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert s.type == "url"
|
|
|
|
assert s.value == "https://example.com/research/index.html"
|
|
|
|
|
|
|
|
|
|
|
|
def test_user_account_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
a = stix2.v21.UserAccount(
|
|
|
|
user_id="1001",
|
|
|
|
account_login="jdoe",
|
|
|
|
account_type="unix",
|
|
|
|
display_name="John Doe",
|
|
|
|
is_service_account=False,
|
|
|
|
is_privileged=False,
|
|
|
|
can_escalate_privs=True,
|
|
|
|
account_created="2016-01-20T12:31:12Z",
|
2018-07-12 20:33:00 +02:00
|
|
|
credential_last_changed="2016-01-20T14:27:43Z",
|
2018-07-03 16:32:04 +02:00
|
|
|
account_first_login="2016-01-20T14:26:07Z",
|
2018-07-13 17:10:05 +02:00
|
|
|
account_last_login="2016-07-22T16:08:28Z",
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert a.user_id == "1001"
|
|
|
|
assert a.account_login == "jdoe"
|
|
|
|
assert a.account_type == "unix"
|
|
|
|
assert a.display_name == "John Doe"
|
|
|
|
assert not a.is_service_account
|
|
|
|
assert not a.is_privileged
|
|
|
|
assert a.can_escalate_privs
|
|
|
|
assert a.account_created == dt.datetime(2016, 1, 20, 12, 31, 12, tzinfo=pytz.utc)
|
2018-07-12 20:33:00 +02:00
|
|
|
assert a.credential_last_changed == dt.datetime(2016, 1, 20, 14, 27, 43, tzinfo=pytz.utc)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert a.account_first_login == dt.datetime(2016, 1, 20, 14, 26, 7, tzinfo=pytz.utc)
|
|
|
|
assert a.account_last_login == dt.datetime(2016, 7, 22, 16, 8, 28, tzinfo=pytz.utc)
|
|
|
|
|
|
|
|
|
|
|
|
def test_user_account_unix_account_ext_example():
|
2018-07-03 16:32:04 +02:00
|
|
|
u = stix2.v21.UNIXAccountExt(
|
|
|
|
gid=1001,
|
|
|
|
groups=["wheel"],
|
|
|
|
home_dir="/home/jdoe",
|
2018-07-13 17:10:05 +02:00
|
|
|
shell="/bin/bash",
|
|
|
|
)
|
2018-07-03 16:32:04 +02:00
|
|
|
a = stix2.v21.UserAccount(
|
|
|
|
user_id="1001",
|
|
|
|
account_login="jdoe",
|
|
|
|
account_type="unix",
|
2018-07-13 17:10:05 +02:00
|
|
|
extensions={'unix-account-ext': u},
|
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert a.extensions['unix-account-ext'].gid == 1001
|
|
|
|
assert a.extensions['unix-account-ext'].groups == ["wheel"]
|
|
|
|
assert a.extensions['unix-account-ext'].home_dir == "/home/jdoe"
|
|
|
|
assert a.extensions['unix-account-ext'].shell == "/bin/bash"
|
|
|
|
|
|
|
|
|
|
|
|
def test_windows_registry_key_example():
|
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(stix2.exceptions.InvalidValueError):
|
2018-07-03 16:32:04 +02:00
|
|
|
stix2.v21.WindowsRegistryValueType(
|
|
|
|
name="Foo",
|
|
|
|
data="qwerty",
|
2018-07-13 17:10:05 +02:00
|
|
|
data_type="string",
|
|
|
|
)
|
2018-07-03 16:32:04 +02:00
|
|
|
|
|
|
|
v = stix2.v21.WindowsRegistryValueType(
|
|
|
|
name="Foo",
|
|
|
|
data="qwerty",
|
2018-07-13 17:10:05 +02:00
|
|
|
data_type="REG_SZ",
|
2018-07-12 20:33:00 +02:00
|
|
|
)
|
2018-07-03 16:32:04 +02:00
|
|
|
w = stix2.v21.WindowsRegistryKey(
|
|
|
|
key="hkey_local_machine\\system\\bar\\foo",
|
2018-07-13 17:10:05 +02:00
|
|
|
values=[v],
|
2018-07-12 20:33:00 +02:00
|
|
|
)
|
2018-07-03 13:00:18 +02:00
|
|
|
assert w.key == "hkey_local_machine\\system\\bar\\foo"
|
2020-01-29 00:13:36 +01:00
|
|
|
assert w["values"][0].name == "Foo"
|
|
|
|
assert w["values"][0].data == "qwerty"
|
|
|
|
assert w["values"][0].data_type == "REG_SZ"
|
2018-12-21 20:33:59 +01:00
|
|
|
# ensure no errors in serialization because of 'values'
|
|
|
|
assert "Foo" in str(w)
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_x509_certificate_example():
|
2018-07-03 15:40:51 +02:00
|
|
|
x509 = stix2.v21.X509Certificate(
|
2018-07-03 13:00:18 +02:00
|
|
|
issuer="C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com", # noqa
|
|
|
|
validity_not_before="2016-03-12T12:00:00Z",
|
|
|
|
validity_not_after="2016-08-21T12:00:00Z",
|
2018-07-13 17:10:05 +02:00
|
|
|
subject="C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org",
|
|
|
|
) # noqa
|
2018-07-03 13:00:18 +02:00
|
|
|
|
|
|
|
assert x509.type == "x509-certificate"
|
|
|
|
assert x509.issuer == "C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com" # noqa
|
|
|
|
assert x509.subject == "C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org" # noqa
|
|
|
|
|
|
|
|
|
2019-11-22 19:24:09 +01:00
|
|
|
def test_x509_certificate_error():
|
|
|
|
|
|
|
|
with pytest.raises(stix2.exceptions.PropertyPresenceError) as excinfo:
|
|
|
|
stix2.v21.X509Certificate(
|
|
|
|
defanged=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
assert excinfo.value.cls == stix2.v21.X509Certificate
|
2019-11-25 21:52:50 +01:00
|
|
|
assert "At least one of the" in str(excinfo.value)
|
|
|
|
assert "properties for X509Certificate must be populated." in str(excinfo.value)
|
2019-11-22 19:24:09 +01:00
|
|
|
|
|
|
|
|
2018-07-03 13:00:18 +02:00
|
|
|
def test_new_version_with_related_objects():
|
2018-07-03 15:40:51 +02:00
|
|
|
data = stix2.v21.ObservedData(
|
2018-07-03 13:00:18 +02:00
|
|
|
first_observed="2016-03-12T12:00:00Z",
|
|
|
|
last_observed="2016-03-12T12:00:00Z",
|
|
|
|
number_observed=1,
|
|
|
|
objects={
|
|
|
|
'src_ip': {
|
|
|
|
'type': 'ipv4-addr',
|
2019-09-05 01:08:34 +02:00
|
|
|
'id': 'ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f',
|
2018-07-13 17:10:05 +02:00
|
|
|
'value': '127.0.0.1/32',
|
2018-07-03 13:00:18 +02:00
|
|
|
},
|
|
|
|
'domain': {
|
|
|
|
'type': 'domain-name',
|
2019-09-05 01:08:34 +02:00
|
|
|
'id': 'domain-name--220a2699-5ebf-5b57-bf02-424964bb19c0',
|
2018-07-03 13:00:18 +02:00
|
|
|
'value': 'example.com',
|
2019-09-05 01:08:34 +02:00
|
|
|
'resolves_to_refs': ['ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f'],
|
2018-07-13 17:10:05 +02:00
|
|
|
},
|
|
|
|
},
|
2018-07-03 13:00:18 +02:00
|
|
|
)
|
|
|
|
new_version = data.new_version(last_observed="2017-12-12T12:00:00Z")
|
|
|
|
assert new_version.last_observed.year == 2017
|
2019-09-05 01:08:34 +02:00
|
|
|
assert new_version.objects['domain'].resolves_to_refs[0] == 'ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f'
|
2019-07-29 22:35:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_objects_deprecation():
|
2019-08-07 16:16:18 +02:00
|
|
|
with pytest.warns(stix2.exceptions.STIXDeprecationWarning):
|
|
|
|
|
2019-07-29 22:35:38 +02:00
|
|
|
stix2.v21.ObservedData(
|
|
|
|
first_observed="2016-03-12T12:00:00Z",
|
|
|
|
last_observed="2016-03-12T12:00:00Z",
|
|
|
|
number_observed=1,
|
|
|
|
objects={
|
|
|
|
"0": {
|
|
|
|
"type": "file",
|
|
|
|
"name": "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|