Merge pull request #275 from khdesai/stix21master

Stix21master, add infrastructure, grouping to the working branch
master
Emmanuelle Vargas-Gonzalez 2019-07-10 10:35:57 -04:00 committed by GitHub
commit b1fa177f07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 469 additions and 83 deletions

View File

@ -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)

View File

@ -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():

View File

@ -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)

View File

@ -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(

View File

@ -24,5 +24,6 @@
],
"object_marking_refs": [
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
]
],
"is_family": false
}

View File

@ -27,7 +27,8 @@
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
],
"spec_version": "2.1",
"type": "malware"
"type": "malware",
"is_family": false
}
],
"type": "bundle"

View File

@ -24,5 +24,6 @@
],
"object_marking_refs": [
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
]
],
"is_family": false
}

View File

@ -24,5 +24,6 @@
],
"object_marking_refs": [
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
]
],
"is_family": false
}

View File

@ -27,7 +27,8 @@
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
],
"spec_version": "2.1",
"type": "malware"
"type": "malware",
"is_family": false
}
],
"type": "bundle"

View File

@ -27,7 +27,8 @@
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
],
"spec_version": "2.1",
"type": "malware"
"type": "malware",
"is_family": false
}
],
"type": "bundle"

View File

@ -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",

View File

@ -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",

View File

@ -31,6 +31,7 @@ BUNDLE = {
"malware_types": [
"ransomware",
],
"is_family": False,
},
{
"type": "relationship",

View File

@ -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):

View File

@ -16,6 +16,7 @@ stix_objs = [
"remote-access-trojan",
],
"modified": "2017-01-27T13:49:53.997Z",
"is_family": False,
"name": "Poison Ivy",
"type": "malware",
},

View File

@ -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)

View File

@ -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"

View File

@ -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'

View File

@ -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)

View File

@ -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)

View File

@ -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():

View File

@ -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)

View File

@ -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])

View File

@ -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,

View File

@ -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)),