Add TimestampProperty

stix2.1
clenk 2017-04-11 12:10:55 -04:00
parent 168105603b
commit 2e3dfe5d84
15 changed files with 93 additions and 46 deletions

View File

@ -4,6 +4,7 @@ from setuptools import setup, find_packages
install_requires = [ install_requires = [
'pytz', 'pytz',
'six', 'six',
'python-dateutil',
] ]
setup( setup(

View File

@ -2,13 +2,13 @@
from .base import _STIXBase from .base import _STIXBase
from .properties import (Property, BooleanProperty, ReferenceProperty, from .properties import (Property, BooleanProperty, ReferenceProperty,
StringProperty) StringProperty, TimestampProperty)
from .utils import NOW from .utils import NOW
COMMON_PROPERTIES = { COMMON_PROPERTIES = {
# 'type' and 'id' should be defined on each individual type # 'type' and 'id' should be defined on each individual type
'created': Property(default=lambda: NOW), 'created': TimestampProperty(default=lambda: NOW),
'modified': Property(default=lambda: NOW), 'modified': TimestampProperty(default=lambda: NOW),
'external_references': Property(), 'external_references': Property(),
'revoked': BooleanProperty(), 'revoked': BooleanProperty(),
'created_by_ref': ReferenceProperty(), 'created_by_ref': ReferenceProperty(),

View File

@ -1,6 +1,9 @@
import re import re
import uuid import uuid
from six import PY2 from six import PY2
import datetime as dt
import pytz
from dateutil import parser
class Property(object): class Property(object):
@ -194,6 +197,30 @@ class BooleanProperty(Property):
raise ValueError("must be a boolean value.") raise ValueError("must be a boolean value.")
class TimestampProperty(Property):
def validate(self, value):
if isinstance(value, dt.datetime):
return value
elif isinstance(value, dt.date):
return dt.datetime.combine(value, dt.time())
try:
return parser.parse(value).astimezone(pytz.utc)
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:
# Isn't a string
raise ValueError("must be a datetime object, date object, or "
"timestamp string.")
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}"
"-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") "-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")

View File

@ -1,7 +1,7 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"created": "2016-05-12T08:17:27.000Z", "created": "2016-05-12T08:17:27Z",
"description": "...", "description": "...",
"external_references": [ "external_references": [
{ {
@ -10,7 +10,7 @@ EXPECTED = """{
} }
], ],
"id": "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", "id": "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
"modified": "2016-05-12T08:17:27.000Z", "modified": "2016-05-12T08:17:27Z",
"name": "Spear Phishing", "name": "Spear Phishing",
"type": "attack-pattern" "type": "attack-pattern"
}""" }"""
@ -19,8 +19,8 @@ EXPECTED = """{
def test_attack_pattern_example(): def test_attack_pattern_example():
ap = stix2.AttackPattern( ap = stix2.AttackPattern(
id="attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", id="attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
created="2016-05-12T08:17:27.000Z", created="2016-05-12T08:17:27Z",
modified="2016-05-12T08:17:27.000Z", modified="2016-05-12T08:17:27Z",
name="Spear Phishing", name="Spear Phishing",
external_references=[{ external_references=[{
"source_name": "capec", "source_name": "capec",

View File

@ -1,11 +1,11 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"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.",
"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"
}""" }"""
@ -15,8 +15,8 @@ def test_campaign_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."
) )

View File

@ -1,11 +1,11 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"created": "2016-04-06T20:03:48.000Z", "created": "2016-04-06T20:03:48Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", "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 ...", "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", "id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
"modified": "2016-04-06T20:03:48.000Z", "modified": "2016-04-06T20:03:48Z",
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter", "name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
"type": "course-of-action" "type": "course-of-action"
}""" }"""
@ -15,8 +15,8 @@ def test_course_of_action_example():
coa = stix2.CourseOfAction( coa = stix2.CourseOfAction(
id="course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", id="course-of-action--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:48.000Z", created="2016-04-06T20:03:48Z",
modified="2016-04-06T20:03:48.000Z", modified="2016-04-06T20:03:48Z",
name="Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter", 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 ..." description="This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
) )

View File

@ -1,10 +1,10 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"created": "2015-12-21T19:59:11.000Z", "created": "2015-12-21T19:59:11Z",
"id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c", "id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
"identity_class": "individual", "identity_class": "individual",
"modified": "2015-12-21T19:59:11.000Z", "modified": "2015-12-21T19:59:11Z",
"name": "John Smith", "name": "John Smith",
"type": "identity" "type": "identity"
}""" }"""
@ -13,8 +13,8 @@ EXPECTED = """{
def test_identity_example(): def test_identity_example():
report = stix2.Identity( report = stix2.Identity(
id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c", id="identity--311b2d2d-f010-5473-83ec-1edf84858f4c",
created="2015-12-21T19:59:11.000Z", created="2015-12-21T19:59:11Z",
modified="2015-12-21T19:59:11.000Z", modified="2015-12-21T19:59:11Z",
name="John Smith", name="John Smith",
identity_class="individual", identity_class="individual",
) )

View File

@ -4,7 +4,7 @@ EXPECTED = """{
"aliases": [ "aliases": [
"Zookeeper" "Zookeeper"
], ],
"created": "2016-04-06T20:03:48.000Z", "created": "2016-04-06T20:03:48Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"description": "Incidents usually feature a shared TTP of a bobcat being released...", "description": "Incidents usually feature a shared TTP of a bobcat being released...",
"goals": [ "goals": [
@ -13,7 +13,7 @@ EXPECTED = """{
"damage" "damage"
], ],
"id": "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29", "id": "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
"modified": "2016-04-06T20:03:48.000Z", "modified": "2016-04-06T20:03:48Z",
"name": "Bobcat Breakin", "name": "Bobcat Breakin",
"type": "intrusion-set" "type": "intrusion-set"
}""" }"""
@ -23,8 +23,8 @@ def test_intrusion_set_example():
intrusion_set = stix2.IntrusionSet( intrusion_set = stix2.IntrusionSet(
id="intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29", id="intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29",
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
created="2016-04-06T20:03:48.000Z", created="2016-04-06T20:03:48Z",
modified="2016-04-06T20:03:48.000Z", modified="2016-04-06T20:03:48Z",
name="Bobcat Breakin", name="Bobcat Breakin",
description="Incidents usually feature a shared TTP of a bobcat being released...", description="Incidents usually feature a shared TTP of a bobcat being released...",
aliases=["Zookeeper"], aliases=["Zookeeper"],

View File

@ -107,8 +107,8 @@ def test_parse_malware(data):
assert mal.type == 'malware' assert mal.type == 'malware'
assert mal.id == MALWARE_ID assert mal.id == MALWARE_ID
assert mal.created == "2016-05-12T08:17:27Z" assert mal.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
assert mal.modified == "2016-05-12T08:17:27Z" assert mal.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
assert mal.labels == ['ransomware'] assert mal.labels == ['ransomware']
assert mal.name == "Cryptolocker" assert mal.name == "Cryptolocker"

View File

@ -1,12 +1,12 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"created": "2016-04-06T19:58:16.000Z", "created": "2016-04-06T19:58:16Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"first_observed": "2015-12-21T19:00:00Z", "first_observed": "2015-12-21T19:00:00Z",
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", "id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
"last_observed": "2015-12-21T19:00:00Z", "last_observed": "2015-12-21T19:00:00Z",
"modified": "2016-04-06T19:58:16.000Z", "modified": "2016-04-06T19:58:16Z",
"number_observed": 50, "number_observed": 50,
"objects": { "objects": {
"0": { "0": {
@ -21,8 +21,8 @@ def test_observed_data_example():
observed_data = stix2.ObservedData( observed_data = stix2.ObservedData(
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
created="2016-04-06T19:58:16.000Z", created="2016-04-06T19:58:16Z",
modified="2016-04-06T19:58:16.000Z", modified="2016-04-06T19:58:16Z",
first_observed="2015-12-21T19:00:00Z", first_observed="2015-12-21T19:00:00Z",
last_observed="2015-12-21T19:00:00Z", last_observed="2015-12-21T19:00:00Z",
number_observed=50, number_observed=50,

View File

@ -2,7 +2,8 @@ import pytest
from stix2.properties import (Property, BooleanProperty, ListProperty, from stix2.properties import (Property, BooleanProperty, ListProperty,
StringProperty, TypeProperty, IDProperty, StringProperty, TypeProperty, IDProperty,
ReferenceProperty) ReferenceProperty, TimestampProperty)
from .constants import FAKE_TIME
def test_property(): def test_property():
@ -131,3 +132,21 @@ def test_reference_property():
assert ref_prop.validate("my-type--3a331bfe-0566-55e1-a4a0-9a2cd355a300") assert ref_prop.validate("my-type--3a331bfe-0566-55e1-a4a0-9a2cd355a300")
with pytest.raises(ValueError): with pytest.raises(ValueError):
ref_prop.validate("foo") ref_prop.validate("foo")
@pytest.mark.parametrize("value", [
'2017-01-01T12:34:56Z',
'2017-01-01 12:34:56',
'Jan 1 2017 12:34:56',
])
def test_timestamp_property_valid(value):
ts_prop = TimestampProperty()
assert ts_prop.validate(value) == FAKE_TIME
def test_timestamp_property_invalid():
ts_prop = TimestampProperty()
with pytest.raises(ValueError):
ts_prop.validate(1)
with pytest.raises(ValueError):
ts_prop.validate("someday sometime")

View File

@ -1,14 +1,14 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"created": "2015-12-21T19:59:11.000Z", "created": "2015-12-21T19:59:11Z",
"created_by_ref": "identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283", "created_by_ref": "identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
"description": "A simple report with an indicator and campaign", "description": "A simple report with an indicator and campaign",
"id": "report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3", "id": "report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
"labels": [ "labels": [
"campaign" "campaign"
], ],
"modified": "2015-12-21T19:59:11.000Z", "modified": "2015-12-21T19:59:11Z",
"name": "The Black Vine Cyberespionage Group", "name": "The Black Vine Cyberespionage Group",
"object_refs": [ "object_refs": [
"indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2", "indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2",
@ -24,8 +24,8 @@ def test_report_example():
report = stix2.Report( report = stix2.Report(
id="report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3", id="report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
created_by_ref="identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283", created_by_ref="identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
created="2015-12-21T19:59:11.000Z", created="2015-12-21T19:59:11Z",
modified="2015-12-21T19:59:11.000Z", modified="2015-12-21T19:59:11Z",
name="The Black Vine Cyberespionage Group", name="The Black Vine Cyberespionage Group",
description="A simple report with an indicator and campaign", description="A simple report with an indicator and campaign",
published="2016-01-201T17:00:00Z", published="2016-01-201T17:00:00Z",

View File

@ -1,14 +1,14 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"created": "2016-04-06T20:03:48.000Z", "created": "2016-04-06T20:03:48Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"description": "The Evil Org threat actor group", "description": "The Evil Org threat actor group",
"id": "threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", "id": "threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
"labels": [ "labels": [
"crime-syndicate" "crime-syndicate"
], ],
"modified": "2016-04-06T20:03:48.000Z", "modified": "2016-04-06T20:03:48Z",
"name": "Evil Org", "name": "Evil Org",
"type": "threat-actor" "type": "threat-actor"
}""" }"""
@ -18,8 +18,8 @@ def test_threat_actor_example():
threat_actor = stix2.ThreatActor( threat_actor = stix2.ThreatActor(
id="threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", id="threat-actor--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:48.000Z", created="2016-04-06T20:03:48Z",
modified="2016-04-06T20:03:48.000Z", modified="2016-04-06T20:03:48Z",
name="Evil Org", name="Evil Org",
description="The Evil Org threat actor group", description="The Evil Org threat actor group",
labels=["crime-syndicate"], labels=["crime-syndicate"],

View File

@ -1,13 +1,13 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"created": "2016-04-06T20:03:48.000Z", "created": "2016-04-06T20:03:48Z",
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
"id": "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", "id": "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
"labels": [ "labels": [
"remote-access" "remote-access"
], ],
"modified": "2016-04-06T20:03:48.000Z", "modified": "2016-04-06T20:03:48Z",
"name": "VNC", "name": "VNC",
"type": "tool" "type": "tool"
}""" }"""
@ -17,8 +17,8 @@ def test_tool_example():
tool = stix2.Tool( tool = stix2.Tool(
id="tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", id="tool--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:48.000Z", created="2016-04-06T20:03:48Z",
modified="2016-04-06T20:03:48.000Z", modified="2016-04-06T20:03:48Z",
name="VNC", name="VNC",
labels=["remote-access"], labels=["remote-access"],
) )

View File

@ -1,7 +1,7 @@
import stix2 import stix2
EXPECTED = """{ EXPECTED = """{
"created": "2016-05-12T08:17:27.000Z", "created": "2016-05-12T08:17:27Z",
"external_references": [ "external_references": [
{ {
"external_id": "CVE-2016-1234", "external_id": "CVE-2016-1234",
@ -9,7 +9,7 @@ EXPECTED = """{
} }
], ],
"id": "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", "id": "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
"modified": "2016-05-12T08:17:27.000Z", "modified": "2016-05-12T08:17:27Z",
"name": "CVE-2016-1234", "name": "CVE-2016-1234",
"type": "vulnerability" "type": "vulnerability"
}""" }"""
@ -18,8 +18,8 @@ EXPECTED = """{
def test_vulnerability_example(): def test_vulnerability_example():
vulnerability = stix2.Vulnerability( vulnerability = stix2.Vulnerability(
id="vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", id="vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
created="2016-05-12T08:17:27.000Z", created="2016-05-12T08:17:27Z",
modified="2016-05-12T08:17:27.000Z", modified="2016-05-12T08:17:27Z",
name="CVE-2016-1234", name="CVE-2016-1234",
external_references=[ external_references=[
stix2.ExternalReference(source_name='cve', stix2.ExternalReference(source_name='cve',