Create v21 test package with new spec changes
parent
c2f5a40986
commit
da5b16dc2f
|
@ -0,0 +1,159 @@
|
|||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import (FAKE_TIME, INDICATOR_KWARGS, MALWARE_KWARGS,
|
||||
RELATIONSHIP_KWARGS)
|
||||
|
||||
|
||||
# Inspired by: http://stackoverflow.com/a/24006251
|
||||
@pytest.fixture
|
||||
def clock(monkeypatch):
|
||||
|
||||
class mydatetime(stix2.utils.STIXdatetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return FAKE_TIME
|
||||
|
||||
monkeypatch.setattr(stix2.utils, 'STIXdatetime', mydatetime)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def uuid4(monkeypatch):
|
||||
def wrapper():
|
||||
data = [0]
|
||||
|
||||
def wrapped():
|
||||
data[0] += 1
|
||||
return "00000000-0000-0000-0000-00000000%04x" % data[0]
|
||||
|
||||
return wrapped
|
||||
monkeypatch.setattr(uuid, "uuid4", wrapper())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def indicator(uuid4, clock):
|
||||
return stix2.Indicator(**INDICATOR_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def malware(uuid4, clock):
|
||||
return stix2.Malware(**MALWARE_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def relationship(uuid4, clock):
|
||||
return stix2.Relationship(**RELATIONSHIP_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def stix_objs1():
|
||||
ind1 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
ind2 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
ind3 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.936Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
ind4 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
ind5 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
return [ind1, ind2, ind3, ind4, ind5]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def stix_objs2():
|
||||
ind6 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-31T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
ind7 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
ind8 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
return [ind6, ind7, ind8]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def real_stix_objs2(stix_objs2):
|
||||
return [stix2.parse(x) for x in stix_objs2]
|
|
@ -0,0 +1,139 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytz
|
||||
|
||||
FAKE_TIME = dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
|
||||
ATTACK_PATTERN_ID = "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061"
|
||||
CAMPAIGN_ID = "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"
|
||||
COURSE_OF_ACTION_ID = "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"
|
||||
IDENTITY_ID = "identity--311b2d2d-f010-5473-83ec-1edf84858f4c"
|
||||
INDICATOR_ID = "indicator--01234567-89ab-cdef-0123-456789abcdef"
|
||||
INTRUSION_SET_ID = "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29"
|
||||
LOCATION_ID = "location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64"
|
||||
MALWARE_ID = "malware--fedcba98-7654-3210-fedc-ba9876543210"
|
||||
MARKING_DEFINITION_ID = "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
|
||||
NOTE_ID = "note--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061"
|
||||
OBSERVED_DATA_ID = "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf"
|
||||
OPINION_ID = "opinion--b01efc25-77b4-4003-b18b-f6e24b5cd9f7"
|
||||
REPORT_ID = "report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3"
|
||||
RELATIONSHIP_ID = "relationship--00000000-1111-2222-3333-444444444444"
|
||||
THREAT_ACTOR_ID = "threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"
|
||||
TOOL_ID = "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"
|
||||
SIGHTING_ID = "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb"
|
||||
VULNERABILITY_ID = "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061"
|
||||
|
||||
MARKING_IDS = [
|
||||
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
"marking-definition--443eb5c3-a76c-4a0a-8caa-e93998e7bc09",
|
||||
"marking-definition--57fcd772-9c1d-41b0-8d1f-3d47713415d9",
|
||||
"marking-definition--462bf1a6-03d2-419c-b74e-eee2238b2de4",
|
||||
"marking-definition--68520ae2-fefe-43a9-84ee-2c2a934d2c7d",
|
||||
"marking-definition--2802dfb1-1019-40a8-8848-68d0ec0e417f",
|
||||
]
|
||||
RELATIONSHIP_IDS = [
|
||||
'relationship--06520621-5352-4e6a-b976-e8fa3d437ffd',
|
||||
'relationship--181c9c09-43e6-45dd-9374-3bec192f05ef',
|
||||
'relationship--a0cbb21c-8daf-4a7f-96aa-7155a4ef8f70'
|
||||
]
|
||||
|
||||
# *_KWARGS contains all required arguments to create an instance of that STIX object
|
||||
# *_MORE_KWARGS contains all the required arguments, plus some optional ones
|
||||
|
||||
ATTACK_PATTERN_KWARGS = dict(
|
||||
name="Phishing",
|
||||
)
|
||||
|
||||
CAMPAIGN_KWARGS = dict(
|
||||
name="Green Group Attacks Against Finance",
|
||||
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
)
|
||||
|
||||
CAMPAIGN_MORE_KWARGS = dict(
|
||||
type='campaign',
|
||||
spec_version='2.1',
|
||||
id=CAMPAIGN_ID,
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:00.000Z",
|
||||
modified="2016-04-06T20:03:00.000Z",
|
||||
name="Green Group Attacks Against Finance",
|
||||
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
)
|
||||
|
||||
COURSE_OF_ACTION_KWARGS = dict(
|
||||
name="Block",
|
||||
)
|
||||
|
||||
IDENTITY_KWARGS = dict(
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
)
|
||||
|
||||
INDICATOR_KWARGS = dict(
|
||||
labels=['malicious-activity'],
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
)
|
||||
|
||||
INTRUSION_SET_KWARGS = dict(
|
||||
name="Bobcat Breakin",
|
||||
)
|
||||
|
||||
MALWARE_KWARGS = dict(
|
||||
labels=['ransomware'],
|
||||
name="Cryptolocker",
|
||||
is_family=False
|
||||
)
|
||||
|
||||
MALWARE_MORE_KWARGS = dict(
|
||||
type='malware',
|
||||
id=MALWARE_ID,
|
||||
created="2016-04-06T20:03:00.000Z",
|
||||
modified="2016-04-06T20:03:00.000Z",
|
||||
labels=['ransomware'],
|
||||
name="Cryptolocker",
|
||||
description="A ransomware related to ...",
|
||||
is_family=False
|
||||
)
|
||||
|
||||
OBSERVED_DATA_KWARGS = dict(
|
||||
first_observed=FAKE_TIME,
|
||||
last_observed=FAKE_TIME,
|
||||
number_observed=1,
|
||||
objects={
|
||||
"0": {
|
||||
"type": "windows-registry-key",
|
||||
"key": "HKEY_LOCAL_MACHINE\\System\\Foo\\Bar",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
REPORT_KWARGS = dict(
|
||||
labels=["campaign"],
|
||||
name="Bad Cybercrime",
|
||||
published=FAKE_TIME,
|
||||
object_refs=[INDICATOR_ID],
|
||||
)
|
||||
|
||||
RELATIONSHIP_KWARGS = dict(
|
||||
relationship_type="indicates",
|
||||
source_ref=INDICATOR_ID,
|
||||
target_ref=MALWARE_ID,
|
||||
)
|
||||
|
||||
SIGHTING_KWARGS = dict(
|
||||
sighting_of_ref=INDICATOR_ID,
|
||||
)
|
||||
|
||||
THREAT_ACTOR_KWARGS = dict(
|
||||
labels=["crime-syndicate"],
|
||||
name="Evil Org",
|
||||
)
|
||||
|
||||
TOOL_KWARGS = dict(
|
||||
labels=["remote-access"],
|
||||
name="VNC",
|
||||
)
|
||||
|
||||
VULNERABILITY_KWARGS = dict(
|
||||
name="Heartbleed",
|
||||
)
|
|
@ -0,0 +1,83 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import ATTACK_PATTERN_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "attack-pattern",
|
||||
"spec_version": "2.1",
|
||||
"id": "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"name": "Spear Phishing",
|
||||
"description": "...",
|
||||
"external_references": [
|
||||
{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-163"
|
||||
}
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_attack_pattern_example():
|
||||
ap = stix2.AttackPattern(
|
||||
id="attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
created="2016-05-12T08:17:27.000Z",
|
||||
modified="2016-05-12T08:17:27.000Z",
|
||||
name="Spear Phishing",
|
||||
external_references=[{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-163"
|
||||
}],
|
||||
description="...",
|
||||
)
|
||||
|
||||
assert str(ap) == EXPECTED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"type": "attack-pattern",
|
||||
"id": "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"description": "...",
|
||||
"external_references": [
|
||||
{
|
||||
"external_id": "CAPEC-163",
|
||||
"source_name": "capec"
|
||||
}
|
||||
],
|
||||
"name": "Spear Phishing",
|
||||
},
|
||||
])
|
||||
def test_parse_attack_pattern(data):
|
||||
ap = stix2.parse(data)
|
||||
|
||||
assert ap.type == 'attack-pattern'
|
||||
assert ap.id == ATTACK_PATTERN_ID
|
||||
assert ap.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert ap.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert ap.description == "..."
|
||||
assert ap.external_references[0].external_id == 'CAPEC-163'
|
||||
assert ap.external_references[0].source_name == 'capec'
|
||||
assert ap.name == "Spear Phishing"
|
||||
|
||||
|
||||
def test_attack_pattern_invalid_labels():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError):
|
||||
stix2.AttackPattern(
|
||||
id="attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
created="2016-05-12T08:17:27Z",
|
||||
modified="2016-05-12T08:17:27Z",
|
||||
name="Spear Phishing",
|
||||
labels=1
|
||||
)
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,25 @@
|
|||
import datetime as dt
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
from stix2.base import STIXJSONEncoder
|
||||
|
||||
|
||||
def test_encode_json_datetime():
|
||||
now = dt.datetime(2017, 3, 22, 0, 0, 0, tzinfo=pytz.UTC)
|
||||
test_dict = {'now': now}
|
||||
|
||||
expected = '{"now": "2017-03-22T00:00:00Z"}'
|
||||
assert json.dumps(test_dict, cls=STIXJSONEncoder) == expected
|
||||
|
||||
|
||||
def test_encode_json_object():
|
||||
obj = object()
|
||||
test_dict = {'obj': obj}
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
json.dumps(test_dict, cls=STIXJSONEncoder)
|
||||
|
||||
assert " is not JSON serializable" in str(excinfo.value)
|
|
@ -0,0 +1,208 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
|
||||
EXPECTED_BUNDLE = """{
|
||||
"type": "bundle",
|
||||
"id": "bundle--00000000-0000-0000-0000-000000000007",
|
||||
"objects": [
|
||||
{
|
||||
"type": "indicator",
|
||||
"spec_version": "2.1",
|
||||
"id": "indicator--00000000-0000-0000-0000-000000000001",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"valid_from": "2017-01-01T12:34:56Z",
|
||||
"labels": [
|
||||
"malicious-activity"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "malware",
|
||||
"spec_version": "2.1",
|
||||
"id": "malware--00000000-0000-0000-0000-000000000003",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Cryptolocker",
|
||||
"labels": [
|
||||
"ransomware"
|
||||
],
|
||||
"is_family": false
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
"spec_version": "2.1",
|
||||
"id": "relationship--00000000-0000-0000-0000-000000000005",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"relationship_type": "indicates",
|
||||
"source_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"target_ref": "malware--fedcba98-7654-3210-fedc-ba9876543210"
|
||||
}
|
||||
]
|
||||
}"""
|
||||
|
||||
EXPECTED_BUNDLE_DICT = {
|
||||
"type": "bundle",
|
||||
"id": "bundle--00000000-0000-0000-0000-000000000007",
|
||||
"objects": [
|
||||
{
|
||||
"type": "indicator",
|
||||
"spec_version": "2.1",
|
||||
"id": "indicator--00000000-0000-0000-0000-000000000001",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"valid_from": "2017-01-01T12:34:56Z",
|
||||
"labels": [
|
||||
"malicious-activity"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "malware",
|
||||
"spec_version": "2.1",
|
||||
"id": "malware--00000000-0000-0000-0000-000000000003",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Cryptolocker",
|
||||
"labels": [
|
||||
"ransomware"
|
||||
],
|
||||
"is_family": False
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
"spec_version": "2.1",
|
||||
"id": "relationship--00000000-0000-0000-0000-000000000005",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"relationship_type": "indicates",
|
||||
"source_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"target_ref": "malware--fedcba98-7654-3210-fedc-ba9876543210"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_empty_bundle():
|
||||
bundle = stix2.Bundle()
|
||||
|
||||
assert bundle.type == "bundle"
|
||||
assert bundle.id.startswith("bundle--")
|
||||
with pytest.raises(AttributeError):
|
||||
assert bundle.objects
|
||||
|
||||
|
||||
def test_bundle_with_wrong_type():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Bundle(type="not-a-bundle")
|
||||
|
||||
assert excinfo.value.cls == stix2.Bundle
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'bundle'."
|
||||
assert str(excinfo.value) == "Invalid value for Bundle 'type': must equal 'bundle'."
|
||||
|
||||
|
||||
def test_bundle_id_must_start_with_bundle():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Bundle(id='my-prefix--')
|
||||
|
||||
assert excinfo.value.cls == stix2.Bundle
|
||||
assert excinfo.value.prop_name == "id"
|
||||
assert excinfo.value.reason == "must start with 'bundle--'."
|
||||
assert str(excinfo.value) == "Invalid value for Bundle 'id': must start with 'bundle--'."
|
||||
|
||||
|
||||
def test_create_bundle1(indicator, malware, relationship):
|
||||
bundle = stix2.Bundle(objects=[indicator, malware, relationship])
|
||||
|
||||
assert str(bundle) == EXPECTED_BUNDLE
|
||||
assert bundle.serialize(pretty=True) == EXPECTED_BUNDLE
|
||||
|
||||
|
||||
def test_create_bundle2(indicator, malware, relationship):
|
||||
bundle = stix2.Bundle(objects=[indicator, malware, relationship])
|
||||
|
||||
assert json.loads(bundle.serialize()) == EXPECTED_BUNDLE_DICT
|
||||
|
||||
|
||||
def test_create_bundle_with_positional_args(indicator, malware, relationship):
|
||||
bundle = stix2.Bundle(indicator, malware, relationship)
|
||||
|
||||
assert str(bundle) == EXPECTED_BUNDLE
|
||||
|
||||
|
||||
def test_create_bundle_with_positional_listarg(indicator, malware, relationship):
|
||||
bundle = stix2.Bundle([indicator, malware, relationship])
|
||||
|
||||
assert str(bundle) == EXPECTED_BUNDLE
|
||||
|
||||
|
||||
def test_create_bundle_with_listarg_and_positional_arg(indicator, malware, relationship):
|
||||
bundle = stix2.Bundle([indicator, malware], relationship)
|
||||
|
||||
assert str(bundle) == EXPECTED_BUNDLE
|
||||
|
||||
|
||||
def test_create_bundle_with_listarg_and_kwarg(indicator, malware, relationship):
|
||||
bundle = stix2.Bundle([indicator, malware], objects=[relationship])
|
||||
|
||||
assert str(bundle) == EXPECTED_BUNDLE
|
||||
|
||||
|
||||
def test_create_bundle_with_arg_listarg_and_kwarg(indicator, malware, relationship):
|
||||
bundle = stix2.Bundle([indicator], malware, objects=[relationship])
|
||||
|
||||
assert str(bundle) == EXPECTED_BUNDLE
|
||||
|
||||
|
||||
def test_create_bundle_invalid(indicator, malware, relationship):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.Bundle(objects=[1])
|
||||
assert excinfo.value.reason == "This property may only contain a dictionary or object"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.Bundle(objects=[{}])
|
||||
assert excinfo.value.reason == "This property may only contain a non-empty dictionary or object"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.Bundle(objects=[{'type': 'bundle'}])
|
||||
assert excinfo.value.reason == 'This property may not contain a Bundle object'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", ["2.1"])
|
||||
def test_parse_bundle(version):
|
||||
bundle = stix2.parse(EXPECTED_BUNDLE, version=version)
|
||||
|
||||
assert bundle.type == "bundle"
|
||||
assert bundle.id.startswith("bundle--")
|
||||
assert type(bundle.objects[0]) is stix2.v21.sdo.Indicator
|
||||
assert bundle.objects[0].type == 'indicator'
|
||||
assert bundle.objects[1].type == 'malware'
|
||||
assert bundle.objects[2].type == 'relationship'
|
||||
|
||||
|
||||
def test_parse_unknown_type():
|
||||
unknown = {
|
||||
"type": "other",
|
||||
"id": "other--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created": "2016-04-06T20:03:00Z",
|
||||
"modified": "2016-04-06T20:03:00Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
"name": "Green Group Attacks Against Finance",
|
||||
}
|
||||
|
||||
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
||||
stix2.parse(unknown)
|
||||
assert str(excinfo.value) == "Can't parse unknown object type 'other'! For custom types, use the CustomObject decorator."
|
||||
|
||||
|
||||
def test_stix_object_property():
|
||||
prop = stix2.v21.bundle.STIXObjectProperty()
|
||||
|
||||
identity = stix2.Identity(name="test", identity_class="individual")
|
||||
assert prop.clean(identity) is identity
|
|
@ -0,0 +1,59 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import CAMPAIGN_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "campaign",
|
||||
"spec_version": "2.1",
|
||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T20:03:00.000Z",
|
||||
"modified": "2016-04-06T20:03:00.000Z",
|
||||
"name": "Green Group Attacks Against Finance",
|
||||
"description": "Campaign by Green Group against a series of targets in the financial services sector."
|
||||
}"""
|
||||
|
||||
|
||||
def test_campaign_example():
|
||||
campaign = stix2.Campaign(
|
||||
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:00Z",
|
||||
modified="2016-04-06T20:03:00Z",
|
||||
name="Green Group Attacks Against Finance",
|
||||
description="Campaign by Green Group against a series of targets in the financial services sector."
|
||||
)
|
||||
|
||||
assert str(campaign) == EXPECTED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"type": "campaign",
|
||||
"spec_version": "2.1",
|
||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created": "2016-04-06T20:03:00Z",
|
||||
"modified": "2016-04-06T20:03:00Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
"name": "Green Group Attacks Against Finance",
|
||||
},
|
||||
])
|
||||
def test_parse_campaign(data):
|
||||
cmpn = stix2.parse(data)
|
||||
|
||||
assert cmpn.type == 'campaign'
|
||||
assert cmpn.id == CAMPAIGN_ID
|
||||
assert cmpn.created == dt.datetime(2016, 4, 6, 20, 3, 0, tzinfo=pytz.utc)
|
||||
assert cmpn.modified == dt.datetime(2016, 4, 6, 20, 3, 0, tzinfo=pytz.utc)
|
||||
assert cmpn.created_by_ref == "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff"
|
||||
assert cmpn.description == "Campaign by Green Group against a series of targets in the financial services sector."
|
||||
assert cmpn.name == "Green Group Attacks Against Finance"
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,59 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import COURSE_OF_ACTION_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "course-of-action",
|
||||
"spec_version": "2.1",
|
||||
"id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
|
||||
}"""
|
||||
|
||||
|
||||
def test_course_of_action_example():
|
||||
coa = stix2.CourseOfAction(
|
||||
id="course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:48.000Z",
|
||||
modified="2016-04-06T20:03:48.000Z",
|
||||
name="Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
description="This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
|
||||
)
|
||||
|
||||
assert str(coa) == EXPECTED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
|
||||
"id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"spec_version": "2.1",
|
||||
"type": "course-of-action"
|
||||
},
|
||||
])
|
||||
def test_parse_course_of_action(data):
|
||||
coa = stix2.parse(data)
|
||||
|
||||
assert coa.type == 'course-of-action'
|
||||
assert coa.id == COURSE_OF_ACTION_ID
|
||||
assert coa.created == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert coa.modified == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert coa.created_by_ref == "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff"
|
||||
assert coa.description == "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
|
||||
assert coa.name == "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter"
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,894 @@
|
|||
import pytest
|
||||
|
||||
import stix2
|
||||
import stix2.base
|
||||
import stix2.v21.sdo
|
||||
|
||||
from .constants import FAKE_TIME, MARKING_DEFINITION_ID
|
||||
|
||||
IDENTITY_CUSTOM_PROP = stix2.Identity(
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
x_foo="bar",
|
||||
allow_custom=True,
|
||||
)
|
||||
|
||||
|
||||
def test_identity_custom_property():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.Identity(
|
||||
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
created="2015-12-21T19:59:11Z",
|
||||
modified="2015-12-21T19:59:11Z",
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
custom_properties="foobar",
|
||||
)
|
||||
assert str(excinfo.value) == "'custom_properties' must be a dictionary"
|
||||
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.Identity(
|
||||
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
created="2015-12-21T19:59:11Z",
|
||||
modified="2015-12-21T19:59:11Z",
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
custom_properties={
|
||||
"foo": "bar",
|
||||
},
|
||||
foo="bar",
|
||||
)
|
||||
assert "Unexpected properties for Identity" in str(excinfo.value)
|
||||
|
||||
identity = stix2.Identity(
|
||||
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
created="2015-12-21T19:59:11Z",
|
||||
modified="2015-12-21T19:59:11Z",
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
custom_properties={
|
||||
"foo": "bar",
|
||||
},
|
||||
)
|
||||
assert identity.foo == "bar"
|
||||
|
||||
|
||||
def test_identity_custom_property_invalid():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.Identity(
|
||||
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
created="2015-12-21T19:59:11Z",
|
||||
modified="2015-12-21T19:59:11Z",
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
x_foo="bar",
|
||||
)
|
||||
assert excinfo.value.cls == stix2.Identity
|
||||
assert excinfo.value.properties == ['x_foo']
|
||||
assert "Unexpected properties for" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_identity_custom_property_allowed():
|
||||
identity = stix2.Identity(
|
||||
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
created="2015-12-21T19:59:11Z",
|
||||
modified="2015-12-21T19:59:11Z",
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
x_foo="bar",
|
||||
allow_custom=True,
|
||||
)
|
||||
assert identity.x_foo == "bar"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
"""{
|
||||
"type": "identity",
|
||||
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"modified": "2015-12-21T19:59:11Z",
|
||||
"name": "John Smith",
|
||||
"identity_class": "individual",
|
||||
"foo": "bar"
|
||||
}""",
|
||||
])
|
||||
def test_parse_identity_custom_property(data):
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
identity = stix2.parse(data)
|
||||
assert excinfo.value.cls == stix2.v21.sdo.Identity
|
||||
assert excinfo.value.properties == ['foo']
|
||||
assert "Unexpected properties for" in str(excinfo.value)
|
||||
|
||||
identity = stix2.parse(data, allow_custom=True)
|
||||
assert identity.foo == "bar"
|
||||
|
||||
|
||||
def test_custom_property_object_in_bundled_object():
|
||||
bundle = stix2.Bundle(IDENTITY_CUSTOM_PROP, allow_custom=True)
|
||||
|
||||
assert bundle.objects[0].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(bundle)
|
||||
|
||||
|
||||
def test_custom_properties_object_in_bundled_object():
|
||||
obj = stix2.Identity(
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
custom_properties={
|
||||
"x_foo": "bar",
|
||||
}
|
||||
)
|
||||
bundle = stix2.Bundle(obj, allow_custom=True)
|
||||
|
||||
assert bundle.objects[0].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(bundle)
|
||||
|
||||
|
||||
def test_custom_property_dict_in_bundled_object():
|
||||
custom_identity = {
|
||||
'type': 'identity',
|
||||
'id': 'identity--311b2d2d-f010-5473-83ec-1edf84858f4c',
|
||||
'created': '2015-12-21T19:59:11Z',
|
||||
'name': 'John Smith',
|
||||
'identity_class': 'individual',
|
||||
'x_foo': 'bar',
|
||||
}
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
|
||||
bundle = stix2.Bundle(custom_identity)
|
||||
|
||||
bundle = stix2.Bundle(custom_identity, allow_custom=True)
|
||||
assert bundle.objects[0].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(bundle)
|
||||
|
||||
|
||||
def test_custom_properties_dict_in_bundled_object():
|
||||
custom_identity = {
|
||||
'type': 'identity',
|
||||
'id': 'identity--311b2d2d-f010-5473-83ec-1edf84858f4c',
|
||||
'created': '2015-12-21T19:59:11Z',
|
||||
'name': 'John Smith',
|
||||
'identity_class': 'individual',
|
||||
'custom_properties': {
|
||||
'x_foo': 'bar',
|
||||
},
|
||||
}
|
||||
bundle = stix2.Bundle(custom_identity)
|
||||
|
||||
assert bundle.objects[0].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(bundle)
|
||||
|
||||
|
||||
def test_custom_property_in_observed_data():
|
||||
artifact = stix2.File(
|
||||
allow_custom=True,
|
||||
name='test',
|
||||
x_foo='bar'
|
||||
)
|
||||
observed_data = stix2.ObservedData(
|
||||
allow_custom=True,
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=0,
|
||||
objects={"0": artifact},
|
||||
)
|
||||
|
||||
assert observed_data.objects['0'].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(observed_data)
|
||||
|
||||
|
||||
def test_custom_property_object_in_observable_extension():
|
||||
ntfs = stix2.NTFSExt(
|
||||
allow_custom=True,
|
||||
sid=1,
|
||||
x_foo='bar',
|
||||
)
|
||||
artifact = stix2.File(
|
||||
name='test',
|
||||
extensions={'ntfs-ext': ntfs},
|
||||
)
|
||||
observed_data = stix2.ObservedData(
|
||||
allow_custom=True,
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=0,
|
||||
objects={"0": artifact},
|
||||
)
|
||||
|
||||
assert observed_data.objects['0'].extensions['ntfs-ext'].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(observed_data)
|
||||
|
||||
|
||||
def test_custom_property_dict_in_observable_extension():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
|
||||
artifact = stix2.File(
|
||||
name='test',
|
||||
extensions={
|
||||
'ntfs-ext': {
|
||||
'sid': 1,
|
||||
'x_foo': 'bar',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
artifact = stix2.File(
|
||||
allow_custom=True,
|
||||
name='test',
|
||||
extensions={
|
||||
'ntfs-ext': {
|
||||
'allow_custom': True,
|
||||
'sid': 1,
|
||||
'x_foo': 'bar',
|
||||
}
|
||||
},
|
||||
)
|
||||
observed_data = stix2.ObservedData(
|
||||
allow_custom=True,
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=0,
|
||||
objects={"0": artifact},
|
||||
)
|
||||
|
||||
assert observed_data.objects['0'].extensions['ntfs-ext'].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(observed_data)
|
||||
|
||||
|
||||
def test_identity_custom_property_revoke():
|
||||
identity = IDENTITY_CUSTOM_PROP.revoke()
|
||||
assert identity.x_foo == "bar"
|
||||
|
||||
|
||||
def test_identity_custom_property_edit_markings():
|
||||
marking_obj = stix2.MarkingDefinition(
|
||||
id=MARKING_DEFINITION_ID,
|
||||
definition_type="statement",
|
||||
definition=stix2.StatementMarking(statement="Copyright 2016, Example Corp")
|
||||
)
|
||||
marking_obj2 = stix2.MarkingDefinition(
|
||||
id=MARKING_DEFINITION_ID,
|
||||
definition_type="statement",
|
||||
definition=stix2.StatementMarking(statement="Another one")
|
||||
)
|
||||
|
||||
# None of the following should throw exceptions
|
||||
identity = IDENTITY_CUSTOM_PROP.add_markings(marking_obj)
|
||||
identity2 = identity.add_markings(marking_obj2, ['x_foo'])
|
||||
identity2.remove_markings(marking_obj.id)
|
||||
identity2.remove_markings(marking_obj2.id, ['x_foo'])
|
||||
identity2.clear_markings()
|
||||
identity2.clear_markings('x_foo')
|
||||
|
||||
|
||||
def test_custom_marking_no_init_1():
|
||||
@stix2.CustomMarking('x-new-obj', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewObj():
|
||||
pass
|
||||
|
||||
no = NewObj(property1='something')
|
||||
assert no.property1 == 'something'
|
||||
|
||||
|
||||
def test_custom_marking_no_init_2():
|
||||
@stix2.CustomMarking('x-new-obj2', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewObj2(object):
|
||||
pass
|
||||
|
||||
no2 = NewObj2(property1='something')
|
||||
assert no2.property1 == 'something'
|
||||
|
||||
|
||||
@stix2.sdo.CustomObject('x-new-type', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
('property2', stix2.properties.IntegerProperty()),
|
||||
])
|
||||
class NewType(object):
|
||||
def __init__(self, property2=None, **kwargs):
|
||||
if property2 and property2 < 10:
|
||||
raise ValueError("'property2' is too small.")
|
||||
if "property3" in kwargs and not isinstance(kwargs.get("property3"), int):
|
||||
raise TypeError("Must be integer!")
|
||||
|
||||
|
||||
def test_custom_object_raises_exception():
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
NewType(property1='something', property3='something', allow_custom=True)
|
||||
|
||||
assert str(excinfo.value) == "Must be integer!"
|
||||
|
||||
|
||||
def test_custom_object_type():
|
||||
nt = NewType(property1='something')
|
||||
assert nt.property1 == 'something'
|
||||
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
NewType(property2=42)
|
||||
assert "No values for required properties" in str(excinfo.value)
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
NewType(property1='something', property2=4)
|
||||
assert "'property2' is too small." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_object_no_init_1():
|
||||
@stix2.sdo.CustomObject('x-new-obj', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewObj():
|
||||
pass
|
||||
|
||||
no = NewObj(property1='something')
|
||||
assert no.property1 == 'something'
|
||||
|
||||
|
||||
def test_custom_object_no_init_2():
|
||||
@stix2.sdo.CustomObject('x-new-obj2', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewObj2(object):
|
||||
pass
|
||||
|
||||
no2 = NewObj2(property1='something')
|
||||
assert no2.property1 == 'something'
|
||||
|
||||
|
||||
def test_custom_object_invalid_type_name():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.sdo.CustomObject('x', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewObj(object):
|
||||
pass # pragma: no cover
|
||||
assert "Invalid type name 'x': " in str(excinfo.value)
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.sdo.CustomObject('x_new_object', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewObj2(object):
|
||||
pass # pragma: no cover
|
||||
assert "Invalid type name 'x_new_object':" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_parse_custom_object_type():
|
||||
nt_string = """{
|
||||
"type": "x-new-type",
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"property1": "something"
|
||||
}"""
|
||||
|
||||
nt = stix2.parse(nt_string, allow_custom=True)
|
||||
assert nt["property1"] == 'something'
|
||||
|
||||
|
||||
def test_parse_unregistered_custom_object_type():
|
||||
nt_string = """{
|
||||
"type": "x-foobar-observable",
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"property1": "something"
|
||||
}"""
|
||||
|
||||
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
||||
stix2.parse(nt_string)
|
||||
assert "Can't parse unknown object type" in str(excinfo.value)
|
||||
assert "use the CustomObject decorator." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_parse_unregistered_custom_object_type_w_allow_custom():
|
||||
"""parse an unknown custom object, allowed by passing
|
||||
'allow_custom' flag
|
||||
"""
|
||||
nt_string = """{
|
||||
"type": "x-foobar-observable",
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"property1": "something"
|
||||
}"""
|
||||
|
||||
custom_obj = stix2.parse(nt_string, allow_custom=True)
|
||||
assert custom_obj["type"] == "x-foobar-observable"
|
||||
|
||||
|
||||
@stix2.observables.CustomObservable('x-new-observable', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
('property2', stix2.properties.IntegerProperty()),
|
||||
('x_property3', stix2.properties.BooleanProperty()),
|
||||
])
|
||||
class NewObservable():
|
||||
def __init__(self, property2=None, **kwargs):
|
||||
if property2 and property2 < 10:
|
||||
raise ValueError("'property2' is too small.")
|
||||
if "property3" in kwargs and not isinstance(kwargs.get("property3"), int):
|
||||
raise TypeError("Must be integer!")
|
||||
|
||||
|
||||
def test_custom_observable_object_1():
|
||||
no = NewObservable(property1='something')
|
||||
assert no.property1 == 'something'
|
||||
|
||||
|
||||
def test_custom_observable_object_2():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
NewObservable(property2=42)
|
||||
assert excinfo.value.properties == ['property1']
|
||||
assert "No values for required properties" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_object_3():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
NewObservable(property1='something', property2=4)
|
||||
assert "'property2' is too small." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_raises_exception():
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
NewObservable(property1='something', property3='something', allow_custom=True)
|
||||
|
||||
assert str(excinfo.value) == "Must be integer!"
|
||||
|
||||
|
||||
def test_custom_observable_object_no_init_1():
|
||||
@stix2.observables.CustomObservable('x-new-observable', [
|
||||
('property1', stix2.properties.StringProperty()),
|
||||
])
|
||||
class NewObs():
|
||||
pass
|
||||
|
||||
no = NewObs(property1='something')
|
||||
assert no.property1 == 'something'
|
||||
|
||||
|
||||
def test_custom_observable_object_no_init_2():
|
||||
@stix2.observables.CustomObservable('x-new-obs2', [
|
||||
('property1', stix2.properties.StringProperty()),
|
||||
])
|
||||
class NewObs2(object):
|
||||
pass
|
||||
|
||||
no2 = NewObs2(property1='something')
|
||||
assert no2.property1 == 'something'
|
||||
|
||||
|
||||
def test_custom_observable_object_invalid_type_name():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomObservable('x', [
|
||||
('property1', stix2.properties.StringProperty()),
|
||||
])
|
||||
class NewObs(object):
|
||||
pass # pragma: no cover
|
||||
assert "Invalid observable type name 'x':" in str(excinfo.value)
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomObservable('x_new_obs', [
|
||||
('property1', stix2.properties.StringProperty()),
|
||||
])
|
||||
class NewObs2(object):
|
||||
pass # pragma: no cover
|
||||
assert "Invalid observable type name 'x_new_obs':" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_object_invalid_ref_property():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomObservable('x-new-obs', [
|
||||
('property_ref', stix2.properties.StringProperty()),
|
||||
])
|
||||
class NewObs():
|
||||
pass
|
||||
assert "is named like an object reference property but is not an ObjectReferenceProperty" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_object_invalid_refs_property():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomObservable('x-new-obs', [
|
||||
('property_refs', stix2.properties.StringProperty()),
|
||||
])
|
||||
class NewObs():
|
||||
pass
|
||||
assert "is named like an object reference list property but is not a ListProperty containing ObjectReferenceProperty" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_object_invalid_refs_list_property():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomObservable('x-new-obs', [
|
||||
('property_refs', stix2.properties.ListProperty(stix2.properties.StringProperty)),
|
||||
])
|
||||
class NewObs():
|
||||
pass
|
||||
assert "is named like an object reference list property but is not a ListProperty containing ObjectReferenceProperty" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_observable_object_invalid_valid_refs():
|
||||
@stix2.observables.CustomObservable('x-new-obs', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
('property_ref', stix2.properties.ObjectReferenceProperty(valid_types='email-addr')),
|
||||
])
|
||||
class NewObs():
|
||||
pass
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
NewObs(_valid_refs=['1'],
|
||||
property1='something',
|
||||
property_ref='1')
|
||||
assert "must be created with _valid_refs as a dict, not a list" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_no_properties_raises_exception():
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@stix2.sdo.CustomObject('x-new-object-type')
|
||||
class NewObject1(object):
|
||||
pass
|
||||
|
||||
|
||||
def test_custom_wrong_properties_arg_raises_exception():
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@stix2.observables.CustomObservable('x-new-object-type', (("prop", stix2.properties.BooleanProperty())))
|
||||
class NewObject2(object):
|
||||
pass
|
||||
|
||||
|
||||
def test_parse_custom_observable_object():
|
||||
nt_string = """{
|
||||
"type": "x-new-observable",
|
||||
"property1": "something"
|
||||
}"""
|
||||
|
||||
nt = stix2.parse_observable(nt_string, [])
|
||||
assert isinstance(nt, stix2.base._STIXBase)
|
||||
assert nt.property1 == 'something'
|
||||
|
||||
|
||||
def test_parse_unregistered_custom_observable_object():
|
||||
nt_string = """{
|
||||
"type": "x-foobar-observable",
|
||||
"property1": "something"
|
||||
}"""
|
||||
|
||||
with pytest.raises(stix2.exceptions.CustomContentError) as excinfo:
|
||||
stix2.parse_observable(nt_string)
|
||||
assert "Can't parse unknown observable type" in str(excinfo.value)
|
||||
|
||||
parsed_custom = stix2.parse_observable(nt_string, allow_custom=True)
|
||||
assert parsed_custom['property1'] == 'something'
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
assert parsed_custom.property1 == 'something'
|
||||
assert not isinstance(parsed_custom, stix2.base._STIXBase)
|
||||
|
||||
|
||||
def test_parse_unregistered_custom_observable_object_with_no_type():
|
||||
nt_string = """{
|
||||
"property1": "something"
|
||||
}"""
|
||||
|
||||
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
||||
stix2.parse_observable(nt_string, allow_custom=True)
|
||||
assert "Can't parse observable with no 'type' property" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_parse_observed_data_with_custom_observable():
|
||||
input_str = """{
|
||||
"type": "observed-data",
|
||||
"id": "observed-data--dc20c4ca-a2a3-4090-a5d5-9558c3af4758",
|
||||
"created": "2016-04-06T19:58:16.000Z",
|
||||
"modified": "2016-04-06T19:58:16.000Z",
|
||||
"first_observed": "2015-12-21T19:00:00Z",
|
||||
"last_observed": "2015-12-21T19:00:00Z",
|
||||
"number_observed": 1,
|
||||
"objects": {
|
||||
"0": {
|
||||
"type": "x-foobar-observable",
|
||||
"property1": "something"
|
||||
}
|
||||
}
|
||||
}"""
|
||||
parsed = stix2.parse(input_str, allow_custom=True)
|
||||
assert parsed.objects['0']['property1'] == 'something'
|
||||
|
||||
|
||||
def test_parse_invalid_custom_observable_object():
|
||||
nt_string = """{
|
||||
"property1": "something"
|
||||
}"""
|
||||
|
||||
with pytest.raises(stix2.exceptions.ParseError) as excinfo:
|
||||
stix2.parse_observable(nt_string)
|
||||
assert "Can't parse observable with no 'type' property" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_observable_custom_property():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
NewObservable(
|
||||
property1='something',
|
||||
custom_properties="foobar",
|
||||
)
|
||||
assert "'custom_properties' must be a dictionary" in str(excinfo.value)
|
||||
|
||||
no = NewObservable(
|
||||
property1='something',
|
||||
custom_properties={
|
||||
"foo": "bar",
|
||||
},
|
||||
)
|
||||
assert no.foo == "bar"
|
||||
|
||||
|
||||
def test_observable_custom_property_invalid():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
NewObservable(
|
||||
property1='something',
|
||||
x_foo="bar",
|
||||
)
|
||||
assert excinfo.value.properties == ['x_foo']
|
||||
assert "Unexpected properties for" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_observable_custom_property_allowed():
|
||||
no = NewObservable(
|
||||
property1='something',
|
||||
x_foo="bar",
|
||||
allow_custom=True,
|
||||
)
|
||||
assert no.x_foo == "bar"
|
||||
|
||||
|
||||
def test_observed_data_with_custom_observable_object():
|
||||
no = NewObservable(property1='something')
|
||||
ob_data = stix2.ObservedData(
|
||||
first_observed=FAKE_TIME,
|
||||
last_observed=FAKE_TIME,
|
||||
number_observed=1,
|
||||
objects={'0': no},
|
||||
allow_custom=True,
|
||||
)
|
||||
assert ob_data.objects['0'].property1 == 'something'
|
||||
|
||||
|
||||
@stix2.observables.CustomExtension(stix2.DomainName, 'x-new-ext', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
('property2', stix2.properties.IntegerProperty()),
|
||||
])
|
||||
class NewExtension():
|
||||
def __init__(self, property2=None, **kwargs):
|
||||
if property2 and property2 < 10:
|
||||
raise ValueError("'property2' is too small.")
|
||||
if "property3" in kwargs and not isinstance(kwargs.get("property3"), int):
|
||||
raise TypeError("Must be integer!")
|
||||
|
||||
|
||||
def test_custom_extension_raises_exception():
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
NewExtension(property1='something', property3='something', allow_custom=True)
|
||||
|
||||
assert str(excinfo.value) == "Must be integer!"
|
||||
|
||||
|
||||
def test_custom_extension():
|
||||
ext = NewExtension(property1='something')
|
||||
assert ext.property1 == 'something'
|
||||
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
NewExtension(property2=42)
|
||||
assert excinfo.value.properties == ['property1']
|
||||
assert str(excinfo.value) == "No values for required properties for _Custom: (property1)."
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
NewExtension(property1='something', property2=4)
|
||||
assert str(excinfo.value) == "'property2' is too small."
|
||||
|
||||
|
||||
def test_custom_extension_wrong_observable_type():
|
||||
# NewExtension is an extension of DomainName, not File
|
||||
ext = NewExtension(property1='something')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.File(name="abc.txt",
|
||||
extensions={
|
||||
"ntfs-ext": ext,
|
||||
})
|
||||
|
||||
assert 'Cannot determine extension type' in excinfo.value.reason
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
"""{
|
||||
"keys": [
|
||||
{
|
||||
"test123": 123,
|
||||
"test345": "aaaa"
|
||||
}
|
||||
]
|
||||
}""",
|
||||
])
|
||||
def test_custom_extension_with_list_and_dict_properties_observable_type(data):
|
||||
@stix2.observables.CustomExtension(stix2.UserAccount, 'some-extension', [
|
||||
('keys', stix2.properties.ListProperty(stix2.properties.DictionaryProperty, required=True))
|
||||
])
|
||||
class SomeCustomExtension:
|
||||
pass
|
||||
|
||||
example = SomeCustomExtension(keys=[{'test123': 123, 'test345': 'aaaa'}])
|
||||
assert data == str(example)
|
||||
|
||||
|
||||
def test_custom_extension_invalid_observable():
|
||||
# These extensions are being applied to improperly-created Observables.
|
||||
# The Observable classes should have been created with the CustomObservable decorator.
|
||||
class Foo(object):
|
||||
pass
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomExtension(Foo, 'x-new-ext', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class FooExtension():
|
||||
pass # pragma: no cover
|
||||
assert str(excinfo.value) == "'observable' must be a valid Observable class!"
|
||||
|
||||
class Bar(stix2.observables._Observable):
|
||||
pass
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomExtension(Bar, 'x-new-ext', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Unknown observable type" in str(excinfo.value)
|
||||
assert "Custom observables must be created with the @CustomObservable decorator." in str(excinfo.value)
|
||||
|
||||
class Baz(stix2.observables._Observable):
|
||||
_type = 'Baz'
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomExtension(Baz, 'x-new-ext', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class BazExtension():
|
||||
pass
|
||||
assert "Unknown observable type" in str(excinfo.value)
|
||||
assert "Custom observables must be created with the @CustomObservable decorator." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_invalid_type_name():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomExtension(stix2.File, 'x', {
|
||||
'property1': stix2.properties.StringProperty(required=True),
|
||||
})
|
||||
class FooExtension():
|
||||
pass # pragma: no cover
|
||||
assert "Invalid extension type name 'x':" in str(excinfo.value)
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomExtension(stix2.File, 'x_new_ext', {
|
||||
'property1': stix2.properties.StringProperty(required=True),
|
||||
})
|
||||
class BlaExtension():
|
||||
pass # pragma: no cover
|
||||
assert "Invalid extension type name 'x_new_ext':" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_no_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomExtension(stix2.DomainName, 'x-new-ext2', None)
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_empty_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomExtension(stix2.DomainName, 'x-new-ext2', [])
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_dict_properties():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
@stix2.observables.CustomExtension(stix2.DomainName, 'x-new-ext2', {})
|
||||
class BarExtension():
|
||||
pass
|
||||
assert "Must supply a list, containing tuples." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_custom_extension_no_init_1():
|
||||
@stix2.observables.CustomExtension(stix2.DomainName, 'x-new-extension', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewExt():
|
||||
pass
|
||||
|
||||
ne = NewExt(property1="foobar")
|
||||
assert ne.property1 == "foobar"
|
||||
|
||||
|
||||
def test_custom_extension_no_init_2():
|
||||
@stix2.observables.CustomExtension(stix2.DomainName, 'x-new-ext2', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewExt2(object):
|
||||
pass
|
||||
|
||||
ne2 = NewExt2(property1="foobar")
|
||||
assert ne2.property1 == "foobar"
|
||||
|
||||
|
||||
def test_parse_observable_with_custom_extension():
|
||||
input_str = """{
|
||||
"type": "domain-name",
|
||||
"value": "example.com",
|
||||
"extensions": {
|
||||
"x-new-ext": {
|
||||
"property1": "foo",
|
||||
"property2": 12
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
parsed = stix2.parse_observable(input_str)
|
||||
assert parsed.extensions['x-new-ext'].property2 == 12
|
||||
|
||||
|
||||
def test_parse_observable_with_unregistered_custom_extension():
|
||||
input_str = """{
|
||||
"type": "domain-name",
|
||||
"value": "example.com",
|
||||
"extensions": {
|
||||
"x-foobar-ext": {
|
||||
"property1": "foo",
|
||||
"property2": 12
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.parse_observable(input_str)
|
||||
assert "Can't parse unknown extension type" in str(excinfo.value)
|
||||
|
||||
parsed_ob = stix2.parse_observable(input_str, allow_custom=True)
|
||||
assert parsed_ob['extensions']['x-foobar-ext']['property1'] == 'foo'
|
||||
assert not isinstance(parsed_ob['extensions']['x-foobar-ext'], stix2.base._STIXBase)
|
||||
|
||||
|
||||
def test_register_custom_object():
|
||||
# Not the way to register custom object.
|
||||
class CustomObject2(object):
|
||||
_type = 'awesome-object'
|
||||
|
||||
stix2._register_type(CustomObject2)
|
||||
# Note that we will always check against newest OBJ_MAP.
|
||||
assert (CustomObject2._type, CustomObject2) in stix2.OBJ_MAP.items()
|
||||
|
||||
|
||||
def test_extension_property_location():
|
||||
assert 'extensions' in stix2.v21.observables.OBJ_MAP_OBSERVABLE['x-new-observable']._properties
|
||||
assert 'extensions' not in stix2.v21.observables.EXT_MAP['domain-name']['x-new-ext']._properties
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
"""{
|
||||
"type": "x-example",
|
||||
"id": "x-example--336d8a9f-91f1-46c5-b142-6441bb9f8b8d",
|
||||
"created": "2018-06-12T16:20:58.059Z",
|
||||
"modified": "2018-06-12T16:20:58.059Z",
|
||||
"dictionary": {
|
||||
"key": {
|
||||
"key_a": "value",
|
||||
"key_b": "value"
|
||||
}
|
||||
}
|
||||
}""",
|
||||
])
|
||||
def test_custom_object_nested_dictionary(data):
|
||||
@stix2.sdo.CustomObject('x-example', [
|
||||
('dictionary', stix2.properties.DictionaryProperty()),
|
||||
])
|
||||
class Example(object):
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
example = Example(id='x-example--336d8a9f-91f1-46c5-b142-6441bb9f8b8d',
|
||||
created='2018-06-12T16:20:58.059Z',
|
||||
modified='2018-06-12T16:20:58.059Z',
|
||||
dictionary={'key': {'key_b': 'value', 'key_a': 'value'}})
|
||||
|
||||
assert data == str(example)
|
|
@ -0,0 +1,117 @@
|
|||
import pytest
|
||||
|
||||
from stix2.datastore import (CompositeDataSource, DataSink, DataSource,
|
||||
DataStoreMixin)
|
||||
from stix2.datastore.filters import Filter
|
||||
from stix2.test.constants import CAMPAIGN_MORE_KWARGS
|
||||
|
||||
|
||||
def test_datasource_abstract_class_raises_error():
|
||||
with pytest.raises(TypeError):
|
||||
DataSource()
|
||||
|
||||
|
||||
def test_datasink_abstract_class_raises_error():
|
||||
with pytest.raises(TypeError):
|
||||
DataSink()
|
||||
|
||||
|
||||
def test_datastore_smoke():
|
||||
assert DataStoreMixin() is not None
|
||||
|
||||
|
||||
def test_datastore_get_raises():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
DataStoreMixin().get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert "DataStoreMixin has no data source to query" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_datastore_all_versions_raises():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
DataStoreMixin().all_versions("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert "DataStoreMixin has no data source to query" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_datastore_query_raises():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
DataStoreMixin().query([Filter("type", "=", "indicator")])
|
||||
assert "DataStoreMixin has no data source to query" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_datastore_creator_of_raises():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
DataStoreMixin().creator_of(CAMPAIGN_MORE_KWARGS)
|
||||
assert "DataStoreMixin has no data source to query" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_datastore_relationships_raises():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
DataStoreMixin().relationships(obj="indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
target_only=True)
|
||||
assert "DataStoreMixin has no data source to query" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_datastore_related_to_raises():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
DataStoreMixin().related_to(obj="indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
target_only=True)
|
||||
assert "DataStoreMixin has no data source to query" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_datastore_add_raises():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
DataStoreMixin().add(CAMPAIGN_MORE_KWARGS)
|
||||
assert "DataStoreMixin has no data sink to put objects in" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_composite_datastore_get_raises_error():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
CompositeDataSource().get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert "CompositeDataSource has no data sources" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_composite_datastore_all_versions_raises_error():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
CompositeDataSource().all_versions("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert "CompositeDataSource has no data sources" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_composite_datastore_query_raises_error():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
CompositeDataSource().query([Filter("type", "=", "indicator")])
|
||||
assert "CompositeDataSource has no data sources" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_composite_datastore_relationships_raises_error():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
CompositeDataSource().relationships(obj="indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
target_only=True)
|
||||
assert "CompositeDataSource has no data sources" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_composite_datastore_related_to_raises_error():
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
CompositeDataSource().related_to(obj="indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
target_only=True)
|
||||
assert "CompositeDataSource has no data sources" == str(excinfo.value)
|
||||
|
||||
|
||||
def test_composite_datastore_add_data_source_raises_error():
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
ind = "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f"
|
||||
CompositeDataSource().add_data_source(ind)
|
||||
assert "DataSource (to be added) is not of type stix2.DataSource. DataSource type is '{}'".format(type(ind)) == str(excinfo.value)
|
||||
|
||||
|
||||
def test_composite_datastore_add_data_sources_raises_error():
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
ind = "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f"
|
||||
CompositeDataSource().add_data_sources(ind)
|
||||
assert "DataSource (to be added) is not of type stix2.DataSource. DataSource type is '{}'".format(type(ind)) == str(excinfo.value)
|
||||
|
||||
|
||||
def test_composite_datastore_no_datasource():
|
||||
cds = CompositeDataSource()
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
cds.get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert 'CompositeDataSource has no data source' in str(excinfo.value)
|
|
@ -0,0 +1,528 @@
|
|||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from stix2 import (Bundle, Campaign, CustomObject, FileSystemSink,
|
||||
FileSystemSource, FileSystemStore, Filter, Identity,
|
||||
Indicator, Malware, Relationship, properties)
|
||||
from stix2.test.constants import (CAMPAIGN_ID, CAMPAIGN_KWARGS, IDENTITY_ID,
|
||||
IDENTITY_KWARGS, INDICATOR_ID,
|
||||
INDICATOR_KWARGS, MALWARE_ID, MALWARE_KWARGS,
|
||||
RELATIONSHIP_IDS)
|
||||
|
||||
FS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fs_store():
|
||||
# create
|
||||
yield FileSystemStore(FS_PATH)
|
||||
|
||||
# remove campaign dir
|
||||
shutil.rmtree(os.path.join(FS_PATH, "campaign"), True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fs_source():
|
||||
# create
|
||||
fs = FileSystemSource(FS_PATH)
|
||||
assert fs.stix_dir == FS_PATH
|
||||
yield fs
|
||||
|
||||
# remove campaign dir
|
||||
shutil.rmtree(os.path.join(FS_PATH, "campaign"), True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fs_sink():
|
||||
# create
|
||||
fs = FileSystemSink(FS_PATH)
|
||||
assert fs.stix_dir == FS_PATH
|
||||
yield fs
|
||||
|
||||
# remove campaign dir
|
||||
shutil.rmtree(os.path.join(FS_PATH, "campaign"), True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bad_json_files():
|
||||
# create erroneous JSON files for tests to make sure handled gracefully
|
||||
|
||||
with open(os.path.join(FS_PATH, "intrusion-set", "intrusion-set--test-non-json.txt"), "w+") as f:
|
||||
f.write("Im not a JSON file")
|
||||
|
||||
with open(os.path.join(FS_PATH, "intrusion-set", "intrusion-set--test-bad-json.json"), "w+") as f:
|
||||
f.write("Im not a JSON formatted file")
|
||||
|
||||
yield True # dummy yield so can have teardown
|
||||
|
||||
os.remove(os.path.join(FS_PATH, "intrusion-set", "intrusion-set--test-non-json.txt"))
|
||||
os.remove(os.path.join(FS_PATH, "intrusion-set", "intrusion-set--test-bad-json.json"))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bad_stix_files():
|
||||
# create erroneous STIX JSON files for tests to make sure handled correctly
|
||||
|
||||
# bad STIX object
|
||||
stix_obj = {
|
||||
"id": "intrusion-set--test-bad-stix",
|
||||
"spec_version": "2.0"
|
||||
# no "type" field
|
||||
}
|
||||
|
||||
with open(os.path.join(FS_PATH, "intrusion-set", "intrusion-set--test-non-stix.json"), "w+") as f:
|
||||
f.write(json.dumps(stix_obj))
|
||||
|
||||
yield True # dummy yield so can have teardown
|
||||
|
||||
os.remove(os.path.join(FS_PATH, "intrusion-set", "intrusion-set--test-non-stix.json"))
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def rel_fs_store():
|
||||
cam = Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
idy = Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
ind = Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
mal = Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
rel1 = Relationship(ind, 'indicates', mal, id=RELATIONSHIP_IDS[0])
|
||||
rel2 = Relationship(mal, 'targets', idy, id=RELATIONSHIP_IDS[1])
|
||||
rel3 = Relationship(cam, 'uses', mal, id=RELATIONSHIP_IDS[2])
|
||||
stix_objs = [cam, idy, ind, mal, rel1, rel2, rel3]
|
||||
fs = FileSystemStore(FS_PATH)
|
||||
for o in stix_objs:
|
||||
fs.add(o)
|
||||
yield fs
|
||||
|
||||
for o in stix_objs:
|
||||
os.remove(os.path.join(FS_PATH, o.type, o.id + '.json'))
|
||||
|
||||
|
||||
def test_filesystem_source_nonexistent_folder():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
FileSystemSource('nonexistent-folder')
|
||||
assert "for STIX data does not exist" in str(excinfo)
|
||||
|
||||
|
||||
def test_filesystem_sink_nonexistent_folder():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
FileSystemSink('nonexistent-folder')
|
||||
assert "for STIX data does not exist" in str(excinfo)
|
||||
|
||||
|
||||
def test_filesystem_source_bad_json_file(fs_source, bad_json_files):
|
||||
# this tests the handling of two bad json files
|
||||
# - one file should just be skipped (silently) as its a ".txt" extension
|
||||
# - one file should be parsed and raise Exception bc its not JSON
|
||||
try:
|
||||
fs_source.get("intrusion-set--test-bad-json")
|
||||
except TypeError as e:
|
||||
assert "intrusion-set--test-bad-json" in str(e)
|
||||
assert "could either not be parsed to JSON or was not valid STIX JSON" in str(e)
|
||||
|
||||
|
||||
def test_filesystem_source_bad_stix_file(fs_source, bad_stix_files):
|
||||
# this tests handling of bad STIX json object
|
||||
try:
|
||||
fs_source.get("intrusion-set--test-non-stix")
|
||||
except TypeError as e:
|
||||
assert "intrusion-set--test-non-stix" in str(e)
|
||||
assert "could either not be parsed to JSON or was not valid STIX JSON" in str(e)
|
||||
|
||||
|
||||
def test_filesytem_source_get_object(fs_source):
|
||||
# get object
|
||||
mal = fs_source.get("malware--6b616fc1-1505-48e3-8b2c-0d19337bff38")
|
||||
assert mal.id == "malware--6b616fc1-1505-48e3-8b2c-0d19337bff38"
|
||||
assert mal.name == "Rover"
|
||||
|
||||
|
||||
def test_filesytem_source_get_nonexistent_object(fs_source):
|
||||
ind = fs_source.get("indicator--6b616fc1-1505-48e3-8b2c-0d19337bff38")
|
||||
assert ind is None
|
||||
|
||||
|
||||
def test_filesytem_source_all_versions(fs_source):
|
||||
# all versions - (currently not a true all versions call as FileSystem cant have multiple versions)
|
||||
id_ = fs_source.get("identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5")
|
||||
assert id_.id == "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5"
|
||||
assert id_.name == "The MITRE Corporation"
|
||||
assert id_.type == "identity"
|
||||
|
||||
|
||||
def test_filesytem_source_query_single(fs_source):
|
||||
# query2
|
||||
is_2 = fs_source.query([Filter("external_references.external_id", '=', "T1027")])
|
||||
assert len(is_2) == 1
|
||||
|
||||
is_2 = is_2[0]
|
||||
assert is_2.id == "attack-pattern--b3d682b6-98f2-4fb0-aa3b-b4df007ca70a"
|
||||
assert is_2.type == "attack-pattern"
|
||||
|
||||
|
||||
def test_filesytem_source_query_multiple(fs_source):
|
||||
# query
|
||||
intrusion_sets = fs_source.query([Filter("type", '=', "intrusion-set")])
|
||||
assert len(intrusion_sets) == 2
|
||||
assert "intrusion-set--a653431d-6a5e-4600-8ad3-609b5af57064" in [is_.id for is_ in intrusion_sets]
|
||||
assert "intrusion-set--f3bdec95-3d62-42d9-a840-29630f6cdc1a" in [is_.id for is_ in intrusion_sets]
|
||||
|
||||
is_1 = [is_ for is_ in intrusion_sets if is_.id == "intrusion-set--f3bdec95-3d62-42d9-a840-29630f6cdc1a"][0]
|
||||
assert "DragonOK" in is_1.aliases
|
||||
assert len(is_1.external_references) == 4
|
||||
|
||||
|
||||
def test_filesystem_sink_add_python_stix_object(fs_sink, fs_source):
|
||||
# add python stix object
|
||||
camp1 = Campaign(name="Hannibal",
|
||||
objective="Targeting Italian and Spanish Diplomat internet accounts",
|
||||
aliases=["War Elephant"])
|
||||
|
||||
fs_sink.add(camp1)
|
||||
|
||||
assert os.path.exists(os.path.join(FS_PATH, "campaign", camp1.id + ".json"))
|
||||
|
||||
camp1_r = fs_source.get(camp1.id)
|
||||
assert camp1_r.id == camp1.id
|
||||
assert camp1_r.name == "Hannibal"
|
||||
assert "War Elephant" in camp1_r.aliases
|
||||
|
||||
os.remove(os.path.join(FS_PATH, "campaign", camp1_r.id + ".json"))
|
||||
|
||||
|
||||
def test_filesystem_sink_add_stix_object_dict(fs_sink, fs_source):
|
||||
# add stix object dict
|
||||
camp2 = {
|
||||
"name": "Aurelius",
|
||||
"type": "campaign",
|
||||
"objective": "German and French Intelligence Services",
|
||||
"aliases": ["Purple Robes"],
|
||||
"id": "campaign--111111b6-1112-4fb0-111b-b111107ca70a",
|
||||
"created": "2017-05-31T21:31:53.197755Z"
|
||||
}
|
||||
|
||||
fs_sink.add(camp2)
|
||||
|
||||
assert os.path.exists(os.path.join(FS_PATH, "campaign", camp2["id"] + ".json"))
|
||||
|
||||
camp2_r = fs_source.get(camp2["id"])
|
||||
assert camp2_r.id == camp2["id"]
|
||||
assert camp2_r.name == camp2["name"]
|
||||
assert "Purple Robes" in camp2_r.aliases
|
||||
|
||||
os.remove(os.path.join(FS_PATH, "campaign", camp2_r.id + ".json"))
|
||||
|
||||
|
||||
def test_filesystem_sink_add_stix_bundle_dict(fs_sink, fs_source):
|
||||
# add stix bundle dict
|
||||
bund = {
|
||||
"type": "bundle",
|
||||
"id": "bundle--112211b6-1112-4fb0-111b-b111107ca70a",
|
||||
"objects": [
|
||||
{
|
||||
"name": "Atilla",
|
||||
"type": "campaign",
|
||||
"objective": "Bulgarian, Albanian and Romanian Intelligence Services",
|
||||
"aliases": ["Huns"],
|
||||
"id": "campaign--133111b6-1112-4fb0-111b-b111107ca70a",
|
||||
"created": "2017-05-31T21:31:53.197755Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
fs_sink.add(bund)
|
||||
|
||||
assert os.path.exists(os.path.join(FS_PATH, "campaign", bund["objects"][0]["id"] + ".json"))
|
||||
|
||||
camp3_r = fs_source.get(bund["objects"][0]["id"])
|
||||
assert camp3_r.id == bund["objects"][0]["id"]
|
||||
assert camp3_r.name == bund["objects"][0]["name"]
|
||||
assert "Huns" in camp3_r.aliases
|
||||
|
||||
os.remove(os.path.join(FS_PATH, "campaign", camp3_r.id + ".json"))
|
||||
|
||||
|
||||
def test_filesystem_sink_add_json_stix_object(fs_sink, fs_source):
|
||||
# add json-encoded stix obj
|
||||
camp4 = '{"type": "campaign", "id":"campaign--144111b6-1112-4fb0-111b-b111107ca70a",'\
|
||||
' "created":"2017-05-31T21:31:53.197755Z", "name": "Ghengis Khan", "objective": "China and Russian infrastructure"}'
|
||||
|
||||
fs_sink.add(camp4)
|
||||
|
||||
assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--144111b6-1112-4fb0-111b-b111107ca70a" + ".json"))
|
||||
|
||||
camp4_r = fs_source.get("campaign--144111b6-1112-4fb0-111b-b111107ca70a")
|
||||
assert camp4_r.id == "campaign--144111b6-1112-4fb0-111b-b111107ca70a"
|
||||
assert camp4_r.name == "Ghengis Khan"
|
||||
|
||||
os.remove(os.path.join(FS_PATH, "campaign", camp4_r.id + ".json"))
|
||||
|
||||
|
||||
def test_filesystem_sink_json_stix_bundle(fs_sink, fs_source):
|
||||
# add json-encoded stix bundle
|
||||
bund2 = '{"type": "bundle", "id": "bundle--332211b6-1132-4fb0-111b-b111107ca70a",' \
|
||||
' "objects": [{"type": "campaign", "id": "campaign--155155b6-1112-4fb0-111b-b111107ca70a",' \
|
||||
' "created":"2017-05-31T21:31:53.197755Z", "name": "Spartacus", "objective": "Oppressive regimes of Africa and Middle East"}]}'
|
||||
fs_sink.add(bund2)
|
||||
|
||||
assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--155155b6-1112-4fb0-111b-b111107ca70a" + ".json"))
|
||||
|
||||
camp5_r = fs_source.get("campaign--155155b6-1112-4fb0-111b-b111107ca70a")
|
||||
assert camp5_r.id == "campaign--155155b6-1112-4fb0-111b-b111107ca70a"
|
||||
assert camp5_r.name == "Spartacus"
|
||||
|
||||
os.remove(os.path.join(FS_PATH, "campaign", camp5_r.id + ".json"))
|
||||
|
||||
|
||||
def test_filesystem_sink_add_objects_list(fs_sink, fs_source):
|
||||
# add list of objects
|
||||
camp6 = Campaign(name="Comanche",
|
||||
objective="US Midwest manufacturing firms, oil refineries, and businesses",
|
||||
aliases=["Horse Warrior"])
|
||||
|
||||
camp7 = {
|
||||
"name": "Napolean",
|
||||
"type": "campaign",
|
||||
"objective": "Central and Eastern Europe military commands and departments",
|
||||
"aliases": ["The Frenchmen"],
|
||||
"id": "campaign--122818b6-1112-4fb0-111b-b111107ca70a",
|
||||
"created": "2017-05-31T21:31:53.197755Z"
|
||||
}
|
||||
|
||||
fs_sink.add([camp6, camp7])
|
||||
|
||||
assert os.path.exists(os.path.join(FS_PATH, "campaign", camp6.id + ".json"))
|
||||
assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--122818b6-1112-4fb0-111b-b111107ca70a" + ".json"))
|
||||
|
||||
camp6_r = fs_source.get(camp6.id)
|
||||
assert camp6_r.id == camp6.id
|
||||
assert "Horse Warrior" in camp6_r.aliases
|
||||
|
||||
camp7_r = fs_source.get(camp7["id"])
|
||||
assert camp7_r.id == camp7["id"]
|
||||
assert "The Frenchmen" in camp7_r.aliases
|
||||
|
||||
# remove all added objects
|
||||
os.remove(os.path.join(FS_PATH, "campaign", camp6_r.id + ".json"))
|
||||
os.remove(os.path.join(FS_PATH, "campaign", camp7_r.id + ".json"))
|
||||
|
||||
|
||||
def test_filesystem_store_get_stored_as_bundle(fs_store):
|
||||
coa = fs_store.get("course-of-action--95ddb356-7ba0-4bd9-a889-247262b8946f")
|
||||
assert coa.id == "course-of-action--95ddb356-7ba0-4bd9-a889-247262b8946f"
|
||||
assert coa.type == "course-of-action"
|
||||
|
||||
|
||||
def test_filesystem_store_get_stored_as_object(fs_store):
|
||||
coa = fs_store.get("course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd")
|
||||
assert coa.id == "course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd"
|
||||
assert coa.type == "course-of-action"
|
||||
|
||||
|
||||
def test_filesystem_store_all_versions(fs_store):
|
||||
# all versions() - (note at this time, all_versions() is still not applicable to FileSystem, as only one version is ever stored)
|
||||
rel = fs_store.all_versions("relationship--70dc6b5c-c524-429e-a6ab-0dd40f0482c1")[0]
|
||||
assert rel.id == "relationship--70dc6b5c-c524-429e-a6ab-0dd40f0482c1"
|
||||
assert rel.type == "relationship"
|
||||
|
||||
|
||||
def test_filesystem_store_query(fs_store):
|
||||
# query()
|
||||
tools = fs_store.query([Filter("labels", "in", "tool")])
|
||||
assert len(tools) == 2
|
||||
assert "tool--242f3da3-4425-4d11-8f5c-b842886da966" in [tool.id for tool in tools]
|
||||
assert "tool--03342581-f790-4f03-ba41-e82e67392e23" in [tool.id for tool in tools]
|
||||
|
||||
|
||||
def test_filesystem_store_query_single_filter(fs_store):
|
||||
query = Filter("labels", "in", "tool")
|
||||
tools = fs_store.query(query)
|
||||
assert len(tools) == 2
|
||||
assert "tool--242f3da3-4425-4d11-8f5c-b842886da966" in [tool.id for tool in tools]
|
||||
assert "tool--03342581-f790-4f03-ba41-e82e67392e23" in [tool.id for tool in tools]
|
||||
|
||||
|
||||
def test_filesystem_store_empty_query(fs_store):
|
||||
results = fs_store.query() # returns all
|
||||
assert len(results) == 26
|
||||
assert "tool--242f3da3-4425-4d11-8f5c-b842886da966" in [obj["id"] for obj in results]
|
||||
assert "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" in [obj["id"] for obj in results]
|
||||
|
||||
|
||||
def test_filesystem_store_query_multiple_filters(fs_store):
|
||||
fs_store.source.filters.add(Filter("labels", "in", "tool"))
|
||||
tools = fs_store.query(Filter("id", "=", "tool--242f3da3-4425-4d11-8f5c-b842886da966"))
|
||||
assert len(tools) == 1
|
||||
assert tools[0].id == "tool--242f3da3-4425-4d11-8f5c-b842886da966"
|
||||
|
||||
|
||||
def test_filesystem_store_query_dont_include_type_folder(fs_store):
|
||||
results = fs_store.query(Filter("type", "!=", "tool"))
|
||||
assert len(results) == 24
|
||||
|
||||
|
||||
def test_filesystem_store_add(fs_store):
|
||||
# add()
|
||||
camp1 = Campaign(name="Great Heathen Army",
|
||||
objective="Targeting the government of United Kingdom and insitutions affiliated with the Church Of England",
|
||||
aliases=["Ragnar"])
|
||||
fs_store.add(camp1)
|
||||
|
||||
camp1_r = fs_store.get(camp1.id)
|
||||
assert camp1_r.id == camp1.id
|
||||
assert camp1_r.name == camp1.name
|
||||
|
||||
# remove
|
||||
os.remove(os.path.join(FS_PATH, "campaign", camp1_r.id + ".json"))
|
||||
|
||||
|
||||
def test_filesystem_store_add_as_bundle():
|
||||
fs_store = FileSystemStore(FS_PATH, bundlify=True)
|
||||
|
||||
camp1 = Campaign(name="Great Heathen Army",
|
||||
objective="Targeting the government of United Kingdom and insitutions affiliated with the Church Of England",
|
||||
aliases=["Ragnar"])
|
||||
fs_store.add(camp1)
|
||||
|
||||
with open(os.path.join(FS_PATH, "campaign", camp1.id + ".json")) as bundle_file:
|
||||
assert '"type": "bundle"' in bundle_file.read()
|
||||
|
||||
camp1_r = fs_store.get(camp1.id)
|
||||
assert camp1_r.id == camp1.id
|
||||
assert camp1_r.name == camp1.name
|
||||
|
||||
shutil.rmtree(os.path.join(FS_PATH, "campaign"), True)
|
||||
|
||||
|
||||
def test_filesystem_add_bundle_object(fs_store):
|
||||
bundle = Bundle()
|
||||
fs_store.add(bundle)
|
||||
|
||||
|
||||
def test_filesystem_store_add_invalid_object(fs_store):
|
||||
ind = ('campaign', 'campaign--111111b6-1112-4fb0-111b-b111107ca70a') # tuple isn't valid
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
fs_store.add(ind)
|
||||
assert 'stix_data must be' in str(excinfo.value)
|
||||
assert 'a STIX object' in str(excinfo.value)
|
||||
assert 'JSON formatted STIX' in str(excinfo.value)
|
||||
assert 'JSON formatted STIX bundle' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_filesystem_object_with_custom_property(fs_store):
|
||||
camp = Campaign(name="Scipio Africanus",
|
||||
objective="Defeat the Carthaginians",
|
||||
x_empire="Roman",
|
||||
allow_custom=True)
|
||||
|
||||
fs_store.add(camp, True)
|
||||
|
||||
camp_r = fs_store.get(camp.id)
|
||||
assert camp_r.id == camp.id
|
||||
assert camp_r.x_empire == camp.x_empire
|
||||
|
||||
|
||||
def test_filesystem_object_with_custom_property_in_bundle(fs_store):
|
||||
camp = Campaign(name="Scipio Africanus",
|
||||
objective="Defeat the Carthaginians",
|
||||
x_empire="Roman",
|
||||
allow_custom=True)
|
||||
|
||||
bundle = Bundle(camp, allow_custom=True)
|
||||
fs_store.add(bundle)
|
||||
|
||||
camp_r = fs_store.get(camp.id)
|
||||
assert camp_r.id == camp.id
|
||||
assert camp_r.x_empire == camp.x_empire
|
||||
|
||||
|
||||
def test_filesystem_custom_object(fs_store):
|
||||
@CustomObject('x-new-obj', [
|
||||
('property1', properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewObj():
|
||||
pass
|
||||
|
||||
newobj = NewObj(property1='something')
|
||||
fs_store.add(newobj)
|
||||
|
||||
newobj_r = fs_store.get(newobj.id)
|
||||
assert newobj_r["id"] == newobj["id"]
|
||||
assert newobj_r["property1"] == 'something'
|
||||
|
||||
# remove dir
|
||||
shutil.rmtree(os.path.join(FS_PATH, "x-new-obj"), True)
|
||||
|
||||
|
||||
def test_relationships(rel_fs_store):
|
||||
mal = rel_fs_store.get(MALWARE_ID)
|
||||
resp = rel_fs_store.relationships(mal)
|
||||
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[0] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[1] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_by_type(rel_fs_store):
|
||||
mal = rel_fs_store.get(MALWARE_ID)
|
||||
resp = rel_fs_store.relationships(mal, relationship_type='indicates')
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0]['id'] == RELATIONSHIP_IDS[0]
|
||||
|
||||
|
||||
def test_relationships_by_source(rel_fs_store):
|
||||
resp = rel_fs_store.relationships(MALWARE_ID, source_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0]['id'] == RELATIONSHIP_IDS[1]
|
||||
|
||||
|
||||
def test_relationships_by_target(rel_fs_store):
|
||||
resp = rel_fs_store.relationships(MALWARE_ID, target_only=True)
|
||||
|
||||
assert len(resp) == 2
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[0] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_by_target_and_type(rel_fs_store):
|
||||
resp = rel_fs_store.relationships(MALWARE_ID, relationship_type='uses', target_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_by_target_and_source(rel_fs_store):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
rel_fs_store.relationships(MALWARE_ID, target_only=True, source_only=True)
|
||||
|
||||
assert 'not both' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_related_to(rel_fs_store):
|
||||
mal = rel_fs_store.get(MALWARE_ID)
|
||||
resp = rel_fs_store.related_to(mal)
|
||||
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
||||
assert any(x['id'] == IDENTITY_ID for x in resp)
|
||||
|
||||
|
||||
def test_related_to_by_source(rel_fs_store):
|
||||
resp = rel_fs_store.related_to(MALWARE_ID, source_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert any(x['id'] == IDENTITY_ID for x in resp)
|
||||
|
||||
|
||||
def test_related_to_by_target(rel_fs_store):
|
||||
resp = rel_fs_store.related_to(MALWARE_ID, target_only=True)
|
||||
|
||||
assert len(resp) == 2
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
|
@ -0,0 +1,465 @@
|
|||
import pytest
|
||||
|
||||
from stix2 import parse
|
||||
from stix2.datastore.filters import Filter, apply_common_filters
|
||||
from stix2.utils import STIXdatetime, parse_into_datetime
|
||||
|
||||
stix_objs = [
|
||||
{
|
||||
"created": "2017-01-27T13:49:53.997Z",
|
||||
"description": "\n\nTITLE:\n\tPoison Ivy",
|
||||
"id": "malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111",
|
||||
"spec_version": "2.1",
|
||||
"labels": [
|
||||
"remote-access-trojan"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.997Z",
|
||||
"name": "Poison Ivy",
|
||||
"type": "malware",
|
||||
"is_family": False
|
||||
},
|
||||
{
|
||||
"created": "2014-05-08T09:00:00.000Z",
|
||||
"id": "indicator--a932fcc6-e032-176c-126f-cb970a5a1ade",
|
||||
"labels": [
|
||||
"file-hash-watchlist"
|
||||
],
|
||||
"modified": "2014-05-08T09:00:00.000Z",
|
||||
"name": "File hash for Poison Ivy variant",
|
||||
"pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2014-05-08T09:00:00.000000Z"
|
||||
},
|
||||
{
|
||||
"created": "2014-05-08T09:00:00.000Z",
|
||||
"granular_markings": [
|
||||
{
|
||||
"marking_ref": "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed",
|
||||
"selectors": [
|
||||
"relationship_type"
|
||||
]
|
||||
}
|
||||
],
|
||||
"id": "relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463",
|
||||
"modified": "2014-05-08T09:00:00.000Z",
|
||||
"object_marking_refs": [
|
||||
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
|
||||
],
|
||||
"relationship_type": "indicates",
|
||||
"revoked": True,
|
||||
"source_ref": "indicator--a932fcc6-e032-176c-126f-cb970a5a1ade",
|
||||
"target_ref": "malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111",
|
||||
"type": "relationship"
|
||||
},
|
||||
{
|
||||
"id": "vulnerability--ee916c28-c7a4-4d0d-ad56-a8d357f89fef",
|
||||
"created": "2016-02-14T00:00:00.000Z",
|
||||
"created_by_ref": "identity--00000000-0000-0000-0000-b8e91df99dc9",
|
||||
"modified": "2016-02-14T00:00:00.000Z",
|
||||
"type": "vulnerability",
|
||||
"name": "CVE-2014-0160",
|
||||
"description": "The (1) TLS...",
|
||||
"external_references": [
|
||||
{
|
||||
"source_name": "cve",
|
||||
"external_id": "CVE-2014-0160"
|
||||
}
|
||||
],
|
||||
"labels": ["heartbleed", "has-logo"]
|
||||
},
|
||||
{
|
||||
"type": "observed-data",
|
||||
"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": 1,
|
||||
"objects": {
|
||||
"0": {
|
||||
"type": "file",
|
||||
"name": "HAL 9000.exe"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
filters = [
|
||||
Filter("type", "!=", "relationship"),
|
||||
Filter("id", "=", "relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463"),
|
||||
Filter("labels", "in", "remote-access-trojan"),
|
||||
Filter("created", ">", "2015-01-01T01:00:00.000Z"),
|
||||
Filter("revoked", "=", True),
|
||||
Filter("revoked", "!=", True),
|
||||
Filter("object_marking_refs", "=", "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"),
|
||||
Filter("granular_markings.selectors", "in", "relationship_type"),
|
||||
Filter("granular_markings.marking_ref", "=", "marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed"),
|
||||
Filter("external_references.external_id", "in", "CVE-2014-0160,CVE-2017-6608"),
|
||||
Filter("created_by_ref", "=", "identity--00000000-0000-0000-0000-b8e91df99dc9"),
|
||||
Filter("object_marking_refs", "=", "marking-definition--613f2e26-0000-0000-0000-b8e91df99dc9"),
|
||||
Filter("granular_markings.selectors", "in", "description"),
|
||||
Filter("external_references.source_name", "=", "CVE"),
|
||||
Filter("objects", "=", {"0": {"type": "file", "name": "HAL 9000.exe"}})
|
||||
]
|
||||
|
||||
# same as above objects but converted to real Python STIX2 objects
|
||||
# to test filters against true Python STIX2 objects
|
||||
real_stix_objs = [parse(stix_obj) for stix_obj in stix_objs]
|
||||
|
||||
|
||||
def test_filter_ops_check():
|
||||
# invalid filters - non supported operators
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
# create Filter that has an operator that is not allowed
|
||||
Filter('modified', '*', 'not supported operator')
|
||||
assert str(excinfo.value) == "Filter operator '*' not supported for specified property: 'modified'"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
Filter("type", "%", "4")
|
||||
assert "Filter operator '%' not supported for specified property" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_filter_value_type_check():
|
||||
# invalid filters - non supported value types
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
Filter('created', '=', object())
|
||||
# On Python 2, the type of object() is `<type 'object'>` On Python 3, it's `<class 'object'>`.
|
||||
assert any([s in str(excinfo.value) for s in ["<type 'object'>", "'<class 'object'>'"]])
|
||||
assert "is not supported. The type must be a Python immutable type or dictionary" in str(excinfo.value)
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
Filter("type", "=", complex(2, -1))
|
||||
assert any([s in str(excinfo.value) for s in ["<type 'complex'>", "'<class 'complex'>'"]])
|
||||
assert "is not supported. The type must be a Python immutable type or dictionary" in str(excinfo.value)
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
Filter("type", "=", set([16, 23]))
|
||||
assert any([s in str(excinfo.value) for s in ["<type 'set'>", "'<class 'set'>'"]])
|
||||
assert "is not supported. The type must be a Python immutable type or dictionary" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_filter_type_underscore_check():
|
||||
# check that Filters where property="type", value (name) doesnt have underscores
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
Filter("type", "=", "oh_underscore")
|
||||
assert "Filter for property 'type' cannot have its value 'oh_underscore'" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_apply_common_filters0():
|
||||
# "Return any object whose type is not relationship"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[0]]))
|
||||
ids = [r['id'] for r in resp]
|
||||
assert stix_objs[0]['id'] in ids
|
||||
assert stix_objs[1]['id'] in ids
|
||||
assert stix_objs[3]['id'] in ids
|
||||
assert len(ids) == 4
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[0]]))
|
||||
ids = [r.id for r in resp]
|
||||
assert real_stix_objs[0].id in ids
|
||||
assert real_stix_objs[1].id in ids
|
||||
assert real_stix_objs[3].id in ids
|
||||
assert len(ids) == 4
|
||||
|
||||
|
||||
def test_apply_common_filters1():
|
||||
# "Return any object that matched id relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[1]]))
|
||||
assert resp[0]['id'] == stix_objs[2]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[1]]))
|
||||
assert resp[0].id == real_stix_objs[2].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_apply_common_filters2():
|
||||
# "Return any object that contains remote-access-trojan in labels"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[2]]))
|
||||
assert resp[0]['id'] == stix_objs[0]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[2]]))
|
||||
assert resp[0].id == real_stix_objs[0].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_apply_common_filters3():
|
||||
# "Return any object created after 2015-01-01T01:00:00.000Z"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[3]]))
|
||||
assert resp[0]['id'] == stix_objs[0]['id']
|
||||
assert len(resp) == 3
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[3]]))
|
||||
assert resp[0].id == real_stix_objs[0].id
|
||||
assert len(resp) == 3
|
||||
|
||||
|
||||
def test_apply_common_filters4():
|
||||
# "Return any revoked object"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[4]]))
|
||||
assert resp[0]['id'] == stix_objs[2]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[4]]))
|
||||
assert resp[0].id == real_stix_objs[2].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_apply_common_filters5():
|
||||
# "Return any object whose not revoked"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[5]]))
|
||||
assert len(resp) == 0
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[5]]))
|
||||
assert len(resp) == 4
|
||||
|
||||
|
||||
def test_apply_common_filters6():
|
||||
# "Return any object that matches marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9 in object_marking_refs"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[6]]))
|
||||
assert resp[0]['id'] == stix_objs[2]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[6]]))
|
||||
assert resp[0].id == real_stix_objs[2].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_apply_common_filters7():
|
||||
# "Return any object that contains relationship_type in their selectors AND
|
||||
# also has marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed in marking_ref"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[7], filters[8]]))
|
||||
assert resp[0]['id'] == stix_objs[2]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[7], filters[8]]))
|
||||
assert resp[0].id == real_stix_objs[2].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_apply_common_filters8():
|
||||
# "Return any object that contains CVE-2014-0160,CVE-2017-6608 in their external_id"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[9]]))
|
||||
assert resp[0]['id'] == stix_objs[3]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[9]]))
|
||||
assert resp[0].id == real_stix_objs[3].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_apply_common_filters9():
|
||||
# "Return any object that matches created_by_ref identity--00000000-0000-0000-0000-b8e91df99dc9"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[10]]))
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[10]]))
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_apply_common_filters10():
|
||||
# "Return any object that matches marking-definition--613f2e26-0000-0000-0000-b8e91df99dc9 in object_marking_refs" (None)
|
||||
resp = list(apply_common_filters(stix_objs, [filters[11]]))
|
||||
assert len(resp) == 0
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[11]]))
|
||||
assert len(resp) == 0
|
||||
|
||||
|
||||
def test_apply_common_filters11():
|
||||
# "Return any object that contains description in its selectors" (None)
|
||||
resp = list(apply_common_filters(stix_objs, [filters[12]]))
|
||||
assert len(resp) == 0
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[12]]))
|
||||
assert len(resp) == 0
|
||||
|
||||
|
||||
def test_apply_common_filters12():
|
||||
# "Return any object that matches CVE in source_name" (None, case sensitive)
|
||||
resp = list(apply_common_filters(stix_objs, [filters[13]]))
|
||||
assert len(resp) == 0
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[13]]))
|
||||
assert len(resp) == 0
|
||||
|
||||
|
||||
def test_apply_common_filters13():
|
||||
# Return any object that matches file object in "objects"
|
||||
resp = list(apply_common_filters(stix_objs, [filters[14]]))
|
||||
assert resp[0]["id"] == stix_objs[4]["id"]
|
||||
assert len(resp) == 1
|
||||
# important additional check to make sure original File dict was
|
||||
# not converted to File object. (this was a deep bug found)
|
||||
assert isinstance(resp[0]["objects"]["0"], dict)
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs, [filters[14]]))
|
||||
assert resp[0].id == real_stix_objs[4].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_datetime_filter_behavior():
|
||||
"""if a filter is initialized with its value being a datetime object
|
||||
OR the STIX object property being filtered on is a datetime object, all
|
||||
resulting comparisons executed are done on the string representations
|
||||
of the datetime objects, as the Filter functionality will convert
|
||||
all datetime objects to there string forms using format_datetim()
|
||||
|
||||
This test makes sure all datetime comparisons are carried out correctly
|
||||
"""
|
||||
filter_with_dt_obj = Filter("created", "=", parse_into_datetime("2016-02-14T00:00:00.000Z", "millisecond"))
|
||||
filter_with_str = Filter("created", "=", "2016-02-14T00:00:00.000Z")
|
||||
|
||||
# check that filter value is converted from datetime to str
|
||||
assert isinstance(filter_with_dt_obj.value, str)
|
||||
|
||||
# compare datetime string to filter w/ datetime obj
|
||||
resp = list(apply_common_filters(stix_objs, [filter_with_dt_obj]))
|
||||
assert len(resp) == 1
|
||||
assert resp[0]["id"] == "vulnerability--ee916c28-c7a4-4d0d-ad56-a8d357f89fef"
|
||||
|
||||
# compare datetime obj to filter w/ datetime obj
|
||||
resp = list(apply_common_filters(real_stix_objs, [filter_with_dt_obj]))
|
||||
assert len(resp) == 1
|
||||
assert resp[0]["id"] == "vulnerability--ee916c28-c7a4-4d0d-ad56-a8d357f89fef"
|
||||
assert isinstance(resp[0].created, STIXdatetime) # make sure original object not altered
|
||||
|
||||
# compare datetime string to filter w/ str
|
||||
resp = list(apply_common_filters(stix_objs, [filter_with_str]))
|
||||
assert len(resp) == 1
|
||||
assert resp[0]["id"] == "vulnerability--ee916c28-c7a4-4d0d-ad56-a8d357f89fef"
|
||||
|
||||
# compare datetime obj to filter w/ str
|
||||
resp = list(apply_common_filters(real_stix_objs, [filter_with_str]))
|
||||
assert len(resp) == 1
|
||||
assert resp[0]["id"] == "vulnerability--ee916c28-c7a4-4d0d-ad56-a8d357f89fef"
|
||||
assert isinstance(resp[0].created, STIXdatetime) # make sure original object not altered
|
||||
|
||||
|
||||
def test_filters0(stix_objs2, real_stix_objs2):
|
||||
# "Return any object modified before 2017-01-28T13:49:53.935Z"
|
||||
resp = list(apply_common_filters(stix_objs2, [Filter("modified", "<", "2017-01-28T13:49:53.935Z")]))
|
||||
assert resp[0]['id'] == stix_objs2[1]['id']
|
||||
assert len(resp) == 2
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs2, [Filter("modified", "<", parse_into_datetime("2017-01-28T13:49:53.935Z"))]))
|
||||
assert resp[0].id == real_stix_objs2[1].id
|
||||
assert len(resp) == 2
|
||||
|
||||
|
||||
def test_filters1(stix_objs2, real_stix_objs2):
|
||||
# "Return any object modified after 2017-01-28T13:49:53.935Z"
|
||||
resp = list(apply_common_filters(stix_objs2, [Filter("modified", ">", "2017-01-28T13:49:53.935Z")]))
|
||||
assert resp[0]['id'] == stix_objs2[0]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs2, [Filter("modified", ">", parse_into_datetime("2017-01-28T13:49:53.935Z"))]))
|
||||
assert resp[0].id == real_stix_objs2[0].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_filters2(stix_objs2, real_stix_objs2):
|
||||
# "Return any object modified after or on 2017-01-28T13:49:53.935Z"
|
||||
resp = list(apply_common_filters(stix_objs2, [Filter("modified", ">=", "2017-01-27T13:49:53.935Z")]))
|
||||
assert resp[0]['id'] == stix_objs2[0]['id']
|
||||
assert len(resp) == 3
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs2, [Filter("modified", ">=", parse_into_datetime("2017-01-27T13:49:53.935Z"))]))
|
||||
assert resp[0].id == real_stix_objs2[0].id
|
||||
assert len(resp) == 3
|
||||
|
||||
|
||||
def test_filters3(stix_objs2, real_stix_objs2):
|
||||
# "Return any object modified before or on 2017-01-28T13:49:53.935Z"
|
||||
resp = list(apply_common_filters(stix_objs2, [Filter("modified", "<=", "2017-01-27T13:49:53.935Z")]))
|
||||
assert resp[0]['id'] == stix_objs2[1]['id']
|
||||
assert len(resp) == 2
|
||||
|
||||
# "Return any object modified before or on 2017-01-28T13:49:53.935Z"
|
||||
fv = Filter("modified", "<=", parse_into_datetime("2017-01-27T13:49:53.935Z"))
|
||||
resp = list(apply_common_filters(real_stix_objs2, [fv]))
|
||||
assert resp[0].id == real_stix_objs2[1].id
|
||||
assert len(resp) == 2
|
||||
|
||||
|
||||
def test_filters4():
|
||||
# Assert invalid Filter cannot be created
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
Filter("modified", "?", "2017-01-27T13:49:53.935Z")
|
||||
assert str(excinfo.value) == ("Filter operator '?' not supported "
|
||||
"for specified property: 'modified'")
|
||||
|
||||
|
||||
def test_filters5(stix_objs2, real_stix_objs2):
|
||||
# "Return any object whose id is not indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f"
|
||||
resp = list(apply_common_filters(stix_objs2, [Filter("id", "!=", "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f")]))
|
||||
assert resp[0]['id'] == stix_objs2[0]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs2, [Filter("id", "!=", "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f")]))
|
||||
assert resp[0].id == real_stix_objs2[0].id
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_filters6(stix_objs2, real_stix_objs2):
|
||||
# Test filtering on non-common property
|
||||
resp = list(apply_common_filters(stix_objs2, [Filter("name", "=", "Malicious site hosting downloader")]))
|
||||
assert resp[0]['id'] == stix_objs2[0]['id']
|
||||
assert len(resp) == 3
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objs2, [Filter("name", "=", "Malicious site hosting downloader")]))
|
||||
assert resp[0].id == real_stix_objs2[0].id
|
||||
assert len(resp) == 3
|
||||
|
||||
|
||||
def test_filters7(stix_objs2, real_stix_objs2):
|
||||
# Test filtering on embedded property
|
||||
obsvd_data_obj = {
|
||||
"type": "observed-data",
|
||||
"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": {
|
||||
"type": "file",
|
||||
"hashes": {
|
||||
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
|
||||
},
|
||||
"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",
|
||||
"CreationDate": "20070412090123-02"
|
||||
},
|
||||
"pdfid0": "DFCE52BD827ECF765649852119D",
|
||||
"pdfid1": "57A1E0F9ED2AE523E313C"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stix_objects = list(stix_objs2) + [obsvd_data_obj]
|
||||
real_stix_objects = list(real_stix_objs2) + [parse(obsvd_data_obj)]
|
||||
|
||||
resp = list(apply_common_filters(stix_objects, [Filter("objects.0.extensions.pdf-ext.version", ">", "1.2")]))
|
||||
assert resp[0]['id'] == stix_objects[3]['id']
|
||||
assert len(resp) == 1
|
||||
|
||||
resp = list(apply_common_filters(real_stix_objects, [Filter("objects.0.extensions.pdf-ext.version", ">", "1.2")]))
|
||||
assert resp[0].id == real_stix_objects[3].id
|
||||
assert len(resp) == 1
|
|
@ -0,0 +1,87 @@
|
|||
import pytest
|
||||
|
||||
from stix2.datastore import CompositeDataSource, make_id
|
||||
from stix2.datastore.filters import Filter
|
||||
from stix2.datastore.memory import MemorySink, MemorySource
|
||||
|
||||
|
||||
def test_add_remove_composite_datasource():
|
||||
cds = CompositeDataSource()
|
||||
ds1 = MemorySource()
|
||||
ds2 = MemorySource()
|
||||
ds3 = MemorySink()
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
cds.add_data_sources([ds1, ds2, ds1, ds3])
|
||||
assert str(excinfo.value) == ("DataSource (to be added) is not of type "
|
||||
"stix2.DataSource. DataSource type is '<class 'stix2.datastore.memory.MemorySink'>'")
|
||||
|
||||
cds.add_data_sources([ds1, ds2, ds1])
|
||||
|
||||
assert len(cds.get_all_data_sources()) == 2
|
||||
|
||||
cds.remove_data_sources([ds1.id, ds2.id])
|
||||
|
||||
assert len(cds.get_all_data_sources()) == 0
|
||||
|
||||
|
||||
def test_composite_datasource_operations(stix_objs1, stix_objs2):
|
||||
BUNDLE1 = dict(id="bundle--%s" % make_id(),
|
||||
objects=stix_objs1,
|
||||
spec_version="2.0",
|
||||
type="bundle")
|
||||
cds1 = CompositeDataSource()
|
||||
ds1_1 = MemorySource(stix_data=BUNDLE1)
|
||||
ds1_2 = MemorySource(stix_data=stix_objs2)
|
||||
|
||||
cds2 = CompositeDataSource()
|
||||
ds2_1 = MemorySource(stix_data=BUNDLE1)
|
||||
ds2_2 = MemorySource(stix_data=stix_objs2)
|
||||
|
||||
cds1.add_data_sources([ds1_1, ds1_2])
|
||||
cds2.add_data_sources([ds2_1, ds2_2])
|
||||
|
||||
indicators = cds1.all_versions("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
|
||||
# In STIX_OBJS2 changed the 'modified' property to a later time...
|
||||
assert len(indicators) == 2
|
||||
|
||||
cds1.add_data_sources([cds2])
|
||||
|
||||
indicator = cds1.get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
|
||||
assert indicator["id"] == "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f"
|
||||
assert indicator["modified"] == "2017-01-31T13:49:53.935Z"
|
||||
assert indicator["type"] == "indicator"
|
||||
|
||||
query1 = [
|
||||
Filter("type", "=", "indicator")
|
||||
]
|
||||
|
||||
query2 = [
|
||||
Filter("valid_from", "=", "2017-01-27T13:49:53.935382Z")
|
||||
]
|
||||
|
||||
cds1.filters.add(query2)
|
||||
|
||||
results = cds1.query(query1)
|
||||
|
||||
# STIX_OBJS2 has indicator with later time, one with different id, one with
|
||||
# original time in STIX_OBJS1
|
||||
assert len(results) == 3
|
||||
|
||||
indicator = cds1.get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
|
||||
assert indicator["id"] == "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f"
|
||||
assert indicator["modified"] == "2017-01-31T13:49:53.935Z"
|
||||
assert indicator["type"] == "indicator"
|
||||
|
||||
# There is only one indicator with different ID. Since we use the same data
|
||||
# when deduplicated, only two indicators (one with different modified).
|
||||
results = cds1.all_versions("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert len(results) == 2
|
||||
|
||||
# Since we have filters already associated with our CompositeSource providing
|
||||
# nothing returns the same as cds1.query(query1) (the associated query is query2)
|
||||
results = cds1.query([])
|
||||
assert len(results) == 3
|
|
@ -0,0 +1,390 @@
|
|||
import json
|
||||
|
||||
from medallion.filters.basic_filter import BasicFilter
|
||||
import pytest
|
||||
from requests.models import Response
|
||||
from taxii2client import Collection, _filter_kwargs_to_query_params
|
||||
|
||||
from stix2 import (Bundle, TAXIICollectionSink, TAXIICollectionSource,
|
||||
TAXIICollectionStore, ThreatActor)
|
||||
from stix2.datastore import DataSourceError
|
||||
from stix2.datastore.filters import Filter
|
||||
|
||||
COLLECTION_URL = 'https://example.com/api1/collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/'
|
||||
|
||||
|
||||
class MockTAXIICollectionEndpoint(Collection):
|
||||
"""Mock for taxii2_client.TAXIIClient"""
|
||||
|
||||
def __init__(self, url, **kwargs):
|
||||
super(MockTAXIICollectionEndpoint, self).__init__(url, **kwargs)
|
||||
self.objects = []
|
||||
|
||||
def add_objects(self, bundle):
|
||||
self._verify_can_write()
|
||||
if isinstance(bundle, str):
|
||||
bundle = json.loads(bundle)
|
||||
for object in bundle.get("objects", []):
|
||||
self.objects.append(object)
|
||||
|
||||
def get_objects(self, **filter_kwargs):
|
||||
self._verify_can_read()
|
||||
query_params = _filter_kwargs_to_query_params(filter_kwargs)
|
||||
if not isinstance(query_params, dict):
|
||||
query_params = json.loads(query_params)
|
||||
full_filter = BasicFilter(query_params or {})
|
||||
objs = full_filter.process_filter(
|
||||
self.objects,
|
||||
("id", "type", "version"),
|
||||
[]
|
||||
)
|
||||
if objs:
|
||||
return Bundle(objects=objs)
|
||||
else:
|
||||
resp = Response()
|
||||
resp.status_code = 404
|
||||
resp.raise_for_status()
|
||||
|
||||
def get_object(self, id, version=None):
|
||||
self._verify_can_read()
|
||||
query_params = None
|
||||
if version:
|
||||
query_params = _filter_kwargs_to_query_params({"version": version})
|
||||
if query_params:
|
||||
query_params = json.loads(query_params)
|
||||
full_filter = BasicFilter(query_params or {})
|
||||
objs = full_filter.process_filter(
|
||||
self.objects,
|
||||
("version",),
|
||||
[]
|
||||
)
|
||||
if objs:
|
||||
return Bundle(objects=objs)
|
||||
else:
|
||||
resp = Response()
|
||||
resp.status_code = 404
|
||||
resp.raise_for_status()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def collection(stix_objs1):
|
||||
mock = MockTAXIICollectionEndpoint(COLLECTION_URL, **{
|
||||
"id": "91a7b528-80eb-42ed-a74d-c6fbd5a26116",
|
||||
"title": "Writable Collection",
|
||||
"description": "This collection is a dropbox for submitting indicators",
|
||||
"can_read": True,
|
||||
"can_write": True,
|
||||
"media_types": [
|
||||
"application/vnd.oasis.stix+json; version=2.0"
|
||||
]
|
||||
})
|
||||
|
||||
mock.objects.extend(stix_objs1)
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def collection_no_rw_access(stix_objs1):
|
||||
mock = MockTAXIICollectionEndpoint(COLLECTION_URL, **{
|
||||
"id": "91a7b528-80eb-42ed-a74d-c6fbd5a26116",
|
||||
"title": "Not writeable or readable Collection",
|
||||
"description": "This collection is a dropbox for submitting indicators",
|
||||
"can_read": False,
|
||||
"can_write": False,
|
||||
"media_types": [
|
||||
"application/vnd.oasis.stix+json; version=2.0"
|
||||
]
|
||||
})
|
||||
|
||||
mock.objects.extend(stix_objs1)
|
||||
return mock
|
||||
|
||||
|
||||
def test_ds_taxii(collection):
|
||||
ds = TAXIICollectionSource(collection)
|
||||
assert ds.collection is not None
|
||||
|
||||
|
||||
def test_add_stix2_object(collection):
|
||||
tc_sink = TAXIICollectionSink(collection)
|
||||
|
||||
# create new STIX threat-actor
|
||||
ta = ThreatActor(name="Teddy Bear",
|
||||
labels=["nation-state"],
|
||||
sophistication="innovator",
|
||||
resource_level="government",
|
||||
goals=[
|
||||
"compromising environment NGOs",
|
||||
"water-hole attacks geared towards energy sector",
|
||||
])
|
||||
|
||||
tc_sink.add(ta)
|
||||
|
||||
|
||||
def test_add_stix2_with_custom_object(collection):
|
||||
tc_sink = TAXIICollectionStore(collection, allow_custom=True)
|
||||
|
||||
# create new STIX threat-actor
|
||||
ta = ThreatActor(name="Teddy Bear",
|
||||
labels=["nation-state"],
|
||||
sophistication="innovator",
|
||||
resource_level="government",
|
||||
goals=[
|
||||
"compromising environment NGOs",
|
||||
"water-hole attacks geared towards energy sector",
|
||||
],
|
||||
foo="bar",
|
||||
allow_custom=True)
|
||||
|
||||
tc_sink.add(ta)
|
||||
|
||||
|
||||
def test_add_list_object(collection, indicator):
|
||||
tc_sink = TAXIICollectionSink(collection)
|
||||
|
||||
# create new STIX threat-actor
|
||||
ta = ThreatActor(name="Teddy Bear",
|
||||
labels=["nation-state"],
|
||||
sophistication="innovator",
|
||||
resource_level="government",
|
||||
goals=[
|
||||
"compromising environment NGOs",
|
||||
"water-hole attacks geared towards energy sector",
|
||||
])
|
||||
|
||||
tc_sink.add([ta, indicator])
|
||||
|
||||
|
||||
def test_add_stix2_bundle_object(collection):
|
||||
tc_sink = TAXIICollectionSink(collection)
|
||||
|
||||
# create new STIX threat-actor
|
||||
ta = ThreatActor(name="Teddy Bear",
|
||||
labels=["nation-state"],
|
||||
sophistication="innovator",
|
||||
resource_level="government",
|
||||
goals=[
|
||||
"compromising environment NGOs",
|
||||
"water-hole attacks geared towards energy sector",
|
||||
])
|
||||
|
||||
tc_sink.add(Bundle(objects=[ta]))
|
||||
|
||||
|
||||
def test_add_str_object(collection):
|
||||
tc_sink = TAXIICollectionSink(collection)
|
||||
|
||||
# create new STIX threat-actor
|
||||
ta = """{
|
||||
"type": "threat-actor",
|
||||
"id": "threat-actor--eddff64f-feb1-4469-b07c-499a73c96415",
|
||||
"created": "2018-04-23T16:40:50.847Z",
|
||||
"modified": "2018-04-23T16:40:50.847Z",
|
||||
"name": "Teddy Bear",
|
||||
"goals": [
|
||||
"compromising environment NGOs",
|
||||
"water-hole attacks geared towards energy sector"
|
||||
],
|
||||
"sophistication": "innovator",
|
||||
"resource_level": "government",
|
||||
"labels": [
|
||||
"nation-state"
|
||||
]
|
||||
}"""
|
||||
|
||||
tc_sink.add(ta)
|
||||
|
||||
|
||||
def test_add_dict_object(collection):
|
||||
tc_sink = TAXIICollectionSink(collection)
|
||||
|
||||
ta = {
|
||||
"type": "threat-actor",
|
||||
"id": "threat-actor--eddff64f-feb1-4469-b07c-499a73c96415",
|
||||
"created": "2018-04-23T16:40:50.847Z",
|
||||
"modified": "2018-04-23T16:40:50.847Z",
|
||||
"name": "Teddy Bear",
|
||||
"goals": [
|
||||
"compromising environment NGOs",
|
||||
"water-hole attacks geared towards energy sector"
|
||||
],
|
||||
"sophistication": "innovator",
|
||||
"resource_level": "government",
|
||||
"labels": [
|
||||
"nation-state"
|
||||
]
|
||||
}
|
||||
|
||||
tc_sink.add(ta)
|
||||
|
||||
|
||||
def test_add_dict_bundle_object(collection):
|
||||
tc_sink = TAXIICollectionSink(collection)
|
||||
|
||||
ta = {
|
||||
"type": "bundle",
|
||||
"id": "bundle--860ccc8d-56c9-4fda-9384-84276fb52fb1",
|
||||
"objects": [
|
||||
{
|
||||
"type": "threat-actor",
|
||||
"id": "threat-actor--dc5a2f41-f76e-425a-81fe-33afc7aabd75",
|
||||
"created": "2018-04-23T18:45:11.390Z",
|
||||
"modified": "2018-04-23T18:45:11.390Z",
|
||||
"name": "Teddy Bear",
|
||||
"goals": [
|
||||
"compromising environment NGOs",
|
||||
"water-hole attacks geared towards energy sector"
|
||||
],
|
||||
"sophistication": "innovator",
|
||||
"resource_level": "government",
|
||||
"labels": [
|
||||
"nation-state"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
tc_sink.add(ta)
|
||||
|
||||
|
||||
def test_get_stix2_object(collection):
|
||||
tc_sink = TAXIICollectionSource(collection)
|
||||
|
||||
objects = tc_sink.get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
|
||||
assert objects
|
||||
|
||||
|
||||
def test_parse_taxii_filters(collection):
|
||||
query = [
|
||||
Filter("added_after", "=", "2016-02-01T00:00:01.000Z"),
|
||||
Filter("id", "=", "taxii stix object ID"),
|
||||
Filter("type", "=", "taxii stix object ID"),
|
||||
Filter("version", "=", "first"),
|
||||
Filter("created_by_ref", "=", "Bane"),
|
||||
]
|
||||
|
||||
taxii_filters_expected = [
|
||||
Filter("added_after", "=", "2016-02-01T00:00:01.000Z"),
|
||||
Filter("id", "=", "taxii stix object ID"),
|
||||
Filter("type", "=", "taxii stix object ID"),
|
||||
Filter("version", "=", "first")
|
||||
]
|
||||
|
||||
ds = TAXIICollectionSource(collection)
|
||||
|
||||
taxii_filters = ds._parse_taxii_filters(query)
|
||||
|
||||
assert taxii_filters == taxii_filters_expected
|
||||
|
||||
|
||||
def test_add_get_remove_filter(collection):
|
||||
ds = TAXIICollectionSource(collection)
|
||||
|
||||
# First 3 filters are valid, remaining properties are erroneous in some way
|
||||
valid_filters = [
|
||||
Filter('type', '=', 'malware'),
|
||||
Filter('id', '!=', 'stix object id'),
|
||||
Filter('labels', 'in', ["heartbleed", "malicious-activity"]),
|
||||
]
|
||||
|
||||
assert len(ds.filters) == 0
|
||||
|
||||
ds.filters.add(valid_filters[0])
|
||||
assert len(ds.filters) == 1
|
||||
|
||||
# Addin the same filter again will have no effect since `filters` acts
|
||||
# like a set
|
||||
ds.filters.add(valid_filters[0])
|
||||
assert len(ds.filters) == 1
|
||||
|
||||
ds.filters.add(valid_filters[1])
|
||||
assert len(ds.filters) == 2
|
||||
|
||||
ds.filters.add(valid_filters[2])
|
||||
assert len(ds.filters) == 3
|
||||
|
||||
assert valid_filters == [f for f in ds.filters]
|
||||
|
||||
# remove
|
||||
ds.filters.remove(valid_filters[0])
|
||||
|
||||
assert len(ds.filters) == 2
|
||||
|
||||
ds.filters.add(valid_filters)
|
||||
|
||||
|
||||
def test_get_all_versions(collection):
|
||||
ds = TAXIICollectionStore(collection)
|
||||
|
||||
indicators = ds.all_versions('indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f')
|
||||
# There are 3 indicators but 2 share the same 'modified' timestamp
|
||||
assert len(indicators) == 2
|
||||
|
||||
|
||||
def test_can_read_error(collection_no_rw_access):
|
||||
"""create a TAXIICOllectionSource with a taxii2client.Collection
|
||||
instance that does not have read access, check ValueError exception is raised"""
|
||||
|
||||
with pytest.raises(DataSourceError) as excinfo:
|
||||
TAXIICollectionSource(collection_no_rw_access)
|
||||
assert "Collection object provided does not have read access" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_can_write_error(collection_no_rw_access):
|
||||
"""create a TAXIICOllectionSink with a taxii2client.Collection
|
||||
instance that does not have write access, check ValueError exception is raised"""
|
||||
|
||||
with pytest.raises(DataSourceError) as excinfo:
|
||||
TAXIICollectionSink(collection_no_rw_access)
|
||||
assert "Collection object provided does not have write access" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_get_404():
|
||||
"""a TAXIICollectionSource.get() call that receives an HTTP 404 response
|
||||
code from the taxii2client should be be returned as None.
|
||||
|
||||
TAXII spec states that a TAXII server can return a 404 for nonexistent
|
||||
resources or lack of access. Decided that None is acceptable reponse
|
||||
to imply that state of the TAXII endpoint.
|
||||
"""
|
||||
|
||||
class TAXIICollection404():
|
||||
can_read = True
|
||||
|
||||
def get_object(self, id, version=None):
|
||||
resp = Response()
|
||||
resp.status_code = 404
|
||||
resp.raise_for_status()
|
||||
|
||||
ds = TAXIICollectionSource(TAXIICollection404())
|
||||
|
||||
# this will raise 404 from mock TAXII Client but TAXIICollectionStore
|
||||
# should handle gracefully and return None
|
||||
stix_obj = ds.get("indicator--1")
|
||||
assert stix_obj is None
|
||||
|
||||
|
||||
def test_all_versions_404(collection):
|
||||
""" a TAXIICollectionSource.all_version() call that recieves an HTTP 404
|
||||
response code from the taxii2client should be returned as an exception"""
|
||||
|
||||
ds = TAXIICollectionStore(collection)
|
||||
|
||||
with pytest.raises(DataSourceError) as excinfo:
|
||||
ds.all_versions("indicator--1")
|
||||
assert "are either not found or access is denied" in str(excinfo.value)
|
||||
assert "404" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_query_404(collection):
|
||||
""" a TAXIICollectionSource.query() call that recieves an HTTP 404
|
||||
response code from the taxii2client should be returned as an exception"""
|
||||
|
||||
ds = TAXIICollectionStore(collection)
|
||||
query = [Filter("type", "=", "malware")]
|
||||
|
||||
with pytest.raises(DataSourceError) as excinfo:
|
||||
ds.query(query=query)
|
||||
assert "are either not found or access is denied" in str(excinfo.value)
|
||||
assert "404" in str(excinfo.value)
|
|
@ -0,0 +1,352 @@
|
|||
import pytest
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import (CAMPAIGN_ID, CAMPAIGN_KWARGS, FAKE_TIME, IDENTITY_ID,
|
||||
IDENTITY_KWARGS, INDICATOR_ID, INDICATOR_KWARGS,
|
||||
MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ds():
|
||||
cam = stix2.Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
idy = stix2.Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
ind = stix2.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
mal = stix2.Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
rel1 = stix2.Relationship(ind, 'indicates', mal, id=RELATIONSHIP_IDS[0])
|
||||
rel2 = stix2.Relationship(mal, 'targets', idy, id=RELATIONSHIP_IDS[1])
|
||||
rel3 = stix2.Relationship(cam, 'uses', mal, id=RELATIONSHIP_IDS[2])
|
||||
stix_objs = [cam, idy, ind, mal, rel1, rel2, rel3]
|
||||
yield stix2.MemoryStore(stix_objs)
|
||||
|
||||
|
||||
def test_object_factory_created_by_ref_str():
|
||||
factory = stix2.ObjectFactory(created_by_ref=IDENTITY_ID)
|
||||
ind = factory.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
assert ind.created_by_ref == IDENTITY_ID
|
||||
|
||||
|
||||
def test_object_factory_created_by_ref_obj():
|
||||
id_obj = stix2.Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
factory = stix2.ObjectFactory(created_by_ref=id_obj)
|
||||
ind = factory.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
assert ind.created_by_ref == IDENTITY_ID
|
||||
|
||||
|
||||
def test_object_factory_override_default():
|
||||
factory = stix2.ObjectFactory(created_by_ref=IDENTITY_ID)
|
||||
new_id = "identity--983b3172-44fe-4a80-8091-eb8098841fe8"
|
||||
ind = factory.create(stix2.Indicator, created_by_ref=new_id, **INDICATOR_KWARGS)
|
||||
assert ind.created_by_ref == new_id
|
||||
|
||||
|
||||
def test_object_factory_created():
|
||||
factory = stix2.ObjectFactory(created=FAKE_TIME)
|
||||
ind = factory.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
assert ind.created == FAKE_TIME
|
||||
assert ind.modified == FAKE_TIME
|
||||
|
||||
|
||||
def test_object_factory_external_reference():
|
||||
ext_ref = stix2.ExternalReference(source_name="ACME Threat Intel",
|
||||
description="Threat report")
|
||||
factory = stix2.ObjectFactory(external_references=ext_ref)
|
||||
ind = factory.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
assert ind.external_references[0].source_name == "ACME Threat Intel"
|
||||
assert ind.external_references[0].description == "Threat report"
|
||||
|
||||
ind2 = factory.create(stix2.Indicator, external_references=None, **INDICATOR_KWARGS)
|
||||
assert 'external_references' not in ind2
|
||||
|
||||
|
||||
def test_object_factory_obj_markings():
|
||||
stmt_marking = stix2.StatementMarking("Copyright 2016, Example Corp")
|
||||
mark_def = stix2.MarkingDefinition(definition_type="statement",
|
||||
definition=stmt_marking)
|
||||
factory = stix2.ObjectFactory(object_marking_refs=[mark_def, stix2.TLP_AMBER])
|
||||
ind = factory.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
assert mark_def.id in ind.object_marking_refs
|
||||
assert stix2.TLP_AMBER.id in ind.object_marking_refs
|
||||
|
||||
factory = stix2.ObjectFactory(object_marking_refs=stix2.TLP_RED)
|
||||
ind = factory.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
assert stix2.TLP_RED.id in ind.object_marking_refs
|
||||
|
||||
|
||||
def test_object_factory_list_append():
|
||||
ext_ref = stix2.ExternalReference(source_name="ACME Threat Intel",
|
||||
description="Threat report from ACME")
|
||||
ext_ref2 = stix2.ExternalReference(source_name="Yet Another Threat Report",
|
||||
description="Threat report from YATR")
|
||||
ext_ref3 = stix2.ExternalReference(source_name="Threat Report #3",
|
||||
description="One more threat report")
|
||||
factory = stix2.ObjectFactory(external_references=ext_ref)
|
||||
ind = factory.create(stix2.Indicator, external_references=ext_ref2, **INDICATOR_KWARGS)
|
||||
assert ind.external_references[1].source_name == "Yet Another Threat Report"
|
||||
|
||||
ind = factory.create(stix2.Indicator, external_references=[ext_ref2, ext_ref3], **INDICATOR_KWARGS)
|
||||
assert ind.external_references[2].source_name == "Threat Report #3"
|
||||
|
||||
|
||||
def test_object_factory_list_replace():
|
||||
ext_ref = stix2.ExternalReference(source_name="ACME Threat Intel",
|
||||
description="Threat report from ACME")
|
||||
ext_ref2 = stix2.ExternalReference(source_name="Yet Another Threat Report",
|
||||
description="Threat report from YATR")
|
||||
factory = stix2.ObjectFactory(external_references=ext_ref, list_append=False)
|
||||
ind = factory.create(stix2.Indicator, external_references=ext_ref2, **INDICATOR_KWARGS)
|
||||
assert len(ind.external_references) == 1
|
||||
assert ind.external_references[0].source_name == "Yet Another Threat Report"
|
||||
|
||||
|
||||
def test_environment_functions():
|
||||
env = stix2.Environment(stix2.ObjectFactory(created_by_ref=IDENTITY_ID),
|
||||
stix2.MemoryStore())
|
||||
|
||||
# Create a STIX object
|
||||
ind = env.create(stix2.Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
assert ind.created_by_ref == IDENTITY_ID
|
||||
|
||||
# Add objects to datastore
|
||||
ind2 = ind.new_version(labels=['benign'])
|
||||
env.add([ind, ind2])
|
||||
|
||||
# Get both versions of the object
|
||||
resp = env.all_versions(INDICATOR_ID)
|
||||
assert len(resp) == 1 # should be 2, but MemoryStore only keeps 1 version of objects
|
||||
|
||||
# Get just the most recent version of the object
|
||||
resp = env.get(INDICATOR_ID)
|
||||
assert resp['labels'][0] == 'benign'
|
||||
|
||||
# Search on something other than id
|
||||
query = [stix2.Filter('type', '=', 'vulnerability')]
|
||||
resp = env.query(query)
|
||||
assert len(resp) == 0
|
||||
|
||||
# See different results after adding filters to the environment
|
||||
env.add_filters([stix2.Filter('type', '=', 'indicator'),
|
||||
stix2.Filter('created_by_ref', '=', IDENTITY_ID)])
|
||||
env.add_filter(stix2.Filter('labels', '=', 'benign')) # should be 'malicious-activity'
|
||||
resp = env.get(INDICATOR_ID)
|
||||
assert resp['labels'][0] == 'benign' # should be 'malicious-activity'
|
||||
|
||||
|
||||
def test_environment_source_and_sink():
|
||||
ind = stix2.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
env = stix2.Environment(source=stix2.MemorySource([ind]), sink=stix2.MemorySink([ind]))
|
||||
assert env.get(INDICATOR_ID).labels[0] == 'malicious-activity'
|
||||
|
||||
|
||||
def test_environment_datastore_and_sink():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.Environment(factory=stix2.ObjectFactory(),
|
||||
store=stix2.MemoryStore(), sink=stix2.MemorySink)
|
||||
assert 'Data store already provided' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_environment_no_datastore():
|
||||
env = stix2.Environment(factory=stix2.ObjectFactory())
|
||||
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
env.add(stix2.Indicator(**INDICATOR_KWARGS))
|
||||
assert 'Environment has no data sink to put objects in' in str(excinfo.value)
|
||||
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
env.get(INDICATOR_ID)
|
||||
assert 'Environment has no data source' in str(excinfo.value)
|
||||
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
env.all_versions(INDICATOR_ID)
|
||||
assert 'Environment has no data source' in str(excinfo.value)
|
||||
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
env.query(INDICATOR_ID)
|
||||
assert 'Environment has no data source' in str(excinfo.value)
|
||||
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
env.relationships(INDICATOR_ID)
|
||||
assert 'Environment has no data source' in str(excinfo.value)
|
||||
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
env.related_to(INDICATOR_ID)
|
||||
assert 'Environment has no data source' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_environment_add_filters():
|
||||
env = stix2.Environment(factory=stix2.ObjectFactory())
|
||||
env.add_filters([INDICATOR_ID])
|
||||
env.add_filter(INDICATOR_ID)
|
||||
|
||||
|
||||
def test_environment_datastore_and_no_object_factory():
|
||||
# Uses a default object factory
|
||||
env = stix2.Environment(store=stix2.MemoryStore())
|
||||
ind = env.create(stix2.Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
assert ind.id == INDICATOR_ID
|
||||
|
||||
|
||||
def test_parse_malware():
|
||||
env = stix2.Environment()
|
||||
data = """{
|
||||
"type": "malware",
|
||||
"spec_version": "2.1",
|
||||
"id": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Cryptolocker",
|
||||
"labels": [
|
||||
"ransomware"
|
||||
],
|
||||
"is_family": false
|
||||
}"""
|
||||
mal = env.parse(data)
|
||||
|
||||
assert mal.type == 'malware'
|
||||
assert mal.id == MALWARE_ID
|
||||
assert mal.created == FAKE_TIME
|
||||
assert mal.modified == FAKE_TIME
|
||||
assert mal.labels == ['ransomware']
|
||||
assert mal.name == "Cryptolocker"
|
||||
|
||||
|
||||
def test_creator_of():
|
||||
identity = stix2.Identity(**IDENTITY_KWARGS)
|
||||
factory = stix2.ObjectFactory(created_by_ref=identity.id)
|
||||
env = stix2.Environment(store=stix2.MemoryStore(), factory=factory)
|
||||
env.add(identity)
|
||||
|
||||
ind = env.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
creator = env.creator_of(ind)
|
||||
assert creator is identity
|
||||
|
||||
|
||||
def test_creator_of_no_datasource():
|
||||
identity = stix2.Identity(**IDENTITY_KWARGS)
|
||||
factory = stix2.ObjectFactory(created_by_ref=identity.id)
|
||||
env = stix2.Environment(factory=factory)
|
||||
|
||||
ind = env.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
with pytest.raises(AttributeError) as excinfo:
|
||||
env.creator_of(ind)
|
||||
assert 'Environment has no data source' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_creator_of_not_found():
|
||||
identity = stix2.Identity(**IDENTITY_KWARGS)
|
||||
factory = stix2.ObjectFactory(created_by_ref=identity.id)
|
||||
env = stix2.Environment(store=stix2.MemoryStore(), factory=factory)
|
||||
|
||||
ind = env.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
creator = env.creator_of(ind)
|
||||
assert creator is None
|
||||
|
||||
|
||||
def test_creator_of_no_created_by_ref():
|
||||
env = stix2.Environment(store=stix2.MemoryStore())
|
||||
ind = env.create(stix2.Indicator, **INDICATOR_KWARGS)
|
||||
creator = env.creator_of(ind)
|
||||
assert creator is None
|
||||
|
||||
|
||||
def test_relationships(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
mal = env.get(MALWARE_ID)
|
||||
resp = env.relationships(mal)
|
||||
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[0] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[1] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_no_id(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
mal = {
|
||||
"type": "malware",
|
||||
"name": "some variant"
|
||||
}
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
env.relationships(mal)
|
||||
assert "object has no 'id' property" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_relationships_by_type(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
mal = env.get(MALWARE_ID)
|
||||
resp = env.relationships(mal, relationship_type='indicates')
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0]['id'] == RELATIONSHIP_IDS[0]
|
||||
|
||||
|
||||
def test_relationships_by_source(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
resp = env.relationships(MALWARE_ID, source_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0]['id'] == RELATIONSHIP_IDS[1]
|
||||
|
||||
|
||||
def test_relationships_by_target(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
resp = env.relationships(MALWARE_ID, target_only=True)
|
||||
|
||||
assert len(resp) == 2
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[0] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_by_target_and_type(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
resp = env.relationships(MALWARE_ID, relationship_type='uses', target_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_by_target_and_source(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
env.relationships(MALWARE_ID, target_only=True, source_only=True)
|
||||
|
||||
assert 'not both' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_related_to(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
mal = env.get(MALWARE_ID)
|
||||
resp = env.related_to(mal)
|
||||
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
||||
assert any(x['id'] == IDENTITY_ID for x in resp)
|
||||
|
||||
|
||||
def test_related_to_no_id(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
mal = {
|
||||
"type": "malware",
|
||||
"name": "some variant"
|
||||
}
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
env.related_to(mal)
|
||||
assert "object has no 'id' property" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_related_to_by_source(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
resp = env.related_to(MALWARE_ID, source_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0]['id'] == IDENTITY_ID
|
||||
|
||||
|
||||
def test_related_to_by_target(ds):
|
||||
env = stix2.Environment(store=ds)
|
||||
resp = env.related_to(MALWARE_ID, target_only=True)
|
||||
|
||||
assert len(resp) == 2
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
|
@ -0,0 +1,122 @@
|
|||
"""Tests for stix.ExternalReference"""
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
|
||||
VERIS = """{
|
||||
"source_name": "veris",
|
||||
"url": "https://github.com/vz-risk/VCDB/blob/master/data/json/0001AA7F-C601-424A-B2B8-BE6C9F5164E7.json",
|
||||
"hashes": {
|
||||
"SHA-256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"
|
||||
},
|
||||
"external_id": "0001AA7F-C601-424A-B2B8-BE6C9F5164E7"
|
||||
}"""
|
||||
|
||||
|
||||
def test_external_reference_veris():
|
||||
ref = stix2.ExternalReference(
|
||||
source_name="veris",
|
||||
external_id="0001AA7F-C601-424A-B2B8-BE6C9F5164E7",
|
||||
hashes={
|
||||
"SHA-256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"
|
||||
},
|
||||
url="https://github.com/vz-risk/VCDB/blob/master/data/json/0001AA7F-C601-424A-B2B8-BE6C9F5164E7.json",
|
||||
)
|
||||
|
||||
assert str(ref) == VERIS
|
||||
|
||||
|
||||
CAPEC = """{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-550"
|
||||
}"""
|
||||
|
||||
|
||||
def test_external_reference_capec():
|
||||
ref = stix2.ExternalReference(
|
||||
source_name="capec",
|
||||
external_id="CAPEC-550",
|
||||
)
|
||||
|
||||
assert str(ref) == CAPEC
|
||||
assert re.match("ExternalReference\\(source_name=u?'capec', external_id=u?'CAPEC-550'\\)", repr(ref))
|
||||
|
||||
|
||||
CAPEC_URL = """{
|
||||
"source_name": "capec",
|
||||
"url": "http://capec.mitre.org/data/definitions/550.html",
|
||||
"external_id": "CAPEC-550"
|
||||
}"""
|
||||
|
||||
|
||||
def test_external_reference_capec_url():
|
||||
ref = stix2.ExternalReference(
|
||||
source_name="capec",
|
||||
external_id="CAPEC-550",
|
||||
url="http://capec.mitre.org/data/definitions/550.html",
|
||||
)
|
||||
|
||||
assert str(ref) == CAPEC_URL
|
||||
|
||||
|
||||
THREAT_REPORT = """{
|
||||
"source_name": "ACME Threat Intel",
|
||||
"description": "Threat report",
|
||||
"url": "http://www.example.com/threat-report.pdf"
|
||||
}"""
|
||||
|
||||
|
||||
def test_external_reference_threat_report():
|
||||
ref = stix2.ExternalReference(
|
||||
source_name="ACME Threat Intel",
|
||||
description="Threat report",
|
||||
url="http://www.example.com/threat-report.pdf",
|
||||
)
|
||||
|
||||
assert str(ref) == THREAT_REPORT
|
||||
|
||||
|
||||
BUGZILLA = """{
|
||||
"source_name": "ACME Bugzilla",
|
||||
"url": "https://www.example.com/bugs/1370",
|
||||
"external_id": "1370"
|
||||
}"""
|
||||
|
||||
|
||||
def test_external_reference_bugzilla():
|
||||
ref = stix2.ExternalReference(
|
||||
source_name="ACME Bugzilla",
|
||||
external_id="1370",
|
||||
url="https://www.example.com/bugs/1370",
|
||||
)
|
||||
|
||||
assert str(ref) == BUGZILLA
|
||||
|
||||
|
||||
OFFLINE = """{
|
||||
"source_name": "ACME Threat Intel",
|
||||
"description": "Threat report"
|
||||
}"""
|
||||
|
||||
|
||||
def test_external_reference_offline():
|
||||
ref = stix2.ExternalReference(
|
||||
source_name="ACME Threat Intel",
|
||||
description="Threat report",
|
||||
)
|
||||
|
||||
assert str(ref) == OFFLINE
|
||||
assert re.match("ExternalReference\\(source_name=u?'ACME Threat Intel', description=u?'Threat report'\\)", repr(ref))
|
||||
# Yikes! This works
|
||||
assert eval("stix2." + repr(ref)) == ref
|
||||
|
||||
|
||||
def test_external_reference_source_required():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.ExternalReference()
|
||||
|
||||
assert excinfo.value.cls == stix2.ExternalReference
|
||||
assert excinfo.value.properties == ["source_name"]
|
|
@ -0,0 +1,18 @@
|
|||
import uuid
|
||||
|
||||
from stix2 import utils
|
||||
|
||||
from .constants import FAKE_TIME
|
||||
|
||||
|
||||
def test_clock(clock):
|
||||
assert utils.STIXdatetime.now() == FAKE_TIME
|
||||
|
||||
|
||||
def test_my_uuid4_fixture(uuid4):
|
||||
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000001"
|
||||
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000002"
|
||||
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000003"
|
||||
for _ in range(256):
|
||||
uuid.uuid4()
|
||||
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000104"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,77 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import IDENTITY_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "identity",
|
||||
"spec_version": "2.1",
|
||||
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
"created": "2015-12-21T19:59:11.000Z",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "John Smith",
|
||||
"identity_class": "individual"
|
||||
}"""
|
||||
|
||||
|
||||
def test_identity_example():
|
||||
identity = stix2.Identity(
|
||||
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
created="2015-12-21T19:59:11.000Z",
|
||||
modified="2015-12-21T19:59:11.000Z",
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
)
|
||||
|
||||
assert str(identity) == EXPECTED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2015-12-21T19:59:11.000Z",
|
||||
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
"identity_class": "individual",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "John Smith",
|
||||
"spec_version": "2.1",
|
||||
"type": "identity"
|
||||
},
|
||||
])
|
||||
def test_parse_identity(data):
|
||||
identity = stix2.parse(data)
|
||||
|
||||
assert identity.type == 'identity'
|
||||
assert identity.id == IDENTITY_ID
|
||||
assert identity.created == dt.datetime(2015, 12, 21, 19, 59, 11, tzinfo=pytz.utc)
|
||||
assert identity.modified == dt.datetime(2015, 12, 21, 19, 59, 11, tzinfo=pytz.utc)
|
||||
assert identity.name == "John Smith"
|
||||
|
||||
|
||||
def test_parse_no_type():
|
||||
with pytest.raises(stix2.exceptions.ParseError):
|
||||
stix2.parse("""
|
||||
{
|
||||
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
"created": "2015-12-21T19:59:11.000Z",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "John Smith",
|
||||
"identity_class": "individual"
|
||||
}""")
|
||||
|
||||
|
||||
def test_identity_with_custom():
|
||||
identity = stix2.Identity(
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
custom_properties={'x_foo': 'bar'}
|
||||
)
|
||||
|
||||
assert identity.x_foo == "bar"
|
||||
assert "x_foo" in identity.object_properties()
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,196 @@
|
|||
import datetime as dt
|
||||
import re
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import FAKE_TIME, INDICATOR_ID, INDICATOR_KWARGS
|
||||
|
||||
EXPECTED_INDICATOR = """{
|
||||
"type": "indicator",
|
||||
"spec_version": "2.1",
|
||||
"id": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"created": "2017-01-01T00:00:01.000Z",
|
||||
"modified": "2017-01-01T00:00:01.000Z",
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"valid_from": "1970-01-01T00:00:01Z",
|
||||
"labels": [
|
||||
"malicious-activity"
|
||||
]
|
||||
}"""
|
||||
|
||||
EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
||||
type='indicator',
|
||||
spec_version='2.1',
|
||||
id='indicator--01234567-89ab-cdef-0123-456789abcdef',
|
||||
created='2017-01-01T00:00:01.000Z',
|
||||
modified='2017-01-01T00:00:01.000Z',
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
valid_from='1970-01-01T00:00:01Z',
|
||||
labels=['malicious-activity']
|
||||
""".split()) + ")"
|
||||
|
||||
|
||||
def test_indicator_with_all_required_properties():
|
||||
now = dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
|
||||
epoch = dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
|
||||
|
||||
ind = stix2.Indicator(
|
||||
type="indicator",
|
||||
id=INDICATOR_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
valid_from=epoch,
|
||||
labels=['malicious-activity'],
|
||||
)
|
||||
|
||||
assert ind.revoked is False
|
||||
assert str(ind) == EXPECTED_INDICATOR
|
||||
rep = re.sub(r"(\[|=| )u('|\"|\\\'|\\\")", r"\g<1>\g<2>", repr(ind))
|
||||
assert rep == EXPECTED_INDICATOR_REPR
|
||||
|
||||
|
||||
def test_indicator_autogenerated_properties(indicator):
|
||||
assert indicator.type == 'indicator'
|
||||
assert indicator.id == 'indicator--00000000-0000-0000-0000-000000000001'
|
||||
assert indicator.created == FAKE_TIME
|
||||
assert indicator.modified == FAKE_TIME
|
||||
assert indicator.labels == ['malicious-activity']
|
||||
assert indicator.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']"
|
||||
assert indicator.valid_from == FAKE_TIME
|
||||
|
||||
assert indicator['type'] == 'indicator'
|
||||
assert indicator['id'] == 'indicator--00000000-0000-0000-0000-000000000001'
|
||||
assert indicator['created'] == FAKE_TIME
|
||||
assert indicator['modified'] == FAKE_TIME
|
||||
assert indicator['labels'] == ['malicious-activity']
|
||||
assert indicator['pattern'] == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']"
|
||||
assert indicator['valid_from'] == FAKE_TIME
|
||||
|
||||
|
||||
def test_indicator_type_must_be_indicator():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Indicator(type='xxx', **INDICATOR_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'indicator'."
|
||||
assert str(excinfo.value) == "Invalid value for Indicator 'type': must equal 'indicator'."
|
||||
|
||||
|
||||
def test_indicator_id_must_start_with_indicator():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Indicator(id='my-prefix--', **INDICATOR_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.prop_name == "id"
|
||||
assert excinfo.value.reason == "must start with 'indicator--'."
|
||||
assert str(excinfo.value) == "Invalid value for Indicator 'id': must start with 'indicator--'."
|
||||
|
||||
|
||||
def test_indicator_required_properties():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.Indicator()
|
||||
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.properties == ["labels", "pattern"]
|
||||
assert str(excinfo.value) == "No values for required properties for Indicator: (labels, pattern)."
|
||||
|
||||
|
||||
def test_indicator_required_property_pattern():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.Indicator(labels=['malicious-activity'])
|
||||
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.properties == ["pattern"]
|
||||
|
||||
|
||||
def test_indicator_created_ref_invalid_format():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Indicator(created_by_ref='myprefix--12345678', **INDICATOR_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.prop_name == "created_by_ref"
|
||||
assert excinfo.value.reason == "must start with 'identity'."
|
||||
assert str(excinfo.value) == "Invalid value for Indicator 'created_by_ref': must start with 'identity'."
|
||||
|
||||
|
||||
def test_indicator_revoked_invalid():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Indicator(revoked='no', **INDICATOR_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.prop_name == "revoked"
|
||||
assert excinfo.value.reason == "must be a boolean value."
|
||||
|
||||
|
||||
def test_cannot_assign_to_indicator_attributes(indicator):
|
||||
with pytest.raises(stix2.exceptions.ImmutableError) as excinfo:
|
||||
indicator.valid_from = dt.datetime.now()
|
||||
|
||||
assert str(excinfo.value) == "Cannot modify 'valid_from' property in 'Indicator' after creation."
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_indicator():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.Indicator(my_custom_property="foo", **INDICATOR_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.properties == ['my_custom_property']
|
||||
assert str(excinfo.value) == "Unexpected properties for Indicator: (my_custom_property)."
|
||||
|
||||
|
||||
def test_created_modified_time_are_identical_by_default():
|
||||
"""By default, the created and modified times should be the same."""
|
||||
ind = stix2.Indicator(**INDICATOR_KWARGS)
|
||||
|
||||
assert ind.created == ind.modified
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED_INDICATOR,
|
||||
{
|
||||
"type": "indicator",
|
||||
"id": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"created": "2017-01-01T00:00:01Z",
|
||||
"modified": "2017-01-01T00:00:01Z",
|
||||
"labels": [
|
||||
"malicious-activity"
|
||||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"valid_from": "1970-01-01T00:00:01Z"
|
||||
},
|
||||
])
|
||||
def test_parse_indicator(data):
|
||||
idctr = stix2.parse(data)
|
||||
|
||||
assert idctr.type == 'indicator'
|
||||
assert idctr.id == INDICATOR_ID
|
||||
assert idctr.created == dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
|
||||
assert idctr.modified == dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
|
||||
assert idctr.valid_from == dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
|
||||
assert idctr.labels[0] == "malicious-activity"
|
||||
assert idctr.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']"
|
||||
|
||||
|
||||
def test_invalid_indicator_pattern():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Indicator(
|
||||
labels=['malicious-activity'],
|
||||
pattern="file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e'",
|
||||
)
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.prop_name == 'pattern'
|
||||
assert 'input is missing square brackets' in excinfo.value.reason
|
||||
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Indicator(
|
||||
labels=['malicious-activity'],
|
||||
pattern='[file:hashes.MD5 = "d41d8cd98f00b204e9800998ecf8427e"]',
|
||||
)
|
||||
assert excinfo.value.cls == stix2.Indicator
|
||||
assert excinfo.value.prop_name == 'pattern'
|
||||
assert 'mismatched input' in excinfo.value.reason
|
|
@ -0,0 +1,78 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import INTRUSION_SET_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "intrusion-set",
|
||||
"spec_version": "2.1",
|
||||
"id": "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Bobcat Breakin",
|
||||
"description": "Incidents usually feature a shared TTP of a bobcat being released...",
|
||||
"aliases": [
|
||||
"Zookeeper"
|
||||
],
|
||||
"goals": [
|
||||
"acquisition-theft",
|
||||
"harassment",
|
||||
"damage"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_intrusion_set_example():
|
||||
intrusion_set = stix2.IntrusionSet(
|
||||
id="intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:48.000Z",
|
||||
modified="2016-04-06T20:03:48.000Z",
|
||||
name="Bobcat Breakin",
|
||||
description="Incidents usually feature a shared TTP of a bobcat being released...",
|
||||
aliases=["Zookeeper"],
|
||||
goals=["acquisition-theft", "harassment", "damage"]
|
||||
)
|
||||
|
||||
assert str(intrusion_set) == EXPECTED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"aliases": [
|
||||
"Zookeeper"
|
||||
],
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"description": "Incidents usually feature a shared TTP of a bobcat being released...",
|
||||
"goals": [
|
||||
"acquisition-theft",
|
||||
"harassment",
|
||||
"damage"
|
||||
],
|
||||
"id": "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Bobcat Breakin",
|
||||
"spec_version": "2.1",
|
||||
"type": "intrusion-set"
|
||||
},
|
||||
])
|
||||
def test_parse_intrusion_set(data):
|
||||
intset = stix2.parse(data)
|
||||
|
||||
assert intset.type == "intrusion-set"
|
||||
assert intset.id == INTRUSION_SET_ID
|
||||
assert intset.created == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert intset.modified == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert intset.goals == ["acquisition-theft", "harassment", "damage"]
|
||||
assert intset.aliases == ["Zookeeper"]
|
||||
assert intset.description == "Incidents usually feature a shared TTP of a bobcat being released..."
|
||||
assert intset.name == "Bobcat Breakin"
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,61 @@
|
|||
"""Tests for stix.ExternalReference"""
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
|
||||
LMCO_RECON = """{
|
||||
"kill_chain_name": "lockheed-martin-cyber-kill-chain",
|
||||
"phase_name": "reconnaissance"
|
||||
}"""
|
||||
|
||||
|
||||
def test_lockheed_martin_cyber_kill_chain():
|
||||
recon = stix2.KillChainPhase(
|
||||
kill_chain_name="lockheed-martin-cyber-kill-chain",
|
||||
phase_name="reconnaissance",
|
||||
)
|
||||
|
||||
assert str(recon) == LMCO_RECON
|
||||
|
||||
|
||||
FOO_PRE_ATTACK = """{
|
||||
"kill_chain_name": "foo",
|
||||
"phase_name": "pre-attack"
|
||||
}"""
|
||||
|
||||
|
||||
def test_kill_chain_example():
|
||||
preattack = stix2.KillChainPhase(
|
||||
kill_chain_name="foo",
|
||||
phase_name="pre-attack",
|
||||
)
|
||||
|
||||
assert str(preattack) == FOO_PRE_ATTACK
|
||||
|
||||
|
||||
def test_kill_chain_required_properties():
|
||||
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.KillChainPhase()
|
||||
|
||||
assert excinfo.value.cls == stix2.KillChainPhase
|
||||
assert excinfo.value.properties == ["kill_chain_name", "phase_name"]
|
||||
|
||||
|
||||
def test_kill_chain_required_property_chain_name():
|
||||
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.KillChainPhase(phase_name="weaponization")
|
||||
|
||||
assert excinfo.value.cls == stix2.KillChainPhase
|
||||
assert excinfo.value.properties == ["kill_chain_name"]
|
||||
|
||||
|
||||
def test_kill_chain_required_property_phase_name():
|
||||
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.KillChainPhase(kill_chain_name="lockheed-martin-cyber-kill-chain")
|
||||
|
||||
assert excinfo.value.cls == stix2.KillChainPhase
|
||||
assert excinfo.value.properties == ["phase_name"]
|
|
@ -12,6 +12,7 @@ LANGUAGE_CONTENT_ID = "language-content--b86bd89f-98bb-4fa9-8cb2-9ad421da981d"
|
|||
|
||||
TEST_CAMPAIGN = """{
|
||||
"type": "campaign",
|
||||
"spec_version": "2.1",
|
||||
"id": "campaign--12a111f0-b824-4baf-a224-83b80237a094",
|
||||
"lang": "en",
|
||||
"created": "2017-02-08T21:31:22.007Z",
|
||||
|
@ -22,6 +23,7 @@ TEST_CAMPAIGN = """{
|
|||
|
||||
TEST_LANGUAGE_CONTENT = u"""{
|
||||
"type": "language-content",
|
||||
"spec_version": "2.1",
|
||||
"id": "language-content--b86bd89f-98bb-4fa9-8cb2-9ad421da981d",
|
||||
"created": "2017-02-08T21:31:22.007Z",
|
||||
"modified": "2017-02-08T21:31:22.007Z",
|
||||
|
@ -51,13 +53,13 @@ def test_language_content_campaign():
|
|||
object_ref=CAMPAIGN_ID,
|
||||
object_modified=now,
|
||||
contents={
|
||||
"de": {
|
||||
"name": "Bank Angriff 1",
|
||||
"description": "Weitere Informationen über Banküberfall"
|
||||
'de': {
|
||||
'name': 'Bank Angriff 1',
|
||||
'description': 'Weitere Informationen über Banküberfall'
|
||||
},
|
||||
"fr": {
|
||||
"name": "Attaque Bank 1",
|
||||
"description": "Plus d'informations sur la crise bancaire"
|
||||
'fr': {
|
||||
'name': 'Attaque Bank 1',
|
||||
'description': 'Plus d\'informations sur la crise bancaire'
|
||||
}
|
||||
}
|
||||
)
|
|
@ -0,0 +1,166 @@
|
|||
import datetime as dt
|
||||
import re
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS
|
||||
|
||||
EXPECTED_MALWARE = """{
|
||||
"type": "malware",
|
||||
"spec_version": "2.1",
|
||||
"id": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"name": "Cryptolocker",
|
||||
"labels": [
|
||||
"ransomware"
|
||||
],
|
||||
"is_family": false
|
||||
}"""
|
||||
|
||||
|
||||
def test_malware_with_all_required_properties():
|
||||
now = dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
|
||||
mal = stix2.Malware(
|
||||
type="malware",
|
||||
id=MALWARE_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
labels=["ransomware"],
|
||||
name="Cryptolocker",
|
||||
is_family=False
|
||||
)
|
||||
|
||||
assert str(mal) == EXPECTED_MALWARE
|
||||
|
||||
|
||||
def test_malware_autogenerated_properties(malware):
|
||||
assert malware.type == 'malware'
|
||||
assert malware.id == 'malware--00000000-0000-0000-0000-000000000001'
|
||||
assert malware.created == FAKE_TIME
|
||||
assert malware.modified == FAKE_TIME
|
||||
assert malware.labels == ['ransomware']
|
||||
assert malware.name == "Cryptolocker"
|
||||
|
||||
assert malware['type'] == 'malware'
|
||||
assert malware['id'] == 'malware--00000000-0000-0000-0000-000000000001'
|
||||
assert malware['created'] == FAKE_TIME
|
||||
assert malware['modified'] == FAKE_TIME
|
||||
assert malware['labels'] == ['ransomware']
|
||||
assert malware['name'] == "Cryptolocker"
|
||||
|
||||
|
||||
def test_malware_type_must_be_malware():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Malware(type='xxx', **MALWARE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Malware
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'malware'."
|
||||
assert str(excinfo.value) == "Invalid value for Malware 'type': must equal 'malware'."
|
||||
|
||||
|
||||
def test_malware_id_must_start_with_malware():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Malware(id='my-prefix--', **MALWARE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Malware
|
||||
assert excinfo.value.prop_name == "id"
|
||||
assert excinfo.value.reason == "must start with 'malware--'."
|
||||
assert str(excinfo.value) == "Invalid value for Malware 'id': must start with 'malware--'."
|
||||
|
||||
|
||||
def test_malware_required_properties():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.Malware()
|
||||
|
||||
assert excinfo.value.cls == stix2.Malware
|
||||
assert excinfo.value.properties == ["is_family", "labels", "name"]
|
||||
|
||||
|
||||
def test_malware_required_property_name():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.Malware(labels=['ransomware'], is_family=False)
|
||||
|
||||
assert excinfo.value.cls == stix2.Malware
|
||||
assert excinfo.value.properties == ["name"]
|
||||
|
||||
|
||||
def test_cannot_assign_to_malware_attributes(malware):
|
||||
with pytest.raises(stix2.exceptions.ImmutableError) as excinfo:
|
||||
malware.name = "Cryptolocker II"
|
||||
|
||||
assert str(excinfo.value) == "Cannot modify 'name' property in 'Malware' after creation."
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_malware():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.Malware(my_custom_property="foo", **MALWARE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Malware
|
||||
assert excinfo.value.properties == ['my_custom_property']
|
||||
assert str(excinfo.value) == "Unexpected properties for Malware: (my_custom_property)."
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED_MALWARE,
|
||||
{
|
||||
"type": "malware",
|
||||
"spec_version": "2.1",
|
||||
"id": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"labels": ["ransomware"],
|
||||
"name": "Cryptolocker",
|
||||
"is_family": False
|
||||
},
|
||||
])
|
||||
def test_parse_malware(data):
|
||||
mal = stix2.parse(data)
|
||||
|
||||
assert mal.type == 'malware'
|
||||
assert mal.id == MALWARE_ID
|
||||
assert mal.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert mal.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert mal.labels == ['ransomware']
|
||||
assert mal.name == "Cryptolocker"
|
||||
|
||||
|
||||
def test_parse_malware_invalid_labels():
|
||||
data = re.compile('\\[.+\\]', re.DOTALL).sub('1', EXPECTED_MALWARE)
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.parse(data)
|
||||
assert "Invalid value for Malware 'labels'" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_parse_malware_kill_chain_phases():
|
||||
kill_chain = """
|
||||
"kill_chain_phases": [
|
||||
{
|
||||
"kill_chain_name": "lockheed-martin-cyber-kill-chain",
|
||||
"phase_name": "reconnaissance"
|
||||
}
|
||||
]"""
|
||||
data = EXPECTED_MALWARE.replace('malware"', 'malware",%s' % kill_chain)
|
||||
mal = stix2.parse(data)
|
||||
assert mal.kill_chain_phases[0].kill_chain_name == "lockheed-martin-cyber-kill-chain"
|
||||
assert mal.kill_chain_phases[0].phase_name == "reconnaissance"
|
||||
assert mal['kill_chain_phases'][0]['kill_chain_name'] == "lockheed-martin-cyber-kill-chain"
|
||||
assert mal['kill_chain_phases'][0]['phase_name'] == "reconnaissance"
|
||||
|
||||
|
||||
def test_parse_malware_clean_kill_chain_phases():
|
||||
kill_chain = """
|
||||
"kill_chain_phases": [
|
||||
{
|
||||
"kill_chain_name": "lockheed-martin-cyber-kill-chain",
|
||||
"phase_name": 1
|
||||
}
|
||||
]"""
|
||||
data = EXPECTED_MALWARE.replace('2.1"', '2.1",%s' % kill_chain)
|
||||
mal = stix2.parse(data)
|
||||
assert mal['kill_chain_phases'][0]['phase_name'] == "1"
|
|
@ -0,0 +1,265 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
from stix2 import TLP_WHITE
|
||||
|
||||
from .constants import MARKING_DEFINITION_ID
|
||||
|
||||
EXPECTED_TLP_MARKING_DEFINITION = """{
|
||||
"type": "marking-definition",
|
||||
"spec_version": "2.1",
|
||||
"id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
"created": "2017-01-20T00:00:00Z",
|
||||
"definition_type": "tlp",
|
||||
"definition": {
|
||||
"tlp": "white"
|
||||
}
|
||||
}"""
|
||||
|
||||
EXPECTED_STATEMENT_MARKING_DEFINITION = """{
|
||||
"type": "marking-definition",
|
||||
"spec_version": "2.1",
|
||||
"id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
"created": "2017-01-20T00:00:00Z",
|
||||
"definition_type": "statement",
|
||||
"definition": {
|
||||
"statement": "Copyright 2016, Example Corp"
|
||||
}
|
||||
}"""
|
||||
|
||||
EXPECTED_CAMPAIGN_WITH_OBJECT_MARKING = """{
|
||||
"type": "campaign",
|
||||
"spec_version": "2.1",
|
||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T20:03:00.000Z",
|
||||
"modified": "2016-04-06T20:03:00.000Z",
|
||||
"name": "Green Group Attacks Against Finance",
|
||||
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
"object_marking_refs": [
|
||||
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
|
||||
]
|
||||
}"""
|
||||
|
||||
EXPECTED_GRANULAR_MARKING = """{
|
||||
"marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
"selectors": [
|
||||
"abc",
|
||||
"abc.[23]",
|
||||
"abc.def",
|
||||
"abc.[2].efg"
|
||||
]
|
||||
}"""
|
||||
|
||||
EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
||||
"type": "campaign",
|
||||
"spec_version": "2.1",
|
||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T20:03:00.000Z",
|
||||
"modified": "2016-04-06T20:03:00.000Z",
|
||||
"name": "Green Group Attacks Against Finance",
|
||||
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
"granular_markings": [
|
||||
{
|
||||
"marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
"selectors": [
|
||||
"description"
|
||||
]
|
||||
}
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_marking_def_example_with_tlp():
|
||||
assert str(TLP_WHITE) == EXPECTED_TLP_MARKING_DEFINITION
|
||||
|
||||
|
||||
def test_marking_def_example_with_statement_positional_argument():
|
||||
marking_definition = stix2.MarkingDefinition(
|
||||
id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
created="2017-01-20T00:00:00.000Z",
|
||||
definition_type="statement",
|
||||
definition=stix2.StatementMarking(statement="Copyright 2016, Example Corp")
|
||||
)
|
||||
|
||||
assert str(marking_definition) == EXPECTED_STATEMENT_MARKING_DEFINITION
|
||||
|
||||
|
||||
def test_marking_def_example_with_kwargs_statement():
|
||||
kwargs = dict(statement="Copyright 2016, Example Corp")
|
||||
marking_definition = stix2.MarkingDefinition(
|
||||
id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
created="2017-01-20T00:00:00.000Z",
|
||||
definition_type="statement",
|
||||
definition=stix2.StatementMarking(**kwargs)
|
||||
)
|
||||
|
||||
assert str(marking_definition) == EXPECTED_STATEMENT_MARKING_DEFINITION
|
||||
|
||||
|
||||
def test_marking_def_invalid_type():
|
||||
with pytest.raises(ValueError):
|
||||
stix2.MarkingDefinition(
|
||||
id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
created="2017-01-20T00:00:00.000Z",
|
||||
definition_type="my-definition-type",
|
||||
definition=stix2.StatementMarking("Copyright 2016, Example Corp")
|
||||
)
|
||||
|
||||
|
||||
def test_campaign_with_markings_example():
|
||||
campaign = stix2.Campaign(
|
||||
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:00Z",
|
||||
modified="2016-04-06T20:03:00Z",
|
||||
name="Green Group Attacks Against Finance",
|
||||
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
object_marking_refs=TLP_WHITE
|
||||
)
|
||||
assert str(campaign) == EXPECTED_CAMPAIGN_WITH_OBJECT_MARKING
|
||||
|
||||
|
||||
def test_granular_example():
|
||||
granular_marking = stix2.GranularMarking(
|
||||
marking_ref="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
selectors=["abc", "abc.[23]", "abc.def", "abc.[2].efg"]
|
||||
)
|
||||
|
||||
assert str(granular_marking) == EXPECTED_GRANULAR_MARKING
|
||||
|
||||
|
||||
def test_granular_example_with_bad_selector():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.GranularMarking(
|
||||
marking_ref="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
selectors=["abc[0]"] # missing "."
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.GranularMarking
|
||||
assert excinfo.value.prop_name == "selectors"
|
||||
assert excinfo.value.reason == "must adhere to selector syntax."
|
||||
assert str(excinfo.value) == "Invalid value for GranularMarking 'selectors': must adhere to selector syntax."
|
||||
|
||||
|
||||
def test_campaign_with_granular_markings_example():
|
||||
campaign = stix2.Campaign(
|
||||
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:00Z",
|
||||
modified="2016-04-06T20:03:00Z",
|
||||
name="Green Group Attacks Against Finance",
|
||||
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
granular_markings=[
|
||||
stix2.GranularMarking(
|
||||
marking_ref="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
selectors=["description"])
|
||||
])
|
||||
assert str(campaign) == EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED_TLP_MARKING_DEFINITION,
|
||||
{
|
||||
"id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||
"type": "marking-definition",
|
||||
"created": "2017-01-20T00:00:00Z",
|
||||
"definition": {
|
||||
"tlp": "white"
|
||||
},
|
||||
"definition_type": "tlp",
|
||||
},
|
||||
])
|
||||
def test_parse_marking_definition(data):
|
||||
gm = stix2.parse(data)
|
||||
|
||||
assert gm.type == 'marking-definition'
|
||||
assert gm.id == MARKING_DEFINITION_ID
|
||||
assert gm.created == dt.datetime(2017, 1, 20, 0, 0, 0, tzinfo=pytz.utc)
|
||||
assert gm.definition.tlp == "white"
|
||||
assert gm.definition_type == "tlp"
|
||||
|
||||
|
||||
@stix2.common.CustomMarking('x-new-marking-type', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
('property2', stix2.properties.IntegerProperty()),
|
||||
])
|
||||
class NewMarking(object):
|
||||
def __init__(self, property2=None, **kwargs):
|
||||
if "property3" in kwargs and not isinstance(kwargs.get("property3"), int):
|
||||
raise TypeError("Must be integer!")
|
||||
|
||||
|
||||
def test_registered_custom_marking():
|
||||
nm = NewMarking(property1='something', property2=55)
|
||||
|
||||
marking_def = stix2.MarkingDefinition(
|
||||
id="marking-definition--00000000-0000-0000-0000-000000000012",
|
||||
created="2017-01-22T00:00:00.000Z",
|
||||
definition_type="x-new-marking-type",
|
||||
definition=nm
|
||||
)
|
||||
|
||||
assert marking_def.type == "marking-definition"
|
||||
assert marking_def.id == "marking-definition--00000000-0000-0000-0000-000000000012"
|
||||
assert marking_def.created == dt.datetime(2017, 1, 22, 0, 0, 0, tzinfo=pytz.utc)
|
||||
assert marking_def.definition.property1 == "something"
|
||||
assert marking_def.definition.property2 == 55
|
||||
assert marking_def.definition_type == "x-new-marking-type"
|
||||
|
||||
|
||||
def test_registered_custom_marking_raises_exception():
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
NewMarking(property1='something', property3='something', allow_custom=True)
|
||||
|
||||
assert str(excinfo.value) == "Must be integer!"
|
||||
|
||||
|
||||
def test_not_registered_marking_raises_exception():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
# Used custom object on purpose to demonstrate a not-registered marking
|
||||
@stix2.sdo.CustomObject('x-new-marking-type2', [
|
||||
('property1', stix2.properties.StringProperty(required=True)),
|
||||
('property2', stix2.properties.IntegerProperty()),
|
||||
])
|
||||
class NewObject2(object):
|
||||
def __init__(self, property2=None, **kwargs):
|
||||
return
|
||||
|
||||
no = NewObject2(property1='something', property2=55)
|
||||
|
||||
stix2.MarkingDefinition(
|
||||
id="marking-definition--00000000-0000-0000-0000-000000000012",
|
||||
created="2017-01-22T00:00:00.000Z",
|
||||
definition_type="x-new-marking-type2",
|
||||
definition=no
|
||||
)
|
||||
|
||||
assert str(excinfo.value) == "definition_type must be a valid marking type"
|
||||
|
||||
|
||||
def test_marking_wrong_type_construction():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
# Test passing wrong type for properties.
|
||||
@stix2.CustomMarking('x-new-marking-type2', ("a", "b"))
|
||||
class NewObject3(object):
|
||||
pass
|
||||
|
||||
assert str(excinfo.value) == "Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]"
|
||||
|
||||
|
||||
def test_campaign_add_markings():
|
||||
campaign = stix2.Campaign(
|
||||
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:00Z",
|
||||
modified="2016-04-06T20:03:00Z",
|
||||
name="Green Group Attacks Against Finance",
|
||||
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
)
|
||||
campaign = campaign.add_markings(TLP_WHITE)
|
||||
assert campaign.object_marking_refs[0] == TLP_WHITE.id
|
|
@ -0,0 +1,341 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
from stix2 import (Bundle, Campaign, CustomObject, Filter, Identity, Indicator,
|
||||
Malware, MemorySource, MemoryStore, Relationship,
|
||||
properties)
|
||||
from stix2.datastore import make_id
|
||||
|
||||
from .constants import (CAMPAIGN_ID, CAMPAIGN_KWARGS, IDENTITY_ID,
|
||||
IDENTITY_KWARGS, INDICATOR_ID, INDICATOR_KWARGS,
|
||||
MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS)
|
||||
|
||||
IND1 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
IND2 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
IND3 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.936Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
IND4 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
IND5 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
IND6 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-31T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
IND7 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
IND8 = {
|
||||
"created": "2017-01-27T13:49:53.935Z",
|
||||
"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",
|
||||
"labels": [
|
||||
"url-watchlist"
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z"
|
||||
}
|
||||
|
||||
STIX_OBJS2 = [IND6, IND7, IND8]
|
||||
STIX_OBJS1 = [IND1, IND2, IND3, IND4, IND5]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mem_store():
|
||||
yield MemoryStore(STIX_OBJS1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mem_source():
|
||||
yield MemorySource(STIX_OBJS1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rel_mem_store():
|
||||
cam = Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
idy = Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
ind = Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
mal = Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
rel1 = Relationship(ind, 'indicates', mal, id=RELATIONSHIP_IDS[0])
|
||||
rel2 = Relationship(mal, 'targets', idy, id=RELATIONSHIP_IDS[1])
|
||||
rel3 = Relationship(cam, 'uses', mal, id=RELATIONSHIP_IDS[2])
|
||||
stix_objs = [cam, idy, ind, mal, rel1, rel2, rel3]
|
||||
yield MemoryStore(stix_objs)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fs_mem_store(request, mem_store):
|
||||
filename = 'memory_test/mem_store.json'
|
||||
mem_store.save_to_file(filename)
|
||||
|
||||
def fin():
|
||||
# teardown, excecuted regardless of exception
|
||||
shutil.rmtree(os.path.dirname(filename))
|
||||
request.addfinalizer(fin)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def test_memory_source_get(mem_source):
|
||||
resp = mem_source.get("indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert resp["id"] == "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f"
|
||||
|
||||
|
||||
def test_memory_source_get_nonexistant_object(mem_source):
|
||||
resp = mem_source.get("tool--d81f86b8-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert resp is None
|
||||
|
||||
|
||||
def test_memory_store_all_versions(mem_store):
|
||||
# Add bundle of items to sink
|
||||
mem_store.add(dict(id="bundle--%s" % make_id(),
|
||||
objects=STIX_OBJS2,
|
||||
spec_version="2.0",
|
||||
type="bundle"))
|
||||
|
||||
resp = mem_store.all_versions("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert len(resp) == 1 # MemoryStore can only store 1 version of each object
|
||||
|
||||
|
||||
def test_memory_store_query(mem_store):
|
||||
query = [Filter('type', '=', 'malware')]
|
||||
resp = mem_store.query(query)
|
||||
assert len(resp) == 0
|
||||
|
||||
|
||||
def test_memory_store_query_single_filter(mem_store):
|
||||
query = Filter('id', '=', 'indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f')
|
||||
resp = mem_store.query(query)
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_memory_store_query_empty_query(mem_store):
|
||||
resp = mem_store.query()
|
||||
# sort since returned in random order
|
||||
resp = sorted(resp, key=lambda k: k['id'])
|
||||
assert len(resp) == 2
|
||||
assert resp[0]['id'] == 'indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f'
|
||||
assert resp[0]['modified'] == '2017-01-27T13:49:53.935Z'
|
||||
assert resp[1]['id'] == 'indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f'
|
||||
assert resp[1]['modified'] == '2017-01-27T13:49:53.936Z'
|
||||
|
||||
|
||||
def test_memory_store_query_multiple_filters(mem_store):
|
||||
mem_store.source.filters.add(Filter('type', '=', 'indicator'))
|
||||
query = Filter('id', '=', 'indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f')
|
||||
resp = mem_store.query(query)
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_memory_store_save_load_file(mem_store, fs_mem_store):
|
||||
filename = fs_mem_store # the fixture fs_mem_store yields filename where the memory store was written to
|
||||
|
||||
# STIX2 contents of mem_store have already been written to file
|
||||
# (this is done in fixture 'fs_mem_store'), so can already read-in here
|
||||
contents = open(os.path.abspath(filename)).read()
|
||||
|
||||
assert '"id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f",' in contents
|
||||
assert '"id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f",' in contents
|
||||
|
||||
mem_store2 = MemoryStore()
|
||||
mem_store2.load_from_file(filename)
|
||||
assert mem_store2.get("indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f")
|
||||
assert mem_store2.get("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f")
|
||||
|
||||
|
||||
def test_memory_store_add_invalid_object(mem_store):
|
||||
ind = ('indicator', IND1) # tuple isn't valid
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
mem_store.add(ind)
|
||||
assert 'stix_data expected to be' in str(excinfo.value)
|
||||
assert 'a python-stix2 object' in str(excinfo.value)
|
||||
assert 'JSON formatted STIX' in str(excinfo.value)
|
||||
assert 'JSON formatted STIX bundle' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_memory_store_object_with_custom_property(mem_store):
|
||||
camp = Campaign(name="Scipio Africanus",
|
||||
objective="Defeat the Carthaginians",
|
||||
x_empire="Roman",
|
||||
allow_custom=True)
|
||||
|
||||
mem_store.add(camp, True)
|
||||
|
||||
camp_r = mem_store.get(camp.id)
|
||||
assert camp_r.id == camp.id
|
||||
assert camp_r.x_empire == camp.x_empire
|
||||
|
||||
|
||||
def test_memory_store_object_with_custom_property_in_bundle(mem_store):
|
||||
camp = Campaign(name="Scipio Africanus",
|
||||
objective="Defeat the Carthaginians",
|
||||
x_empire="Roman",
|
||||
allow_custom=True)
|
||||
|
||||
bundle = Bundle(camp, allow_custom=True)
|
||||
mem_store.add(bundle, True)
|
||||
|
||||
bundle_r = mem_store.get(bundle.id)
|
||||
camp_r = bundle_r['objects'][0]
|
||||
assert camp_r.id == camp.id
|
||||
assert camp_r.x_empire == camp.x_empire
|
||||
|
||||
|
||||
def test_memory_store_custom_object(mem_store):
|
||||
@CustomObject('x-new-obj', [
|
||||
('property1', properties.StringProperty(required=True)),
|
||||
])
|
||||
class NewObj():
|
||||
pass
|
||||
|
||||
newobj = NewObj(property1='something')
|
||||
mem_store.add(newobj, True)
|
||||
|
||||
newobj_r = mem_store.get(newobj.id)
|
||||
assert newobj_r.id == newobj.id
|
||||
assert newobj_r.property1 == 'something'
|
||||
|
||||
|
||||
def test_relationships(rel_mem_store):
|
||||
mal = rel_mem_store.get(MALWARE_ID)
|
||||
resp = rel_mem_store.relationships(mal)
|
||||
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[0] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[1] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_by_type(rel_mem_store):
|
||||
mal = rel_mem_store.get(MALWARE_ID)
|
||||
resp = rel_mem_store.relationships(mal, relationship_type='indicates')
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0]['id'] == RELATIONSHIP_IDS[0]
|
||||
|
||||
|
||||
def test_relationships_by_source(rel_mem_store):
|
||||
resp = rel_mem_store.relationships(MALWARE_ID, source_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0]['id'] == RELATIONSHIP_IDS[1]
|
||||
|
||||
|
||||
def test_relationships_by_target(rel_mem_store):
|
||||
resp = rel_mem_store.relationships(MALWARE_ID, target_only=True)
|
||||
|
||||
assert len(resp) == 2
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[0] for x in resp)
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_by_target_and_type(rel_mem_store):
|
||||
resp = rel_mem_store.relationships(MALWARE_ID, relationship_type='uses', target_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert any(x['id'] == RELATIONSHIP_IDS[2] for x in resp)
|
||||
|
||||
|
||||
def test_relationships_by_target_and_source(rel_mem_store):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
rel_mem_store.relationships(MALWARE_ID, target_only=True, source_only=True)
|
||||
|
||||
assert 'not both' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_related_to(rel_mem_store):
|
||||
mal = rel_mem_store.get(MALWARE_ID)
|
||||
resp = rel_mem_store.related_to(mal)
|
||||
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
||||
assert any(x['id'] == IDENTITY_ID for x in resp)
|
||||
|
||||
|
||||
def test_related_to_by_source(rel_mem_store):
|
||||
resp = rel_mem_store.related_to(MALWARE_ID, source_only=True)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert any(x['id'] == IDENTITY_ID for x in resp)
|
||||
|
||||
|
||||
def test_related_to_by_target(rel_mem_store):
|
||||
resp = rel_mem_store.related_to(MALWARE_ID, target_only=True)
|
||||
|
||||
assert len(resp) == 2
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
|
@ -0,0 +1,552 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from stix2 import TLP_AMBER, Malware, exceptions, markings
|
||||
|
||||
from .constants import FAKE_TIME, MALWARE_ID
|
||||
from .constants import MALWARE_KWARGS as MALWARE_KWARGS_CONST
|
||||
from .constants import MARKING_IDS
|
||||
|
||||
"""Tests for the Data Markings API."""
|
||||
|
||||
MALWARE_KWARGS = MALWARE_KWARGS_CONST.copy()
|
||||
MALWARE_KWARGS.update({
|
||||
'id': MALWARE_ID,
|
||||
'created': FAKE_TIME,
|
||||
'modified': FAKE_TIME,
|
||||
})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
(
|
||||
Malware(**MALWARE_KWARGS),
|
||||
Malware(object_marking_refs=[MARKING_IDS[0]],
|
||||
**MALWARE_KWARGS),
|
||||
MARKING_IDS[0],
|
||||
),
|
||||
(
|
||||
MALWARE_KWARGS,
|
||||
dict(object_marking_refs=[MARKING_IDS[0]],
|
||||
**MALWARE_KWARGS),
|
||||
MARKING_IDS[0],
|
||||
),
|
||||
(
|
||||
Malware(**MALWARE_KWARGS),
|
||||
Malware(object_marking_refs=[TLP_AMBER.id],
|
||||
**MALWARE_KWARGS),
|
||||
TLP_AMBER,
|
||||
),
|
||||
])
|
||||
def test_add_markings_one_marking(data):
|
||||
before = data[0]
|
||||
after = data[1]
|
||||
|
||||
before = markings.add_markings(before, data[2], None)
|
||||
|
||||
for m in before["object_marking_refs"]:
|
||||
assert m in after["object_marking_refs"]
|
||||
|
||||
|
||||
def test_add_markings_multiple_marking():
|
||||
before = Malware(
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
|
||||
after = Malware(
|
||||
object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1]],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
|
||||
before = markings.add_markings(before, [MARKING_IDS[0], MARKING_IDS[1]], None)
|
||||
|
||||
for m in before["object_marking_refs"]:
|
||||
assert m in after["object_marking_refs"]
|
||||
|
||||
|
||||
def test_add_markings_combination():
|
||||
before = Malware(
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
after = Malware(
|
||||
object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1]],
|
||||
granular_markings=[
|
||||
{
|
||||
"selectors": ["labels"],
|
||||
"marking_ref": MARKING_IDS[2]
|
||||
},
|
||||
{
|
||||
"selectors": ["name"],
|
||||
"marking_ref": MARKING_IDS[3]
|
||||
}
|
||||
],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
|
||||
before = markings.add_markings(before, MARKING_IDS[0], None)
|
||||
before = markings.add_markings(before, MARKING_IDS[1], None)
|
||||
before = markings.add_markings(before, MARKING_IDS[2], "labels")
|
||||
before = markings.add_markings(before, MARKING_IDS[3], "name")
|
||||
|
||||
for m in before["granular_markings"]:
|
||||
assert m in after["granular_markings"]
|
||||
|
||||
for m in before["object_marking_refs"]:
|
||||
assert m in after["object_marking_refs"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
([""]),
|
||||
(""),
|
||||
([]),
|
||||
([MARKING_IDS[0], 456])
|
||||
])
|
||||
def test_add_markings_bad_markings(data):
|
||||
before = Malware(
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
with pytest.raises(exceptions.InvalidValueError):
|
||||
before = markings.add_markings(before, data, None)
|
||||
|
||||
assert "object_marking_refs" not in before
|
||||
|
||||
|
||||
GET_MARKINGS_TEST_DATA = \
|
||||
{
|
||||
"a": 333,
|
||||
"b": "value",
|
||||
"c": [
|
||||
17,
|
||||
"list value",
|
||||
{
|
||||
"g": "nested",
|
||||
"h": 45
|
||||
}
|
||||
],
|
||||
"x": {
|
||||
"y": [
|
||||
"hello",
|
||||
88
|
||||
],
|
||||
"z": {
|
||||
"foo1": "bar",
|
||||
"foo2": 65
|
||||
}
|
||||
},
|
||||
"object_marking_refs": ["11"],
|
||||
"granular_markings": [
|
||||
{
|
||||
"marking_ref": "1",
|
||||
"selectors": ["a"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "2",
|
||||
"selectors": ["c"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "3",
|
||||
"selectors": ["c.[1]"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "4",
|
||||
"selectors": ["c.[2]"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "5",
|
||||
"selectors": ["c.[2].g"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "6",
|
||||
"selectors": ["x"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "7",
|
||||
"selectors": ["x.y"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "8",
|
||||
"selectors": ["x.y.[1]"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "9",
|
||||
"selectors": ["x.z"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "10",
|
||||
"selectors": ["x.z.foo2"]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [GET_MARKINGS_TEST_DATA])
|
||||
def test_get_markings_object_marking(data):
|
||||
assert set(markings.get_markings(data, None)) == set(["11"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [GET_MARKINGS_TEST_DATA])
|
||||
def test_get_markings_object_and_granular_combinations(data):
|
||||
"""Test multiple combinations for inherited and descendant markings."""
|
||||
assert set(markings.get_markings(data, "a", False, False)) == set(["1"])
|
||||
assert set(markings.get_markings(data, "a", True, False)) == set(["1", "11"])
|
||||
assert set(markings.get_markings(data, "a", True, True)) == set(["1", "11"])
|
||||
assert set(markings.get_markings(data, "a", False, True)) == set(["1"])
|
||||
|
||||
assert set(markings.get_markings(data, "b", False, False)) == set([])
|
||||
assert set(markings.get_markings(data, "b", True, False)) == set(["11"])
|
||||
assert set(markings.get_markings(data, "b", True, True)) == set(["11"])
|
||||
assert set(markings.get_markings(data, "b", False, True)) == set([])
|
||||
|
||||
assert set(markings.get_markings(data, "c", False, False)) == set(["2"])
|
||||
assert set(markings.get_markings(data, "c", True, False)) == set(["2", "11"])
|
||||
assert set(markings.get_markings(data, "c", True, True)) == set(["2", "3", "4", "5", "11"])
|
||||
assert set(markings.get_markings(data, "c", False, True)) == set(["2", "3", "4", "5"])
|
||||
|
||||
assert set(markings.get_markings(data, "c.[0]", False, False)) == set([])
|
||||
assert set(markings.get_markings(data, "c.[0]", True, False)) == set(["2", "11"])
|
||||
assert set(markings.get_markings(data, "c.[0]", True, True)) == set(["2", "11"])
|
||||
assert set(markings.get_markings(data, "c.[0]", False, True)) == set([])
|
||||
|
||||
assert set(markings.get_markings(data, "c.[1]", False, False)) == set(["3"])
|
||||
assert set(markings.get_markings(data, "c.[1]", True, False)) == set(["2", "3", "11"])
|
||||
assert set(markings.get_markings(data, "c.[1]", True, True)) == set(["2", "3", "11"])
|
||||
assert set(markings.get_markings(data, "c.[1]", False, True)) == set(["3"])
|
||||
|
||||
assert set(markings.get_markings(data, "c.[2]", False, False)) == set(["4"])
|
||||
assert set(markings.get_markings(data, "c.[2]", True, False)) == set(["2", "4", "11"])
|
||||
assert set(markings.get_markings(data, "c.[2]", True, True)) == set(["2", "4", "5", "11"])
|
||||
assert set(markings.get_markings(data, "c.[2]", False, True)) == set(["4", "5"])
|
||||
|
||||
assert set(markings.get_markings(data, "c.[2].g", False, False)) == set(["5"])
|
||||
assert set(markings.get_markings(data, "c.[2].g", True, False)) == set(["2", "4", "5", "11"])
|
||||
assert set(markings.get_markings(data, "c.[2].g", True, True)) == set(["2", "4", "5", "11"])
|
||||
assert set(markings.get_markings(data, "c.[2].g", False, True)) == set(["5"])
|
||||
|
||||
assert set(markings.get_markings(data, "x", False, False)) == set(["6"])
|
||||
assert set(markings.get_markings(data, "x", True, False)) == set(["6", "11"])
|
||||
assert set(markings.get_markings(data, "x", True, True)) == set(["6", "7", "8", "9", "10", "11"])
|
||||
assert set(markings.get_markings(data, "x", False, True)) == set(["6", "7", "8", "9", "10"])
|
||||
|
||||
assert set(markings.get_markings(data, "x.y", False, False)) == set(["7"])
|
||||
assert set(markings.get_markings(data, "x.y", True, False)) == set(["6", "7", "11"])
|
||||
assert set(markings.get_markings(data, "x.y", True, True)) == set(["6", "7", "8", "11"])
|
||||
assert set(markings.get_markings(data, "x.y", False, True)) == set(["7", "8"])
|
||||
|
||||
assert set(markings.get_markings(data, "x.y.[0]", False, False)) == set([])
|
||||
assert set(markings.get_markings(data, "x.y.[0]", True, False)) == set(["6", "7", "11"])
|
||||
assert set(markings.get_markings(data, "x.y.[0]", True, True)) == set(["6", "7", "11"])
|
||||
assert set(markings.get_markings(data, "x.y.[0]", False, True)) == set([])
|
||||
|
||||
assert set(markings.get_markings(data, "x.y.[1]", False, False)) == set(["8"])
|
||||
assert set(markings.get_markings(data, "x.y.[1]", True, False)) == set(["6", "7", "8", "11"])
|
||||
assert set(markings.get_markings(data, "x.y.[1]", True, True)) == set(["6", "7", "8", "11"])
|
||||
assert set(markings.get_markings(data, "x.y.[1]", False, True)) == set(["8"])
|
||||
|
||||
assert set(markings.get_markings(data, "x.z", False, False)) == set(["9"])
|
||||
assert set(markings.get_markings(data, "x.z", True, False)) == set(["6", "9", "11"])
|
||||
assert set(markings.get_markings(data, "x.z", True, True)) == set(["6", "9", "10", "11"])
|
||||
assert set(markings.get_markings(data, "x.z", False, True)) == set(["9", "10"])
|
||||
|
||||
assert set(markings.get_markings(data, "x.z.foo1", False, False)) == set([])
|
||||
assert set(markings.get_markings(data, "x.z.foo1", True, False)) == set(["6", "9", "11"])
|
||||
assert set(markings.get_markings(data, "x.z.foo1", True, True)) == set(["6", "9", "11"])
|
||||
assert set(markings.get_markings(data, "x.z.foo1", False, True)) == set([])
|
||||
|
||||
assert set(markings.get_markings(data, "x.z.foo2", False, False)) == set(["10"])
|
||||
assert set(markings.get_markings(data, "x.z.foo2", True, False)) == set(["6", "9", "10", "11"])
|
||||
assert set(markings.get_markings(data, "x.z.foo2", True, True)) == set(["6", "9", "10", "11"])
|
||||
assert set(markings.get_markings(data, "x.z.foo2", False, True)) == set(["10"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
(
|
||||
Malware(object_marking_refs=[MARKING_IDS[0]],
|
||||
**MALWARE_KWARGS),
|
||||
Malware(**MALWARE_KWARGS),
|
||||
),
|
||||
(
|
||||
dict(object_marking_refs=[MARKING_IDS[0]],
|
||||
**MALWARE_KWARGS),
|
||||
MALWARE_KWARGS,
|
||||
),
|
||||
])
|
||||
def test_remove_markings_object_level(data):
|
||||
before = data[0]
|
||||
after = data[1]
|
||||
|
||||
before = markings.remove_markings(before, MARKING_IDS[0], None)
|
||||
|
||||
assert 'object_marking_refs' not in before
|
||||
assert 'object_marking_refs' not in after
|
||||
|
||||
modified = after['modified']
|
||||
after = markings.remove_markings(after, MARKING_IDS[0], None)
|
||||
modified == after['modified']
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
(
|
||||
Malware(object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS),
|
||||
Malware(object_marking_refs=[MARKING_IDS[1]],
|
||||
**MALWARE_KWARGS),
|
||||
[MARKING_IDS[0], MARKING_IDS[2]],
|
||||
),
|
||||
(
|
||||
dict(object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS),
|
||||
dict(object_marking_refs=[MARKING_IDS[1]],
|
||||
**MALWARE_KWARGS),
|
||||
[MARKING_IDS[0], MARKING_IDS[2]],
|
||||
),
|
||||
(
|
||||
Malware(object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], TLP_AMBER.id],
|
||||
**MALWARE_KWARGS),
|
||||
Malware(object_marking_refs=[MARKING_IDS[1]],
|
||||
**MALWARE_KWARGS),
|
||||
[MARKING_IDS[0], TLP_AMBER],
|
||||
),
|
||||
])
|
||||
def test_remove_markings_multiple(data):
|
||||
before = data[0]
|
||||
after = data[1]
|
||||
|
||||
before = markings.remove_markings(before, data[2], None)
|
||||
|
||||
assert before['object_marking_refs'] == after['object_marking_refs']
|
||||
|
||||
|
||||
def test_remove_markings_bad_markings():
|
||||
before = Malware(
|
||||
object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
markings.remove_markings(before, [MARKING_IDS[4]], None)
|
||||
assert str(excinfo.value) == "Marking ['%s'] was not found in Malware!" % MARKING_IDS[4]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
(
|
||||
Malware(object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS),
|
||||
Malware(**MALWARE_KWARGS),
|
||||
),
|
||||
(
|
||||
dict(object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS),
|
||||
MALWARE_KWARGS,
|
||||
),
|
||||
])
|
||||
def test_clear_markings(data):
|
||||
before = data[0]
|
||||
after = data[1]
|
||||
|
||||
before = markings.clear_markings(before, None)
|
||||
|
||||
assert 'object_marking_refs' not in before
|
||||
assert 'object_marking_refs' not in after
|
||||
|
||||
|
||||
def test_is_marked_object_and_granular_combinations():
|
||||
"""Test multiple combinations for inherited and descendant markings."""
|
||||
test_sdo = \
|
||||
{
|
||||
"a": 333,
|
||||
"b": "value",
|
||||
"c": [
|
||||
17,
|
||||
"list value",
|
||||
{
|
||||
"g": "nested",
|
||||
"h": 45
|
||||
}
|
||||
],
|
||||
"x": {
|
||||
"y": [
|
||||
"hello",
|
||||
88
|
||||
],
|
||||
"z": {
|
||||
"foo1": "bar",
|
||||
"foo2": 65
|
||||
}
|
||||
},
|
||||
"object_marking_refs": "11",
|
||||
"granular_markings": [
|
||||
{
|
||||
"marking_ref": "1",
|
||||
"selectors": ["a"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "2",
|
||||
"selectors": ["c"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "3",
|
||||
"selectors": ["c.[1]"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "4",
|
||||
"selectors": ["c.[2]"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "5",
|
||||
"selectors": ["c.[2].g"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "6",
|
||||
"selectors": ["x"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "7",
|
||||
"selectors": ["x.y"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "8",
|
||||
"selectors": ["x.y.[1]"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "9",
|
||||
"selectors": ["x.z"]
|
||||
},
|
||||
{
|
||||
"marking_ref": "10",
|
||||
"selectors": ["x.z.foo2"]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
assert markings.is_marked(test_sdo, ["1"], "a", False, False)
|
||||
assert markings.is_marked(test_sdo, ["1", "11"], "a", True, False)
|
||||
assert markings.is_marked(test_sdo, ["1", "11"], "a", True, True)
|
||||
assert markings.is_marked(test_sdo, ["1"], "a", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, "b", inherited=False, descendants=False) is False
|
||||
assert markings.is_marked(test_sdo, ["11"], "b", True, False)
|
||||
assert markings.is_marked(test_sdo, ["11"], "b", True, True)
|
||||
assert markings.is_marked(test_sdo, "b", inherited=False, descendants=True) is False
|
||||
|
||||
assert markings.is_marked(test_sdo, ["2"], "c", False, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "11"], "c", True, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "3", "4", "5", "11"], "c", True, True)
|
||||
assert markings.is_marked(test_sdo, ["2", "3", "4", "5"], "c", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, "c.[0]", inherited=False, descendants=False) is False
|
||||
assert markings.is_marked(test_sdo, ["2", "11"], "c.[0]", True, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "11"], "c.[0]", True, True)
|
||||
assert markings.is_marked(test_sdo, "c.[0]", inherited=False, descendants=True) is False
|
||||
|
||||
assert markings.is_marked(test_sdo, ["3"], "c.[1]", False, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "3", "11"], "c.[1]", True, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "3", "11"], "c.[1]", True, True)
|
||||
assert markings.is_marked(test_sdo, ["3"], "c.[1]", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, ["4"], "c.[2]", False, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "4", "11"], "c.[2]", True, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "4", "5", "11"], "c.[2]", True, True)
|
||||
assert markings.is_marked(test_sdo, ["4", "5"], "c.[2]", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, ["5"], "c.[2].g", False, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "4", "5", "11"], "c.[2].g", True, False)
|
||||
assert markings.is_marked(test_sdo, ["2", "4", "5", "11"], "c.[2].g", True, True)
|
||||
assert markings.is_marked(test_sdo, ["5"], "c.[2].g", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, ["6"], "x", False, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "11"], "x", True, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "7", "8", "9", "10", "11"], "x", True, True)
|
||||
assert markings.is_marked(test_sdo, ["6", "7", "8", "9", "10"], "x", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, ["7"], "x.y", False, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "7", "11"], "x.y", True, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "7", "8", "11"], "x.y", True, True)
|
||||
assert markings.is_marked(test_sdo, ["7", "8"], "x.y", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, "x.y.[0]", inherited=False, descendants=False) is False
|
||||
assert markings.is_marked(test_sdo, ["6", "7", "11"], "x.y.[0]", True, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "7", "11"], "x.y.[0]", True, True)
|
||||
assert markings.is_marked(test_sdo, "x.y.[0]", inherited=False, descendants=True) is False
|
||||
|
||||
assert markings.is_marked(test_sdo, ["8"], "x.y.[1]", False, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "7", "8", "11"], "x.y.[1]", True, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "7", "8", "11"], "x.y.[1]", True, True)
|
||||
assert markings.is_marked(test_sdo, ["8"], "x.y.[1]", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, ["9"], "x.z", False, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "9", "11"], "x.z", True, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "9", "10", "11"], "x.z", True, True)
|
||||
assert markings.is_marked(test_sdo, ["9", "10"], "x.z", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, "x.z.foo1", inherited=False, descendants=False) is False
|
||||
assert markings.is_marked(test_sdo, ["6", "9", "11"], "x.z.foo1", True, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "9", "11"], "x.z.foo1", True, True)
|
||||
assert markings.is_marked(test_sdo, "x.z.foo1", inherited=False, descendants=True) is False
|
||||
|
||||
assert markings.is_marked(test_sdo, ["10"], "x.z.foo2", False, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "9", "10", "11"], "x.z.foo2", True, False)
|
||||
assert markings.is_marked(test_sdo, ["6", "9", "10", "11"], "x.z.foo2", True, True)
|
||||
assert markings.is_marked(test_sdo, ["10"], "x.z.foo2", False, True)
|
||||
|
||||
assert markings.is_marked(test_sdo, ["11"], None, True, True)
|
||||
assert markings.is_marked(test_sdo, ["2"], None, True, True) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
(
|
||||
Malware(object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS),
|
||||
Malware(**MALWARE_KWARGS),
|
||||
),
|
||||
(
|
||||
dict(object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS),
|
||||
MALWARE_KWARGS,
|
||||
),
|
||||
])
|
||||
def test_is_marked_no_markings(data):
|
||||
marked = data[0]
|
||||
nonmarked = data[1]
|
||||
|
||||
assert markings.is_marked(marked)
|
||||
assert markings.is_marked(nonmarked) is False
|
||||
|
||||
|
||||
def test_set_marking():
|
||||
before = Malware(
|
||||
object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
after = Malware(
|
||||
object_marking_refs=[MARKING_IDS[4], MARKING_IDS[5]],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
|
||||
before = markings.set_markings(before, [MARKING_IDS[4], MARKING_IDS[5]], None)
|
||||
|
||||
for m in before["object_marking_refs"]:
|
||||
assert m in [MARKING_IDS[4], MARKING_IDS[5]]
|
||||
|
||||
assert [MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]] not in before["object_marking_refs"]
|
||||
|
||||
for x in before["object_marking_refs"]:
|
||||
assert x in after["object_marking_refs"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
([]),
|
||||
([""]),
|
||||
(""),
|
||||
([MARKING_IDS[4], 687])
|
||||
])
|
||||
def test_set_marking_bad_input(data):
|
||||
before = Malware(
|
||||
object_marking_refs=[MARKING_IDS[0]],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
after = Malware(
|
||||
object_marking_refs=[MARKING_IDS[0]],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
with pytest.raises(exceptions.InvalidValueError):
|
||||
before = markings.set_markings(before, data, None)
|
||||
|
||||
assert before == after
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,380 @@
|
|||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
|
||||
|
||||
def test_create_comparison_expression():
|
||||
|
||||
exp = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
|
||||
stix2.HashConstant("aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", "SHA-256")) # noqa
|
||||
assert str(exp) == "file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f'"
|
||||
|
||||
|
||||
def test_boolean_expression():
|
||||
exp1 = stix2.MatchesComparisonExpression("email-message:from_ref.value",
|
||||
stix2.StringConstant(".+\\@example\\.com$"))
|
||||
exp2 = stix2.MatchesComparisonExpression("email-message:body_multipart[*].body_raw_ref.name",
|
||||
stix2.StringConstant("^Final Report.+\\.exe$"))
|
||||
exp = stix2.AndBooleanExpression([exp1, exp2])
|
||||
assert str(exp) == "email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$'" # noqa
|
||||
|
||||
|
||||
def test_boolean_expression_with_parentheses():
|
||||
exp1 = stix2.MatchesComparisonExpression(stix2.ObjectPath("email-message",
|
||||
[stix2.ReferenceObjectPathComponent("from_ref"),
|
||||
stix2.BasicObjectPathComponent("value")]),
|
||||
stix2.StringConstant(".+\\@example\\.com$"))
|
||||
exp2 = stix2.MatchesComparisonExpression("email-message:body_multipart[*].body_raw_ref.name",
|
||||
stix2.StringConstant("^Final Report.+\\.exe$"))
|
||||
exp = stix2.ParentheticalExpression(stix2.AndBooleanExpression([exp1, exp2]))
|
||||
assert str(exp) == "(email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$')" # noqa
|
||||
|
||||
|
||||
def test_hash_followed_by_registryKey_expression_python_constant():
|
||||
hash_exp = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
||||
stix2.HashConstant("79054025255fb1a26e4bc422aef54eb4", "MD5"))
|
||||
o_exp1 = stix2.ObservationExpression(hash_exp)
|
||||
reg_exp = stix2.EqualityComparisonExpression(stix2.ObjectPath("windows-registry-key", ["key"]),
|
||||
stix2.StringConstant("HKEY_LOCAL_MACHINE\\foo\\bar"))
|
||||
o_exp2 = stix2.ObservationExpression(reg_exp)
|
||||
fb_exp = stix2.FollowedByObservationExpression([o_exp1, o_exp2])
|
||||
para_exp = stix2.ParentheticalExpression(fb_exp)
|
||||
qual_exp = stix2.WithinQualifier(300)
|
||||
exp = stix2.QualifiedObservationExpression(para_exp, qual_exp)
|
||||
assert str(exp) == "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [windows-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS" # noqa
|
||||
|
||||
|
||||
def test_hash_followed_by_registryKey_expression():
|
||||
hash_exp = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
||||
stix2.HashConstant("79054025255fb1a26e4bc422aef54eb4", "MD5"))
|
||||
o_exp1 = stix2.ObservationExpression(hash_exp)
|
||||
reg_exp = stix2.EqualityComparisonExpression(stix2.ObjectPath("windows-registry-key", ["key"]),
|
||||
stix2.StringConstant("HKEY_LOCAL_MACHINE\\foo\\bar"))
|
||||
o_exp2 = stix2.ObservationExpression(reg_exp)
|
||||
fb_exp = stix2.FollowedByObservationExpression([o_exp1, o_exp2])
|
||||
para_exp = stix2.ParentheticalExpression(fb_exp)
|
||||
qual_exp = stix2.WithinQualifier(stix2.IntegerConstant(300))
|
||||
exp = stix2.QualifiedObservationExpression(para_exp, qual_exp)
|
||||
assert str(exp) == "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [windows-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS" # noqa
|
||||
|
||||
|
||||
def test_file_observable_expression():
|
||||
exp1 = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
|
||||
stix2.HashConstant(
|
||||
"aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f",
|
||||
'SHA-256'))
|
||||
exp2 = stix2.EqualityComparisonExpression("file:mime_type", stix2.StringConstant("application/x-pdf"))
|
||||
bool_exp = stix2.AndBooleanExpression([exp1, exp2])
|
||||
exp = stix2.ObservationExpression(bool_exp)
|
||||
assert str(exp) == "[file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f' AND file:mime_type = 'application/x-pdf']" # noqa
|
||||
|
||||
|
||||
@pytest.mark.parametrize("observation_class, op", [
|
||||
(stix2.AndObservationExpression, 'AND'),
|
||||
(stix2.OrObservationExpression, 'OR'),
|
||||
])
|
||||
def test_multiple_file_observable_expression(observation_class, op):
|
||||
exp1 = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
|
||||
stix2.HashConstant(
|
||||
"bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c",
|
||||
'SHA-256'))
|
||||
exp2 = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
||||
stix2.HashConstant("cead3f77f6cda6ec00f57d76c9a6879f", "MD5"))
|
||||
bool1_exp = stix2.OrBooleanExpression([exp1, exp2])
|
||||
exp3 = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
|
||||
stix2.HashConstant(
|
||||
"aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f",
|
||||
'SHA-256'))
|
||||
op1_exp = stix2.ObservationExpression(bool1_exp)
|
||||
op2_exp = stix2.ObservationExpression(exp3)
|
||||
exp = observation_class([op1_exp, op2_exp])
|
||||
assert str(exp) == "[file:hashes.'SHA-256' = 'bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c' OR file:hashes.MD5 = 'cead3f77f6cda6ec00f57d76c9a6879f'] {} [file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f']".format(op) # noqa
|
||||
|
||||
|
||||
def test_root_types():
|
||||
ast = stix2.ObservationExpression(
|
||||
stix2.AndBooleanExpression(
|
||||
[stix2.ParentheticalExpression(
|
||||
stix2.OrBooleanExpression([
|
||||
stix2.EqualityComparisonExpression("a:b", stix2.StringConstant("1")),
|
||||
stix2.EqualityComparisonExpression("b:c", stix2.StringConstant("2"))])),
|
||||
stix2.EqualityComparisonExpression(u"b:d", stix2.StringConstant("3"))]))
|
||||
assert str(ast) == "[(a:b = '1' OR b:c = '2') AND b:d = '3']"
|
||||
|
||||
|
||||
def test_artifact_payload():
|
||||
exp1 = stix2.EqualityComparisonExpression("artifact:mime_type",
|
||||
"application/vnd.tcpdump.pcap")
|
||||
exp2 = stix2.MatchesComparisonExpression("artifact:payload_bin",
|
||||
stix2.StringConstant("\\xd4\\xc3\\xb2\\xa1\\x02\\x00\\x04\\x00"))
|
||||
and_exp = stix2.AndBooleanExpression([exp1, exp2])
|
||||
exp = stix2.ObservationExpression(and_exp)
|
||||
assert str(exp) == "[artifact:mime_type = 'application/vnd.tcpdump.pcap' AND artifact:payload_bin MATCHES '\\\\xd4\\\\xc3\\\\xb2\\\\xa1\\\\x02\\\\x00\\\\x04\\\\x00']" # noqa
|
||||
|
||||
|
||||
def test_greater_than_python_constant():
|
||||
exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy",
|
||||
7.0)
|
||||
exp = stix2.ObservationExpression(exp1)
|
||||
assert str(exp) == "[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]"
|
||||
|
||||
|
||||
def test_greater_than():
|
||||
exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy",
|
||||
stix2.FloatConstant(7.0))
|
||||
exp = stix2.ObservationExpression(exp1)
|
||||
assert str(exp) == "[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]"
|
||||
|
||||
|
||||
def test_less_than():
|
||||
exp = stix2.LessThanComparisonExpression("file:size",
|
||||
1024)
|
||||
assert str(exp) == "file:size < 1024"
|
||||
|
||||
|
||||
def test_greater_than_or_equal():
|
||||
exp = stix2.GreaterThanEqualComparisonExpression("file:size",
|
||||
1024)
|
||||
assert str(exp) == "file:size >= 1024"
|
||||
|
||||
|
||||
def test_less_than_or_equal():
|
||||
exp = stix2.LessThanEqualComparisonExpression("file:size",
|
||||
1024)
|
||||
assert str(exp) == "file:size <= 1024"
|
||||
|
||||
|
||||
def test_not():
|
||||
exp = stix2.LessThanComparisonExpression("file:size",
|
||||
1024,
|
||||
negated=True)
|
||||
assert str(exp) == "file:size NOT < 1024"
|
||||
|
||||
|
||||
def test_and_observable_expression():
|
||||
exp1 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||
"unix"),
|
||||
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||
stix2.StringConstant("1007")),
|
||||
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||
"Peter")])
|
||||
exp2 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||
"unix"),
|
||||
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||
stix2.StringConstant("1008")),
|
||||
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||
"Paul")])
|
||||
exp3 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||
"unix"),
|
||||
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||
stix2.StringConstant("1009")),
|
||||
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||
"Mary")])
|
||||
exp = stix2.AndObservationExpression([stix2.ObservationExpression(exp1),
|
||||
stix2.ObservationExpression(exp2),
|
||||
stix2.ObservationExpression(exp3)])
|
||||
assert str(exp) == "[user-account:account_type = 'unix' AND user-account:user_id = '1007' AND user-account:account_login = 'Peter'] AND [user-account:account_type = 'unix' AND user-account:user_id = '1008' AND user-account:account_login = 'Paul'] AND [user-account:account_type = 'unix' AND user-account:user_id = '1009' AND user-account:account_login = 'Mary']" # noqa
|
||||
|
||||
|
||||
def test_invalid_and_observable_expression():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:display_name",
|
||||
"admin"),
|
||||
stix2.EqualityComparisonExpression("email-addr:display_name",
|
||||
stix2.StringConstant("admin"))])
|
||||
assert "All operands to an 'AND' expression must have the same object type" in str(excinfo)
|
||||
|
||||
|
||||
def test_hex():
|
||||
exp_and = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("file:mime_type",
|
||||
"image/bmp"),
|
||||
stix2.EqualityComparisonExpression("file:magic_number_hex",
|
||||
stix2.HexConstant("ffd8"))])
|
||||
exp = stix2.ObservationExpression(exp_and)
|
||||
assert str(exp) == "[file:mime_type = 'image/bmp' AND file:magic_number_hex = h'ffd8']"
|
||||
|
||||
|
||||
def test_multiple_qualifiers():
|
||||
exp_and = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("network-traffic:dst_ref.type",
|
||||
"domain-name"),
|
||||
stix2.EqualityComparisonExpression("network-traffic:dst_ref.value",
|
||||
"example.com")])
|
||||
exp_ob = stix2.ObservationExpression(exp_and)
|
||||
qual_rep = stix2.RepeatQualifier(5)
|
||||
qual_within = stix2.WithinQualifier(stix2.IntegerConstant(1800))
|
||||
exp = stix2.QualifiedObservationExpression(stix2.QualifiedObservationExpression(exp_ob, qual_rep), qual_within)
|
||||
assert str(exp) == "[network-traffic:dst_ref.type = 'domain-name' AND network-traffic:dst_ref.value = 'example.com'] REPEATS 5 TIMES WITHIN 1800 SECONDS" # noqa
|
||||
|
||||
|
||||
def test_set_op():
|
||||
exp = stix2.ObservationExpression(stix2.IsSubsetComparisonExpression("network-traffic:dst_ref.value",
|
||||
"2001:0db8:dead:beef:0000:0000:0000:0000/64"))
|
||||
assert str(exp) == "[network-traffic:dst_ref.value ISSUBSET '2001:0db8:dead:beef:0000:0000:0000:0000/64']"
|
||||
|
||||
|
||||
def test_timestamp():
|
||||
ts = stix2.TimestampConstant('2014-01-13T07:03:17Z')
|
||||
assert str(ts) == "t'2014-01-13T07:03:17Z'"
|
||||
|
||||
|
||||
def test_boolean():
|
||||
exp = stix2.EqualityComparisonExpression("email-message:is_multipart",
|
||||
True)
|
||||
assert str(exp) == "email-message:is_multipart = true"
|
||||
|
||||
|
||||
def test_binary():
|
||||
const = stix2.BinaryConstant("dGhpcyBpcyBhIHRlc3Q=")
|
||||
exp = stix2.EqualityComparisonExpression("artifact:payload_bin",
|
||||
const)
|
||||
assert str(exp) == "artifact:payload_bin = b'dGhpcyBpcyBhIHRlc3Q='"
|
||||
|
||||
|
||||
def test_list():
|
||||
exp = stix2.InComparisonExpression("process:name",
|
||||
['proccy', 'proximus', 'badproc'])
|
||||
assert str(exp) == "process:name IN ('proccy', 'proximus', 'badproc')"
|
||||
|
||||
|
||||
def test_list2():
|
||||
# alternate way to construct an "IN" Comparison Expression
|
||||
exp = stix2.EqualityComparisonExpression("process:name",
|
||||
['proccy', 'proximus', 'badproc'])
|
||||
assert str(exp) == "process:name IN ('proccy', 'proximus', 'badproc')"
|
||||
|
||||
|
||||
def test_invalid_constant_type():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.EqualityComparisonExpression("artifact:payload_bin",
|
||||
{'foo': 'bar'})
|
||||
assert 'Unable to create a constant' in str(excinfo)
|
||||
|
||||
|
||||
def test_invalid_integer_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.IntegerConstant('foo')
|
||||
assert 'must be an integer' in str(excinfo)
|
||||
|
||||
|
||||
def test_invalid_timestamp_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.TimestampConstant('foo')
|
||||
assert 'must be a datetime object or timestamp string' in str(excinfo)
|
||||
|
||||
|
||||
def test_invalid_float_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.FloatConstant('foo')
|
||||
assert 'must be a float' in str(excinfo)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data, result", [
|
||||
(True, True),
|
||||
(False, False),
|
||||
('True', True),
|
||||
('False', False),
|
||||
('true', True),
|
||||
('false', False),
|
||||
('t', True),
|
||||
('f', False),
|
||||
('T', True),
|
||||
('F', False),
|
||||
(1, True),
|
||||
(0, False),
|
||||
])
|
||||
def test_boolean_constant(data, result):
|
||||
boolean = stix2.BooleanConstant(data)
|
||||
assert boolean.value == result
|
||||
|
||||
|
||||
def test_invalid_boolean_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.BooleanConstant('foo')
|
||||
assert 'must be a boolean' in str(excinfo)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("hashtype, data", [
|
||||
('MD5', 'zzz'),
|
||||
('ssdeep', 'zzz=='),
|
||||
])
|
||||
def test_invalid_hash_constant(hashtype, data):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.HashConstant(data, hashtype)
|
||||
assert 'is not a valid {} hash'.format(hashtype) in str(excinfo)
|
||||
|
||||
|
||||
def test_invalid_hex_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.HexConstant('mm')
|
||||
assert "must contain an even number of hexadecimal characters" in str(excinfo)
|
||||
|
||||
|
||||
def test_invalid_binary_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.BinaryConstant('foo')
|
||||
assert 'must contain a base64' in str(excinfo)
|
||||
|
||||
|
||||
def test_escape_quotes_and_backslashes():
|
||||
exp = stix2.MatchesComparisonExpression("file:name",
|
||||
"^Final Report.+\\.exe$")
|
||||
assert str(exp) == "file:name MATCHES '^Final Report.+\\\\.exe$'"
|
||||
|
||||
|
||||
def test_like():
|
||||
exp = stix2.LikeComparisonExpression("directory:path",
|
||||
"C:\\Windows\\%\\foo")
|
||||
assert str(exp) == "directory:path LIKE 'C:\\\\Windows\\\\%\\\\foo'"
|
||||
|
||||
|
||||
def test_issuperset():
|
||||
exp = stix2.IsSupersetComparisonExpression("ipv4-addr:value",
|
||||
"198.51.100.0/24")
|
||||
assert str(exp) == "ipv4-addr:value ISSUPERSET '198.51.100.0/24'"
|
||||
|
||||
|
||||
def test_repeat_qualifier():
|
||||
qual = stix2.RepeatQualifier(stix2.IntegerConstant(5))
|
||||
assert str(qual) == 'REPEATS 5 TIMES'
|
||||
|
||||
|
||||
def test_invalid_repeat_qualifier():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.RepeatQualifier('foo')
|
||||
assert 'is not a valid argument for a Repeat Qualifier' in str(excinfo)
|
||||
|
||||
|
||||
def test_invalid_within_qualifier():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.WithinQualifier('foo')
|
||||
assert 'is not a valid argument for a Within Qualifier' in str(excinfo)
|
||||
|
||||
|
||||
def test_startstop_qualifier():
|
||||
qual = stix2.StartStopQualifier(stix2.TimestampConstant('2016-06-01T00:00:00Z'),
|
||||
datetime.datetime(2017, 3, 12, 8, 30, 0))
|
||||
assert str(qual) == "START t'2016-06-01T00:00:00Z' STOP t'2017-03-12T08:30:00Z'"
|
||||
|
||||
qual2 = stix2.StartStopQualifier(datetime.date(2016, 6, 1),
|
||||
stix2.TimestampConstant('2016-07-01T00:00:00Z'))
|
||||
assert str(qual2) == "START t'2016-06-01T00:00:00Z' STOP t'2016-07-01T00:00:00Z'"
|
||||
|
||||
|
||||
def test_invalid_startstop_qualifier():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.StartStopQualifier('foo',
|
||||
stix2.TimestampConstant('2016-06-01T00:00:00Z'))
|
||||
assert 'is not a valid argument for a Start/Stop Qualifier' in str(excinfo)
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.StartStopQualifier(datetime.date(2016, 6, 1),
|
||||
'foo')
|
||||
assert 'is not a valid argument for a Start/Stop Qualifier' in str(excinfo)
|
||||
|
||||
|
||||
def test_make_constant_already_a_constant():
|
||||
str_const = stix2.StringConstant('Foo')
|
||||
result = stix2.patterns.make_constant(str_const)
|
||||
assert result is str_const
|
|
@ -0,0 +1,17 @@
|
|||
import pickle
|
||||
|
||||
import stix2
|
||||
|
||||
|
||||
def test_pickling():
|
||||
"""
|
||||
Ensure a pickle/unpickle cycle works okay.
|
||||
"""
|
||||
identity = stix2.Identity(
|
||||
id="identity--d66cb89d-5228-4983-958c-fa84ef75c88c",
|
||||
name="alice",
|
||||
description="this is a pickle test",
|
||||
identity_class="some_class"
|
||||
)
|
||||
|
||||
pickle.loads(pickle.dumps(identity))
|
|
@ -0,0 +1,364 @@
|
|||
import pytest
|
||||
|
||||
from stix2 import CustomObject, EmailMIMEComponent, ExtensionsProperty, TCPExt
|
||||
from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError
|
||||
from stix2.v20.properties import (BinaryProperty, BooleanProperty,
|
||||
DictionaryProperty, EmbeddedObjectProperty,
|
||||
EnumProperty, FloatProperty, HashesProperty,
|
||||
HexProperty, IDProperty, IntegerProperty,
|
||||
ListProperty, Property, ReferenceProperty,
|
||||
StringProperty, TimestampProperty,
|
||||
TypeProperty)
|
||||
|
||||
from .constants import FAKE_TIME
|
||||
|
||||
|
||||
def test_property():
|
||||
p = Property()
|
||||
|
||||
assert p.required is False
|
||||
assert p.clean('foo') == 'foo'
|
||||
assert p.clean(3) == 3
|
||||
|
||||
|
||||
def test_basic_clean():
|
||||
class Prop(Property):
|
||||
|
||||
def clean(self, value):
|
||||
if value == 42:
|
||||
return value
|
||||
else:
|
||||
raise ValueError("Must be 42")
|
||||
|
||||
p = Prop()
|
||||
|
||||
assert p.clean(42) == 42
|
||||
with pytest.raises(ValueError):
|
||||
p.clean(41)
|
||||
|
||||
|
||||
def test_property_default():
|
||||
class Prop(Property):
|
||||
|
||||
def default(self):
|
||||
return 77
|
||||
|
||||
p = Prop()
|
||||
|
||||
assert p.default() == 77
|
||||
|
||||
|
||||
def test_fixed_property():
|
||||
p = Property(fixed="2.0")
|
||||
|
||||
assert p.clean("2.0")
|
||||
with pytest.raises(ValueError):
|
||||
assert p.clean("x") is False
|
||||
with pytest.raises(ValueError):
|
||||
assert p.clean(2.0) is False
|
||||
|
||||
assert p.default() == "2.0"
|
||||
assert p.clean(p.default())
|
||||
|
||||
|
||||
def test_list_property():
|
||||
p = ListProperty(StringProperty)
|
||||
|
||||
assert p.clean(['abc', 'xyz'])
|
||||
with pytest.raises(ValueError):
|
||||
p.clean([])
|
||||
|
||||
|
||||
def test_string_property():
|
||||
prop = StringProperty()
|
||||
|
||||
assert prop.clean('foobar')
|
||||
assert prop.clean(1)
|
||||
assert prop.clean([1, 2, 3])
|
||||
|
||||
|
||||
def test_type_property():
|
||||
prop = TypeProperty('my-type')
|
||||
|
||||
assert prop.clean('my-type')
|
||||
with pytest.raises(ValueError):
|
||||
prop.clean('not-my-type')
|
||||
assert prop.clean(prop.default())
|
||||
|
||||
|
||||
def test_id_property():
|
||||
idprop = IDProperty('my-type')
|
||||
|
||||
assert idprop.clean('my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
idprop.clean('not-my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c')
|
||||
assert str(excinfo.value) == "must start with 'my-type--'."
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
idprop.clean('my-type--foo')
|
||||
assert str(excinfo.value) == "must have a valid UUID after the prefix."
|
||||
|
||||
assert idprop.clean(idprop.default())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
2,
|
||||
-1,
|
||||
3.14,
|
||||
False,
|
||||
])
|
||||
def test_integer_property_valid(value):
|
||||
int_prop = IntegerProperty()
|
||||
assert int_prop.clean(value) is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
"something",
|
||||
StringProperty(),
|
||||
])
|
||||
def test_integer_property_invalid(value):
|
||||
int_prop = IntegerProperty()
|
||||
with pytest.raises(ValueError):
|
||||
int_prop.clean(value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
2,
|
||||
-1,
|
||||
3.14,
|
||||
False,
|
||||
])
|
||||
def test_float_property_valid(value):
|
||||
int_prop = FloatProperty()
|
||||
assert int_prop.clean(value) is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
"something",
|
||||
StringProperty(),
|
||||
])
|
||||
def test_float_property_invalid(value):
|
||||
int_prop = FloatProperty()
|
||||
with pytest.raises(ValueError):
|
||||
int_prop.clean(value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
True,
|
||||
False,
|
||||
'True',
|
||||
'False',
|
||||
'true',
|
||||
'false',
|
||||
'TRUE',
|
||||
'FALSE',
|
||||
'T',
|
||||
'F',
|
||||
't',
|
||||
'f',
|
||||
1,
|
||||
0,
|
||||
])
|
||||
def test_boolean_property_valid(value):
|
||||
bool_prop = BooleanProperty()
|
||||
|
||||
assert bool_prop.clean(value) is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
'abc',
|
||||
['false'],
|
||||
{'true': 'true'},
|
||||
2,
|
||||
-1,
|
||||
])
|
||||
def test_boolean_property_invalid(value):
|
||||
bool_prop = BooleanProperty()
|
||||
with pytest.raises(ValueError):
|
||||
bool_prop.clean(value)
|
||||
|
||||
|
||||
def test_reference_property():
|
||||
ref_prop = ReferenceProperty()
|
||||
|
||||
assert ref_prop.clean("my-type--3a331bfe-0566-55e1-a4a0-9a2cd355a300")
|
||||
with pytest.raises(ValueError):
|
||||
ref_prop.clean("foo")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
'2017-01-01T12:34:56Z',
|
||||
'2017-01-01 12:34:56',
|
||||
'Jan 1 2017 12:34:56',
|
||||
])
|
||||
def test_timestamp_property_valid(value):
|
||||
ts_prop = TimestampProperty()
|
||||
assert ts_prop.clean(value) == FAKE_TIME
|
||||
|
||||
|
||||
def test_timestamp_property_invalid():
|
||||
ts_prop = TimestampProperty()
|
||||
with pytest.raises(ValueError):
|
||||
ts_prop.clean(1)
|
||||
with pytest.raises(ValueError):
|
||||
ts_prop.clean("someday sometime")
|
||||
|
||||
|
||||
def test_binary_property():
|
||||
bin_prop = BinaryProperty()
|
||||
|
||||
assert bin_prop.clean("TG9yZW0gSXBzdW0=")
|
||||
with pytest.raises(ValueError):
|
||||
bin_prop.clean("foobar")
|
||||
|
||||
|
||||
def test_hex_property():
|
||||
hex_prop = HexProperty()
|
||||
|
||||
assert hex_prop.clean("4c6f72656d20497073756d")
|
||||
with pytest.raises(ValueError):
|
||||
hex_prop.clean("foobar")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("d", [
|
||||
{'description': 'something'},
|
||||
[('abc', 1), ('bcd', 2), ('cde', 3)],
|
||||
])
|
||||
def test_dictionary_property_valid(d):
|
||||
dict_prop = DictionaryProperty()
|
||||
assert dict_prop.clean(d)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("d", [
|
||||
[{'a': 'something'}, "Invalid dictionary key a: (shorter than 3 characters)."],
|
||||
[{'a'*300: 'something'}, "Invalid dictionary key aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
"aaaaaaaaaaaaaaaaaaaaaaa: (longer than 256 characters)."],
|
||||
[{'Hey!': 'something'}, "Invalid dictionary key Hey!: (contains characters other thanlowercase a-z, "
|
||||
"uppercase A-Z, numerals 0-9, hyphen (-), or underscore (_))."],
|
||||
])
|
||||
def test_dictionary_property_invalid_key(d):
|
||||
dict_prop = DictionaryProperty()
|
||||
|
||||
with pytest.raises(DictionaryKeyError) as excinfo:
|
||||
dict_prop.clean(d[0])
|
||||
|
||||
assert str(excinfo.value) == d[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("d", [
|
||||
({}, "The dictionary property must contain a non-empty dictionary"),
|
||||
# TODO: This error message could be made more helpful. The error is caused
|
||||
# because `json.loads()` doesn't like the *single* quotes around the key
|
||||
# name, even though they are valid in a Python dictionary. While technically
|
||||
# accurate (a string is not a dictionary), if we want to be able to load
|
||||
# string-encoded "dictionaries" that are, we need a better error message
|
||||
# or an alternative to `json.loads()` ... and preferably *not* `eval()`. :-)
|
||||
# Changing the following to `'{"description": "something"}'` does not cause
|
||||
# any ValueError to be raised.
|
||||
("{'description': 'something'}", "The dictionary property must contain a dictionary"),
|
||||
])
|
||||
def test_dictionary_property_invalid(d):
|
||||
dict_prop = DictionaryProperty()
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
dict_prop.clean(d[0])
|
||||
assert str(excinfo.value) == d[1]
|
||||
|
||||
|
||||
def test_property_list_of_dictionary():
|
||||
@CustomObject('x-new-obj', [
|
||||
('property1', ListProperty(DictionaryProperty(), required=True)),
|
||||
])
|
||||
class NewObj():
|
||||
pass
|
||||
|
||||
test_obj = NewObj(property1=[{'foo': 'bar'}])
|
||||
assert test_obj.property1[0]['foo'] == 'bar'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
{"sha256": "6db12788c37247f2316052e142f42f4b259d6561751e5f401a1ae2a6df9c674b"},
|
||||
[('MD5', '2dfb1bcc980200c6706feee399d41b3f'), ('RIPEMD-160', 'b3a8cd8a27c90af79b3c81754f267780f443dfef')],
|
||||
])
|
||||
def test_hashes_property_valid(value):
|
||||
hash_prop = HashesProperty()
|
||||
assert hash_prop.clean(value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
{"MD5": "a"},
|
||||
{"SHA-256": "2dfb1bcc980200c6706feee399d41b3f"},
|
||||
])
|
||||
def test_hashes_property_invalid(value):
|
||||
hash_prop = HashesProperty()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
hash_prop.clean(value)
|
||||
|
||||
|
||||
def test_embedded_property():
|
||||
emb_prop = EmbeddedObjectProperty(type=EmailMIMEComponent)
|
||||
mime = EmailMIMEComponent(
|
||||
content_type="text/plain; charset=utf-8",
|
||||
content_disposition="inline",
|
||||
body="Cats are funny!"
|
||||
)
|
||||
assert emb_prop.clean(mime)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
emb_prop.clean("string")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [
|
||||
['a', 'b', 'c'],
|
||||
('a', 'b', 'c'),
|
||||
'b',
|
||||
])
|
||||
def test_enum_property_valid(value):
|
||||
enum_prop = EnumProperty(value)
|
||||
assert enum_prop.clean('b')
|
||||
|
||||
|
||||
def test_enum_property_invalid():
|
||||
enum_prop = EnumProperty(['a', 'b', 'c'])
|
||||
with pytest.raises(ValueError):
|
||||
enum_prop.clean('z')
|
||||
|
||||
|
||||
def test_extension_property_valid():
|
||||
ext_prop = ExtensionsProperty(enclosing_type='file')
|
||||
assert ext_prop({
|
||||
'windows-pebinary-ext': {
|
||||
'pe_type': 'exe'
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
1,
|
||||
{'foobar-ext': {
|
||||
'pe_type': 'exe'
|
||||
}},
|
||||
])
|
||||
def test_extension_property_invalid(data):
|
||||
ext_prop = ExtensionsProperty(enclosing_type='file')
|
||||
with pytest.raises(ValueError):
|
||||
ext_prop.clean(data)
|
||||
|
||||
|
||||
def test_extension_property_invalid_type():
|
||||
ext_prop = ExtensionsProperty(enclosing_type='indicator')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
ext_prop.clean({
|
||||
'windows-pebinary-ext': {
|
||||
'pe_type': 'exe'
|
||||
}}
|
||||
)
|
||||
assert 'no extensions defined' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_extension_at_least_one_property_constraint():
|
||||
with pytest.raises(AtLeastOnePropertyError):
|
||||
TCPExt()
|
|
@ -0,0 +1,162 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import (FAKE_TIME, INDICATOR_ID, MALWARE_ID, RELATIONSHIP_ID,
|
||||
RELATIONSHIP_KWARGS)
|
||||
|
||||
EXPECTED_RELATIONSHIP = """{
|
||||
"type": "relationship",
|
||||
"spec_version": "2.1",
|
||||
"id": "relationship--00000000-1111-2222-3333-444444444444",
|
||||
"created": "2016-04-06T20:06:37.000Z",
|
||||
"modified": "2016-04-06T20:06:37.000Z",
|
||||
"relationship_type": "indicates",
|
||||
"source_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"target_ref": "malware--fedcba98-7654-3210-fedc-ba9876543210"
|
||||
}"""
|
||||
|
||||
|
||||
def test_relationship_all_required_properties():
|
||||
now = dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
|
||||
rel = stix2.Relationship(
|
||||
type='relationship',
|
||||
id=RELATIONSHIP_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
relationship_type='indicates',
|
||||
source_ref=INDICATOR_ID,
|
||||
target_ref=MALWARE_ID,
|
||||
)
|
||||
assert str(rel) == EXPECTED_RELATIONSHIP
|
||||
|
||||
|
||||
def test_relationship_autogenerated_properties(relationship):
|
||||
assert relationship.type == 'relationship'
|
||||
assert relationship.id == 'relationship--00000000-0000-0000-0000-000000000001'
|
||||
assert relationship.created == FAKE_TIME
|
||||
assert relationship.modified == FAKE_TIME
|
||||
assert relationship.relationship_type == 'indicates'
|
||||
assert relationship.source_ref == INDICATOR_ID
|
||||
assert relationship.target_ref == MALWARE_ID
|
||||
|
||||
assert relationship['type'] == 'relationship'
|
||||
assert relationship['id'] == 'relationship--00000000-0000-0000-0000-000000000001'
|
||||
assert relationship['created'] == FAKE_TIME
|
||||
assert relationship['modified'] == FAKE_TIME
|
||||
assert relationship['relationship_type'] == 'indicates'
|
||||
assert relationship['source_ref'] == INDICATOR_ID
|
||||
assert relationship['target_ref'] == MALWARE_ID
|
||||
|
||||
|
||||
def test_relationship_type_must_be_relationship():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Relationship(type='xxx', **RELATIONSHIP_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Relationship
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'relationship'."
|
||||
assert str(excinfo.value) == "Invalid value for Relationship 'type': must equal 'relationship'."
|
||||
|
||||
|
||||
def test_relationship_id_must_start_with_relationship():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Relationship(id='my-prefix--', **RELATIONSHIP_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Relationship
|
||||
assert excinfo.value.prop_name == "id"
|
||||
assert excinfo.value.reason == "must start with 'relationship--'."
|
||||
assert str(excinfo.value) == "Invalid value for Relationship 'id': must start with 'relationship--'."
|
||||
|
||||
|
||||
def test_relationship_required_property_relationship_type():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.Relationship()
|
||||
assert excinfo.value.cls == stix2.Relationship
|
||||
assert excinfo.value.properties == ["relationship_type", "source_ref", "target_ref"]
|
||||
|
||||
|
||||
def test_relationship_missing_some_required_properties():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.Relationship(relationship_type='indicates')
|
||||
|
||||
assert excinfo.value.cls == stix2.Relationship
|
||||
assert excinfo.value.properties == ["source_ref", "target_ref"]
|
||||
|
||||
|
||||
def test_relationship_required_properties_target_ref():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.Relationship(
|
||||
relationship_type='indicates',
|
||||
source_ref=INDICATOR_ID
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.Relationship
|
||||
assert excinfo.value.properties == ["target_ref"]
|
||||
|
||||
|
||||
def test_cannot_assign_to_relationship_attributes(relationship):
|
||||
with pytest.raises(stix2.exceptions.ImmutableError) as excinfo:
|
||||
relationship.relationship_type = "derived-from"
|
||||
|
||||
assert str(excinfo.value) == "Cannot modify 'relationship_type' property in 'Relationship' after creation."
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_relationship():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.Relationship(my_custom_property="foo", **RELATIONSHIP_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Relationship
|
||||
assert excinfo.value.properties == ['my_custom_property']
|
||||
assert str(excinfo.value) == "Unexpected properties for Relationship: (my_custom_property)."
|
||||
|
||||
|
||||
def test_create_relationship_from_objects_rather_than_ids(indicator, malware):
|
||||
rel = stix2.Relationship(
|
||||
relationship_type="indicates",
|
||||
source_ref=indicator,
|
||||
target_ref=malware,
|
||||
)
|
||||
|
||||
assert rel.relationship_type == 'indicates'
|
||||
assert rel.source_ref == 'indicator--00000000-0000-0000-0000-000000000001'
|
||||
assert rel.target_ref == 'malware--00000000-0000-0000-0000-000000000003'
|
||||
assert rel.id == 'relationship--00000000-0000-0000-0000-000000000005'
|
||||
|
||||
|
||||
def test_create_relationship_with_positional_args(indicator, malware):
|
||||
rel = stix2.Relationship(indicator, 'indicates', malware)
|
||||
|
||||
assert rel.relationship_type == 'indicates'
|
||||
assert rel.source_ref == 'indicator--00000000-0000-0000-0000-000000000001'
|
||||
assert rel.target_ref == 'malware--00000000-0000-0000-0000-000000000003'
|
||||
assert rel.id == 'relationship--00000000-0000-0000-0000-000000000005'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED_RELATIONSHIP,
|
||||
{
|
||||
"created": "2016-04-06T20:06:37Z",
|
||||
"id": "relationship--00000000-1111-2222-3333-444444444444",
|
||||
"modified": "2016-04-06T20:06:37Z",
|
||||
"relationship_type": "indicates",
|
||||
"source_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"target_ref": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
||||
"spec_version": "2.1",
|
||||
"type": "relationship"
|
||||
},
|
||||
])
|
||||
def test_parse_relationship(data):
|
||||
rel = stix2.parse(data)
|
||||
|
||||
assert rel.type == 'relationship'
|
||||
assert rel.id == RELATIONSHIP_ID
|
||||
assert rel.created == dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
assert rel.modified == dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
assert rel.relationship_type == "indicates"
|
||||
assert rel.source_ref == "indicator--01234567-89ab-cdef-0123-456789abcdef"
|
||||
assert rel.target_ref == "malware--fedcba98-7654-3210-fedc-ba9876543210"
|
|
@ -0,0 +1,132 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import INDICATOR_KWARGS, REPORT_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "report",
|
||||
"spec_version": "2.1",
|
||||
"id": "report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
|
||||
"created_by_ref": "identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
|
||||
"created": "2015-12-21T19:59:11.000Z",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "The Black Vine Cyberespionage Group",
|
||||
"description": "A simple report with an indicator and campaign",
|
||||
"published": "2016-01-20T17:00:00Z",
|
||||
"object_refs": [
|
||||
"indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
|
||||
"campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c",
|
||||
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
|
||||
],
|
||||
"labels": [
|
||||
"campaign"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_report_example():
|
||||
report = stix2.Report(
|
||||
id="report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
|
||||
created_by_ref="identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
|
||||
created="2015-12-21T19:59:11.000Z",
|
||||
modified="2015-12-21T19:59:11.000Z",
|
||||
name="The Black Vine Cyberespionage Group",
|
||||
description="A simple report with an indicator and campaign",
|
||||
published="2016-01-20T17:00:00Z",
|
||||
labels=["campaign"],
|
||||
object_refs=[
|
||||
"indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
|
||||
"campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c",
|
||||
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
|
||||
],
|
||||
)
|
||||
|
||||
assert str(report) == EXPECTED
|
||||
|
||||
|
||||
def test_report_example_objects_in_object_refs():
|
||||
report = stix2.Report(
|
||||
id="report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
|
||||
created_by_ref="identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
|
||||
created="2015-12-21T19:59:11.000Z",
|
||||
modified="2015-12-21T19:59:11.000Z",
|
||||
name="The Black Vine Cyberespionage Group",
|
||||
description="A simple report with an indicator and campaign",
|
||||
published="2016-01-20T17:00:00Z",
|
||||
labels=["campaign"],
|
||||
object_refs=[
|
||||
stix2.Indicator(id="indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2", **INDICATOR_KWARGS),
|
||||
"campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c",
|
||||
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
|
||||
],
|
||||
)
|
||||
|
||||
assert str(report) == EXPECTED
|
||||
|
||||
|
||||
def test_report_example_objects_in_object_refs_with_bad_id():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Report(
|
||||
id="report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
|
||||
created_by_ref="identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
|
||||
created="2015-12-21T19:59:11.000Z",
|
||||
modified="2015-12-21T19:59:11.000Z",
|
||||
name="The Black Vine Cyberespionage Group",
|
||||
description="A simple report with an indicator and campaign",
|
||||
published="2016-01-20T17:00:00Z",
|
||||
labels=["campaign"],
|
||||
object_refs=[
|
||||
stix2.Indicator(id="indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2", **INDICATOR_KWARGS),
|
||||
"campaign-83422c77-904c-4dc1-aff5-5c38f3a2c55c", # the "bad" id, missing a "-"
|
||||
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
|
||||
],
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.Report
|
||||
assert excinfo.value.prop_name == "object_refs"
|
||||
assert excinfo.value.reason == "must match <object-type>--<guid>."
|
||||
assert str(excinfo.value) == "Invalid value for Report 'object_refs': must match <object-type>--<guid>."
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2015-12-21T19:59:11.000Z",
|
||||
"created_by_ref": "identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
|
||||
"description": "A simple report with an indicator and campaign",
|
||||
"id": "report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
|
||||
"labels": [
|
||||
"campaign"
|
||||
],
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "The Black Vine Cyberespionage Group",
|
||||
"object_refs": [
|
||||
"indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
|
||||
"campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c",
|
||||
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
|
||||
],
|
||||
"published": "2016-01-20T17:00:00Z",
|
||||
"spec_version": "2.1",
|
||||
"type": "report"
|
||||
},
|
||||
])
|
||||
def test_parse_report(data):
|
||||
rept = stix2.parse(data)
|
||||
|
||||
assert rept.type == 'report'
|
||||
assert rept.id == REPORT_ID
|
||||
assert rept.created == dt.datetime(2015, 12, 21, 19, 59, 11, tzinfo=pytz.utc)
|
||||
assert rept.modified == dt.datetime(2015, 12, 21, 19, 59, 11, tzinfo=pytz.utc)
|
||||
assert rept.created_by_ref == "identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283"
|
||||
assert rept.object_refs == ["indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
|
||||
"campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c",
|
||||
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"]
|
||||
assert rept.description == "A simple report with an indicator and campaign"
|
||||
assert rept.labels == ["campaign"]
|
||||
assert rept.name == "The Black Vine Cyberespionage Group"
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,115 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import INDICATOR_ID, SIGHTING_ID, SIGHTING_KWARGS
|
||||
|
||||
EXPECTED_SIGHTING = """{
|
||||
"type": "sighting",
|
||||
"spec_version": "2.1",
|
||||
"id": "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb",
|
||||
"created": "2016-04-06T20:06:37.000Z",
|
||||
"modified": "2016-04-06T20:06:37.000Z",
|
||||
"sighting_of_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"where_sighted_refs": [
|
||||
"identity--8cc7afd6-5455-4d2b-a736-e614ee631d99"
|
||||
]
|
||||
}"""
|
||||
|
||||
BAD_SIGHTING = """{
|
||||
"created": "2016-04-06T20:06:37.000Z",
|
||||
"id": "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb",
|
||||
"modified": "2016-04-06T20:06:37.000Z",
|
||||
"sighting_of_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"spec_version": "2.1",
|
||||
"type": "sighting",
|
||||
"where_sighted_refs": [
|
||||
"malware--8cc7afd6-5455-4d2b-a736-e614ee631d99"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_sighting_all_required_properties():
|
||||
now = dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
|
||||
s = stix2.Sighting(
|
||||
type='sighting',
|
||||
id=SIGHTING_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
sighting_of_ref=INDICATOR_ID,
|
||||
where_sighted_refs=["identity--8cc7afd6-5455-4d2b-a736-e614ee631d99"]
|
||||
)
|
||||
assert str(s) == EXPECTED_SIGHTING
|
||||
|
||||
|
||||
def test_sighting_bad_where_sighted_refs():
|
||||
now = dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Sighting(
|
||||
type='sighting',
|
||||
id=SIGHTING_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
sighting_of_ref=INDICATOR_ID,
|
||||
where_sighted_refs=["malware--8cc7afd6-5455-4d2b-a736-e614ee631d99"]
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.Sighting
|
||||
assert excinfo.value.prop_name == "where_sighted_refs"
|
||||
assert excinfo.value.reason == "must start with 'identity'."
|
||||
assert str(excinfo.value) == "Invalid value for Sighting 'where_sighted_refs': must start with 'identity'."
|
||||
|
||||
|
||||
def test_sighting_type_must_be_sightings():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.Sighting(type='xxx', **SIGHTING_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Sighting
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'sighting'."
|
||||
assert str(excinfo.value) == "Invalid value for Sighting 'type': must equal 'sighting'."
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_sighting():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.Sighting(my_custom_property="foo", **SIGHTING_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.Sighting
|
||||
assert excinfo.value.properties == ['my_custom_property']
|
||||
assert str(excinfo.value) == "Unexpected properties for Sighting: (my_custom_property)."
|
||||
|
||||
|
||||
def test_create_sighting_from_objects_rather_than_ids(malware): # noqa: F811
|
||||
rel = stix2.Sighting(sighting_of_ref=malware)
|
||||
|
||||
assert rel.sighting_of_ref == 'malware--00000000-0000-0000-0000-000000000001'
|
||||
assert rel.id == 'sighting--00000000-0000-0000-0000-000000000003'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED_SIGHTING,
|
||||
{
|
||||
"created": "2016-04-06T20:06:37Z",
|
||||
"id": "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb",
|
||||
"modified": "2016-04-06T20:06:37Z",
|
||||
"sighting_of_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"type": "sighting",
|
||||
"where_sighted_refs": [
|
||||
"identity--8cc7afd6-5455-4d2b-a736-e614ee631d99"
|
||||
]
|
||||
},
|
||||
])
|
||||
def test_parse_sighting(data):
|
||||
sighting = stix2.parse(data)
|
||||
|
||||
assert sighting.type == 'sighting'
|
||||
assert sighting.id == SIGHTING_ID
|
||||
assert sighting.created == dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
assert sighting.modified == dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
assert sighting.sighting_of_ref == "indicator--01234567-89ab-cdef-0123-456789abcdef"
|
||||
assert sighting.where_sighted_refs == ["identity--8cc7afd6-5455-4d2b-a736-e614ee631d99"]
|
|
@ -0,0 +1,67 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import THREAT_ACTOR_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "threat-actor",
|
||||
"spec_version": "2.1",
|
||||
"id": "threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Evil Org",
|
||||
"description": "The Evil Org threat actor group",
|
||||
"labels": [
|
||||
"crime-syndicate"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_threat_actor_example():
|
||||
threat_actor = stix2.ThreatActor(
|
||||
id="threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:48.000Z",
|
||||
modified="2016-04-06T20:03:48.000Z",
|
||||
name="Evil Org",
|
||||
description="The Evil Org threat actor group",
|
||||
labels=["crime-syndicate"],
|
||||
)
|
||||
|
||||
assert str(threat_actor) == EXPECTED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"description": "The Evil Org threat actor group",
|
||||
"id": "threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"labels": [
|
||||
"crime-syndicate"
|
||||
],
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Evil Org",
|
||||
"spec_version": "2.1",
|
||||
"type": "threat-actor"
|
||||
},
|
||||
])
|
||||
def test_parse_threat_actor(data):
|
||||
actor = stix2.parse(data)
|
||||
|
||||
assert actor.type == 'threat-actor'
|
||||
assert actor.id == THREAT_ACTOR_ID
|
||||
assert actor.created == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert actor.modified == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert actor.created_by_ref == "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff"
|
||||
assert actor.description == "The Evil Org threat actor group"
|
||||
assert actor.name == "Evil Org"
|
||||
assert actor.labels == ["crime-syndicate"]
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,97 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import TOOL_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "tool",
|
||||
"spec_version": "2.1",
|
||||
"id": "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "VNC",
|
||||
"labels": [
|
||||
"remote-access"
|
||||
]
|
||||
}"""
|
||||
|
||||
EXPECTED_WITH_REVOKED = """{
|
||||
"type": "tool",
|
||||
"spec_version": "2.1",
|
||||
"id": "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "VNC",
|
||||
"revoked": false,
|
||||
"labels": [
|
||||
"remote-access"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_tool_example():
|
||||
tool = stix2.Tool(
|
||||
id="tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:48.000Z",
|
||||
modified="2016-04-06T20:03:48.000Z",
|
||||
name="VNC",
|
||||
labels=["remote-access"],
|
||||
)
|
||||
|
||||
assert str(tool) == EXPECTED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2016-04-06T20:03:48Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"id": "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"labels": [
|
||||
"remote-access"
|
||||
],
|
||||
"modified": "2016-04-06T20:03:48Z",
|
||||
"name": "VNC",
|
||||
"spec_version": "2.1",
|
||||
"type": "tool"
|
||||
},
|
||||
])
|
||||
def test_parse_tool(data):
|
||||
tool = stix2.parse(data)
|
||||
|
||||
assert tool.type == 'tool'
|
||||
assert tool.id == TOOL_ID
|
||||
assert tool.created == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert tool.modified == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert tool.created_by_ref == "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff"
|
||||
assert tool.labels == ["remote-access"]
|
||||
assert tool.name == "VNC"
|
||||
|
||||
|
||||
def test_tool_no_workbench_wrappers():
|
||||
tool = stix2.Tool(name='VNC', labels=['remote-access'])
|
||||
with pytest.raises(AttributeError):
|
||||
tool.created_by()
|
||||
|
||||
|
||||
def test_tool_serialize_with_defaults():
|
||||
tool = stix2.Tool(
|
||||
id="tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T20:03:48.000Z",
|
||||
modified="2016-04-06T20:03:48.000Z",
|
||||
name="VNC",
|
||||
labels=["remote-access"],
|
||||
)
|
||||
|
||||
assert tool.serialize(pretty=True, include_optional_defaults=True) == EXPECTED_WITH_REVOKED
|
||||
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,210 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime as dt
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2.utils
|
||||
|
||||
amsterdam = pytz.timezone('Europe/Amsterdam')
|
||||
eastern = pytz.timezone('US/Eastern')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dttm, timestamp', [
|
||||
(dt.datetime(2017, 1, 1, tzinfo=pytz.utc), '2017-01-01T00:00:00Z'),
|
||||
(amsterdam.localize(dt.datetime(2017, 1, 1)), '2016-12-31T23:00:00Z'),
|
||||
(eastern.localize(dt.datetime(2017, 1, 1, 12, 34, 56)), '2017-01-01T17:34:56Z'),
|
||||
(eastern.localize(dt.datetime(2017, 7, 1)), '2017-07-01T04:00:00Z'),
|
||||
(dt.datetime(2017, 7, 1), '2017-07-01T00:00:00Z'),
|
||||
(dt.datetime(2017, 7, 1, 0, 0, 0, 1), '2017-07-01T00:00:00.000001Z'),
|
||||
(stix2.utils.STIXdatetime(2017, 7, 1, 0, 0, 0, 1, precision='millisecond'), '2017-07-01T00:00:00.000Z'),
|
||||
(stix2.utils.STIXdatetime(2017, 7, 1, 0, 0, 0, 1, precision='second'), '2017-07-01T00:00:00Z'),
|
||||
])
|
||||
def test_timestamp_formatting(dttm, timestamp):
|
||||
assert stix2.utils.format_datetime(dttm) == timestamp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('timestamp, dttm', [
|
||||
(dt.datetime(2017, 1, 1, 0, tzinfo=pytz.utc), dt.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc)),
|
||||
(dt.date(2017, 1, 1), dt.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc)),
|
||||
('2017-01-01T00:00:00Z', dt.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc)),
|
||||
('2017-01-01T02:00:00+2:00', dt.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc)),
|
||||
('2017-01-01T00:00:00', dt.datetime(2017, 1, 1, 0, 0, 0, tzinfo=pytz.utc)),
|
||||
])
|
||||
def test_parse_datetime(timestamp, dttm):
|
||||
assert stix2.utils.parse_into_datetime(timestamp) == dttm
|
||||
|
||||
|
||||
@pytest.mark.parametrize('timestamp, dttm, precision', [
|
||||
('2017-01-01T01:02:03.000001', dt.datetime(2017, 1, 1, 1, 2, 3, 0, tzinfo=pytz.utc), 'millisecond'),
|
||||
('2017-01-01T01:02:03.001', dt.datetime(2017, 1, 1, 1, 2, 3, 1000, tzinfo=pytz.utc), 'millisecond'),
|
||||
('2017-01-01T01:02:03.1', dt.datetime(2017, 1, 1, 1, 2, 3, 100000, tzinfo=pytz.utc), 'millisecond'),
|
||||
('2017-01-01T01:02:03.45', dt.datetime(2017, 1, 1, 1, 2, 3, 450000, tzinfo=pytz.utc), 'millisecond'),
|
||||
('2017-01-01T01:02:03.45', dt.datetime(2017, 1, 1, 1, 2, 3, tzinfo=pytz.utc), 'second'),
|
||||
])
|
||||
def test_parse_datetime_precision(timestamp, dttm, precision):
|
||||
assert stix2.utils.parse_into_datetime(timestamp, precision) == dttm
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ts', [
|
||||
'foobar',
|
||||
1,
|
||||
])
|
||||
def test_parse_datetime_invalid(ts):
|
||||
with pytest.raises(ValueError):
|
||||
stix2.utils.parse_into_datetime('foobar')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data', [
|
||||
{"a": 1},
|
||||
'{"a": 1}',
|
||||
StringIO(u'{"a": 1}'),
|
||||
[("a", 1,)],
|
||||
])
|
||||
def test_get_dict(data):
|
||||
assert stix2.utils._get_dict(data)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data', [
|
||||
1,
|
||||
[1],
|
||||
['a', 1],
|
||||
"foobar",
|
||||
])
|
||||
def test_get_dict_invalid(data):
|
||||
with pytest.raises(ValueError):
|
||||
stix2.utils._get_dict(data)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('stix_id, type', [
|
||||
('malware--d69c8146-ab35-4d50-8382-6fc80e641d43', 'malware'),
|
||||
('intrusion-set--899ce53f-13a0-479b-a0e4-67d46e241542', 'intrusion-set')
|
||||
])
|
||||
def test_get_type_from_id(stix_id, type):
|
||||
assert stix2.utils.get_type_from_id(stix_id) == type
|
||||
|
||||
|
||||
def test_deduplicate(stix_objs1):
|
||||
unique = stix2.utils.deduplicate(stix_objs1)
|
||||
|
||||
# Only 3 objects are unique
|
||||
# 2 id's vary
|
||||
# 2 modified times vary for a particular id
|
||||
|
||||
assert len(unique) == 3
|
||||
|
||||
ids = [obj['id'] for obj in unique]
|
||||
mods = [obj['modified'] for obj in unique]
|
||||
|
||||
assert "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f" in ids
|
||||
assert "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f" in ids
|
||||
assert "2017-01-27T13:49:53.935Z" in mods
|
||||
assert "2017-01-27T13:49:53.936Z" in mods
|
||||
|
||||
|
||||
@pytest.mark.parametrize('object, tuple_to_find, expected_index', [
|
||||
(stix2.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"
|
||||
},
|
||||
"1": {
|
||||
"type": "ipv4-addr",
|
||||
"value": "198.51.100.3"
|
||||
},
|
||||
"2": {
|
||||
"type": "network-traffic",
|
||||
"src_ref": "1",
|
||||
"protocols": [
|
||||
"tcp",
|
||||
"http"
|
||||
],
|
||||
"extensions": {
|
||||
"http-request-ext": {
|
||||
"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",
|
||||
"Host": "www.example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
), ('1', {"type": "ipv4-addr", "value": "198.51.100.3"}), 1),
|
||||
({
|
||||
"type": "x-example",
|
||||
"id": "x-example--d5413db2-c26c-42e0-b0e0-ec800a310bfb",
|
||||
"created": "2018-06-11T01:25:22.063Z",
|
||||
"modified": "2018-06-11T01:25:22.063Z",
|
||||
"dictionary": {
|
||||
"key": {
|
||||
"key_one": "value",
|
||||
"key_two": "value"
|
||||
}
|
||||
}
|
||||
}, ('key', {'key_one': 'value', 'key_two': 'value'}), 0),
|
||||
({
|
||||
"type": "language-content",
|
||||
"id": "language-content--b86bd89f-98bb-4fa9-8cb2-9ad421da981d",
|
||||
"created": "2017-02-08T21:31:22.007Z",
|
||||
"modified": "2017-02-08T21:31:22.007Z",
|
||||
"object_ref": "campaign--12a111f0-b824-4baf-a224-83b80237a094",
|
||||
"object_modified": "2017-02-08T21:31:22.007Z",
|
||||
"contents": {
|
||||
"de": {
|
||||
"name": "Bank Angriff 1",
|
||||
"description": "Weitere Informationen über Banküberfall"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Attaque Bank 1",
|
||||
"description": "Plus d'informations sur la crise bancaire"
|
||||
}
|
||||
}
|
||||
}, ('fr', {"name": "Attaque Bank 1", "description": "Plus d'informations sur la crise bancaire"}), 1)
|
||||
])
|
||||
def test_find_property_index(object, tuple_to_find, expected_index):
|
||||
assert stix2.utils.find_property_index(
|
||||
object,
|
||||
*tuple_to_find
|
||||
) == expected_index
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dict_value, tuple_to_find, expected_index', [
|
||||
({
|
||||
"contents": {
|
||||
"de": {
|
||||
"name": "Bank Angriff 1",
|
||||
"description": "Weitere Informationen über Banküberfall"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Attaque Bank 1",
|
||||
"description": "Plus d'informations sur la crise bancaire"
|
||||
},
|
||||
"es": {
|
||||
"name": "Ataque al Banco",
|
||||
"description": "Mas informacion sobre el ataque al banco"
|
||||
}
|
||||
}
|
||||
}, ('es', {"name": "Ataque al Banco", "description": "Mas informacion sobre el ataque al banco"}), 1), # Sorted alphabetically
|
||||
({
|
||||
'my_list': [
|
||||
{"key_one": 1},
|
||||
{"key_two": 2}
|
||||
]
|
||||
}, ('key_one', 1), 0)
|
||||
])
|
||||
def test_iterate_over_values(dict_value, tuple_to_find, expected_index):
|
||||
assert stix2.utils._find_property_in_seq(dict_value.values(), *tuple_to_find) == expected_index
|
|
@ -0,0 +1,252 @@
|
|||
import pytest
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import CAMPAIGN_MORE_KWARGS
|
||||
|
||||
|
||||
def test_making_new_version():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
|
||||
campaign_v2 = campaign_v1.new_version(name="fred")
|
||||
|
||||
assert campaign_v1.id == campaign_v2.id
|
||||
assert campaign_v1.created_by_ref == campaign_v2.created_by_ref
|
||||
assert campaign_v1.created == campaign_v2.created
|
||||
assert campaign_v1.name != campaign_v2.name
|
||||
assert campaign_v2.name == "fred"
|
||||
assert campaign_v1.description == campaign_v2.description
|
||||
assert campaign_v1.modified < campaign_v2.modified
|
||||
|
||||
|
||||
def test_making_new_version_with_unset():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
|
||||
campaign_v2 = campaign_v1.new_version(description=None)
|
||||
|
||||
assert campaign_v1.id == campaign_v2.id
|
||||
assert campaign_v1.created_by_ref == campaign_v2.created_by_ref
|
||||
assert campaign_v1.created == campaign_v2.created
|
||||
assert campaign_v1.name == campaign_v2.name
|
||||
with pytest.raises(AttributeError):
|
||||
assert campaign_v2.description
|
||||
assert campaign_v1.modified < campaign_v2.modified
|
||||
|
||||
|
||||
def test_making_new_version_with_embedded_object():
|
||||
campaign_v1 = stix2.Campaign(
|
||||
external_references=[{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-163"
|
||||
}],
|
||||
**CAMPAIGN_MORE_KWARGS
|
||||
)
|
||||
|
||||
campaign_v2 = campaign_v1.new_version(external_references=[{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-164"
|
||||
}])
|
||||
|
||||
assert campaign_v1.id == campaign_v2.id
|
||||
assert campaign_v1.created_by_ref == campaign_v2.created_by_ref
|
||||
assert campaign_v1.created == campaign_v2.created
|
||||
assert campaign_v1.name == campaign_v2.name
|
||||
assert campaign_v1.description == campaign_v2.description
|
||||
assert campaign_v1.modified < campaign_v2.modified
|
||||
assert campaign_v1.external_references[0].external_id != campaign_v2.external_references[0].external_id
|
||||
|
||||
|
||||
def test_revoke():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
|
||||
campaign_v2 = campaign_v1.revoke()
|
||||
|
||||
assert campaign_v1.id == campaign_v2.id
|
||||
assert campaign_v1.created_by_ref == campaign_v2.created_by_ref
|
||||
assert campaign_v1.created == campaign_v2.created
|
||||
assert campaign_v1.name == campaign_v2.name
|
||||
assert campaign_v1.description == campaign_v2.description
|
||||
assert campaign_v1.modified < campaign_v2.modified
|
||||
|
||||
assert campaign_v2.revoked
|
||||
|
||||
|
||||
def test_versioning_error_invalid_property():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
|
||||
with pytest.raises(stix2.exceptions.UnmodifiablePropertyError) as excinfo:
|
||||
campaign_v1.new_version(type="threat-actor")
|
||||
|
||||
assert str(excinfo.value) == "These properties cannot be changed when making a new version: type."
|
||||
|
||||
|
||||
def test_versioning_error_bad_modified_value():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
campaign_v1.new_version(modified="2015-04-06T20:03:00.000Z")
|
||||
|
||||
assert excinfo.value.cls == stix2.Campaign
|
||||
assert excinfo.value.prop_name == "modified"
|
||||
assert excinfo.value.reason == "The new modified datetime cannot be before than or equal to the current modified datetime." \
|
||||
"It cannot be equal, as according to STIX 2 specification, objects that are different " \
|
||||
"but have the same id and modified timestamp do not have defined consumer behavior."
|
||||
|
||||
msg = "Invalid value for {0} '{1}': {2}"
|
||||
msg = msg.format(stix2.Campaign.__name__, "modified",
|
||||
"The new modified datetime cannot be before than or equal to the current modified datetime."
|
||||
"It cannot be equal, as according to STIX 2 specification, objects that are different "
|
||||
"but have the same id and modified timestamp do not have defined consumer behavior.")
|
||||
assert str(excinfo.value) == msg
|
||||
|
||||
|
||||
def test_versioning_error_usetting_required_property():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
campaign_v1.new_version(name=None)
|
||||
|
||||
assert excinfo.value.cls == stix2.Campaign
|
||||
assert excinfo.value.properties == ["name"]
|
||||
|
||||
msg = "No values for required properties for {0}: ({1})."
|
||||
msg = msg.format(stix2.Campaign.__name__, "name")
|
||||
assert str(excinfo.value) == msg
|
||||
|
||||
|
||||
def test_versioning_error_new_version_of_revoked():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
campaign_v2 = campaign_v1.revoke()
|
||||
|
||||
with pytest.raises(stix2.exceptions.RevokeError) as excinfo:
|
||||
campaign_v2.new_version(name="barney")
|
||||
assert str(excinfo.value) == "Cannot create a new version of a revoked object."
|
||||
|
||||
assert excinfo.value.called_by == "new_version"
|
||||
assert str(excinfo.value) == "Cannot create a new version of a revoked object."
|
||||
|
||||
|
||||
def test_versioning_error_revoke_of_revoked():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
campaign_v2 = campaign_v1.revoke()
|
||||
|
||||
with pytest.raises(stix2.exceptions.RevokeError) as excinfo:
|
||||
campaign_v2.revoke()
|
||||
assert str(excinfo.value) == "Cannot revoke an already revoked object."
|
||||
|
||||
assert excinfo.value.called_by == "revoke"
|
||||
assert str(excinfo.value) == "Cannot revoke an already revoked object."
|
||||
|
||||
|
||||
def test_making_new_version_dict():
|
||||
campaign_v1 = CAMPAIGN_MORE_KWARGS
|
||||
campaign_v2 = stix2.utils.new_version(CAMPAIGN_MORE_KWARGS, name="fred")
|
||||
|
||||
assert campaign_v1['id'] == campaign_v2['id']
|
||||
assert campaign_v1['created_by_ref'] == campaign_v2['created_by_ref']
|
||||
assert campaign_v1['created'] == campaign_v2['created']
|
||||
assert campaign_v1['name'] != campaign_v2['name']
|
||||
assert campaign_v2['name'] == "fred"
|
||||
assert campaign_v1['description'] == campaign_v2['description']
|
||||
assert stix2.utils.parse_into_datetime(campaign_v1['modified'], precision='millisecond') < campaign_v2['modified']
|
||||
|
||||
|
||||
def test_versioning_error_dict_bad_modified_value():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.utils.new_version(CAMPAIGN_MORE_KWARGS, modified="2015-04-06T20:03:00.000Z")
|
||||
|
||||
assert excinfo.value.cls == dict
|
||||
assert excinfo.value.prop_name == "modified"
|
||||
assert excinfo.value.reason == "The new modified datetime cannot be before than or equal to the current modified datetime." \
|
||||
"It cannot be equal, as according to STIX 2 specification, objects that are different " \
|
||||
"but have the same id and modified timestamp do not have defined consumer behavior."
|
||||
|
||||
|
||||
def test_versioning_error_dict_no_modified_value():
|
||||
campaign_v1 = {
|
||||
'type': 'campaign',
|
||||
'id': "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
'created': "2016-04-06T20:03:00.000Z",
|
||||
'name': "Green Group Attacks Against Finance",
|
||||
}
|
||||
campaign_v2 = stix2.utils.new_version(campaign_v1, modified="2017-04-06T20:03:00.000Z")
|
||||
|
||||
assert str(campaign_v2['modified']) == "2017-04-06T20:03:00.000Z"
|
||||
|
||||
|
||||
def test_making_new_version_invalid_cls():
|
||||
campaign_v1 = "This is a campaign."
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.utils.new_version(campaign_v1, name="fred")
|
||||
|
||||
assert 'cannot create new version of object of this type' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_revoke_dict():
|
||||
campaign_v1 = CAMPAIGN_MORE_KWARGS
|
||||
campaign_v2 = stix2.utils.revoke(campaign_v1)
|
||||
|
||||
assert campaign_v1['id'] == campaign_v2['id']
|
||||
assert campaign_v1['created_by_ref'] == campaign_v2['created_by_ref']
|
||||
assert campaign_v1['created'] == campaign_v2['created']
|
||||
assert campaign_v1['name'] == campaign_v2['name']
|
||||
assert campaign_v1['description'] == campaign_v2['description']
|
||||
assert stix2.utils.parse_into_datetime(campaign_v1['modified'], precision='millisecond') < campaign_v2['modified']
|
||||
|
||||
assert campaign_v2['revoked']
|
||||
|
||||
|
||||
def test_versioning_error_revoke_of_revoked_dict():
|
||||
campaign_v1 = CAMPAIGN_MORE_KWARGS
|
||||
campaign_v2 = stix2.utils.revoke(campaign_v1)
|
||||
|
||||
with pytest.raises(stix2.exceptions.RevokeError) as excinfo:
|
||||
stix2.utils.revoke(campaign_v2)
|
||||
|
||||
assert excinfo.value.called_by == "revoke"
|
||||
|
||||
|
||||
def test_revoke_invalid_cls():
|
||||
campaign_v1 = "This is a campaign."
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.utils.revoke(campaign_v1)
|
||||
|
||||
assert 'cannot revoke object of this type' in str(excinfo.value)
|
||||
|
||||
|
||||
def test_remove_custom_stix_property():
|
||||
mal = stix2.Malware(name="ColePowers",
|
||||
labels=["rootkit"],
|
||||
is_family=False,
|
||||
x_custom="armada",
|
||||
allow_custom=True)
|
||||
|
||||
mal_nc = stix2.utils.remove_custom_stix(mal)
|
||||
|
||||
assert "x_custom" not in mal_nc
|
||||
assert stix2.utils.parse_into_datetime(mal["modified"], precision="millisecond") < stix2.utils.parse_into_datetime(mal_nc["modified"],
|
||||
precision="millisecond")
|
||||
|
||||
|
||||
def test_remove_custom_stix_object():
|
||||
@stix2.CustomObject("x-animal", [
|
||||
("species", stix2.properties.StringProperty(required=True)),
|
||||
("animal_class", stix2.properties.StringProperty()),
|
||||
])
|
||||
class Animal(object):
|
||||
pass
|
||||
|
||||
animal = Animal(species="lion", animal_class="mammal")
|
||||
|
||||
nc = stix2.utils.remove_custom_stix(animal)
|
||||
|
||||
assert nc is None
|
||||
|
||||
|
||||
def test_remove_custom_stix_no_custom():
|
||||
campaign_v1 = stix2.Campaign(**CAMPAIGN_MORE_KWARGS)
|
||||
campaign_v2 = stix2.utils.remove_custom_stix(campaign_v1)
|
||||
|
||||
assert len(campaign_v1.keys()) == len(campaign_v2.keys())
|
||||
assert campaign_v1.id == campaign_v2.id
|
||||
assert campaign_v1.description == campaign_v2.description
|
|
@ -0,0 +1,69 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import VULNERABILITY_ID
|
||||
|
||||
EXPECTED = """{
|
||||
"type": "vulnerability",
|
||||
"spec_version": "2.1",
|
||||
"id": "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"name": "CVE-2016-1234",
|
||||
"external_references": [
|
||||
{
|
||||
"source_name": "cve",
|
||||
"external_id": "CVE-2016-1234"
|
||||
}
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_vulnerability_example():
|
||||
vulnerability = stix2.Vulnerability(
|
||||
id="vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
created="2016-05-12T08:17:27.000Z",
|
||||
modified="2016-05-12T08:17:27.000Z",
|
||||
name="CVE-2016-1234",
|
||||
external_references=[
|
||||
stix2.ExternalReference(source_name='cve',
|
||||
external_id="CVE-2016-1234"),
|
||||
],
|
||||
)
|
||||
|
||||
assert str(vulnerability) == EXPECTED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2016-05-12T08:17:27Z",
|
||||
"external_references": [
|
||||
{
|
||||
"external_id": "CVE-2016-1234",
|
||||
"source_name": "cve"
|
||||
}
|
||||
],
|
||||
"id": "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
"modified": "2016-05-12T08:17:27Z",
|
||||
"name": "CVE-2016-1234",
|
||||
"spec_version": "2.1",
|
||||
"type": "vulnerability"
|
||||
},
|
||||
])
|
||||
def test_parse_vulnerability(data):
|
||||
vuln = stix2.parse(data)
|
||||
|
||||
assert vuln.type == 'vulnerability'
|
||||
assert vuln.id == VULNERABILITY_ID
|
||||
assert vuln.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert vuln.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert vuln.name == "CVE-2016-1234"
|
||||
assert vuln.external_references[0].external_id == "CVE-2016-1234"
|
||||
assert vuln.external_references[0].source_name == "cve"
|
||||
|
||||
# TODO: Add other examples
|
|
@ -0,0 +1,316 @@
|
|||
import os
|
||||
|
||||
import stix2
|
||||
from stix2 import Bundle
|
||||
from stix2.workbench import (AttackPattern, Campaign, CourseOfAction,
|
||||
ExternalReference, FileSystemSource, Filter,
|
||||
Identity, Indicator, IntrusionSet, Malware,
|
||||
MarkingDefinition, ObservedData, Relationship,
|
||||
Report, StatementMarking, ThreatActor, Tool,
|
||||
Vulnerability, add_data_source, all_versions,
|
||||
attack_patterns, campaigns, courses_of_action,
|
||||
create, get, identities, indicators,
|
||||
intrusion_sets, malware, observed_data, query,
|
||||
reports, save, set_default_created,
|
||||
set_default_creator, set_default_external_refs,
|
||||
set_default_object_marking_refs, threat_actors,
|
||||
tools, vulnerabilities)
|
||||
|
||||
from .constants import (ATTACK_PATTERN_ID, ATTACK_PATTERN_KWARGS, CAMPAIGN_ID,
|
||||
CAMPAIGN_KWARGS, COURSE_OF_ACTION_ID,
|
||||
COURSE_OF_ACTION_KWARGS, IDENTITY_ID, IDENTITY_KWARGS,
|
||||
INDICATOR_ID, INDICATOR_KWARGS, INTRUSION_SET_ID,
|
||||
INTRUSION_SET_KWARGS, MALWARE_ID, MALWARE_KWARGS,
|
||||
OBSERVED_DATA_ID, OBSERVED_DATA_KWARGS, REPORT_ID,
|
||||
REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS,
|
||||
TOOL_ID, TOOL_KWARGS, VULNERABILITY_ID,
|
||||
VULNERABILITY_KWARGS)
|
||||
|
||||
|
||||
def test_workbench_environment():
|
||||
|
||||
# Create a STIX object
|
||||
ind = create(Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
save(ind)
|
||||
|
||||
resp = get(INDICATOR_ID)
|
||||
assert resp['labels'][0] == 'malicious-activity'
|
||||
|
||||
resp = all_versions(INDICATOR_ID)
|
||||
assert len(resp) == 1
|
||||
|
||||
# Search on something other than id
|
||||
q = [Filter('type', '=', 'vulnerability')]
|
||||
resp = query(q)
|
||||
assert len(resp) == 0
|
||||
|
||||
|
||||
def test_workbench_get_all_attack_patterns():
|
||||
mal = AttackPattern(id=ATTACK_PATTERN_ID, **ATTACK_PATTERN_KWARGS)
|
||||
save(mal)
|
||||
|
||||
resp = attack_patterns()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == ATTACK_PATTERN_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_campaigns():
|
||||
cam = Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
save(cam)
|
||||
|
||||
resp = campaigns()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == CAMPAIGN_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_courses_of_action():
|
||||
coa = CourseOfAction(id=COURSE_OF_ACTION_ID, **COURSE_OF_ACTION_KWARGS)
|
||||
save(coa)
|
||||
|
||||
resp = courses_of_action()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == COURSE_OF_ACTION_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_identities():
|
||||
idty = Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
save(idty)
|
||||
|
||||
resp = identities()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == IDENTITY_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_indicators():
|
||||
resp = indicators()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == INDICATOR_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_intrusion_sets():
|
||||
ins = IntrusionSet(id=INTRUSION_SET_ID, **INTRUSION_SET_KWARGS)
|
||||
save(ins)
|
||||
|
||||
resp = intrusion_sets()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == INTRUSION_SET_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_malware():
|
||||
mal = Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
save(mal)
|
||||
|
||||
resp = malware()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == MALWARE_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_observed_data():
|
||||
od = ObservedData(id=OBSERVED_DATA_ID, **OBSERVED_DATA_KWARGS)
|
||||
save(od)
|
||||
|
||||
resp = observed_data()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == OBSERVED_DATA_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_reports():
|
||||
rep = Report(id=REPORT_ID, **REPORT_KWARGS)
|
||||
save(rep)
|
||||
|
||||
resp = reports()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == REPORT_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_threat_actors():
|
||||
thr = ThreatActor(id=THREAT_ACTOR_ID, **THREAT_ACTOR_KWARGS)
|
||||
save(thr)
|
||||
|
||||
resp = threat_actors()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == THREAT_ACTOR_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_tools():
|
||||
tool = Tool(id=TOOL_ID, **TOOL_KWARGS)
|
||||
save(tool)
|
||||
|
||||
resp = tools()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == TOOL_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_vulnerabilities():
|
||||
vuln = Vulnerability(id=VULNERABILITY_ID, **VULNERABILITY_KWARGS)
|
||||
save(vuln)
|
||||
|
||||
resp = vulnerabilities()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == VULNERABILITY_ID
|
||||
|
||||
|
||||
def test_workbench_add_to_bundle():
|
||||
vuln = Vulnerability(**VULNERABILITY_KWARGS)
|
||||
bundle = Bundle(vuln)
|
||||
assert bundle.objects[0].name == 'Heartbleed'
|
||||
|
||||
|
||||
def test_workbench_relationships():
|
||||
rel = Relationship(INDICATOR_ID, 'indicates', MALWARE_ID)
|
||||
save(rel)
|
||||
|
||||
ind = get(INDICATOR_ID)
|
||||
resp = ind.relationships()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].relationship_type == 'indicates'
|
||||
assert resp[0].source_ref == INDICATOR_ID
|
||||
assert resp[0].target_ref == MALWARE_ID
|
||||
|
||||
|
||||
def test_workbench_created_by():
|
||||
intset = IntrusionSet(name="Breach 123", created_by_ref=IDENTITY_ID)
|
||||
save(intset)
|
||||
creator = intset.created_by()
|
||||
assert creator.id == IDENTITY_ID
|
||||
|
||||
|
||||
def test_workbench_related():
|
||||
rel1 = Relationship(MALWARE_ID, 'targets', IDENTITY_ID)
|
||||
rel2 = Relationship(CAMPAIGN_ID, 'uses', MALWARE_ID)
|
||||
save([rel1, rel2])
|
||||
|
||||
resp = get(MALWARE_ID).related()
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
||||
assert any(x['id'] == IDENTITY_ID for x in resp)
|
||||
|
||||
resp = get(MALWARE_ID).related(relationship_type='indicates')
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_workbench_related_with_filters():
|
||||
malware = Malware(labels=["ransomware"], name="CryptorBit", created_by_ref=IDENTITY_ID,
|
||||
is_family=False)
|
||||
rel = Relationship(malware.id, 'variant-of', MALWARE_ID)
|
||||
save([malware, rel])
|
||||
|
||||
filters = [Filter('created_by_ref', '=', IDENTITY_ID)]
|
||||
resp = get(MALWARE_ID).related(filters=filters)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0].name == malware.name
|
||||
assert resp[0].created_by_ref == IDENTITY_ID
|
||||
|
||||
# filters arg can also be single filter
|
||||
resp = get(MALWARE_ID).related(filters=filters[0])
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_add_data_source():
|
||||
fs_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data")
|
||||
fs = FileSystemSource(fs_path)
|
||||
add_data_source(fs)
|
||||
|
||||
resp = tools()
|
||||
assert len(resp) == 3
|
||||
resp_ids = [tool.id for tool in resp]
|
||||
assert TOOL_ID in resp_ids
|
||||
assert 'tool--03342581-f790-4f03-ba41-e82e67392e23' in resp_ids
|
||||
assert 'tool--242f3da3-4425-4d11-8f5c-b842886da966' in resp_ids
|
||||
|
||||
|
||||
def test_additional_filter():
|
||||
resp = tools(Filter('created_by_ref', '=', 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5'))
|
||||
assert len(resp) == 2
|
||||
|
||||
|
||||
def test_additional_filters_list():
|
||||
resp = tools([Filter('created_by_ref', '=', 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5'),
|
||||
Filter('name', '=', 'Windows Credential Editor')])
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_default_creator():
|
||||
set_default_creator(IDENTITY_ID)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
|
||||
assert 'created_by_ref' not in CAMPAIGN_KWARGS
|
||||
assert campaign.created_by_ref == IDENTITY_ID
|
||||
|
||||
|
||||
def test_default_created_timestamp():
|
||||
timestamp = "2018-03-19T01:02:03.000Z"
|
||||
set_default_created(timestamp)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
|
||||
assert 'created' not in CAMPAIGN_KWARGS
|
||||
assert stix2.utils.format_datetime(campaign.created) == timestamp
|
||||
assert stix2.utils.format_datetime(campaign.modified) == timestamp
|
||||
|
||||
|
||||
def test_default_external_refs():
|
||||
ext_ref = ExternalReference(source_name="ACME Threat Intel",
|
||||
description="Threat report")
|
||||
set_default_external_refs(ext_ref)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
|
||||
assert campaign.external_references[0].source_name == "ACME Threat Intel"
|
||||
assert campaign.external_references[0].description == "Threat report"
|
||||
|
||||
|
||||
def test_default_object_marking_refs():
|
||||
stmt_marking = StatementMarking("Copyright 2016, Example Corp")
|
||||
mark_def = MarkingDefinition(definition_type="statement",
|
||||
definition=stmt_marking)
|
||||
set_default_object_marking_refs(mark_def)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
|
||||
assert campaign.object_marking_refs[0] == mark_def.id
|
||||
|
||||
|
||||
def test_workbench_custom_property_object_in_observable_extension():
|
||||
ntfs = stix2.NTFSExt(
|
||||
allow_custom=True,
|
||||
sid=1,
|
||||
x_foo='bar',
|
||||
)
|
||||
artifact = stix2.File(
|
||||
name='test',
|
||||
extensions={'ntfs-ext': ntfs},
|
||||
)
|
||||
observed_data = ObservedData(
|
||||
allow_custom=True,
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=0,
|
||||
objects={"0": artifact},
|
||||
)
|
||||
|
||||
assert observed_data.objects['0'].extensions['ntfs-ext'].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(observed_data)
|
||||
|
||||
|
||||
def test_workbench_custom_property_dict_in_observable_extension():
|
||||
artifact = stix2.File(
|
||||
allow_custom=True,
|
||||
name='test',
|
||||
extensions={
|
||||
'ntfs-ext': {
|
||||
'allow_custom': True,
|
||||
'sid': 1,
|
||||
'x_foo': 'bar',
|
||||
}
|
||||
},
|
||||
)
|
||||
observed_data = ObservedData(
|
||||
allow_custom=True,
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=0,
|
||||
objects={"0": artifact},
|
||||
)
|
||||
|
||||
assert observed_data.objects['0'].extensions['ntfs-ext'].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(observed_data)
|
Loading…
Reference in New Issue