Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding **optional** interoperability parameter to avoid issues loading STIX documents with non random UUIDs #620

Open
wants to merge 65 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
067d76b
chg: Added parameter to accept UUIDs not v4
chrisr3d Dec 5, 2018
86536b4
fix: Avoiding our additional parameter to be in the parsed STIX objects
chrisr3d Dec 5, 2018
3ae38fe
fix: Applying the same parameter as for IDs in created_by_ref UUIDs
chrisr3d Dec 5, 2018
3850a04
fix: Applying the interoperability parameter to UUIDs referenced in v…
chrisr3d Dec 11, 2018
f0ac7ae
add: Added tests for the new parameter
chrisr3d Dec 11, 2018
a68a43a
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Dec 14, 2018
939a2d5
add: Applying interoperability parameter to v2.1 objects
chrisr3d Dec 14, 2018
f560049
Splitted interoperability tests for both versions
chrisr3d Dec 18, 2018
383bd56
fix: Added the interoperability parameter to new_inner objects _STIXB…
chrisr3d Dec 21, 2018
f527e27
fix: Using raw string in the regular expression compilation
chrisr3d Dec 26, 2018
8370935
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jan 10, 2019
407f346
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jan 14, 2019
469d17b
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Feb 6, 2019
f049c98
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Feb 8, 2019
28db2df
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Feb 12, 2019
61e9fc0
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Mar 18, 2019
92c0582
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Apr 29, 2019
565acc7
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jun 25, 2019
b204b9f
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jul 1, 2019
a739c11
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Sep 3, 2019
adbaec1
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 + fix…
chrisr3d Oct 14, 2019
f89940e
fix: Added missing param to the id validation function
chrisr3d Oct 14, 2019
8809418
fix: Updated interoperability tests with required arguments
chrisr3d Oct 14, 2019
bf45f26
fix: Making pep8 happy
chrisr3d Oct 14, 2019
bdba2c0
fix: Removed comment
chrisr3d Oct 14, 2019
4f1d680
fix: Making python imports happy in travis
chrisr3d Oct 14, 2019
e2a4129
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Oct 21, 2019
31d944b
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Dec 12, 2019
c8cd849
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jan 8, 2020
36e4b41
fix: Avoiding inconsistency in the id prefixes causing uuid check issues
chrisr3d Jan 8, 2020
cbe109d
fix: Avoiding issues with some extensions that are not dict but defau…
chrisr3d Jan 8, 2020
0a188f9
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jan 8, 2020
0cc3a45
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jan 13, 2020
ec1fbb5
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jan 17, 2020
96946d9
fix: Avoid errors with custom object ids in the list of object refs i…
chrisr3d Jan 17, 2020
dece917
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Jan 31, 2020
5aaf077
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Feb 5, 2020
0f0bc42
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Mar 2, 2020
77ca5ae
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Mar 9, 2020
65a943d
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Apr 2, 2020
8e95dbf
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d Apr 15, 2020
e4f0855
fix: Diffusing interoperability parameter to all included objects & r…
chrisr3d Apr 15, 2020
ca61b06
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d May 22, 2020
808dd94
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Jun 24, 2020
fc27071
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Jun 30, 2020
87d178d
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Jul 9, 2020
0d6db44
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Jul 9, 2020
8e19ad0
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Sep 9, 2020
40b8af3
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Sep 12, 2020
52d806b
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Oct 13, 2020
f0f8091
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Oct 28, 2020
24374e7
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Jan 29, 2021
8093e07
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Mar 24, 2021
081f360
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Jun 30, 2021
03f4ae1
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Jul 21, 2021
8ab39f7
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Aug 31, 2021
519a5cb
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Oct 6, 2021
7dda337
Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into …
chrisr3d Feb 9, 2022
6ec45d8
fix: [interoperability] Added missing interoperability flag in Observ…
chrisr3d Feb 10, 2022
eea34eb
fix: [interoperability] Avoiding validation issues with extensions pr…
chrisr3d Feb 10, 2022
d229bd3
Merge branch 'master' of github.com:oasis-open/cti-python-stix2
chrisr3d May 16, 2022
9a966f4
chg: [interoperability] Removed changes on interoperability parameters
chrisr3d May 23, 2022
2764105
chg: [interoperability] The interoperability is managed directly with…
chrisr3d May 23, 2022
6255766
fix: [interoperability] Added missing `interoperability` param to Pro…
chrisr3d May 23, 2022
4b45b47
Merge branch 'master' of https://github.com/oasis-open/cti-python-stix2
chrisr3d Mar 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions stix2/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def get_required_properties(properties):
class _STIXBase(collections.abc.Mapping):
"""Base class for STIX object types"""

def _check_property(self, prop_name, prop, kwargs, allow_custom):
def _check_property(self, prop_name, prop, kwargs, allow_custom, interoperability):
if prop_name not in kwargs:
if hasattr(prop, 'default'):
value = prop.default()
Expand All @@ -46,10 +46,11 @@ def _check_property(self, prop_name, prop, kwargs, allow_custom):

has_custom = False
if prop_name in kwargs:
arguments = [kwargs[prop_name], allow_custom]
if isinstance(prop, self.__INTEROPERABILITY_types):
arguments.append(interoperability)
try:
kwargs[prop_name], has_custom = prop.clean(
kwargs[prop_name], allow_custom,
)
kwargs[prop_name], has_custom = prop.clean(*arguments)
except InvalidValueError:
# No point in wrapping InvalidValueError in another
# InvalidValueError... so let those propagate.
Expand Down Expand Up @@ -114,12 +115,20 @@ def _check_object_constraints(self):
for m in self.get('granular_markings', []):
validate(self, m.get('selectors'))

def __init__(self, allow_custom=False, **kwargs):
def __init__(self, allow_custom=False, interoperability=False, **kwargs):
cls = self.__class__

# Use the same timestamp for any auto-generated datetimes
self.__now = get_timestamp()

self.__INTEROPERABILITY_types = (
stix2.properties.EmbeddedObjectProperty, stix2.properties.EnumProperty,
stix2.properties.ExtensionsProperty, stix2.properties.DictionaryProperty,
stix2.properties.HashesProperty, stix2.properties.IDProperty,
stix2.properties.ListProperty, stix2.properties.OpenVocabProperty,
stix2.properties.ReferenceProperty, stix2.properties.SelectorProperty,
)

custom_props = kwargs.pop('custom_properties', {})
if custom_props and not isinstance(custom_props, dict):
raise ValueError("'custom_properties' must be a dictionary")
Expand Down Expand Up @@ -203,7 +212,7 @@ def __init__(self, allow_custom=False, **kwargs):
prop = defined_properties.get(prop_name)
if prop:
temp_custom = self._check_property(
prop_name, prop, setting_kwargs, allow_custom,
prop_name, prop, setting_kwargs, allow_custom, interoperability,
)

has_custom = has_custom or temp_custom
Expand Down Expand Up @@ -293,7 +302,7 @@ def __deepcopy__(self, memo):
if isinstance(self, _Observable):
# Assume: valid references in the original object are still valid in the new version
new_inner['_valid_refs'] = {'*': '*'}
return cls(allow_custom=True, **new_inner)
return cls(allow_custom=True, interoperability=False, **new_inner)

def properties_populated(self):
return list(self._inner.keys())
Expand Down Expand Up @@ -411,8 +420,8 @@ def _check_ref(self, ref, prop, prop_name):
if ref_type not in allowed_types:
raise InvalidObjRefError(self.__class__, prop_name, "object reference '%s' is of an invalid type '%s'" % (ref, ref_type))

def _check_property(self, prop_name, prop, kwargs, allow_custom):
has_custom = super(_Observable, self)._check_property(prop_name, prop, kwargs, allow_custom)
def _check_property(self, prop_name, prop, kwargs, allow_custom, interoperability):
has_custom = super(_Observable, self)._check_property(prop_name, prop, kwargs, allow_custom, interoperability)

if prop_name in kwargs:
from .properties import ObjectReferenceProperty
Expand Down
12 changes: 6 additions & 6 deletions stix2/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .utils import _get_dict, detect_spec_version


def parse(data, allow_custom=False, version=None):
def parse(data, allow_custom=False, interoperability=False, version=None):
"""Convert a string, dict or file-like object into a STIX object.

Args:
Expand Down Expand Up @@ -37,12 +37,12 @@ def parse(data, allow_custom=False, version=None):
obj = _get_dict(data)

# convert dict to full python-stix2 obj
obj = dict_to_stix2(obj, allow_custom, version)
obj = dict_to_stix2(obj, allow_custom, interoperability, version)

return obj


def dict_to_stix2(stix_dict, allow_custom=False, version=None):
def dict_to_stix2(stix_dict, allow_custom=False, interoperability=False, version=None):
"""convert dictionary to full python-stix2 object

Args:
Expand Down Expand Up @@ -96,10 +96,10 @@ def dict_to_stix2(stix_dict, allow_custom=False, version=None):
return stix_dict
raise ParseError("Can't parse unknown object type '%s'! For custom types, use the CustomObject decorator." % obj_type)

return obj_class(allow_custom=allow_custom, **stix_dict)
return obj_class(allow_custom=allow_custom, interoperability=interoperability, **stix_dict)


def parse_observable(data, _valid_refs=None, allow_custom=False, version=None):
def parse_observable(data, _valid_refs=None, allow_custom=False, interoperability=False, version=None):
"""Deserialize a string or file-like object into a STIX Cyber Observable
object.

Expand Down Expand Up @@ -144,4 +144,4 @@ def parse_observable(data, _valid_refs=None, allow_custom=False, version=None):
"use the CustomObservable decorator." % obj['type'],
)

return obj_class(allow_custom=allow_custom, **obj)
return obj_class(allow_custom=allow_custom, interoperability=interoperability, **obj)
56 changes: 31 additions & 25 deletions stix2/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@
)
from .version import DEFAULT_VERSION

ID_REGEX_interoperability = re.compile(
r"[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}$",
)
TYPE_REGEX = re.compile(r'^-?[a-z0-9]+(-[a-z0-9]+)*-?$')
TYPE_21_REGEX = re.compile(r'^([a-z][a-z0-9]*)+([a-z0-9-]+)*-?$')
ERROR_INVALID_ID = (
"not a valid STIX identifier, must match <object-type>--<UUID>: {}"
)


def _check_uuid(uuid_str, spec_version):
def _check_uuid(uuid_str, spec_version, interoperability):
"""
Check whether the given UUID string is valid with respect to the given STIX
spec version. STIX 2.0 requires UUIDv4; 2.1 only requires the RFC 4122
Expand All @@ -39,6 +42,9 @@ def _check_uuid(uuid_str, spec_version):
:return: True if the UUID is valid, False if not
:raises ValueError: If uuid_str is malformed
"""
if interoperability:
return ID_REGEX_interoperability.match(uuid_str)

uuid_obj = uuid.UUID(uuid_str)

ok = uuid_obj.variant == uuid.RFC_4122
Expand All @@ -48,7 +54,7 @@ def _check_uuid(uuid_str, spec_version):
return ok


def _validate_id(id_, spec_version, required_prefix):
def _validate_id(id_, spec_version, required_prefix, interoperability=False):
"""
Check the STIX identifier for correctness, raise an exception if there are
errors.
Expand All @@ -71,7 +77,7 @@ def _validate_id(id_, spec_version, required_prefix):
idx = id_.index("--")
uuid_part = id_[idx+2:]

result = _check_uuid(uuid_part, spec_version)
result = _check_uuid(uuid_part, spec_version, interoperability)
except ValueError:
# replace their ValueError with ours
raise ValueError(ERROR_INVALID_ID.format(id_))
Expand Down Expand Up @@ -170,7 +176,7 @@ class Property(object):

"""

def _default_clean(self, value, allow_custom=False):
def _default_clean(self, value, allow_custom=False, interoperability=False):
if value != self._fixed_value:
raise ValueError("must equal '{}'.".format(self._fixed_value))
return value, False
Expand Down Expand Up @@ -224,7 +230,7 @@ def __init__(self, contained, **kwargs):

super(ListProperty, self).__init__(**kwargs)

def clean(self, value, allow_custom):
def clean(self, value, allow_custom, interoperability=False):
try:
iter(value)
except TypeError:
Expand All @@ -237,7 +243,7 @@ def clean(self, value, allow_custom):
has_custom = False
if isinstance(self.contained, Property):
for item in value:
valid, temp_custom = self.contained.clean(item, allow_custom)
valid, temp_custom = self.contained.clean(item, allow_custom, interoperability)
result.append(valid)
has_custom = has_custom or temp_custom

Expand All @@ -248,7 +254,7 @@ def clean(self, value, allow_custom):

elif isinstance(item, collections.abc.Mapping):
# attempt a mapping-like usage...
valid = self.contained(allow_custom=allow_custom, **item)
valid = self.contained(allow_custom=allow_custom, interoperability=interoperability, **item)

else:
raise ValueError(
Expand All @@ -275,7 +281,7 @@ class StringProperty(Property):
def __init__(self, **kwargs):
super(StringProperty, self).__init__(**kwargs)

def clean(self, value, allow_custom=False):
def clean(self, value, allow_custom=False, interoperability=False):
if not isinstance(value, str):
value = str(value)
return value, False
Expand All @@ -296,8 +302,8 @@ def __init__(self, type, spec_version=DEFAULT_VERSION):
self.spec_version = spec_version
super(IDProperty, self).__init__()

def clean(self, value, allow_custom=False):
_validate_id(value, self.spec_version, self.required_prefix)
def clean(self, value, allow_custom=False, interoperability=False):
_validate_id(value, self.spec_version, self.required_prefix, interoperability)
return value, False

def default(self):
Expand All @@ -311,7 +317,7 @@ def __init__(self, min=None, max=None, **kwargs):
self.max = max
super(IntegerProperty, self).__init__(**kwargs)

def clean(self, value, allow_custom=False):
def clean(self, value, allow_custom=False, interoperability=False):
try:
value = int(value)
except Exception:
Expand Down Expand Up @@ -391,7 +397,7 @@ def __init__(self, spec_version=DEFAULT_VERSION, **kwargs):
self.spec_version = spec_version
super(DictionaryProperty, self).__init__(**kwargs)

def clean(self, value, allow_custom=False):
def clean(self, value, allow_custom=False, interoperability=False):
try:
dictified = _get_dict(value)
except ValueError:
Expand Down Expand Up @@ -434,7 +440,7 @@ def __init__(self, spec_hash_names, spec_version=DEFAULT_VERSION, **kwargs):
if alg:
self.__alg_to_spec_name[alg] = spec_hash_name

def clean(self, value, allow_custom):
def clean(self, value, allow_custom, interoperability=False):
# ignore the has_custom return value here; there is no customization
# of DictionaryProperties.
clean_dict, _ = super().clean(value, allow_custom)
Expand Down Expand Up @@ -541,12 +547,12 @@ def __init__(self, valid_types=None, invalid_types=None, spec_version=DEFAULT_VE

super(ReferenceProperty, self).__init__(**kwargs)

def clean(self, value, allow_custom):
def clean(self, value, allow_custom, interoperability=False):
if isinstance(value, _STIXBase):
value = value.id
value = str(value)

_validate_id(value, self.spec_version, None)
_validate_id(value, self.spec_version, None, interoperability)

obj_type = get_type_from_id(value)

Expand Down Expand Up @@ -619,7 +625,7 @@ def clean(self, value, allow_custom):

class SelectorProperty(Property):

def clean(self, value, allow_custom=False):
def clean(self, value, allow_custom=False, interoperability=False):
if not SELECTOR_REGEX.match(value):
raise ValueError("must adhere to selector syntax.")
return value, False
Expand All @@ -640,7 +646,7 @@ def __init__(self, type, **kwargs):
self.type = type
super(EmbeddedObjectProperty, self).__init__(**kwargs)

def clean(self, value, allow_custom):
def clean(self, value, allow_custom, interoperability=False):
if isinstance(value, dict):
value = self.type(allow_custom=allow_custom, **value)
elif not isinstance(value, self.type):
Expand Down Expand Up @@ -668,8 +674,8 @@ def __init__(self, allowed, **kwargs):
self.allowed = allowed
super(EnumProperty, self).__init__(**kwargs)

def clean(self, value, allow_custom):
cleaned_value, _ = super(EnumProperty, self).clean(value, allow_custom)
def clean(self, value, allow_custom, interoperability=False):
cleaned_value, _ = super(EnumProperty, self).clean(value, allow_custom, interoperability)

if cleaned_value not in self.allowed:
raise ValueError("value '{}' is not valid for this enumeration.".format(cleaned_value))
Expand All @@ -689,9 +695,9 @@ def __init__(self, allowed, **kwargs):
allowed = [allowed]
self.allowed = allowed

def clean(self, value, allow_custom):
def clean(self, value, allow_custom, interoperability=False):
cleaned_value, _ = super(OpenVocabProperty, self).clean(
value, allow_custom,
value, allow_custom, interoperability,
)

# Disabled: it was decided that enforcing this is too strict (might
Expand Down Expand Up @@ -770,7 +776,7 @@ class ExtensionsProperty(DictionaryProperty):
def __init__(self, spec_version=DEFAULT_VERSION, required=False):
super(ExtensionsProperty, self).__init__(spec_version=spec_version, required=required)

def clean(self, value, allow_custom):
def clean(self, value, allow_custom, interoperability=False):
try:
dictified = _get_dict(value)
# get deep copy since we are going modify the dict and might
Expand All @@ -785,7 +791,7 @@ def clean(self, value, allow_custom):
cls = class_for_type(key, self.spec_version, "extensions")
if cls:
if isinstance(subvalue, dict):
ext = cls(allow_custom=allow_custom, **subvalue)
ext = cls(allow_custom=allow_custom, interoperability=interoperability, **subvalue)
elif isinstance(subvalue, cls):
# If already an instance of the registered class, assume
# it's valid
Expand Down Expand Up @@ -836,7 +842,7 @@ def __init__(self, spec_version=DEFAULT_VERSION, *args, **kwargs):
self.spec_version = spec_version
super(STIXObjectProperty, self).__init__(*args, **kwargs)

def clean(self, value, allow_custom):
def clean(self, value, allow_custom, interoperability=False):
# Any STIX Object (SDO, SRO, or Marking Definition) can be added to
# a bundle with no further checks.
stix2_classes = {'_DomainObject', '_RelationshipObject', 'MarkingDefinition'}
Expand Down Expand Up @@ -876,7 +882,7 @@ def clean(self, value, allow_custom):
"containing objects of a different spec version.",
)

parsed_obj = parse(dictified, allow_custom=allow_custom)
parsed_obj = parse(dictified, allow_custom=allow_custom, interoperability=interoperability)

if isinstance(parsed_obj, _STIXBase):
has_custom = parsed_obj.has_custom
Expand Down
Loading