Improve is_sdo() et al utility functions with respect to
dict/mapping values: do a simple verification of the value's STIX version, not just its type. Added a lot more unit tests to test behavior on dicts. To make the implementation work, I had to move the detect_spec_version() function out of the parsing module and into utils. So that required small changes at all its previous call sites.pull/1/head
parent
f8c86f7352
commit
fe2330af07
|
@ -4,7 +4,7 @@ import copy
|
||||||
|
|
||||||
from . import registry
|
from . import registry
|
||||||
from .exceptions import ParseError
|
from .exceptions import ParseError
|
||||||
from .utils import _get_dict
|
from .utils import _get_dict, detect_spec_version
|
||||||
|
|
||||||
|
|
||||||
def parse(data, allow_custom=False, version=None):
|
def parse(data, allow_custom=False, version=None):
|
||||||
|
@ -42,46 +42,6 @@ def parse(data, allow_custom=False, version=None):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def _detect_spec_version(stix_dict):
|
|
||||||
"""
|
|
||||||
Given a dict representing a STIX object, try to detect what spec version
|
|
||||||
it is likely to comply with.
|
|
||||||
|
|
||||||
:param stix_dict: A dict with some STIX content. Must at least have a
|
|
||||||
"type" property.
|
|
||||||
:return: A STIX version in "X.Y" format
|
|
||||||
"""
|
|
||||||
|
|
||||||
obj_type = stix_dict["type"]
|
|
||||||
|
|
||||||
if 'spec_version' in stix_dict:
|
|
||||||
# For STIX 2.0, applies to bundles only.
|
|
||||||
# For STIX 2.1+, applies to SCOs, SDOs, SROs, and markings only.
|
|
||||||
v = stix_dict['spec_version']
|
|
||||||
elif "id" not in stix_dict:
|
|
||||||
# Only 2.0 SCOs don't have ID properties
|
|
||||||
v = "2.0"
|
|
||||||
elif obj_type == 'bundle':
|
|
||||||
# Bundle without a spec_version property: must be 2.1. But to
|
|
||||||
# future-proof, use max version over all contained SCOs, with 2.1
|
|
||||||
# minimum.
|
|
||||||
v = max(
|
|
||||||
"2.1",
|
|
||||||
max(
|
|
||||||
_detect_spec_version(obj) for obj in stix_dict["objects"]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
elif obj_type in registry.STIX2_OBJ_MAPS["2.1"]["observables"]:
|
|
||||||
# Non-bundle object with an ID and without spec_version. Could be a
|
|
||||||
# 2.1 SCO or 2.0 SDO/SRO/marking. Check for 2.1 SCO...
|
|
||||||
v = "2.1"
|
|
||||||
else:
|
|
||||||
# Not a 2.1 SCO; must be a 2.0 object.
|
|
||||||
v = "2.0"
|
|
||||||
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
def dict_to_stix2(stix_dict, allow_custom=False, version=None):
|
def dict_to_stix2(stix_dict, allow_custom=False, version=None):
|
||||||
"""convert dictionary to full python-stix2 object
|
"""convert dictionary to full python-stix2 object
|
||||||
|
|
||||||
|
@ -115,7 +75,7 @@ def dict_to_stix2(stix_dict, allow_custom=False, version=None):
|
||||||
raise ParseError("Can't parse object with no 'type' property: %s" % str(stix_dict))
|
raise ParseError("Can't parse object with no 'type' property: %s" % str(stix_dict))
|
||||||
|
|
||||||
if not version:
|
if not version:
|
||||||
version = _detect_spec_version(stix_dict)
|
version = detect_spec_version(stix_dict)
|
||||||
|
|
||||||
OBJ_MAP = dict(
|
OBJ_MAP = dict(
|
||||||
registry.STIX2_OBJ_MAPS[version]['objects'],
|
registry.STIX2_OBJ_MAPS[version]['objects'],
|
||||||
|
@ -165,7 +125,7 @@ def parse_observable(data, _valid_refs=None, allow_custom=False, version=None):
|
||||||
obj['_valid_refs'] = _valid_refs or []
|
obj['_valid_refs'] = _valid_refs or []
|
||||||
|
|
||||||
if not version:
|
if not version:
|
||||||
version = _detect_spec_version(obj)
|
version = detect_spec_version(obj)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
OBJ_MAP_OBSERVABLE = registry.STIX2_OBJ_MAPS[version]['observables']
|
OBJ_MAP_OBSERVABLE = registry.STIX2_OBJ_MAPS[version]['observables']
|
||||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from stix2.parsing import _detect_spec_version
|
from stix2.utils import detect_spec_version
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -207,6 +207,6 @@ from stix2.parsing import _detect_spec_version
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_spec_version_detect(obj_dict, expected_ver):
|
def test_spec_version_detect(obj_dict, expected_ver):
|
||||||
detected_ver = _detect_spec_version(obj_dict)
|
detected_ver = detect_spec_version(obj_dict)
|
||||||
|
|
||||||
assert detected_ver == expected_ver
|
assert detected_ver == expected_ver
|
||||||
|
|
|
@ -30,11 +30,6 @@ def test_is_sdo(type_, stix_version):
|
||||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||||
assert stix2.utils.is_sdo(id_, stix_version)
|
assert stix2.utils.is_sdo(id_, stix_version)
|
||||||
|
|
||||||
d = {
|
|
||||||
"type": type_
|
|
||||||
}
|
|
||||||
assert stix2.utils.is_sdo(d, stix_version)
|
|
||||||
|
|
||||||
assert stix2.utils.is_stix_type(
|
assert stix2.utils.is_stix_type(
|
||||||
type_, stix_version, stix2.utils.STIXTypeClass.SDO
|
type_, stix_version, stix2.utils.STIXTypeClass.SDO
|
||||||
)
|
)
|
||||||
|
@ -97,11 +92,6 @@ def test_is_sco(type_, stix_version):
|
||||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||||
assert stix2.utils.is_sco(id_, stix_version)
|
assert stix2.utils.is_sco(id_, stix_version)
|
||||||
|
|
||||||
d = {
|
|
||||||
"type": type_
|
|
||||||
}
|
|
||||||
assert stix2.utils.is_sco(d, stix_version)
|
|
||||||
|
|
||||||
assert stix2.utils.is_stix_type(
|
assert stix2.utils.is_stix_type(
|
||||||
type_, stix_version, stix2.utils.STIXTypeClass.SCO
|
type_, stix_version, stix2.utils.STIXTypeClass.SCO
|
||||||
)
|
)
|
||||||
|
@ -147,11 +137,6 @@ def test_is_sro(type_, stix_version):
|
||||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||||
assert stix2.utils.is_sro(id_, stix_version)
|
assert stix2.utils.is_sro(id_, stix_version)
|
||||||
|
|
||||||
d = {
|
|
||||||
"type": type_
|
|
||||||
}
|
|
||||||
assert stix2.utils.is_sro(d, stix_version)
|
|
||||||
|
|
||||||
assert stix2.utils.is_stix_type(
|
assert stix2.utils.is_stix_type(
|
||||||
type_, stix_version, stix2.utils.STIXTypeClass.SRO
|
type_, stix_version, stix2.utils.STIXTypeClass.SRO
|
||||||
)
|
)
|
||||||
|
@ -191,11 +176,6 @@ def test_is_marking(stix_version):
|
||||||
id_ = "marking-definition--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
id_ = "marking-definition--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||||
assert stix2.utils.is_marking(id_, stix_version)
|
assert stix2.utils.is_marking(id_, stix_version)
|
||||||
|
|
||||||
d = {
|
|
||||||
"type": "marking-definition"
|
|
||||||
}
|
|
||||||
assert stix2.utils.is_marking(d, stix_version)
|
|
||||||
|
|
||||||
assert stix2.utils.is_stix_type(
|
assert stix2.utils.is_stix_type(
|
||||||
"marking-definition", stix_version, "marking-definition"
|
"marking-definition", stix_version, "marking-definition"
|
||||||
)
|
)
|
||||||
|
@ -244,11 +224,6 @@ def test_is_object(type_, stix_version):
|
||||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||||
assert stix2.utils.is_object(id_, stix_version)
|
assert stix2.utils.is_object(id_, stix_version)
|
||||||
|
|
||||||
d = {
|
|
||||||
"type": type_
|
|
||||||
}
|
|
||||||
assert stix2.utils.is_object(d, stix_version)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||||
def test_is_not_object(stix_version):
|
def test_is_not_object(stix_version):
|
||||||
|
|
|
@ -237,3 +237,146 @@ def test_find_property_index(object, tuple_to_find, expected_index):
|
||||||
)
|
)
|
||||||
def test_iterate_over_values(dict_value, tuple_to_find, expected_index):
|
def test_iterate_over_values(dict_value, tuple_to_find, expected_index):
|
||||||
assert stix2.serialization._find_property_in_seq(dict_value.values(), *tuple_to_find) == expected_index
|
assert stix2.serialization._find_property_in_seq(dict_value.values(), *tuple_to_find) == expected_index
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"type_", [
|
||||||
|
"attack-pattern",
|
||||||
|
"campaign",
|
||||||
|
"course-of-action",
|
||||||
|
"identity",
|
||||||
|
"indicator",
|
||||||
|
"intrusion-set",
|
||||||
|
"malware",
|
||||||
|
"observed-data",
|
||||||
|
"report",
|
||||||
|
"threat-actor",
|
||||||
|
"tool",
|
||||||
|
"vulnerability"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_sdo_dict(type_):
|
||||||
|
d = {
|
||||||
|
"type": type_
|
||||||
|
}
|
||||||
|
assert stix2.utils.is_sdo(d, "2.0")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "software", "spec_version": "2.1"},
|
||||||
|
{"type": "software"},
|
||||||
|
{"type": "identity", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
{"type": "bundle", "spec_version": "2.1"},
|
||||||
|
{"type": "bundle"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content"},
|
||||||
|
{"type": "relationship", "spec_version": "2.1"},
|
||||||
|
{"type": "relationship"},
|
||||||
|
{"type": "foo", "spec_version": "2.1"},
|
||||||
|
{"type": "foo"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_not_sdo_dict(dict_):
|
||||||
|
assert not stix2.utils.is_sdo(dict_, "2.0")
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_sco_dict():
|
||||||
|
d = {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert stix2.utils.is_sco(d, "2.0")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "identity"},
|
||||||
|
{"type": "identity", "spec_version": "2.1"},
|
||||||
|
{"type": "software", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
{"type": "bundle", "spec_version": "2.1"},
|
||||||
|
{"type": "bundle"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content"},
|
||||||
|
{"type": "relationship", "spec_version": "2.1"},
|
||||||
|
{"type": "relationship"},
|
||||||
|
{"type": "foo", "spec_version": "2.1"},
|
||||||
|
{"type": "foo"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_not_sco_dict(dict_):
|
||||||
|
assert not stix2.utils.is_sco(dict_, "2.0")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "relationship"},
|
||||||
|
{"type": "sighting"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_sro_dict(dict_):
|
||||||
|
assert stix2.utils.is_sro(dict_, "2.0")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "identity", "spec_version": "2.1"},
|
||||||
|
{"type": "identity"},
|
||||||
|
{"type": "software", "spec_version": "2.1"},
|
||||||
|
{"type": "software"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
{"type": "bundle", "spec_version": "2.1"},
|
||||||
|
{"type": "bundle"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content"},
|
||||||
|
{"type": "relationship", "spec_version": "2.1"},
|
||||||
|
{"type": "sighting", "spec_version": "2.1"},
|
||||||
|
{"type": "foo", "spec_version": "2.1"},
|
||||||
|
{"type": "foo"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_not_sro_dict(dict_):
|
||||||
|
assert not stix2.utils.is_sro(dict_, "2.0")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "identity"},
|
||||||
|
{"type": "software"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
{
|
||||||
|
"type": "bundle",
|
||||||
|
"id": "bundle--8f431680-6278-4767-ba43-5edb682d7086",
|
||||||
|
"spec_version": "2.0",
|
||||||
|
"objects": [
|
||||||
|
{"type": "identity"},
|
||||||
|
{"type": "software"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_object_dict(dict_):
|
||||||
|
assert stix2.utils.is_object(dict_, "2.0")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "identity", "spec_version": "2.1"},
|
||||||
|
{"type": "software", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "bundle", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
{"type": "relationship", "spec_version": "2.1"},
|
||||||
|
{"type": "sighting", "spec_version": "2.1"},
|
||||||
|
{"type": "foo", "spec_version": "2.1"},
|
||||||
|
{"type": "foo"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_not_object_dict(dict_):
|
||||||
|
assert not stix2.utils.is_object(dict_, "2.0")
|
||||||
|
|
|
@ -243,9 +243,22 @@ def test_iterate_over_values(dict_value, tuple_to_find, expected_index):
|
||||||
assert stix2.serialization._find_property_in_seq(dict_value.values(), *tuple_to_find) == expected_index
|
assert stix2.serialization._find_property_in_seq(dict_value.values(), *tuple_to_find) == expected_index
|
||||||
|
|
||||||
|
|
||||||
# Only 2.1-specific types/behaviors tested here.
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"type_", [
|
"type_", [
|
||||||
|
"attack-pattern",
|
||||||
|
"campaign",
|
||||||
|
"course-of-action",
|
||||||
|
"identity",
|
||||||
|
"indicator",
|
||||||
|
"intrusion-set",
|
||||||
|
"malware",
|
||||||
|
"observed-data",
|
||||||
|
"report",
|
||||||
|
"threat-actor",
|
||||||
|
"tool",
|
||||||
|
"vulnerability",
|
||||||
|
|
||||||
|
# New in 2.1
|
||||||
"grouping",
|
"grouping",
|
||||||
"infrastructure",
|
"infrastructure",
|
||||||
"location",
|
"location",
|
||||||
|
@ -254,27 +267,127 @@ def test_iterate_over_values(dict_value, tuple_to_find, expected_index):
|
||||||
"opinion"
|
"opinion"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_is_sdo(type_):
|
def test_is_sdo_dict(type_):
|
||||||
assert stix2.utils.is_sdo(type_, "2.1")
|
|
||||||
|
|
||||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
|
||||||
assert stix2.utils.is_sdo(id_, "2.1")
|
|
||||||
|
|
||||||
d = {
|
d = {
|
||||||
"type": type_
|
"type": type_,
|
||||||
|
"spec_version": "2.1"
|
||||||
}
|
}
|
||||||
assert stix2.utils.is_sdo(d, "2.1")
|
assert stix2.utils.is_sdo(d, "2.1")
|
||||||
|
|
||||||
assert stix2.utils.is_stix_type(
|
|
||||||
type_, "2.1", stix2.utils.STIXTypeClass.SDO
|
@pytest.mark.parametrize(
|
||||||
)
|
"dict_", [
|
||||||
|
{"type": "software", "spec_version": "2.1"},
|
||||||
|
{"type": "software"},
|
||||||
|
{"type": "identity"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
{"type": "bundle", "spec_version": "2.1"},
|
||||||
|
{"type": "bundle"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content"},
|
||||||
|
{"type": "relationship", "spec_version": "2.1"},
|
||||||
|
{"type": "relationship"},
|
||||||
|
{"type": "foo", "spec_version": "2.1"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_not_sdo_dict(dict_):
|
||||||
|
assert not stix2.utils.is_sdo(dict_, "2.1")
|
||||||
|
|
||||||
|
|
||||||
def test_type_checks_language_content():
|
def test_is_sco_dict():
|
||||||
assert stix2.utils.is_object("language-content", "2.1")
|
d = {
|
||||||
assert not stix2.utils.is_sdo("language-content", "2.1")
|
"type": "file",
|
||||||
assert not stix2.utils.is_sco("language-content", "2.1")
|
"spec_version": "2.1"
|
||||||
assert not stix2.utils.is_sro("language-content", "2.1")
|
}
|
||||||
assert stix2.utils.is_stix_type(
|
|
||||||
"language-content", "2.1", "language-content"
|
assert stix2.utils.is_sco(d, "2.1")
|
||||||
)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "identity"},
|
||||||
|
{"type": "identity", "spec_version": "2.1"},
|
||||||
|
{"type": "software"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
{"type": "bundle", "spec_version": "2.1"},
|
||||||
|
{"type": "bundle"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content"},
|
||||||
|
{"type": "relationship", "spec_version": "2.1"},
|
||||||
|
{"type": "relationship"},
|
||||||
|
{"type": "foo", "spec_version": "2.1"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_not_sco_dict(dict_):
|
||||||
|
assert not stix2.utils.is_sco(dict_, "2.1")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "relationship", "spec_version": "2.1"},
|
||||||
|
{"type": "sighting", "spec_version": "2.1"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_sro_dict(dict_):
|
||||||
|
assert stix2.utils.is_sro(dict_, "2.1")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "identity", "spec_version": "2.1"},
|
||||||
|
{"type": "identity"},
|
||||||
|
{"type": "software", "spec_version": "2.1"},
|
||||||
|
{"type": "software"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
{"type": "bundle", "spec_version": "2.1"},
|
||||||
|
{"type": "bundle"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content"},
|
||||||
|
{"type": "relationship"},
|
||||||
|
{"type": "sighting"},
|
||||||
|
{"type": "foo", "spec_version": "2.1"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_not_sro_dict(dict_):
|
||||||
|
assert not stix2.utils.is_sro(dict_, "2.1")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "identity", "spec_version": "2.1"},
|
||||||
|
{"type": "software", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
{
|
||||||
|
"type": "bundle",
|
||||||
|
"id": "bundle--8f431680-6278-4767-ba43-5edb682d7086",
|
||||||
|
"objects": [
|
||||||
|
{"type": "identity", "spec_version": "2.1"},
|
||||||
|
{"type": "software", "spec_version": "2.1"},
|
||||||
|
{"type": "marking-definition", "spec_version": "2.1"},
|
||||||
|
{"type": "language-content", "spec_version": "2.1"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_object_dict(dict_):
|
||||||
|
assert stix2.utils.is_object(dict_, "2.1")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"dict_", [
|
||||||
|
{"type": "identity"},
|
||||||
|
{"type": "software"},
|
||||||
|
{"type": "marking-definition"},
|
||||||
|
{"type": "bundle"},
|
||||||
|
{"type": "language-content"},
|
||||||
|
{"type": "relationship"},
|
||||||
|
{"type": "sighting"},
|
||||||
|
{"type": "foo"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_is_not_object_dict(dict_):
|
||||||
|
assert not stix2.utils.is_object(dict_, "2.1")
|
||||||
|
|
165
stix2/utils.py
165
stix2/utils.py
|
@ -1,5 +1,6 @@
|
||||||
"""Utility functions and classes for the STIX2 library."""
|
"""Utility functions and classes for the STIX2 library."""
|
||||||
|
|
||||||
|
import collections.abc
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import enum
|
import enum
|
||||||
import json
|
import json
|
||||||
|
@ -318,6 +319,46 @@ def get_type_from_id(stix_id):
|
||||||
return stix_id.split('--', 1)[0]
|
return stix_id.split('--', 1)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def detect_spec_version(stix_dict):
|
||||||
|
"""
|
||||||
|
Given a dict representing a STIX object, try to detect what spec version
|
||||||
|
it is likely to comply with.
|
||||||
|
|
||||||
|
:param stix_dict: A dict with some STIX content. Must at least have a
|
||||||
|
"type" property.
|
||||||
|
:return: A STIX version in "X.Y" format
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj_type = stix_dict["type"]
|
||||||
|
|
||||||
|
if 'spec_version' in stix_dict:
|
||||||
|
# For STIX 2.0, applies to bundles only.
|
||||||
|
# For STIX 2.1+, applies to SCOs, SDOs, SROs, and markings only.
|
||||||
|
v = stix_dict['spec_version']
|
||||||
|
elif "id" not in stix_dict:
|
||||||
|
# Only 2.0 SCOs don't have ID properties
|
||||||
|
v = "2.0"
|
||||||
|
elif obj_type == 'bundle':
|
||||||
|
# Bundle without a spec_version property: must be 2.1. But to
|
||||||
|
# future-proof, use max version over all contained SCOs, with 2.1
|
||||||
|
# minimum.
|
||||||
|
v = max(
|
||||||
|
"2.1",
|
||||||
|
max(
|
||||||
|
detect_spec_version(obj) for obj in stix_dict["objects"]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
elif obj_type in mappings.STIX2_OBJ_MAPS["2.1"]["observables"]:
|
||||||
|
# Non-bundle object with an ID and without spec_version. Could be a
|
||||||
|
# 2.1 SCO or 2.0 SDO/SRO/marking. Check for 2.1 SCO...
|
||||||
|
v = "2.1"
|
||||||
|
else:
|
||||||
|
# Not a 2.1 SCO; must be a 2.0 object.
|
||||||
|
v = "2.0"
|
||||||
|
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
def _stix_type_of(value):
|
def _stix_type_of(value):
|
||||||
"""
|
"""
|
||||||
Get a STIX type from the given value: if a STIX ID is passed, the type
|
Get a STIX type from the given value: if a STIX ID is passed, the type
|
||||||
|
@ -342,55 +383,93 @@ def _stix_type_of(value):
|
||||||
|
|
||||||
def is_sdo(value, stix_version=stix2.version.DEFAULT_VERSION):
|
def is_sdo(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||||
"""
|
"""
|
||||||
Determine whether the given object, type, or ID is/is for an SDO.
|
Determine whether the given object, type, or ID is/is for an SDO of the
|
||||||
|
given STIX version. If value is a type or ID, this just checks whether
|
||||||
|
the type was registered as an SDO in the given STIX version. If a mapping,
|
||||||
|
*simple* STIX version inference is additionally done on the value, and the
|
||||||
|
result is checked against stix_version. It does not attempt to fully
|
||||||
|
validate the value.
|
||||||
|
|
||||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||||
as a string
|
as a string
|
||||||
:param stix_version: A STIX version as a string
|
:param stix_version: A STIX version as a string
|
||||||
:return: True if the type of the given value is an SDO type; False
|
:return: True if the type of the given value is an SDO type of the given
|
||||||
if not
|
version; False if not
|
||||||
"""
|
"""
|
||||||
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
|
||||||
type_ = _stix_type_of(value)
|
result = True
|
||||||
result = type_ in cls_maps["objects"] and type_ not in {
|
if isinstance(value, collections.abc.Mapping):
|
||||||
"relationship", "sighting", "marking-definition", "bundle",
|
value_stix_version = detect_spec_version(value)
|
||||||
"language-content"
|
if value_stix_version != stix_version:
|
||||||
}
|
result = False
|
||||||
|
|
||||||
|
if result:
|
||||||
|
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
||||||
|
type_ = _stix_type_of(value)
|
||||||
|
result = type_ in cls_maps["objects"] and type_ not in {
|
||||||
|
"relationship", "sighting", "marking-definition", "bundle",
|
||||||
|
"language-content"
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def is_sco(value, stix_version=stix2.version.DEFAULT_VERSION):
|
def is_sco(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||||
"""
|
"""
|
||||||
Determine whether the given object, type, or ID is/is for an SCO.
|
Determine whether the given object, type, or ID is/is for an SCO of the
|
||||||
|
given STIX version. If value is a type or ID, this just checks whether
|
||||||
|
the type was registered as an SCO in the given STIX version. If a mapping,
|
||||||
|
*simple* STIX version inference is additionally done on the value, and the
|
||||||
|
result is checked against stix_version. It does not attempt to fully
|
||||||
|
validate the value.
|
||||||
|
|
||||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||||
as a string
|
as a string
|
||||||
:param stix_version: A STIX version as a string
|
:param stix_version: A STIX version as a string
|
||||||
:return: True if the type of the given value is an SCO type; False
|
:return: True if the type of the given value is an SCO type of the given
|
||||||
if not
|
version; False if not
|
||||||
"""
|
"""
|
||||||
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
|
||||||
type_ = _stix_type_of(value)
|
result = True
|
||||||
result = type_ in cls_maps["observables"]
|
if isinstance(value, collections.abc.Mapping):
|
||||||
|
value_stix_version = detect_spec_version(value)
|
||||||
|
if value_stix_version != stix_version:
|
||||||
|
result = False
|
||||||
|
|
||||||
|
if result:
|
||||||
|
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
||||||
|
type_ = _stix_type_of(value)
|
||||||
|
result = type_ in cls_maps["observables"]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def is_sro(value, stix_version=stix2.version.DEFAULT_VERSION):
|
def is_sro(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||||
"""
|
"""
|
||||||
Determine whether the given object, type, or ID is/is for an SRO.
|
Determine whether the given object, type, or ID is/is for an SRO of the
|
||||||
|
given STIX version. If value is a type or ID, this just checks whether
|
||||||
|
the type was registered as an SRO in the given STIX version. If a mapping,
|
||||||
|
*simple* STIX version inference is additionally done on the value, and the
|
||||||
|
result is checked against stix_version. It does not attempt to fully
|
||||||
|
validate the value.
|
||||||
|
|
||||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||||
as a string
|
as a string
|
||||||
:param stix_version: A STIX version as a string
|
:param stix_version: A STIX version as a string
|
||||||
:return: True if the type of the given value is an SRO type; False
|
:return: True if the type of the given value is an SRO type of the given
|
||||||
if not
|
version; False if not
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# No STIX version dependence here yet...
|
result = True
|
||||||
type_ = _stix_type_of(value)
|
if isinstance(value, collections.abc.Mapping):
|
||||||
result = type_ in ("sighting", "relationship")
|
value_stix_version = detect_spec_version(value)
|
||||||
|
if value_stix_version != stix_version:
|
||||||
|
result = False
|
||||||
|
|
||||||
|
if result:
|
||||||
|
# No need to check registration in this case
|
||||||
|
type_ = _stix_type_of(value)
|
||||||
|
result = type_ in ("sighting", "relationship")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -398,7 +477,11 @@ def is_sro(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||||
def is_object(value, stix_version=stix2.version.DEFAULT_VERSION):
|
def is_object(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||||
"""
|
"""
|
||||||
Determine whether an object, type, or ID is/is for any STIX object. This
|
Determine whether an object, type, or ID is/is for any STIX object. This
|
||||||
includes all SDOs, SCOs, meta-objects, and bundle.
|
includes all SDOs, SCOs, meta-objects, and bundle. If value is a type or
|
||||||
|
ID, this just checks whether the type was registered in the given STIX
|
||||||
|
version. If a mapping, *simple* STIX version inference is additionally
|
||||||
|
done on the value, and the result is checked against stix_version. It does
|
||||||
|
not attempt to fully validate the value.
|
||||||
|
|
||||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||||
as a string
|
as a string
|
||||||
|
@ -406,24 +489,46 @@ def is_object(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||||
:return: True if the type of the given value is a valid STIX type with
|
:return: True if the type of the given value is a valid STIX type with
|
||||||
respect to the given STIX version; False if not
|
respect to the given STIX version; False if not
|
||||||
"""
|
"""
|
||||||
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
|
||||||
type_ = _stix_type_of(value)
|
result = True
|
||||||
result = type_ in cls_maps["observables"] or type_ in cls_maps["objects"]
|
if isinstance(value, collections.abc.Mapping):
|
||||||
|
value_stix_version = detect_spec_version(value)
|
||||||
|
if value_stix_version != stix_version:
|
||||||
|
result = False
|
||||||
|
|
||||||
|
if result:
|
||||||
|
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
||||||
|
type_ = _stix_type_of(value)
|
||||||
|
result = type_ in cls_maps["observables"] \
|
||||||
|
or type_ in cls_maps["objects"]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def is_marking(value, stix_version=stix2.version.DEFAULT_VERSION):
|
def is_marking(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||||
"""Determines whether the given value is/is for a marking definition.
|
"""
|
||||||
|
Determine whether the given object, type, or ID is/is for an marking
|
||||||
|
definition of the given STIX version. If value is a type or ID, this just
|
||||||
|
checks whether the type was registered as an SDO in the given STIX version.
|
||||||
|
If a mapping, *simple* STIX version inference is additionally done on the
|
||||||
|
value, and the result is checked against stix_version. It does not attempt
|
||||||
|
to fully validate the value.
|
||||||
|
|
||||||
:param value: A STIX object, object ID, or type as a string.
|
:param value: A STIX object, object ID, or type as a string.
|
||||||
:param stix_version: A STIX version as a string
|
:param stix_version: A STIX version as a string
|
||||||
:return: True if the value is/is for a marking definition, False otherwise.
|
:return: True if the value is/is for a marking definition, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# No STIX version dependence here yet...
|
result = True
|
||||||
type_ = _stix_type_of(value)
|
if isinstance(value, collections.abc.Mapping):
|
||||||
result = type_ == "marking-definition"
|
value_stix_version = detect_spec_version(value)
|
||||||
|
if value_stix_version != stix_version:
|
||||||
|
result = False
|
||||||
|
|
||||||
|
if result:
|
||||||
|
# No need to check registration in this case
|
||||||
|
type_ = _stix_type_of(value)
|
||||||
|
result = type_ == "marking-definition"
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -464,7 +569,7 @@ def is_stix_type(value, stix_version=stix2.version.DEFAULT_VERSION, *types):
|
||||||
# Assume a string STIX type is given instead of a class enum,
|
# Assume a string STIX type is given instead of a class enum,
|
||||||
# and just check for exact match.
|
# and just check for exact match.
|
||||||
obj_type = _stix_type_of(value)
|
obj_type = _stix_type_of(value)
|
||||||
result = is_object(obj_type, stix_version) and obj_type == type_
|
result = obj_type == type_ and is_object(value, stix_version)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
break
|
break
|
||||||
|
|
|
@ -10,7 +10,7 @@ from six.moves.collections_abc import Mapping
|
||||||
|
|
||||||
import stix2.base
|
import stix2.base
|
||||||
import stix2.registry
|
import stix2.registry
|
||||||
from stix2.utils import get_timestamp, parse_into_datetime
|
from stix2.utils import get_timestamp, parse_into_datetime, detect_spec_version
|
||||||
import stix2.v20
|
import stix2.v20
|
||||||
|
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
|
@ -87,7 +87,7 @@ def _is_versionable(data):
|
||||||
# (is_21 means 2.1 or later; try not to be 2.1-specific)
|
# (is_21 means 2.1 or later; try not to be 2.1-specific)
|
||||||
is_21 = True
|
is_21 = True
|
||||||
elif isinstance(data, dict):
|
elif isinstance(data, dict):
|
||||||
stix_version = stix2.parsing._detect_spec_version(data)
|
stix_version = detect_spec_version(data)
|
||||||
is_21 = stix_version != "2.0"
|
is_21 = stix_version != "2.0"
|
||||||
|
|
||||||
# Then, determine versionability.
|
# Then, determine versionability.
|
||||||
|
|
Loading…
Reference in New Issue