Fix TimestampProperty
- improved timestamp formatting - python-stix2 will only include subsecond values if they don't equal 0 - in Python 3.6, datetime.astimezone doesn't throw an error on naive timestamps as in previous versionsstix2.1
parent
35981025c5
commit
b4f116a33f
|
@ -1,7 +1,8 @@
|
||||||
"""STIX 2.0 Marking Objects"""
|
"""STIX 2.0 Marking Objects"""
|
||||||
|
|
||||||
from .base import _STIXBase
|
from .base import _STIXBase
|
||||||
from .properties import IDProperty, TypeProperty, ListProperty, ReferenceProperty, Property, SelectorProperty
|
from .properties import (IDProperty, TypeProperty, ListProperty, TimestampProperty,
|
||||||
|
ReferenceProperty, Property, SelectorProperty)
|
||||||
from .utils import NOW
|
from .utils import NOW
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ class GranularMarking(_STIXBase):
|
||||||
class MarkingDefinition(_STIXBase):
|
class MarkingDefinition(_STIXBase):
|
||||||
_type = 'marking-definition'
|
_type = 'marking-definition'
|
||||||
_properties = {
|
_properties = {
|
||||||
'created': Property(default=lambda: NOW),
|
'created': TimestampProperty(default=lambda: NOW),
|
||||||
'external_references': Property(),
|
'external_references': Property(),
|
||||||
'created_by_ref': ReferenceProperty(type="identity"),
|
'created_by_ref': ReferenceProperty(type="identity"),
|
||||||
'object_marking_refs': ListProperty(ReferenceProperty(type="marking-definition")),
|
'object_marking_refs': ListProperty(ReferenceProperty(type="marking-definition")),
|
||||||
|
|
|
@ -206,25 +206,26 @@ class BooleanProperty(Property):
|
||||||
class TimestampProperty(Property):
|
class TimestampProperty(Property):
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if isinstance(value, dt.datetime):
|
if isinstance(value, dt.date):
|
||||||
return value
|
if hasattr(value, 'hour'):
|
||||||
elif isinstance(value, dt.date):
|
return value
|
||||||
return dt.datetime.combine(value, dt.time())
|
else:
|
||||||
|
# Add a time component
|
||||||
|
return dt.datetime.combine(value, dt.time(), tzinfo=pytz.timezone('US/Eastern'))
|
||||||
|
|
||||||
|
# value isn't a date or datetime object so assume it's a string
|
||||||
try:
|
try:
|
||||||
return parser.parse(value).astimezone(pytz.utc)
|
parsed = parser.parse(value)
|
||||||
except ValueError:
|
|
||||||
# Doesn't have timezone info in the string
|
|
||||||
try:
|
|
||||||
return pytz.utc.localize(parser.parse(value))
|
|
||||||
except TypeError:
|
|
||||||
# Unknown format
|
|
||||||
raise ValueError("must be a datetime object, date object, or "
|
|
||||||
"timestamp string in a recognizable format.")
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# Isn't a string
|
# Unknown format
|
||||||
raise ValueError("must be a datetime object, date object, or "
|
raise ValueError("must be a datetime object, date object, or "
|
||||||
"timestamp string.")
|
"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
|
||||||
|
# TODO Should we default to system local timezone instead?
|
||||||
|
return pytz.utc.localize(parsed)
|
||||||
|
|
||||||
|
|
||||||
REF_REGEX = re.compile("^[a-z][a-z-]+[a-z]--[0-9a-fA-F]{8}-[0-9a-fA-F]{4}"
|
REF_REGEX = re.compile("^[a-z][a-z-]+[a-z]--[0-9a-fA-F]{8}-[0-9a-fA-F]{4}"
|
||||||
|
|
|
@ -3,7 +3,7 @@ from stix2.markings import TLP_WHITE
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
EXPECTED_TLP_MARKING_DEFINITION = """{
|
EXPECTED_TLP_MARKING_DEFINITION = """{
|
||||||
"created": "2017-01-20T00:00:00.000Z",
|
"created": "2017-01-20T00:00:00Z",
|
||||||
"definition": {
|
"definition": {
|
||||||
"tlp": "white"
|
"tlp": "white"
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@ EXPECTED_TLP_MARKING_DEFINITION = """{
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
EXPECTED_STATEMENT_MARKING_DEFINITION = """{
|
EXPECTED_STATEMENT_MARKING_DEFINITION = """{
|
||||||
"created": "2017-01-20T00:00:00.000Z",
|
"created": "2017-01-20T00:00:00Z",
|
||||||
"definition": {
|
"definition": {
|
||||||
"statement": "Copyright 2016, Example Corp"
|
"statement": "Copyright 2016, Example Corp"
|
||||||
},
|
},
|
||||||
|
@ -33,7 +33,7 @@ EXPECTED_GRANULAR_MARKING = """{
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
||||||
"created": "2016-04-06T20:03:00.000Z",
|
"created": "2016-04-06T20:03:00Z",
|
||||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||||
"granular_markings": [
|
"granular_markings": [
|
||||||
|
@ -45,7 +45,7 @@ EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
"modified": "2016-04-06T20:03:00.000Z",
|
"modified": "2016-04-06T20:03:00Z",
|
||||||
"name": "Green Group Attacks Against Finance",
|
"name": "Green Group Attacks Against Finance",
|
||||||
"type": "campaign"
|
"type": "campaign"
|
||||||
}"""
|
}"""
|
||||||
|
@ -100,8 +100,8 @@ def test_campaign_with_granular_markings_example():
|
||||||
campaign = stix2.Campaign(
|
campaign = stix2.Campaign(
|
||||||
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
created="2016-04-06T20:03:00.000Z",
|
created="2016-04-06T20:03:00Z",
|
||||||
modified="2016-04-06T20:03:00.000Z",
|
modified="2016-04-06T20:03:00Z",
|
||||||
name="Green Group Attacks Against Finance",
|
name="Green Group Attacks Against Finance",
|
||||||
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
||||||
granular_markings=[
|
granular_markings=[
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Utility functions and classes for the stix2 library."""
|
"""Utility functions and classes for the stix2 library."""
|
||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
import time
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
# Sentinel value for fields that should be set to the current time.
|
# Sentinel value for fields that should be set to the current time.
|
||||||
|
@ -15,12 +15,20 @@ def get_timestamp():
|
||||||
|
|
||||||
|
|
||||||
def format_datetime(dttm):
|
def format_datetime(dttm):
|
||||||
# TODO: how to handle naive datetime
|
# 1. Convert to timezone-aware
|
||||||
|
# 2. Convert to UTC
|
||||||
|
# 3. Format in ISO format
|
||||||
|
# 4. Add subsecond value if non-zero
|
||||||
|
# 5. Add "Z"
|
||||||
|
|
||||||
# 1. Convert to UTC
|
try:
|
||||||
# 2. Format in ISO format
|
zoned = dttm.astimezone(pytz.utc)
|
||||||
# 3. Strip off "+00:00"
|
except ValueError:
|
||||||
# 4. Add "Z"
|
# dttm is timezone-naive
|
||||||
|
tz_name = time.tzname[time.localtime().tm_isdst]
|
||||||
# TODO: how to handle timestamps with subsecond 0's
|
zoned = pytz.timezone(tz_name).localize(dttm).astimezone(pytz.utc)
|
||||||
return dttm.astimezone(pytz.utc).isoformat()[:-6] + "Z"
|
ts = zoned.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
if zoned.microsecond > 0:
|
||||||
|
ms = zoned.strftime("%f")
|
||||||
|
ts = ts + '.' + ms.rstrip("0")
|
||||||
|
return ts + "Z"
|
||||||
|
|
Loading…
Reference in New Issue