added remove_custom_stix() utility; tweaked new_version() to make it a little safer

stix2.0
= 2017-11-21 14:39:50 -05:00
parent ec42182cb1
commit 0347bb52fd
1 changed files with 53 additions and 3 deletions

View File

@ -16,6 +16,9 @@ from .exceptions import (InvalidValueError, RevokeError,
# timestamps in a single object, the timestamps will vary by a few microseconds. # timestamps in a single object, the timestamps will vary by a few microseconds.
NOW = object() NOW = object()
# STIX object properties that cannot be modified
STIX_UNMOD_PROPERTIES = ["created", "created_by_ref", "id", "type"]
class STIXdatetime(dt.datetime): class STIXdatetime(dt.datetime):
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
@ -215,7 +218,7 @@ def new_version(data, **kwargs):
properties_to_change = kwargs.keys() properties_to_change = kwargs.keys()
# Make sure certain properties aren't trying to change # Make sure certain properties aren't trying to change
for prop in ["created", "created_by_ref", "id", "type"]: for prop in STIX_UNMOD_PROPERTIES:
if prop in properties_to_change: if prop in properties_to_change:
unchangable_properties.append(prop) unchangable_properties.append(prop)
if unchangable_properties: if unchangable_properties:
@ -227,8 +230,11 @@ def new_version(data, **kwargs):
elif 'modified' in data: elif 'modified' in data:
old_modified_property = parse_into_datetime(data.get('modified'), precision='millisecond') old_modified_property = parse_into_datetime(data.get('modified'), precision='millisecond')
new_modified_property = parse_into_datetime(kwargs['modified'], precision='millisecond') new_modified_property = parse_into_datetime(kwargs['modified'], precision='millisecond')
if new_modified_property < old_modified_property: if new_modified_property <= old_modified_property:
raise InvalidValueError(cls, 'modified', "The new modified datetime cannot be before the current modified datatime.") raise InvalidValueError(cls, 'modified',
"The new modified datetime cannot be before the current modified datetime. The new modified time can "
"also not be equal to the current modified datetime because if a consumer receives two objects that are "
"different, but have the same id and modified timestamp, it is not defined how the consumer handles the objects.")
new_obj_inner.update(kwargs) new_obj_inner.update(kwargs)
# Exclude properties with a value of 'None' in case data is not an instance of a _STIXBase subclass # Exclude properties with a value of 'None' in case data is not an instance of a _STIXBase subclass
return cls(**{k: v for k, v in new_obj_inner.items() if v is not None}) return cls(**{k: v for k, v in new_obj_inner.items() if v is not None})
@ -255,3 +261,47 @@ def get_class_hierarchy_names(obj):
for cls in obj.__class__.__mro__: for cls in obj.__class__.__mro__:
names.append(cls.__name__) names.append(cls.__name__)
return names return names
def remove_custom_stix(stix_obj):
"""remove any custom STIX objects or properties
Args:
stix_obj (dict OR python-stix obj): a single python-stix object
or dict of a STIX object
"""
if stix_obj["type"].startswith("x_"):
# if entire object is custom, discard
return None
custom_props = []
for prop in stix_obj.items():
if prop[0].startswith("x_"):
# for every custom property, record it and set value to None
# (so we can pass it to new_version() and it will be dropped)
custom_props.append((prop[0], None))
if custom_props:
# obtain set of object properties that can be transferred
# to a new object version. This is 1)custom props with their
# values set to None, and 2)any properties left that are not
# unmodifiable STIX properties or the "modified" property
# set of properties that are not supplied to new_version()
# to be used for updating properties. This includes unmodifiable
# properties (properties that new_version() just re-uses from the
# existing STIX object) and the "modified" property. We dont supply the
# "modified" property so that new_version() creates a new datetime
# value for this property
non_supplied_props = STIX_UNMOD_PROPERTIES + ["modified"]
props = [(prop, stix_obj[prop]) for prop in stix_obj if prop not in non_supplied_props]
# add to set the custom properties we want to get rid of (with their value=None)
props.extend(custom_props)
return new_version(stix_obj, **(dict(props)))
else:
return stix_obj