From 41e541959d3c174917030fcee99c07150f8a05f4 Mon Sep 17 00:00:00 2001 From: "Desai, Kartikey H" Date: Mon, 24 Feb 2020 21:11:42 -0500 Subject: [PATCH 1/4] Add _id_contributing_properties functionality to custom SCOs. Tests coming soon. Fixes #351 --- stix2/custom.py | 4 +++- stix2/v21/observables.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/stix2/custom.py b/stix2/custom.py index a00498b..81991e9 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -53,7 +53,7 @@ def _custom_marking_builder(cls, type, properties, version): return _CustomMarking -def _custom_observable_builder(cls, type, properties, version): +def _custom_observable_builder(cls, type, properties, version, id_contrib_props=[]): class _CustomObservable(cls, _Observable): if not re.match(TYPE_REGEX, type): @@ -98,6 +98,8 @@ def _custom_observable_builder(cls, type, properties, version): _type = type _properties = OrderedDict(properties) + if version != '2.0': + _id_contributing_properties = id_contrib_props def __init__(self, **kwargs): _Observable.__init__(self, **kwargs) diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py index ed560a6..5c23362 100644 --- a/stix2/v21/observables.py +++ b/stix2/v21/observables.py @@ -966,7 +966,7 @@ class X509Certificate(_Observable): self._check_at_least_one_property(att_list) -def CustomObservable(type='x-custom-observable', properties=None): +def CustomObservable(type='x-custom-observable', properties=None, id_contrib_props=[]): """Custom STIX Cyber Observable Object type decorator. Example: @@ -987,7 +987,7 @@ def CustomObservable(type='x-custom-observable', properties=None): properties, [('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=type))], ])) - return _custom_observable_builder(cls, type, _properties, '2.1') + return _custom_observable_builder(cls, type, _properties, '2.1', id_contrib_props) return wrapper From 055ad97a7aee4e14d1acd8258e9eeccf38254578 Mon Sep 17 00:00:00 2001 From: "Desai, Kartikey H" Date: Thu, 27 Feb 2020 15:15:37 -0500 Subject: [PATCH 2/4] Add tests for _id_contributing_properties for custom observables --- stix2/test/v21/test_custom.py | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py index 9c650eb..b46288d 100644 --- a/stix2/test/v21/test_custom.py +++ b/stix2/test/v21/test_custom.py @@ -1,3 +1,5 @@ +import uuid + import pytest import stix2 @@ -665,6 +667,76 @@ def test_observed_data_with_custom_observable_object(): assert ob_data.objects['0'].property1 == 'something' +def test_custom_observable_object_det_id_1(): + @stix2.v21.CustomObservable( + 'x-det-id-observable-1', [ + ('property1', stix2.properties.StringProperty(required=True)), + ('property2', stix2.properties.IntegerProperty()), + ], [ + 'property1', + ], + ) + class DetIdObs1(): + pass + + dio_1 = DetIdObs1(property1='I am property1!', property2=42) + dio_2 = DetIdObs1(property1='I am property1!', property2=24) + assert dio_1.property1 == dio_2.property1 == 'I am property1!' + assert dio_1.id == dio_2.id + + uuid_obj = uuid.UUID(dio_1.id[-36:]) + assert uuid_obj.variant == uuid.RFC_4122 + assert uuid_obj.version == 5 + + dio_3 = DetIdObs1(property1='I am property1!', property2=42) + dio_4 = DetIdObs1(property1='I am also property1!', property2=24) + assert dio_3.property1 == 'I am property1!' + assert dio_4.property1 == 'I am also property1!' + assert dio_3.id != dio_4.id + + +def test_custom_observable_object_det_id_2(): + @stix2.v21.CustomObservable( + 'x-det-id-observable-2', [ + ('property1', stix2.properties.StringProperty(required=True)), + ('property2', stix2.properties.IntegerProperty()), + ], [ + 'property1', 'property2', + ], + ) + class DetIdObs2(): + pass + + dio_1 = DetIdObs2(property1='I am property1!', property2=42) + dio_2 = DetIdObs2(property1='I am property1!', property2=42) + assert dio_1.property1 == dio_2.property1 == 'I am property1!' + assert dio_1.property2 == dio_2.property2 == 42 + assert dio_1.id == dio_2.id + + dio_3 = DetIdObs2(property1='I am property1!', property2=42) + dio_4 = DetIdObs2(property1='I am also property1!', property2=42) + assert dio_3.property1 == 'I am property1!' + assert dio_4.property1 == 'I am also property1!' + assert dio_3.property2 == dio_4.property2 == 42 + assert dio_3.id != dio_4.id + + +def test_custom_observable_object_no_id_contrib_props(): + @stix2.v21.CustomObservable( + 'x-det-id-observable-3', [ + ('property1', stix2.properties.StringProperty(required=True)), + ], + ) + class DetIdObs3(): + pass + + dio = DetIdObs3(property1="I am property1!") + + uuid_obj = uuid.UUID(dio.id[-36:]) + assert uuid_obj.variant == uuid.RFC_4122 + assert uuid_obj.version == 4 + + @stix2.v21.CustomExtension( stix2.v21.DomainName, 'x-new-ext', [ ('property1', stix2.properties.StringProperty(required=True)), From fc95b400ff4794b0418fab427fd6324163aee352 Mon Sep 17 00:00:00 2001 From: "Desai, Kartikey H" Date: Wed, 4 Mar 2020 14:29:35 -0500 Subject: [PATCH 3/4] Change default parameters from empty lists to None. Fixes #351 --- stix2/custom.py | 2 +- stix2/v21/observables.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/stix2/custom.py b/stix2/custom.py index 81991e9..0db9d25 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -53,7 +53,7 @@ def _custom_marking_builder(cls, type, properties, version): return _CustomMarking -def _custom_observable_builder(cls, type, properties, version, id_contrib_props=[]): +def _custom_observable_builder(cls, type, properties, version, id_contrib_props=None): class _CustomObservable(cls, _Observable): if not re.match(TYPE_REGEX, type): diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py index 5c23362..88d2f6c 100644 --- a/stix2/v21/observables.py +++ b/stix2/v21/observables.py @@ -966,7 +966,7 @@ class X509Certificate(_Observable): self._check_at_least_one_property(att_list) -def CustomObservable(type='x-custom-observable', properties=None, id_contrib_props=[]): +def CustomObservable(type='x-custom-observable', properties=None, id_contrib_props=None): """Custom STIX Cyber Observable Object type decorator. Example: @@ -980,6 +980,9 @@ def CustomObservable(type='x-custom-observable', properties=None, id_contrib_pro ... pass """ + if id_contrib_props is None: + id_contrib_props = [] + def wrapper(cls): _properties = list(itertools.chain.from_iterable([ [('type', TypeProperty(type))], From a5cd0fdc505e5cc6376331232ea4f71e0f4c131d Mon Sep 17 00:00:00 2001 From: "Desai, Kartikey H" Date: Wed, 4 Mar 2020 14:46:55 -0500 Subject: [PATCH 4/4] Change location of None-check for id_contrib_props. Fixes #351 --- stix2/custom.py | 3 +++ stix2/v21/observables.py | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stix2/custom.py b/stix2/custom.py index 0db9d25..802fd07 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -54,6 +54,9 @@ def _custom_marking_builder(cls, type, properties, version): def _custom_observable_builder(cls, type, properties, version, id_contrib_props=None): + if id_contrib_props is None: + id_contrib_props = [] + class _CustomObservable(cls, _Observable): if not re.match(TYPE_REGEX, type): diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py index 88d2f6c..e8c1925 100644 --- a/stix2/v21/observables.py +++ b/stix2/v21/observables.py @@ -980,9 +980,6 @@ def CustomObservable(type='x-custom-observable', properties=None, id_contrib_pro ... pass """ - if id_contrib_props is None: - id_contrib_props = [] - def wrapper(cls): _properties = list(itertools.chain.from_iterable([ [('type', TypeProperty(type))],