Skip to content

Commit 6d3f1e1

Browse files
Merge pull request #240 from MagsenAbbeThales/master
fix: spec_version & version from modified/created
2 parents c0f3a47 + 02449c4 commit 6d3f1e1

4 files changed

Lines changed: 84 additions & 20 deletions

File tree

opentaxii/persistence/sqldb/api.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from opentaxii.persistence import OpenTAXII2PersistenceAPI, OpenTAXIIPersistenceAPI
1515
from opentaxii.persistence.sqldb import taxii2models
1616
from opentaxii.taxii2 import entities
17-
from opentaxii.taxii2.utils import DATETIMEFORMAT
17+
from opentaxii.taxii2.utils import get_object_version
1818

1919
from . import converters as conv
2020
from .models import (
@@ -1005,9 +1005,7 @@ def add_objects(
10051005
self.db.session.commit()
10061006
job_details = []
10071007
for obj in objects:
1008-
version = datetime.datetime.strptime(
1009-
obj["modified"], DATETIMEFORMAT
1010-
).replace(tzinfo=datetime.timezone.utc)
1008+
version = get_object_version(obj)
10111009
if (
10121010
not self.db.session.query(literal(True))
10131011
.filter(

opentaxii/taxii2/utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,33 @@ def taxii2_datetimeformat(input_value: datetime.datetime) -> str:
1515
:rtype: string
1616
"""
1717
return input_value.astimezone(datetime.timezone.utc).strftime(DATETIMEFORMAT)
18+
19+
20+
def get_object_version(obj: dict) -> datetime.datetime:
21+
"""
22+
The TAXII version is inferred in this order:
23+
24+
1. ``modified`` field, if present
25+
2. ``created`` field, if present
26+
3. 1970-01-01T00:00:00+00:00 otherwise
27+
28+
This is guided by 3.4.1 Supported Fields for Match [#]_:
29+
30+
If a STIX object is not versioned (and therefore does not have a modified
31+
timestamp) then this version parameter MUST use the created timestamp. If
32+
an object does not have a created or modified timestamp or any other
33+
version information that can be used, then the server should use a value for
34+
the version that is consistent to the server.
35+
36+
.. [#] https://docs.oasis-open.org/cti/taxii/v2.1/os/taxii-v2.1-os.html#_Toc31107518
37+
"""
38+
if "modified" in obj:
39+
return datetime.datetime.strptime(obj["modified"], DATETIMEFORMAT).replace(
40+
tzinfo=datetime.timezone.utc
41+
)
42+
elif "created" in obj:
43+
return datetime.datetime.strptime(obj["created"], DATETIMEFORMAT).replace(
44+
tzinfo=datetime.timezone.utc
45+
)
46+
else:
47+
return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc)

tests/taxii2/test_taxii2_sqldb.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from opentaxii.persistence.sqldb.api import Taxii2SQLDatabaseAPI
77
from opentaxii.persistence.sqldb.taxii2models import Job, JobDetail, STIXObject
88
from opentaxii.taxii2 import entities
9-
from opentaxii.taxii2.utils import DATETIMEFORMAT
9+
from opentaxii.taxii2.utils import get_object_version
1010
from tests.taxii2.utils import (
1111
API_ROOTS,
1212
API_ROOTS_WITH_DEFAULT,
@@ -794,7 +794,37 @@ def test_get_objects(
794794
"valid_from": "2016-01-01T00:00:00Z",
795795
}
796796
], # objects
797-
id="single object",
797+
id="single-object-version-modified",
798+
),
799+
pytest.param(
800+
API_ROOTS[0].id, # api_root_id
801+
COLLECTIONS[5].id, # collection_id
802+
[
803+
{
804+
"definition": {
805+
"statement": "Copyright 2015-2025, The MITRE Corporation. [...]"
806+
},
807+
"id": "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168",
808+
"type": "marking-definition",
809+
"created": "2017-06-01T00:00:00.000Z",
810+
"created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
811+
"definition_type": "statement",
812+
"spec_version": "2.1",
813+
}
814+
], # objects
815+
id="single-object-version-created",
816+
),
817+
pytest.param(
818+
API_ROOTS[0].id, # api_root_id
819+
COLLECTIONS[5].id, # collection_id
820+
[
821+
{
822+
"type": "x-no-version",
823+
"id": "x-no-version--5e113376-8a13-432d-b711-92f566ebbd92",
824+
"spec_version": "2.1",
825+
}
826+
], # objects
827+
id="single-object-version-created",
798828
),
799829
pytest.param(
800830
API_ROOTS[0].id, # api_root_id
@@ -869,9 +899,7 @@ def test_add_objects(
869899
id=job_detail.id,
870900
job_id=job.id,
871901
stix_id=obj["id"],
872-
version=datetime.datetime.strptime(
873-
obj["modified"], DATETIMEFORMAT
874-
).replace(tzinfo=datetime.timezone.utc),
902+
version=get_object_version(obj),
875903
message="",
876904
status="success",
877905
)
@@ -894,10 +922,7 @@ def test_add_objects(
894922
taxii2_sqldb_api.db.session.query(STIXObject)
895923
.filter(
896924
STIXObject.id == obj["id"],
897-
STIXObject.version
898-
== datetime.datetime.strptime(obj["modified"], DATETIMEFORMAT).replace(
899-
tzinfo=datetime.timezone.utc
900-
),
925+
STIXObject.version == get_object_version(obj),
901926
)
902927
.one()
903928
)
@@ -906,9 +931,7 @@ def test_add_objects(
906931
assert db_obj.type == obj["type"]
907932
assert db_obj.spec_version == obj["spec_version"]
908933
assert isinstance(db_obj.date_added, datetime.datetime)
909-
assert db_obj.version == datetime.datetime.strptime(
910-
obj["modified"], DATETIMEFORMAT
911-
).replace(tzinfo=datetime.timezone.utc)
934+
assert db_obj.version == get_object_version(obj)
912935
assert db_obj.serialized_data == {
913936
key: value
914937
for (key, value) in obj.items()
@@ -921,9 +944,7 @@ def test_add_objects(
921944
)
922945
assert db_job_detail.job_id == db_job.id
923946
assert db_job_detail.stix_id == obj["id"]
924-
assert db_job_detail.version == datetime.datetime.strptime(
925-
obj["modified"], DATETIMEFORMAT
926-
).replace(tzinfo=datetime.timezone.utc)
947+
assert db_job_detail.version == get_object_version(obj)
927948
assert db_job_detail.message == ""
928949
assert db_job_detail.status == "success"
929950

tests/taxii2/test_taxii2_utils.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from opentaxii.taxii2.utils import taxii2_datetimeformat
5+
from opentaxii.taxii2.utils import get_object_version, taxii2_datetimeformat
66

77

88
@pytest.mark.parametrize(
@@ -73,3 +73,18 @@
7373
)
7474
def test_taxii2_datetimeformat(input_value, expected):
7575
assert taxii2_datetimeformat(input_value) == expected
76+
77+
78+
def test_get_object_version():
79+
assert get_object_version(
80+
{
81+
"modified": "2022-01-01T13:30:00.000000Z",
82+
"created": "2016-04-06T20:06:37.000Z",
83+
}
84+
) == datetime.datetime(2022, 1, 1, 13, 30, 00, 0, datetime.timezone.utc)
85+
assert get_object_version(
86+
{"created": "2016-04-06T20:06:37.000Z"}
87+
) == datetime.datetime(2016, 4, 6, 20, 6, 37, 0, datetime.timezone.utc)
88+
assert get_object_version({}) == datetime.datetime.fromtimestamp(
89+
0, tz=datetime.timezone.utc
90+
)

0 commit comments

Comments
 (0)