diff --git a/stix2/custom.py b/stix2/custom.py index a00498b..802fd07 100644 --- a/stix2/custom.py +++ b/stix2/custom.py @@ -53,7 +53,10 @@ 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=None): + if id_contrib_props is None: + id_contrib_props = [] + class _CustomObservable(cls, _Observable): if not re.match(TYPE_REGEX, type): @@ -98,6 +101,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/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)), diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py index ed560a6..e8c1925 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=None): """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