Changed STIX object initialization to formulate a property order
and process properties in that order. This establishes iteration order on object properties, making the object_properties() method unnecessary. So the latter method has been deleted. All uses of that method have been removed. Removed unnecessary deepcopy() in STIXJSONEncoder, to improve efficiency. This uncovered a bug which had been affecting STIXdatetime instances. Not deepcopying doesn't trip the bug, which can change serialization format. This caused a unit test to fail, which was checking serialization format. I fixed the unit test. Fixed a bug in _STIXBase.__repr__ which caused it to omit all properties with falsey values. This caused several unit tests to break, since they were written against the old buggy repr format. Notably, 'revoked=False' was never included in reprs before, but it is now.pull/1/head
parent
8bbf5fa461
commit
2cda97cf5e
|
@ -36,34 +36,6 @@ def get_required_properties(properties):
|
||||||
class _STIXBase(collections.abc.Mapping):
|
class _STIXBase(collections.abc.Mapping):
|
||||||
"""Base class for STIX object types"""
|
"""Base class for STIX object types"""
|
||||||
|
|
||||||
def object_properties(self):
|
|
||||||
"""
|
|
||||||
Get a list of property names in a particular order: spec order for
|
|
||||||
spec defined properties, followed by toplevel-property-extension
|
|
||||||
properties (any order), followed by custom properties (any order).
|
|
||||||
|
|
||||||
The returned list doesn't include only defined+extension properties,
|
|
||||||
nor does it include only assigned properties (i.e. those this object
|
|
||||||
actually possesses). It's a mix of both: the spec defined property
|
|
||||||
group and extension group include all of them, regardless of whether
|
|
||||||
they're present on this object; the custom group include only names of
|
|
||||||
properties present on this object.
|
|
||||||
|
|
||||||
:return: A list of property names
|
|
||||||
"""
|
|
||||||
if self.__property_order is None:
|
|
||||||
custom_props = sorted(
|
|
||||||
self.keys() - self._properties.keys()
|
|
||||||
- self.__ext_property_names
|
|
||||||
)
|
|
||||||
|
|
||||||
# Any custom properties to the bottom
|
|
||||||
self.__property_order = list(self._properties) \
|
|
||||||
+ list(self.__ext_property_names) \
|
|
||||||
+ custom_props
|
|
||||||
|
|
||||||
return self.__property_order
|
|
||||||
|
|
||||||
def _check_property(self, prop_name, prop, kwargs, allow_custom):
|
def _check_property(self, prop_name, prop, kwargs, allow_custom):
|
||||||
if prop_name not in kwargs:
|
if prop_name not in kwargs:
|
||||||
if hasattr(prop, 'default'):
|
if hasattr(prop, 'default'):
|
||||||
|
@ -206,20 +178,33 @@ class _STIXBase(collections.abc.Mapping):
|
||||||
self._properties, registered_toplevel_extension_props
|
self._properties, registered_toplevel_extension_props
|
||||||
)
|
)
|
||||||
|
|
||||||
# object_properties() needs this; cache it here to avoid needing to
|
assigned_properties = collections.ChainMap(kwargs, custom_props)
|
||||||
# recompute.
|
|
||||||
self.__ext_property_names = set(registered_toplevel_extension_props)
|
|
||||||
# object_properties() will compute this on first call, based on
|
|
||||||
# __ext_property_names above. Maybe it makes sense to not compute this
|
|
||||||
# unless really necessary.
|
|
||||||
self.__property_order = None
|
|
||||||
|
|
||||||
# Remove any keyword arguments whose value is None or [] (i.e. empty list)
|
# Establish property order: spec-defined, toplevel extension, custom.
|
||||||
setting_kwargs = {
|
toplevel_extension_props = registered_toplevel_extension_props.keys() \
|
||||||
k: v
|
| (kwargs.keys() - self._properties.keys() - custom_kwargs)
|
||||||
for k, v in itertools.chain(kwargs.items(), custom_props.items())
|
property_order = itertools.chain(
|
||||||
if v is not None and v != []
|
self._properties,
|
||||||
}
|
toplevel_extension_props,
|
||||||
|
sorted(all_custom_prop_names)
|
||||||
|
)
|
||||||
|
|
||||||
|
setting_kwargs = {}
|
||||||
|
|
||||||
|
has_custom = bool(all_custom_prop_names)
|
||||||
|
for prop_name in property_order:
|
||||||
|
|
||||||
|
prop_val = assigned_properties.get(prop_name)
|
||||||
|
if prop_val not in (None, []):
|
||||||
|
setting_kwargs[prop_name] = prop_val
|
||||||
|
|
||||||
|
prop = defined_properties.get(prop_name)
|
||||||
|
if prop:
|
||||||
|
temp_custom = self._check_property(
|
||||||
|
prop_name, prop, setting_kwargs, allow_custom,
|
||||||
|
)
|
||||||
|
|
||||||
|
has_custom = has_custom or temp_custom
|
||||||
|
|
||||||
# Detect any missing required properties
|
# Detect any missing required properties
|
||||||
required_properties = set(
|
required_properties = set(
|
||||||
|
@ -229,14 +214,6 @@ class _STIXBase(collections.abc.Mapping):
|
||||||
if missing_kwargs:
|
if missing_kwargs:
|
||||||
raise MissingPropertiesError(cls, missing_kwargs)
|
raise MissingPropertiesError(cls, missing_kwargs)
|
||||||
|
|
||||||
has_custom = bool(all_custom_prop_names)
|
|
||||||
for prop_name, prop_metadata in defined_properties.items():
|
|
||||||
temp_custom = self._check_property(
|
|
||||||
prop_name, prop_metadata, setting_kwargs, allow_custom,
|
|
||||||
)
|
|
||||||
|
|
||||||
has_custom = has_custom or temp_custom
|
|
||||||
|
|
||||||
# Cache defaulted optional properties for serialization
|
# Cache defaulted optional properties for serialization
|
||||||
defaulted = []
|
defaulted = []
|
||||||
for name, prop in defined_properties.items():
|
for name, prop in defined_properties.items():
|
||||||
|
@ -304,7 +281,7 @@ class _STIXBase(collections.abc.Mapping):
|
||||||
return self.serialize()
|
return self.serialize()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
props = ', '.join([f"{k}={self[k]!r}" for k in self.object_properties() if self.get(k)])
|
props = ', '.join([f"{k}={self[k]!r}" for k in self])
|
||||||
return f'{self.__class__.__name__}({props})'
|
return f'{self.__class__.__name__}({props})'
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
|
|
|
@ -24,7 +24,7 @@ class STIXJSONEncoder(json.JSONEncoder):
|
||||||
if isinstance(obj, (dt.date, dt.datetime)):
|
if isinstance(obj, (dt.date, dt.datetime)):
|
||||||
return format_datetime(obj)
|
return format_datetime(obj)
|
||||||
elif isinstance(obj, stix2.base._STIXBase):
|
elif isinstance(obj, stix2.base._STIXBase):
|
||||||
tmp_obj = dict(copy.deepcopy(obj))
|
tmp_obj = dict(obj)
|
||||||
for prop_name in obj._defaulted_optional_properties:
|
for prop_name in obj._defaulted_optional_properties:
|
||||||
del tmp_obj[prop_name]
|
del tmp_obj[prop_name]
|
||||||
return tmp_obj
|
return tmp_obj
|
||||||
|
@ -177,7 +177,7 @@ def find_property_index(obj, search_key, search_value):
|
||||||
|
|
||||||
if isinstance(obj, stix2.base._STIXBase):
|
if isinstance(obj, stix2.base._STIXBase):
|
||||||
if search_key in obj and obj[search_key] == search_value:
|
if search_key in obj and obj[search_key] == search_value:
|
||||||
idx = _find(obj.object_properties(), search_key)
|
idx = _find(list(obj), search_key)
|
||||||
else:
|
else:
|
||||||
idx = _find_property_in_seq(obj.values(), search_key, search_value)
|
idx = _find_property_in_seq(obj.values(), search_key, search_value)
|
||||||
elif isinstance(obj, dict):
|
elif isinstance(obj, dict):
|
||||||
|
|
|
@ -74,6 +74,6 @@ def test_identity_with_custom():
|
||||||
)
|
)
|
||||||
|
|
||||||
assert identity.x_foo == "bar"
|
assert identity.x_foo == "bar"
|
||||||
assert "x_foo" in identity.object_properties()
|
assert "x_foo" in identity
|
||||||
|
|
||||||
# TODO: Add other examples
|
# TODO: Add other examples
|
||||||
|
|
|
@ -28,6 +28,7 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join(
|
||||||
modified='2017-01-01T00:00:01.000Z',
|
modified='2017-01-01T00:00:01.000Z',
|
||||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||||
valid_from='1970-01-01T00:00:01Z',
|
valid_from='1970-01-01T00:00:01Z',
|
||||||
|
revoked=False,
|
||||||
labels=['malicious-activity']
|
labels=['malicious-activity']
|
||||||
""".split(),
|
""".split(),
|
||||||
) + ")"
|
) + ")"
|
||||||
|
|
|
@ -21,7 +21,7 @@ EXPECTED_TLP_MARKING_DEFINITION = """{
|
||||||
EXPECTED_STATEMENT_MARKING_DEFINITION = """{
|
EXPECTED_STATEMENT_MARKING_DEFINITION = """{
|
||||||
"type": "marking-definition",
|
"type": "marking-definition",
|
||||||
"id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
"id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
"created": "2017-01-20T00:00:00Z",
|
"created": "2017-01-20T00:00:00.000Z",
|
||||||
"definition_type": "statement",
|
"definition_type": "statement",
|
||||||
"definition": {
|
"definition": {
|
||||||
"statement": "Copyright 2016, Example Corp"
|
"statement": "Copyright 2016, Example Corp"
|
||||||
|
|
|
@ -105,4 +105,3 @@ def test_extension_definition_with_custom():
|
||||||
)
|
)
|
||||||
|
|
||||||
assert extension_definition.x_foo == "bar"
|
assert extension_definition.x_foo == "bar"
|
||||||
assert "x_foo" in extension_definition.object_properties()
|
|
||||||
|
|
|
@ -77,6 +77,5 @@ def test_identity_with_custom():
|
||||||
)
|
)
|
||||||
|
|
||||||
assert identity.x_foo == "bar"
|
assert identity.x_foo == "bar"
|
||||||
assert "x_foo" in identity.object_properties()
|
|
||||||
|
|
||||||
# TODO: Add other examples
|
# TODO: Add other examples
|
||||||
|
|
|
@ -78,4 +78,3 @@ def test_incident_with_custom():
|
||||||
)
|
)
|
||||||
|
|
||||||
assert incident.x_foo == "bar"
|
assert incident.x_foo == "bar"
|
||||||
assert "x_foo" in incident.object_properties()
|
|
||||||
|
|
|
@ -30,7 +30,8 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join(
|
||||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||||
pattern_type='stix',
|
pattern_type='stix',
|
||||||
pattern_version='2.1',
|
pattern_version='2.1',
|
||||||
valid_from='1970-01-01T00:00:01Z'
|
valid_from='1970-01-01T00:00:01Z',
|
||||||
|
revoked=False
|
||||||
""".split(),
|
""".split(),
|
||||||
) + ")"
|
) + ")"
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ EXPECTED_LOCATION_1_REPR = "Location(" + " ".join(
|
||||||
created='2016-04-06T20:03:00.000Z',
|
created='2016-04-06T20:03:00.000Z',
|
||||||
modified='2016-04-06T20:03:00.000Z',
|
modified='2016-04-06T20:03:00.000Z',
|
||||||
latitude=48.8566,
|
latitude=48.8566,
|
||||||
longitude=2.3522""".split(),
|
longitude=2.3522,
|
||||||
|
revoked=False""".split(),
|
||||||
) + ")"
|
) + ")"
|
||||||
|
|
||||||
EXPECTED_LOCATION_2 = """{
|
EXPECTED_LOCATION_2 = """{
|
||||||
|
@ -47,7 +48,8 @@ EXPECTED_LOCATION_2_REPR = "Location(" + " ".join(
|
||||||
id='location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64',
|
id='location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64',
|
||||||
created='2016-04-06T20:03:00.000Z',
|
created='2016-04-06T20:03:00.000Z',
|
||||||
modified='2016-04-06T20:03:00.000Z',
|
modified='2016-04-06T20:03:00.000Z',
|
||||||
region='northern-america'""".split(),
|
region='northern-america',
|
||||||
|
revoked=False""".split(),
|
||||||
) + ")"
|
) + ")"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ EXPECTED_OPINION_REPR = "Note(" + " ".join((
|
||||||
content='%s',
|
content='%s',
|
||||||
authors=['John Doe'],
|
authors=['John Doe'],
|
||||||
object_refs=['campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f'],
|
object_refs=['campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f'],
|
||||||
|
revoked=False,
|
||||||
external_references=[ExternalReference(source_name='job-tracker', external_id='job-id-1234')]
|
external_references=[ExternalReference(source_name='job-tracker', external_id='job-id-1234')]
|
||||||
""" % CONTENT
|
""" % CONTENT
|
||||||
).split()) + ")"
|
).split()) + ")"
|
||||||
|
|
|
@ -38,7 +38,8 @@ EXPECTED_OPINION_REPR = "Opinion(" + " ".join((
|
||||||
modified='2016-05-12T08:17:27.000Z',
|
modified='2016-05-12T08:17:27.000Z',
|
||||||
explanation="%s",
|
explanation="%s",
|
||||||
opinion='strongly-disagree',
|
opinion='strongly-disagree',
|
||||||
object_refs=['relationship--16d2358f-3b0d-4c88-b047-0da2f7ed4471']
|
object_refs=['relationship--16d2358f-3b0d-4c88-b047-0da2f7ed4471'],
|
||||||
|
revoked=False
|
||||||
""" % EXPLANATION
|
""" % EXPLANATION
|
||||||
).split()) + ")"
|
).split()) + ")"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue