Merge pull request #275 from khdesai/stix21master
Stix21master, add infrastructure, grouping to the working branchmaster
commit
b1fa177f07
|
@ -125,15 +125,13 @@ def rel_fs_store():
|
|||
|
||||
|
||||
def test_filesystem_source_nonexistent_folder():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.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):
|
||||
|
@ -441,9 +439,8 @@ def test_filesystem_attempt_stix_file_overwrite(fs_store):
|
|||
)
|
||||
|
||||
# Now attempt to overwrite the existing file
|
||||
with pytest.raises(DataSourceError) as excinfo:
|
||||
with pytest.raises(DataSourceError):
|
||||
fs_store.add(camp8)
|
||||
assert "Attempted to overwrite file" in str(excinfo)
|
||||
|
||||
os.remove(filepath)
|
||||
|
||||
|
|
|
@ -257,7 +257,7 @@ def test_and_observable_expression():
|
|||
|
||||
|
||||
def test_invalid_and_observable_expression():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.AndBooleanExpression([
|
||||
stix2.EqualityComparisonExpression(
|
||||
"user-account:display_name",
|
||||
|
@ -268,7 +268,6 @@ def test_invalid_and_observable_expression():
|
|||
stix2.StringConstant("admin"),
|
||||
),
|
||||
])
|
||||
assert "All operands to an 'AND' expression must have the same object type" in str(excinfo)
|
||||
|
||||
|
||||
def test_hex():
|
||||
|
@ -352,30 +351,26 @@ def test_list2():
|
|||
|
||||
|
||||
def test_invalid_constant_type():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.IntegerConstant('foo')
|
||||
assert 'must be an integer' in str(excinfo)
|
||||
|
||||
|
||||
def test_invalid_timestamp_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.FloatConstant('foo')
|
||||
assert 'must be a float' in str(excinfo)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -400,9 +395,8 @@ def test_boolean_constant(data, result):
|
|||
|
||||
|
||||
def test_invalid_boolean_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.BooleanConstant('foo')
|
||||
assert 'must be a boolean' in str(excinfo)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -412,21 +406,18 @@ def test_invalid_boolean_constant():
|
|||
],
|
||||
)
|
||||
def test_invalid_hash_constant(hashtype, data):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.BinaryConstant('foo')
|
||||
assert 'must contain a base64' in str(excinfo)
|
||||
|
||||
|
||||
def test_escape_quotes_and_backslashes():
|
||||
|
@ -459,15 +450,13 @@ def test_repeat_qualifier():
|
|||
|
||||
|
||||
def test_invalid_repeat_qualifier():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.WithinQualifier('foo')
|
||||
assert 'is not a valid argument for a Within Qualifier' in str(excinfo)
|
||||
|
||||
|
||||
def test_startstop_qualifier():
|
||||
|
@ -485,19 +474,17 @@ def test_startstop_qualifier():
|
|||
|
||||
|
||||
def test_invalid_startstop_qualifier():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
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():
|
||||
|
|
|
@ -5,7 +5,8 @@ import pytest
|
|||
import stix2
|
||||
|
||||
from .constants import (
|
||||
FAKE_TIME, INDICATOR_KWARGS, MALWARE_KWARGS, RELATIONSHIP_KWARGS,
|
||||
FAKE_TIME, GROUPING_KWARGS, INDICATOR_KWARGS, INFRASTRUCTURE_KWARGS,
|
||||
MALWARE_KWARGS, RELATIONSHIP_KWARGS,
|
||||
)
|
||||
|
||||
|
||||
|
@ -39,6 +40,16 @@ def indicator(uuid4, clock):
|
|||
return stix2.v21.Indicator(**INDICATOR_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def infrastructure(uuid4, clock):
|
||||
return stix2.v21.Infrastructure(**INFRASTRUCTURE_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def grouping(uuid4, clock):
|
||||
return stix2.v21.Grouping(**GROUPING_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def malware(uuid4, clock):
|
||||
return stix2.v21.Malware(**MALWARE_KWARGS)
|
||||
|
|
|
@ -7,8 +7,10 @@ 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"
|
||||
GROUPING_ID = "grouping--753abcde-3141-5926-ace5-0a810b1ff996"
|
||||
IDENTITY_ID = "identity--311b2d2d-f010-4473-83ec-1edf84858f4c"
|
||||
INDICATOR_ID = "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7"
|
||||
INFRASTRUCTURE_ID = "infrastructure--3000ae1b-784c-f03d-8abc-0a625b2ff018"
|
||||
INTRUSION_SET_ID = "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29"
|
||||
LOCATION_ID = "location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64"
|
||||
MALWARE_ID = "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e"
|
||||
|
@ -70,6 +72,11 @@ COURSE_OF_ACTION_KWARGS = dict(
|
|||
name="Block",
|
||||
)
|
||||
|
||||
GROUPING_KWARGS = dict(
|
||||
name="Harry Potter and the Leet Hackers",
|
||||
context="suspicious-activity",
|
||||
)
|
||||
|
||||
IDENTITY_KWARGS = dict(
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
|
@ -78,6 +85,12 @@ IDENTITY_KWARGS = dict(
|
|||
INDICATOR_KWARGS = dict(
|
||||
indicator_types=['malicious-activity'],
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
valid_from="2017-01-01T12:34:56Z",
|
||||
)
|
||||
|
||||
INFRASTRUCTURE_KWARGS = dict(
|
||||
name="Poison Ivy C2",
|
||||
infrastructure_types=["command-and-control"],
|
||||
)
|
||||
|
||||
INTRUSION_SET_KWARGS = dict(
|
||||
|
@ -87,6 +100,7 @@ INTRUSION_SET_KWARGS = dict(
|
|||
MALWARE_KWARGS = dict(
|
||||
malware_types=['ransomware'],
|
||||
name="Cryptolocker",
|
||||
is_family=False,
|
||||
)
|
||||
|
||||
MALWARE_MORE_KWARGS = dict(
|
||||
|
@ -97,6 +111,7 @@ MALWARE_MORE_KWARGS = dict(
|
|||
malware_types=['ransomware'],
|
||||
name="Cryptolocker",
|
||||
description="A ransomware related to ...",
|
||||
is_family=False,
|
||||
)
|
||||
|
||||
OBSERVED_DATA_KWARGS = dict(
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
],
|
||||
"object_marking_refs": [
|
||||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
],
|
||||
"spec_version": "2.1",
|
||||
"type": "malware"
|
||||
"type": "malware",
|
||||
"is_family": false
|
||||
}
|
||||
],
|
||||
"type": "bundle"
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
],
|
||||
"object_marking_refs": [
|
||||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
],
|
||||
"object_marking_refs": [
|
||||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
],
|
||||
"spec_version": "2.1",
|
||||
"type": "malware"
|
||||
"type": "malware",
|
||||
"is_family": false
|
||||
}
|
||||
],
|
||||
"type": "bundle"
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
],
|
||||
"spec_version": "2.1",
|
||||
"type": "malware"
|
||||
"type": "malware",
|
||||
"is_family": false
|
||||
}
|
||||
],
|
||||
"type": "bundle"
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
"object_marking_refs": [
|
||||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
],
|
||||
"type": "malware"
|
||||
"type": "malware",
|
||||
"is_family": false
|
||||
}
|
||||
],
|
||||
"spec_version": "2.0",
|
||||
|
|
|
@ -31,7 +31,8 @@ EXPECTED_BUNDLE = """{
|
|||
"name": "Cryptolocker",
|
||||
"malware_types": [
|
||||
"ransomware"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
|
@ -72,6 +73,7 @@ EXPECTED_BUNDLE_DICT = {
|
|||
"malware_types": [
|
||||
"ransomware",
|
||||
],
|
||||
"is_family": False,
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
|
@ -244,6 +246,7 @@ def test_bundle_obj_id_found():
|
|||
"malware_types": [
|
||||
"ransomware",
|
||||
],
|
||||
"is_family": False,
|
||||
},
|
||||
{
|
||||
"type": "malware",
|
||||
|
@ -255,6 +258,7 @@ def test_bundle_obj_id_found():
|
|||
"malware_types": [
|
||||
"ransomware",
|
||||
],
|
||||
"is_family": False,
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
|
|
|
@ -31,6 +31,7 @@ BUNDLE = {
|
|||
"malware_types": [
|
||||
"ransomware",
|
||||
],
|
||||
"is_family": False,
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
|
|
|
@ -124,15 +124,13 @@ def rel_fs_store():
|
|||
|
||||
|
||||
def test_filesystem_source_nonexistent_folder():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.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):
|
||||
|
|
|
@ -16,6 +16,7 @@ stix_objs = [
|
|||
"remote-access-trojan",
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.997Z",
|
||||
"is_family": False,
|
||||
"name": "Poison Ivy",
|
||||
"type": "malware",
|
||||
},
|
||||
|
|
|
@ -219,7 +219,8 @@ def test_parse_malware():
|
|||
"name": "Cryptolocker",
|
||||
"malware_types": [
|
||||
"ransomware"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}"""
|
||||
mal = env.parse(data, version="2.1")
|
||||
|
||||
|
@ -230,6 +231,7 @@ def test_parse_malware():
|
|||
assert mal.modified == FAKE_TIME
|
||||
assert mal.malware_types == ['ransomware']
|
||||
assert mal.name == "Cryptolocker"
|
||||
assert not mal.is_family
|
||||
|
||||
|
||||
def test_creator_of():
|
||||
|
@ -351,6 +353,7 @@ def test_related_to_no_id(ds):
|
|||
mal = {
|
||||
"type": "malware",
|
||||
"name": "some variant",
|
||||
"is_family": False,
|
||||
}
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
env.related_to(mal)
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import FAKE_TIME, GROUPING_ID, GROUPING_KWARGS
|
||||
|
||||
EXPECTED_GROUPING = """{
|
||||
"type": "grouping",
|
||||
"spec_version": "2.1",
|
||||
"id": "grouping--753abcde-3141-5926-ace5-0a810b1ff996",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Harry Potter and the Leet Hackers",
|
||||
"context": "suspicious-activity"
|
||||
}"""
|
||||
|
||||
|
||||
def test_grouping_with_all_required_properties():
|
||||
now = dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
|
||||
grp = stix2.v21.Grouping(
|
||||
type="grouping",
|
||||
id=GROUPING_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
name="Harry Potter and the Leet Hackers",
|
||||
context="suspicious-activity",
|
||||
)
|
||||
|
||||
assert str(grp) == EXPECTED_GROUPING
|
||||
|
||||
|
||||
def test_grouping_autogenerated_properties(grouping):
|
||||
assert grouping.type == 'grouping'
|
||||
assert grouping.id == 'grouping--00000000-0000-4000-8000-000000000001'
|
||||
assert grouping.created == FAKE_TIME
|
||||
assert grouping.modified == FAKE_TIME
|
||||
assert grouping.name == "Harry Potter and the Leet Hackers"
|
||||
assert grouping.context == "suspicious-activity"
|
||||
|
||||
assert grouping['type'] == 'grouping'
|
||||
assert grouping['id'] == 'grouping--00000000-0000-4000-8000-000000000001'
|
||||
assert grouping['created'] == FAKE_TIME
|
||||
assert grouping['modified'] == FAKE_TIME
|
||||
assert grouping['name'] == "Harry Potter and the Leet Hackers"
|
||||
assert grouping['context'] == "suspicious-activity"
|
||||
|
||||
|
||||
def test_grouping_type_must_be_grouping():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Grouping(type='xxx', **GROUPING_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Grouping
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'grouping'."
|
||||
assert str(excinfo.value) == "Invalid value for Grouping 'type': must equal 'grouping'."
|
||||
|
||||
|
||||
def test_grouping_id_must_start_with_grouping():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Grouping(id='my-prefix--', **GROUPING_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Grouping
|
||||
assert excinfo.value.prop_name == "id"
|
||||
assert excinfo.value.reason == "must start with 'grouping--'."
|
||||
assert str(excinfo.value) == "Invalid value for Grouping 'id': must start with 'grouping--'."
|
||||
|
||||
|
||||
def test_grouping_required_properties():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.v21.Grouping()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Grouping
|
||||
assert excinfo.value.properties == ["context"]
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_grouping():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.v21.Grouping(my_custom_property="foo", **GROUPING_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Grouping
|
||||
assert excinfo.value.properties == ['my_custom_property']
|
||||
assert str(excinfo.value) == "Unexpected properties for Grouping: (my_custom_property)."
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
EXPECTED_GROUPING,
|
||||
{
|
||||
"type": "grouping",
|
||||
"spec_version": "2.1",
|
||||
"id": GROUPING_ID,
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Harry Potter and the Leet Hackers",
|
||||
"context": "suspicious-activity",
|
||||
},
|
||||
],
|
||||
)
|
||||
def test_parse_grouping(data):
|
||||
grp = stix2.parse(data)
|
||||
|
||||
assert grp.type == 'grouping'
|
||||
assert grp.spec_version == '2.1'
|
||||
assert grp.id == GROUPING_ID
|
||||
assert grp.created == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert grp.modified == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert grp.name == "Harry Potter and the Leet Hackers"
|
||||
assert grp.context == "suspicious-activity"
|
|
@ -98,8 +98,8 @@ def test_indicator_required_properties():
|
|||
stix2.v21.Indicator()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.properties == ["indicator_types", "pattern"]
|
||||
assert str(excinfo.value) == "No values for required properties for Indicator: (indicator_types, pattern)."
|
||||
assert excinfo.value.properties == ["indicator_types", "pattern", "valid_from"]
|
||||
assert str(excinfo.value) == "No values for required properties for Indicator: (indicator_types, pattern, valid_from)."
|
||||
|
||||
|
||||
def test_indicator_required_property_pattern():
|
||||
|
@ -107,7 +107,7 @@ def test_indicator_required_property_pattern():
|
|||
stix2.v21.Indicator(indicator_types=['malicious-activity'])
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.properties == ["pattern"]
|
||||
assert excinfo.value.properties == ["pattern", "valid_from"]
|
||||
|
||||
|
||||
def test_indicator_created_ref_invalid_format():
|
||||
|
@ -184,6 +184,7 @@ def test_invalid_indicator_pattern():
|
|||
stix2.v21.Indicator(
|
||||
indicator_types=['malicious-activity'],
|
||||
pattern="file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e'",
|
||||
valid_from="2017-01-01T12:34:56Z",
|
||||
)
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.prop_name == 'pattern'
|
||||
|
@ -193,6 +194,7 @@ def test_invalid_indicator_pattern():
|
|||
stix2.v21.Indicator(
|
||||
indicator_types=['malicious-activity'],
|
||||
pattern='[file:hashes.MD5 = "d41d8cd98f00b204e9800998ecf8427e"]',
|
||||
valid_from="2017-01-01T12:34:56Z",
|
||||
)
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.prop_name == 'pattern'
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import FAKE_TIME, INFRASTRUCTURE_ID, INFRASTRUCTURE_KWARGS
|
||||
|
||||
EXPECTED_INFRASTRUCTURE = """{
|
||||
"type": "infrastructure",
|
||||
"spec_version": "2.1",
|
||||
"id": "infrastructure--3000ae1b-784c-f03d-8abc-0a625b2ff018",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Poison Ivy C2",
|
||||
"infrastructure_types": [
|
||||
"command-and-control"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_infrastructure_with_all_required_properties():
|
||||
now = dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
|
||||
infra = stix2.v21.Infrastructure(
|
||||
type="infrastructure",
|
||||
id=INFRASTRUCTURE_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
name="Poison Ivy C2",
|
||||
infrastructure_types=["command-and-control"],
|
||||
)
|
||||
|
||||
assert str(infra) == EXPECTED_INFRASTRUCTURE
|
||||
|
||||
|
||||
def test_infrastructure_autogenerated_properties(infrastructure):
|
||||
assert infrastructure.type == 'infrastructure'
|
||||
assert infrastructure.id == 'infrastructure--00000000-0000-4000-8000-000000000001'
|
||||
assert infrastructure.created == FAKE_TIME
|
||||
assert infrastructure.modified == FAKE_TIME
|
||||
assert infrastructure.infrastructure_types == ['command-and-control']
|
||||
assert infrastructure.name == "Poison Ivy C2"
|
||||
|
||||
assert infrastructure['type'] == 'infrastructure'
|
||||
assert infrastructure['id'] == 'infrastructure--00000000-0000-4000-8000-000000000001'
|
||||
assert infrastructure['created'] == FAKE_TIME
|
||||
assert infrastructure['modified'] == FAKE_TIME
|
||||
assert infrastructure['infrastructure_types'] == ['command-and-control']
|
||||
assert infrastructure['name'] == "Poison Ivy C2"
|
||||
|
||||
|
||||
def test_infrastructure_type_must_be_infrastructure():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Infrastructure(type='xxx', **INFRASTRUCTURE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'infrastructure'."
|
||||
assert str(excinfo.value) == "Invalid value for Infrastructure 'type': must equal 'infrastructure'."
|
||||
|
||||
|
||||
def test_infrastructure_id_must_start_with_infrastructure():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Infrastructure(id='my-prefix--', **INFRASTRUCTURE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.prop_name == "id"
|
||||
assert excinfo.value.reason == "must start with 'infrastructure--'."
|
||||
assert str(excinfo.value) == "Invalid value for Infrastructure 'id': must start with 'infrastructure--'."
|
||||
|
||||
|
||||
def test_infrastructure_required_properties():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.v21.Infrastructure()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.properties == ["infrastructure_types", "name"]
|
||||
|
||||
|
||||
def test_infrastructure_required_property_name():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.v21.Infrastructure(infrastructure_types=['command-and-control'])
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.properties == ["name"]
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_infrastructure():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.v21.Infrastructure(my_custom_property="foo", **INFRASTRUCTURE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.properties == ['my_custom_property']
|
||||
assert str(excinfo.value) == "Unexpected properties for Infrastructure: (my_custom_property)."
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
EXPECTED_INFRASTRUCTURE,
|
||||
{
|
||||
"type": "infrastructure",
|
||||
"spec_version": "2.1",
|
||||
"id": INFRASTRUCTURE_ID,
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"infrastructure_types": ["command-and-control"],
|
||||
"name": "Poison Ivy C2",
|
||||
},
|
||||
],
|
||||
)
|
||||
def test_parse_infrastructure(data):
|
||||
infra = stix2.parse(data)
|
||||
|
||||
assert infra.type == 'infrastructure'
|
||||
assert infra.spec_version == '2.1'
|
||||
assert infra.id == INFRASTRUCTURE_ID
|
||||
assert infra.created == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert infra.modified == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert infra.infrastructure_types == ['command-and-control']
|
||||
assert infra.name == 'Poison Ivy C2'
|
||||
|
||||
|
||||
def test_parse_infrastructure_kill_chain_phases():
|
||||
kill_chain = """
|
||||
"kill_chain_phases": [
|
||||
{
|
||||
"kill_chain_name": "lockheed-martin-cyber-kill-chain",
|
||||
"phase_name": "reconnaissance"
|
||||
}
|
||||
]"""
|
||||
data = EXPECTED_INFRASTRUCTURE.replace('infrastructure"', 'infrastructure",%s' % kill_chain)
|
||||
infra = stix2.parse(data, version="2.1")
|
||||
assert infra.kill_chain_phases[0].kill_chain_name == "lockheed-martin-cyber-kill-chain"
|
||||
assert infra.kill_chain_phases[0].phase_name == "reconnaissance"
|
||||
assert infra['kill_chain_phases'][0]['kill_chain_name'] == "lockheed-martin-cyber-kill-chain"
|
||||
assert infra['kill_chain_phases'][0]['phase_name'] == "reconnaissance"
|
||||
|
||||
|
||||
def test_parse_infrastructure_clean_kill_chain_phases():
|
||||
kill_chain = """
|
||||
"kill_chain_phases": [
|
||||
{
|
||||
"kill_chain_name": "lockheed-martin-cyber-kill-chain",
|
||||
"phase_name": 1
|
||||
}
|
||||
]"""
|
||||
data = EXPECTED_INFRASTRUCTURE.replace('2.1"', '2.1",%s' % kill_chain)
|
||||
infra = stix2.parse(data, version="2.1")
|
||||
assert infra['kill_chain_phases'][0]['phase_name'] == "1"
|
||||
|
||||
|
||||
def test_infrastructure_invalid_last_before_first():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.v21.Infrastructure(first_seen="2017-01-01T12:34:56.000Z", last_seen="2017-01-01T12:33:56.000Z", **INFRASTRUCTURE_KWARGS)
|
||||
|
||||
assert "'last_seen' must be greater than or equal to 'first_seen'" in str(excinfo.value)
|
|
@ -17,7 +17,8 @@ EXPECTED_MALWARE = """{
|
|||
"name": "Cryptolocker",
|
||||
"malware_types": [
|
||||
"ransomware"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}"""
|
||||
|
||||
|
||||
|
@ -31,6 +32,7 @@ def test_malware_with_all_required_properties():
|
|||
modified=now,
|
||||
malware_types=["ransomware"],
|
||||
name="Cryptolocker",
|
||||
is_family=False,
|
||||
)
|
||||
|
||||
assert str(mal) == EXPECTED_MALWARE
|
||||
|
@ -77,7 +79,7 @@ def test_malware_required_properties():
|
|||
stix2.v21.Malware()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Malware
|
||||
assert excinfo.value.properties == ["malware_types", "name"]
|
||||
assert excinfo.value.properties == ["is_family", "malware_types", "name"]
|
||||
|
||||
|
||||
def test_malware_required_property_name():
|
||||
|
@ -85,7 +87,7 @@ def test_malware_required_property_name():
|
|||
stix2.v21.Malware(malware_types=['ransomware'])
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Malware
|
||||
assert excinfo.value.properties == ["name"]
|
||||
assert excinfo.value.properties == ["is_family", "name"]
|
||||
|
||||
|
||||
def test_cannot_assign_to_malware_attributes(malware):
|
||||
|
@ -115,6 +117,7 @@ def test_invalid_kwarg_to_malware():
|
|||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"malware_types": ["ransomware"],
|
||||
"name": "Cryptolocker",
|
||||
"is_family": False,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
@ -128,6 +131,7 @@ def test_parse_malware(data):
|
|||
assert mal.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert mal.malware_types == ['ransomware']
|
||||
assert mal.name == 'Cryptolocker'
|
||||
assert not mal.is_family
|
||||
|
||||
|
||||
def test_parse_malware_invalid_labels():
|
||||
|
@ -164,3 +168,10 @@ def test_parse_malware_clean_kill_chain_phases():
|
|||
data = EXPECTED_MALWARE.replace('2.1"', '2.1",%s' % kill_chain)
|
||||
mal = stix2.parse(data, version="2.1")
|
||||
assert mal['kill_chain_phases'][0]['phase_name'] == "1"
|
||||
|
||||
|
||||
def test_malware_invalid_last_before_first():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.v21.Malware(first_seen="2017-01-01T12:34:56.000Z", last_seen="2017-01-01T12:33:56.000Z", **MALWARE_KWARGS)
|
||||
|
||||
assert "'last_seen' must be greater than or equal to 'first_seen'" in str(excinfo.value)
|
||||
|
|
|
@ -257,7 +257,7 @@ def test_and_observable_expression():
|
|||
|
||||
|
||||
def test_invalid_and_observable_expression():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.AndBooleanExpression([
|
||||
stix2.EqualityComparisonExpression(
|
||||
"user-account:display_name",
|
||||
|
@ -268,7 +268,6 @@ def test_invalid_and_observable_expression():
|
|||
stix2.StringConstant("admin"),
|
||||
),
|
||||
])
|
||||
assert "All operands to an 'AND' expression must have the same object type" in str(excinfo)
|
||||
|
||||
|
||||
def test_hex():
|
||||
|
@ -352,30 +351,26 @@ def test_list2():
|
|||
|
||||
|
||||
def test_invalid_constant_type():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.IntegerConstant('foo')
|
||||
assert 'must be an integer' in str(excinfo)
|
||||
|
||||
|
||||
def test_invalid_timestamp_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.FloatConstant('foo')
|
||||
assert 'must be a float' in str(excinfo)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -400,9 +395,8 @@ def test_boolean_constant(data, result):
|
|||
|
||||
|
||||
def test_invalid_boolean_constant():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.BooleanConstant('foo')
|
||||
assert 'must be a boolean' in str(excinfo)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -412,21 +406,18 @@ def test_invalid_boolean_constant():
|
|||
],
|
||||
)
|
||||
def test_invalid_hash_constant(hashtype, data):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.BinaryConstant('foo')
|
||||
assert 'must contain a base64' in str(excinfo)
|
||||
|
||||
|
||||
def test_escape_quotes_and_backslashes():
|
||||
|
@ -459,15 +450,13 @@ def test_repeat_qualifier():
|
|||
|
||||
|
||||
def test_invalid_repeat_qualifier():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
stix2.WithinQualifier('foo')
|
||||
assert 'is not a valid argument for a Within Qualifier' in str(excinfo)
|
||||
|
||||
|
||||
def test_startstop_qualifier():
|
||||
|
@ -485,19 +474,17 @@ def test_startstop_qualifier():
|
|||
|
||||
|
||||
def test_invalid_startstop_qualifier():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
with pytest.raises(ValueError):
|
||||
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():
|
||||
|
|
|
@ -230,6 +230,7 @@ def test_remove_custom_stix_property():
|
|||
malware_types=["rootkit"],
|
||||
x_custom="armada",
|
||||
allow_custom=True,
|
||||
is_family=False,
|
||||
)
|
||||
|
||||
mal_nc = stix2.utils.remove_custom_stix(mal)
|
||||
|
|
|
@ -199,7 +199,7 @@ def test_workbench_related():
|
|||
def test_workbench_related_with_filters():
|
||||
malware = Malware(
|
||||
malware_types=["ransomware"], name="CryptorBit",
|
||||
created_by_ref=IDENTITY_ID,
|
||||
created_by_ref=IDENTITY_ID, is_family=False,
|
||||
)
|
||||
rel = Relationship(malware.id, 'variant-of', MALWARE_ID)
|
||||
save([malware, rel])
|
||||
|
|
|
@ -32,9 +32,10 @@ from .observables import (
|
|||
X509Certificate, X509V3ExtenstionsType,
|
||||
)
|
||||
from .sdo import (
|
||||
AttackPattern, Campaign, CourseOfAction, CustomObject, Identity, Indicator,
|
||||
IntrusionSet, Location, Malware, MalwareAnalysis, Note, ObservedData,
|
||||
Opinion, Report, ThreatActor, Tool, Vulnerability,
|
||||
AttackPattern, Campaign, CourseOfAction, CustomObject, Grouping, Identity,
|
||||
Indicator, Infrastructure, IntrusionSet, Location, Malware,
|
||||
MalwareAnalysis, Note, ObservedData, Opinion, Report, ThreatActor, Tool,
|
||||
Vulnerability,
|
||||
)
|
||||
from .sro import Relationship, Sighting
|
||||
|
||||
|
@ -43,8 +44,10 @@ OBJ_MAP = {
|
|||
'bundle': Bundle,
|
||||
'campaign': Campaign,
|
||||
'course-of-action': CourseOfAction,
|
||||
'grouping': Grouping,
|
||||
'identity': Identity,
|
||||
'indicator': Indicator,
|
||||
'infrastructure': Infrastructure,
|
||||
'intrusion-set': IntrusionSet,
|
||||
'language-content': LanguageContent,
|
||||
'location': Location,
|
||||
|
|
|
@ -122,6 +122,34 @@ class CourseOfAction(STIXDomainObject):
|
|||
)
|
||||
|
||||
|
||||
class Grouping(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
"""For more detailed information on this object's properties, see
|
||||
`the STIX 2.1 specification <link here>`__.
|
||||
"""
|
||||
|
||||
_type = 'grouping'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('created_by_ref', ReferenceProperty(type='identity', spec_version='2.1')),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('context', StringProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty)),
|
||||
])
|
||||
|
||||
|
||||
class Identity(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
"""For more detailed information on this object's properties, see
|
||||
|
@ -170,7 +198,7 @@ class Indicator(STIXDomainObject):
|
|||
('description', StringProperty()),
|
||||
('indicator_types', ListProperty(StringProperty, required=True)),
|
||||
('pattern', PatternProperty(required=True)),
|
||||
('valid_from', TimestampProperty(default=lambda: NOW)),
|
||||
('valid_from', TimestampProperty(default=lambda: NOW, required=True)),
|
||||
('valid_until', TimestampProperty()),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
|
@ -193,6 +221,46 @@ class Indicator(STIXDomainObject):
|
|||
raise ValueError(msg.format(self))
|
||||
|
||||
|
||||
class Infrastructure(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
"""For more detailed information on this object's properties, see
|
||||
`the STIX 2.1 specification <link here>`__.
|
||||
"""
|
||||
|
||||
_type = 'infrastructure'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('created_by_ref', ReferenceProperty(type='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('infrastructure_types', ListProperty(StringProperty, required=True)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(self.__class__, self)._check_object_constraints()
|
||||
|
||||
first_seen = self.get('first_seen')
|
||||
last_seen = self.get('last_seen')
|
||||
|
||||
if first_seen and last_seen and last_seen < first_seen:
|
||||
msg = "{0.id} 'last_seen' must be greater than or equal to 'first_seen'"
|
||||
raise ValueError(msg.format(self))
|
||||
|
||||
|
||||
class IntrusionSet(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
"""For more detailed information on this object's properties, see
|
||||
|
@ -346,7 +414,16 @@ class Malware(STIXDomainObject):
|
|||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('malware_types', ListProperty(StringProperty, required=True)),
|
||||
('is_family', BooleanProperty(required=True)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('os_execution_envs', ListProperty(StringProperty)),
|
||||
('architecture_execution_envs', ListProperty(StringProperty)),
|
||||
('implementation_languages', ListProperty(StringProperty)),
|
||||
('capabilities', ListProperty(StringProperty)),
|
||||
('sample_refs', ListProperty(ReferenceProperty(spec_version='2.1'))),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
|
@ -356,6 +433,16 @@ class Malware(STIXDomainObject):
|
|||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(self.__class__, self)._check_object_constraints()
|
||||
|
||||
first_seen = self.get('first_seen')
|
||||
last_seen = self.get('last_seen')
|
||||
|
||||
if first_seen and last_seen and last_seen < first_seen:
|
||||
msg = "{0.id} 'last_seen' must be greater than or equal to 'first_seen'"
|
||||
raise ValueError(msg.format(self))
|
||||
|
||||
|
||||
class MalwareAnalysis(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
|
@ -596,6 +683,7 @@ class Tool(STIXDomainObject):
|
|||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('tool_types', ListProperty(StringProperty, required=True)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('tool_version', StringProperty()),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
|
|
Loading…
Reference in New Issue