Merge pull request #25 from oasis-open/issue24

Add timestamp precision for `created` and `modified`
stix2.1
Greg Back 2017-06-29 20:41:15 +00:00 committed by GitHub
commit f8e3a4f0e8
24 changed files with 170 additions and 137 deletions

View File

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

View File

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

View File

@ -215,7 +215,7 @@ class WindowsPEBinaryExt(_Extension):
'imphash': StringProperty(),
'machine_hex': HexProperty(),
'number_of_sections': IntegerProperty(),
'time_date_stamp': TimestampProperty(),
'time_date_stamp': TimestampProperty(precision='second'),
'pointer_to_symbol_table_hex': HexProperty(),
'number_of_symbols': IntegerProperty(),
'size_of_optional_header': IntegerProperty(),

View File

@ -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,12 @@ 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
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)
def clean(self, value):
return parse_into_datetime(value, self.precision)
class ObservableProperty(Property):

View File

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

View File

@ -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": [
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,8 @@ eastern = pytz.timezone('US/Eastern')
(eastern.localize(dt.datetime(2017, 7, 1)), '2017-07-01T04:00:00Z'),
(dt.datetime(2017, 7, 1), '2017-07-01T00:00:00Z'),
(dt.datetime(2017, 7, 1, 0, 0, 0, 1), '2017-07-01T00:00:00.000001Z'),
(stix2.utils.STIXdatetime(2017, 7, 1, 0, 0, 0, 1, precision='millisecond'), '2017-07-01T00:00:00.000Z'),
(stix2.utils.STIXdatetime(2017, 7, 1, 0, 0, 0, 1, precision='second'), '2017-07-01T00:00:00Z'),
])
def test_timestamp_formatting(dttm, timestamp):
assert stix2.utils.format_datetime(dttm) == timestamp
@ -33,6 +35,17 @@ def test_parse_datetime(timestamp, dttm):
assert stix2.utils.parse_into_datetime(timestamp) == dttm
@pytest.mark.parametrize('timestamp, dttm, precision', [
('2017-01-01T01:02:03.000001', dt.datetime(2017, 1, 1, 1, 2, 3, 0, tzinfo=pytz.utc), 'millisecond'),
('2017-01-01T01:02:03.001', dt.datetime(2017, 1, 1, 1, 2, 3, 1000, tzinfo=pytz.utc), 'millisecond'),
('2017-01-01T01:02:03.1', dt.datetime(2017, 1, 1, 1, 2, 3, 100000, tzinfo=pytz.utc), 'millisecond'),
('2017-01-01T01:02:03.45', dt.datetime(2017, 1, 1, 1, 2, 3, 450000, tzinfo=pytz.utc), 'millisecond'),
('2017-01-01T01:02:03.45', dt.datetime(2017, 1, 1, 1, 2, 3, tzinfo=pytz.utc), 'second'),
])
def test_parse_datetime_precision(timestamp, dttm, precision):
assert stix2.utils.parse_into_datetime(timestamp, precision) == dttm
@pytest.mark.parametrize('ts', [
'foobar',
1,

View File

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

View File

@ -12,15 +12,29 @@ 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, dttm.month, dttm.day, dttm.hour, dttm.minute,
dttm.second, dttm.microsecond, dttm.tzinfo)
# self will be an instance of STIXdatetime, not dt.datetime
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 +43,53 @@ 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 == 'second':
pass # Alredy precise to the second
elif 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 == 'second':
ts = ts.replace(microsecond=0)
elif precision == 'millisecond':
ms_len = len(str(ms))
if ms_len > 3:
# Truncate to millisecond precision
factor = 10 ** (ms_len - 3)
ts = ts.replace(microsecond=(ts.microsecond // factor) * factor)
else:
ts = ts.replace(microsecond=0)
return STIXdatetime(ts, precision=precision)
def get_dict(data):