parent
07ccf9ec03
commit
e01ce132db
|
@ -176,7 +176,7 @@ class _STIXBase(collections.Mapping):
|
|||
if 'modified' not in kwargs:
|
||||
kwargs['modified'] = get_timestamp()
|
||||
else:
|
||||
new_modified_property = parse_into_datetime(kwargs['modified'])
|
||||
new_modified_property = parse_into_datetime(kwargs['modified'], precision='millisecond')
|
||||
if new_modified_property < self.modified:
|
||||
raise InvalidValueError(cls, 'modified', "The new modified datetime cannot be before the current modified datatime.")
|
||||
new_obj_inner.update(kwargs)
|
||||
|
|
|
@ -7,8 +7,8 @@ from .utils import NOW
|
|||
|
||||
COMMON_PROPERTIES = {
|
||||
# 'type' and 'id' should be defined on each individual type
|
||||
'created': TimestampProperty(default=lambda: NOW),
|
||||
'modified': TimestampProperty(default=lambda: NOW),
|
||||
'created': TimestampProperty(default=lambda: NOW, precision='millisecond'),
|
||||
'modified': TimestampProperty(default=lambda: NOW, precision='millisecond'),
|
||||
'external_references': ListProperty(ExternalReference),
|
||||
'revoked': BooleanProperty(),
|
||||
'labels': ListProperty(StringProperty),
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
import base64
|
||||
import binascii
|
||||
import collections
|
||||
import datetime as dt
|
||||
import inspect
|
||||
import re
|
||||
import uuid
|
||||
|
||||
from dateutil import parser
|
||||
import pytz
|
||||
from six import text_type
|
||||
|
||||
from .base import _Observable, _STIXBase
|
||||
from .exceptions import DictionaryKeyError
|
||||
from .utils import get_dict
|
||||
from .utils import get_dict, parse_into_datetime
|
||||
|
||||
|
||||
class Property(object):
|
||||
|
@ -215,26 +212,15 @@ class BooleanProperty(Property):
|
|||
|
||||
class TimestampProperty(Property):
|
||||
|
||||
def clean(self, value):
|
||||
if isinstance(value, dt.date):
|
||||
if hasattr(value, 'hour'):
|
||||
return value
|
||||
else:
|
||||
# Add a time component
|
||||
return dt.datetime.combine(value, dt.time(), tzinfo=pytz.utc)
|
||||
def __init__(self, precision=None, **kwargs):
|
||||
self.precision = precision
|
||||
super(TimestampProperty, self).__init__(**kwargs)
|
||||
|
||||
# value isn't a date or datetime object so assume it's a string
|
||||
def clean(self, value):
|
||||
try:
|
||||
parsed = parser.parse(value)
|
||||
except TypeError:
|
||||
# Unknown format
|
||||
raise ValueError("must be a datetime object, date object, or "
|
||||
"timestamp string in a recognizable format.")
|
||||
if parsed.tzinfo:
|
||||
return parsed.astimezone(pytz.utc)
|
||||
else:
|
||||
# Doesn't have timezone info in the string; assume UTC
|
||||
return pytz.utc.localize(parsed)
|
||||
return parse_into_datetime(value, self.precision)
|
||||
except ValueError:
|
||||
raise
|
||||
|
||||
|
||||
class ObservableProperty(Property):
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import datetime as dt
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
@ -13,12 +12,12 @@ from .constants import (FAKE_TIME, INDICATOR_KWARGS, MALWARE_KWARGS,
|
|||
@pytest.fixture
|
||||
def clock(monkeypatch):
|
||||
|
||||
class mydatetime(dt.datetime):
|
||||
class mydatetime(stix2.utils.STIXdatetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return FAKE_TIME
|
||||
|
||||
monkeypatch.setattr(dt, 'datetime', mydatetime)
|
||||
monkeypatch.setattr(stix2.utils, 'STIXdatetime', mydatetime)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -9,7 +9,7 @@ from .constants import ATTACK_PATTERN_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2016-05-12T08:17:27Z",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"description": "...",
|
||||
"external_references": [
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ EXPECTED = """{
|
|||
}
|
||||
],
|
||||
"id": "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
"modified": "2016-05-12T08:17:27Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"name": "Spear Phishing",
|
||||
"type": "attack-pattern"
|
||||
}"""
|
||||
|
@ -27,8 +27,8 @@ EXPECTED = """{
|
|||
def test_attack_pattern_example():
|
||||
ap = stix2.AttackPattern(
|
||||
id="attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
created="2016-05-12T08:17:27Z",
|
||||
modified="2016-05-12T08:17:27Z",
|
||||
created="2016-05-12T08:17:27.000Z",
|
||||
modified="2016-05-12T08:17:27.000Z",
|
||||
name="Spear Phishing",
|
||||
external_references=[{
|
||||
"source_name": "capec",
|
||||
|
@ -45,8 +45,8 @@ def test_attack_pattern_example():
|
|||
{
|
||||
"type": "attack-pattern",
|
||||
"id": "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
"created": "2016-05-12T08:17:27Z",
|
||||
"modified": "2016-05-12T08:17:27Z",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"description": "...",
|
||||
"external_references": [
|
||||
{
|
||||
|
|
|
@ -7,30 +7,30 @@ EXPECTED_BUNDLE = """{
|
|||
"id": "bundle--00000000-0000-0000-0000-000000000004",
|
||||
"objects": [
|
||||
{
|
||||
"created": "2017-01-01T12:34:56Z",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"id": "indicator--00000000-0000-0000-0000-000000000001",
|
||||
"labels": [
|
||||
"malicious-activity"
|
||||
],
|
||||
"modified": "2017-01-01T12:34:56Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-01T12:34:56Z"
|
||||
},
|
||||
{
|
||||
"created": "2017-01-01T12:34:56Z",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"id": "malware--00000000-0000-0000-0000-000000000002",
|
||||
"labels": [
|
||||
"ransomware"
|
||||
],
|
||||
"modified": "2017-01-01T12:34:56Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Cryptolocker",
|
||||
"type": "malware"
|
||||
},
|
||||
{
|
||||
"created": "2017-01-01T12:34:56Z",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"id": "relationship--00000000-0000-0000-0000-000000000003",
|
||||
"modified": "2017-01-01T12:34:56Z",
|
||||
"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",
|
||||
|
|
|
@ -9,11 +9,11 @@ from .constants import CAMPAIGN_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2016-04-06T20:03:00Z",
|
||||
"created": "2016-04-06T20:03:00.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"modified": "2016-04-06T20:03:00Z",
|
||||
"modified": "2016-04-06T20:03:00.000Z",
|
||||
"name": "Green Group Attacks Against Finance",
|
||||
"type": "campaign"
|
||||
}"""
|
||||
|
@ -23,8 +23,8 @@ 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",
|
||||
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."
|
||||
)
|
||||
|
@ -37,8 +37,8 @@ def test_campaign_example():
|
|||
{
|
||||
"type": "campaign",
|
||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created": "2016-04-06T20:03:00Z",
|
||||
"modified": "2016-04-06T20:03:00Z",
|
||||
"created": "2016-04-06T20:03:00.000Z",
|
||||
"modified": "2016-04-06T20:03:00.000Z",
|
||||
"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",
|
||||
|
|
|
@ -9,11 +9,11 @@ from .constants import COURSE_OF_ACTION_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2016-04-06T20:03:48Z",
|
||||
"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:48Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"type": "course-of-action"
|
||||
}"""
|
||||
|
@ -23,8 +23,8 @@ 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:48Z",
|
||||
modified="2016-04-06T20:03:48Z",
|
||||
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 ..."
|
||||
)
|
||||
|
@ -35,11 +35,11 @@ def test_course_of_action_example():
|
|||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2016-04-06T20:03:48Z",
|
||||
"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:48Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"type": "course-of-action"
|
||||
},
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import datetime as dt
|
||||
import uuid
|
||||
|
||||
from stix2 import utils
|
||||
|
||||
from .constants import FAKE_TIME
|
||||
|
||||
|
||||
def test_clock(clock):
|
||||
assert dt.datetime.now() == FAKE_TIME
|
||||
assert utils.STIXdatetime.now() == FAKE_TIME
|
||||
|
||||
|
||||
def test_my_uuid4_fixture(uuid4):
|
||||
|
|
|
@ -9,10 +9,10 @@ from .constants import IDENTITY_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"created": "2015-12-21T19:59:11.000Z",
|
||||
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
"identity_class": "individual",
|
||||
"modified": "2015-12-21T19:59:11Z",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "John Smith",
|
||||
"type": "identity"
|
||||
}"""
|
||||
|
@ -21,8 +21,8 @@ EXPECTED = """{
|
|||
def test_identity_example():
|
||||
identity = stix2.Identity(
|
||||
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
created="2015-12-21T19:59:11Z",
|
||||
modified="2015-12-21T19:59:11Z",
|
||||
created="2015-12-21T19:59:11.000Z",
|
||||
modified="2015-12-21T19:59:11.000Z",
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
)
|
||||
|
@ -33,10 +33,10 @@ def test_identity_example():
|
|||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"created": "2015-12-21T19:59:11.000Z",
|
||||
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
"identity_class": "individual",
|
||||
"modified": "2015-12-21T19:59:11Z",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "John Smith",
|
||||
"type": "identity"
|
||||
},
|
||||
|
@ -56,8 +56,8 @@ def test_parse_no_type():
|
|||
stix2.parse("""
|
||||
{
|
||||
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"modified": "2015-12-21T19:59:11Z",
|
||||
"created": "2015-12-21T19:59:11.000Z",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "John Smith",
|
||||
"identity_class": "individual"
|
||||
}""")
|
||||
|
|
|
@ -10,22 +10,22 @@ from .constants import FAKE_TIME, INDICATOR_ID, INDICATOR_KWARGS
|
|||
|
||||
|
||||
EXPECTED_INDICATOR = """{
|
||||
"created": "2017-01-01T00:00:01Z",
|
||||
"created": "2017-01-01T00:00:01.000Z",
|
||||
"id": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"labels": [
|
||||
"malicious-activity"
|
||||
],
|
||||
"modified": "2017-01-01T00:00:01Z",
|
||||
"modified": "2017-01-01T00:00:01.000Z",
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"type": "indicator",
|
||||
"valid_from": "1970-01-01T00:00:01Z"
|
||||
}"""
|
||||
|
||||
EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
||||
created=datetime.datetime(2017, 1, 1, 0, 0, 1, tzinfo=<UTC>),
|
||||
created=STIXdatetime(2017, 1, 1, 0, 0, 1, tzinfo=<UTC>),
|
||||
id='indicator--01234567-89ab-cdef-0123-456789abcdef',
|
||||
labels=['malicious-activity'],
|
||||
modified=datetime.datetime(2017, 1, 1, 0, 0, 1, tzinfo=<UTC>),
|
||||
modified=STIXdatetime(2017, 1, 1, 0, 0, 1, tzinfo=<UTC>),
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
type='indicator',
|
||||
valid_from=datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=<UTC>)
|
||||
|
@ -48,6 +48,8 @@ def test_indicator_with_all_required_properties():
|
|||
|
||||
assert str(ind) == EXPECTED_INDICATOR
|
||||
rep = re.sub(r"(\[|=| )u('|\"|\\\'|\\\")", r"\g<1>\g<2>", repr(ind))
|
||||
print(rep)
|
||||
print(EXPECTED_INDICATOR_REPR)
|
||||
assert rep == EXPECTED_INDICATOR_REPR
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ EXPECTED = """{
|
|||
"aliases": [
|
||||
"Zookeeper"
|
||||
],
|
||||
"created": "2016-04-06T20:03:48Z",
|
||||
"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": [
|
||||
|
@ -21,7 +21,7 @@ EXPECTED = """{
|
|||
"damage"
|
||||
],
|
||||
"id": "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
|
||||
"modified": "2016-04-06T20:03:48Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Bobcat Breakin",
|
||||
"type": "intrusion-set"
|
||||
}"""
|
||||
|
@ -31,8 +31,8 @@ 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:48Z",
|
||||
modified="2016-04-06T20:03:48Z",
|
||||
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"],
|
||||
|
@ -48,7 +48,7 @@ def test_intrusion_set_example():
|
|||
"aliases": [
|
||||
"Zookeeper"
|
||||
],
|
||||
"created": "2016-04-06T20:03:48Z",
|
||||
"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": [
|
||||
|
@ -57,7 +57,7 @@ def test_intrusion_set_example():
|
|||
"damage"
|
||||
],
|
||||
"id": "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
|
||||
"modified": "2016-04-06T20:03:48Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Bobcat Breakin",
|
||||
"type": "intrusion-set"
|
||||
},
|
||||
|
|
|
@ -10,12 +10,12 @@ from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS
|
|||
|
||||
|
||||
EXPECTED_MALWARE = """{
|
||||
"created": "2016-05-12T08:17:27Z",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"id": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
||||
"labels": [
|
||||
"ransomware"
|
||||
],
|
||||
"modified": "2016-05-12T08:17:27Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"name": "Cryptolocker",
|
||||
"type": "malware"
|
||||
}"""
|
||||
|
@ -109,8 +109,8 @@ def test_invalid_kwarg_to_malware():
|
|||
{
|
||||
"type": "malware",
|
||||
"id": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
||||
"created": "2016-05-12T08:17:27Z",
|
||||
"modified": "2016-05-12T08:17:27Z",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"labels": ["ransomware"],
|
||||
"name": "Cryptolocker",
|
||||
},
|
||||
|
|
|
@ -40,7 +40,7 @@ EXPECTED_GRANULAR_MARKING = """{
|
|||
}"""
|
||||
|
||||
EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
||||
"created": "2016-04-06T20:03:00Z",
|
||||
"created": "2016-04-06T20:03:00.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
"granular_markings": [
|
||||
|
@ -52,7 +52,7 @@ EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
|||
}
|
||||
],
|
||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"modified": "2016-04-06T20:03:00Z",
|
||||
"modified": "2016-04-06T20:03:00.000Z",
|
||||
"name": "Green Group Attacks Against Finance",
|
||||
"type": "campaign"
|
||||
}"""
|
||||
|
|
|
@ -10,12 +10,12 @@ from .constants import OBSERVED_DATA_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2016-04-06T19:58:16Z",
|
||||
"created": "2016-04-06T19:58:16.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"first_observed": "2015-12-21T19:00:00Z",
|
||||
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
"last_observed": "2015-12-21T19:00:00Z",
|
||||
"modified": "2016-04-06T19:58:16Z",
|
||||
"modified": "2016-04-06T19:58:16.000Z",
|
||||
"number_observed": 50,
|
||||
"objects": {
|
||||
"0": {
|
||||
|
@ -31,8 +31,8 @@ def test_observed_data_example():
|
|||
observed_data = stix2.ObservedData(
|
||||
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T19:58:16Z",
|
||||
modified="2016-04-06T19:58:16Z",
|
||||
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,
|
||||
|
@ -48,12 +48,12 @@ def test_observed_data_example():
|
|||
|
||||
|
||||
EXPECTED_WITH_REF = """{
|
||||
"created": "2016-04-06T19:58:16Z",
|
||||
"created": "2016-04-06T19:58:16.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"first_observed": "2015-12-21T19:00:00Z",
|
||||
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
"last_observed": "2015-12-21T19:00:00Z",
|
||||
"modified": "2016-04-06T19:58:16Z",
|
||||
"modified": "2016-04-06T19:58:16.000Z",
|
||||
"number_observed": 50,
|
||||
"objects": {
|
||||
"0": {
|
||||
|
@ -76,8 +76,8 @@ def test_observed_data_example_with_refs():
|
|||
observed_data = stix2.ObservedData(
|
||||
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T19:58:16Z",
|
||||
modified="2016-04-06T19:58:16Z",
|
||||
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,
|
||||
|
@ -102,8 +102,8 @@ def test_observed_data_example_with_bad_refs():
|
|||
stix2.ObservedData(
|
||||
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T19:58:16Z",
|
||||
modified="2016-04-06T19:58:16Z",
|
||||
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,
|
||||
|
@ -130,11 +130,11 @@ def test_observed_data_example_with_bad_refs():
|
|||
{
|
||||
"type": "observed-data",
|
||||
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
"created": "2016-04-06T19:58:16Z",
|
||||
"created": "2016-04-06T19:58:16.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"first_observed": "2015-12-21T19:00:00Z",
|
||||
"last_observed": "2015-12-21T19:00:00Z",
|
||||
"modified": "2016-04-06T19:58:16Z",
|
||||
"modified": "2016-04-06T19:58:16.000Z",
|
||||
"number_observed": 50,
|
||||
"objects": {
|
||||
"0": {
|
||||
|
@ -466,12 +466,12 @@ def test_parse_basic_tcp_traffic_with_error(data):
|
|||
|
||||
|
||||
EXPECTED_PROCESS_OD = """{
|
||||
"created": "2016-04-06T19:58:16Z",
|
||||
"created": "2016-04-06T19:58:16.000Z",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"first_observed": "2015-12-21T19:00:00Z",
|
||||
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
"last_observed": "2015-12-21T19:00:00Z",
|
||||
"modified": "2016-04-06T19:58:16Z",
|
||||
"modified": "2016-04-06T19:58:16.000Z",
|
||||
"number_observed": 50,
|
||||
"objects": {
|
||||
"0": {
|
||||
|
@ -499,8 +499,8 @@ def test_observed_data_with_process_example():
|
|||
observed_data = stix2.ObservedData(
|
||||
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T19:58:16Z",
|
||||
modified="2016-04-06T19:58:16Z",
|
||||
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,
|
||||
|
|
|
@ -10,9 +10,9 @@ from .constants import (FAKE_TIME, INDICATOR_ID, MALWARE_ID, RELATIONSHIP_ID,
|
|||
|
||||
|
||||
EXPECTED_RELATIONSHIP = """{
|
||||
"created": "2016-04-06T20:06:37Z",
|
||||
"created": "2016-04-06T20:06:37.000Z",
|
||||
"id": "relationship--00000000-1111-2222-3333-444444444444",
|
||||
"modified": "2016-04-06T20:06:37Z",
|
||||
"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",
|
||||
|
|
|
@ -9,14 +9,14 @@ from .constants import INDICATOR_KWARGS, REPORT_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"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:11Z",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "The Black Vine Cyberespionage Group",
|
||||
"object_refs": [
|
||||
"indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
|
||||
|
@ -32,8 +32,8 @@ 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:11Z",
|
||||
modified="2015-12-21T19:59:11Z",
|
||||
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",
|
||||
|
@ -95,14 +95,14 @@ def test_report_example_objects_in_object_refs_with_bad_id():
|
|||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2015-12-21T19:59:11Z",
|
||||
"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:11Z",
|
||||
"modified": "2015-12-21T19:59:11.000Z",
|
||||
"name": "The Black Vine Cyberespionage Group",
|
||||
"object_refs": [
|
||||
"indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
|
||||
|
|
|
@ -9,9 +9,9 @@ from .constants import INDICATOR_ID, SIGHTING_ID, SIGHTING_KWARGS
|
|||
|
||||
|
||||
EXPECTED_SIGHTING = """{
|
||||
"created": "2016-04-06T20:06:37Z",
|
||||
"created": "2016-04-06T20:06:37.000Z",
|
||||
"id": "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb",
|
||||
"modified": "2016-04-06T20:06:37Z",
|
||||
"modified": "2016-04-06T20:06:37.000Z",
|
||||
"sighting_of_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"type": "sighting",
|
||||
"where_sighted_refs": [
|
||||
|
@ -20,9 +20,9 @@ EXPECTED_SIGHTING = """{
|
|||
}"""
|
||||
|
||||
BAD_SIGHTING = """{
|
||||
"created": "2016-04-06T20:06:37Z",
|
||||
"created": "2016-04-06T20:06:37.000Z",
|
||||
"id": "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb",
|
||||
"modified": "2016-04-06T20:06:37Z",
|
||||
"modified": "2016-04-06T20:06:37.000Z",
|
||||
"sighting_of_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||
"type": "sighting",
|
||||
"where_sighted_refs": [
|
||||
|
|
|
@ -9,14 +9,14 @@ from .constants import THREAT_ACTOR_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2016-04-06T20:03:48Z",
|
||||
"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:48Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Evil Org",
|
||||
"type": "threat-actor"
|
||||
}"""
|
||||
|
@ -26,8 +26,8 @@ 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:48Z",
|
||||
modified="2016-04-06T20:03:48Z",
|
||||
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"],
|
||||
|
@ -39,14 +39,14 @@ def test_threat_actor_example():
|
|||
@pytest.mark.parametrize("data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2016-04-06T20:03:48Z",
|
||||
"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:48Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Evil Org",
|
||||
"type": "threat-actor"
|
||||
},
|
||||
|
|
|
@ -9,13 +9,13 @@ from .constants import TOOL_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2016-04-06T20:03:48Z",
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"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",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "VNC",
|
||||
"type": "tool"
|
||||
}"""
|
||||
|
@ -25,8 +25,8 @@ 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:48Z",
|
||||
modified="2016-04-06T20:03:48Z",
|
||||
created="2016-04-06T20:03:48.000Z",
|
||||
modified="2016-04-06T20:03:48.000Z",
|
||||
name="VNC",
|
||||
labels=["remote-access"],
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ from .constants import VULNERABILITY_ID
|
|||
|
||||
|
||||
EXPECTED = """{
|
||||
"created": "2016-05-12T08:17:27Z",
|
||||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"external_references": [
|
||||
{
|
||||
"external_id": "CVE-2016-1234",
|
||||
|
@ -17,7 +17,7 @@ EXPECTED = """{
|
|||
}
|
||||
],
|
||||
"id": "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
"modified": "2016-05-12T08:17:27Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"name": "CVE-2016-1234",
|
||||
"type": "vulnerability"
|
||||
}"""
|
||||
|
@ -26,8 +26,8 @@ EXPECTED = """{
|
|||
def test_vulnerability_example():
|
||||
vulnerability = stix2.Vulnerability(
|
||||
id="vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
created="2016-05-12T08:17:27Z",
|
||||
modified="2016-05-12T08:17:27Z",
|
||||
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',
|
||||
|
|
|
@ -12,15 +12,34 @@ import pytz
|
|||
NOW = object()
|
||||
|
||||
|
||||
class STIXdatetime(dt.datetime):
|
||||
def __new__(cls, *args, **kwargs):
|
||||
precision = kwargs.pop('precision', None)
|
||||
if isinstance(args[0], dt.datetime): # Allow passing in a datetime object
|
||||
dttm = args[0]
|
||||
args = (dttm.year,)
|
||||
kwargs['month'] = dttm.month
|
||||
kwargs['day'] = dttm.day
|
||||
kwargs['hour'] = dttm.hour
|
||||
kwargs['minute'] = dttm.minute
|
||||
kwargs['second'] = dttm.second
|
||||
kwargs['microsecond'] = dttm.microsecond
|
||||
kwargs['tzinfo'] = dttm.tzinfo
|
||||
self = dt.datetime.__new__(cls, *args, **kwargs)
|
||||
self.precision = precision
|
||||
return self
|
||||
|
||||
|
||||
def get_timestamp():
|
||||
return dt.datetime.now(tz=pytz.UTC)
|
||||
return STIXdatetime.now(tz=pytz.UTC)
|
||||
|
||||
|
||||
def format_datetime(dttm):
|
||||
# 1. Convert to timezone-aware
|
||||
# 2. Convert to UTC
|
||||
# 3. Format in ISO format
|
||||
# 4. Add subsecond value if non-zero
|
||||
# 4. Ensure correct precision
|
||||
# 4a. Add subsecond value if non-zero and precision not defined
|
||||
# 5. Add "Z"
|
||||
|
||||
if dttm.tzinfo is None or dttm.tzinfo.utcoffset(dttm) is None:
|
||||
|
@ -29,32 +48,47 @@ def format_datetime(dttm):
|
|||
else:
|
||||
zoned = dttm.astimezone(pytz.utc)
|
||||
ts = zoned.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
if zoned.microsecond > 0:
|
||||
ms = zoned.strftime("%f")
|
||||
ms = zoned.strftime("%f")
|
||||
precision = getattr(dttm, "precision", None)
|
||||
if precision:
|
||||
if precision == "millisecond":
|
||||
ts = ts + '.' + ms[:3]
|
||||
elif zoned.microsecond > 0:
|
||||
ts = ts + '.' + ms.rstrip("0")
|
||||
return ts + "Z"
|
||||
|
||||
|
||||
def parse_into_datetime(value):
|
||||
def parse_into_datetime(value, precision=None):
|
||||
if isinstance(value, dt.date):
|
||||
if hasattr(value, 'hour'):
|
||||
return value
|
||||
ts = value
|
||||
else:
|
||||
# Add a time component
|
||||
return dt.datetime.combine(value, dt.time(0, 0, tzinfo=pytz.utc))
|
||||
|
||||
# value isn't a date or datetime object so assume it's a string
|
||||
try:
|
||||
parsed = parser.parse(value)
|
||||
except (TypeError, ValueError):
|
||||
# Unknown format
|
||||
raise ValueError("must be a datetime object, date object, or "
|
||||
"timestamp string in a recognizable format.")
|
||||
if parsed.tzinfo:
|
||||
return parsed.astimezone(pytz.utc)
|
||||
ts = dt.datetime.combine(value, dt.time(0, 0, tzinfo=pytz.utc))
|
||||
else:
|
||||
# Doesn't have timezone info in the string; assume UTC
|
||||
return pytz.utc.localize(parsed)
|
||||
# value isn't a date or datetime object so assume it's a string
|
||||
try:
|
||||
parsed = parser.parse(value)
|
||||
except (TypeError, ValueError):
|
||||
# Unknown format
|
||||
raise ValueError("must be a datetime object, date object, or "
|
||||
"timestamp string in a recognizable format.")
|
||||
if parsed.tzinfo:
|
||||
ts = parsed.astimezone(pytz.utc)
|
||||
else:
|
||||
# Doesn't have timezone info in the string; assume UTC
|
||||
ts = pytz.utc.localize(parsed)
|
||||
|
||||
# Ensure correct precision
|
||||
if not precision:
|
||||
return ts
|
||||
ms = ts.microsecond
|
||||
if precision == 'millisecond':
|
||||
ms_len = len(str(ms))
|
||||
if ms_len > 3:
|
||||
# Truncate to millisecond precision
|
||||
return ts.replace(microsecond=(ts.microsecond // (10 ** (ms_len - 3))))
|
||||
return STIXdatetime(ts, precision=precision)
|
||||
|
||||
|
||||
def get_dict(data):
|
||||
|
|
Loading…
Reference in New Issue