Skip to content

Commit dc0a494

Browse files
committed
Behavior changes to Observable/Observables in Indicator.
1 parent 1119f4d commit dc0a494

1 file changed

Lines changed: 96 additions & 60 deletions

File tree

stix/indicator/indicator.py

Lines changed: 96 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
RelatedIndicator, RelatedCampaignRef, RelatedPackageRefs)
2020
from stix.common.vocabs import VocabField, IndicatorType
2121
from stix.common.kill_chains import KillChainPhasesReference
22+
from stix import utils
2223
import stix.bindings.indicator as indicator_binding
2324

2425
# relative
@@ -180,7 +181,7 @@ class Indicator(stix.BaseCoreComponent):
180181
_try_cast = False
181182

182183
producer = fields.TypedField("Producer", InformationSource)
183-
observable = fields.TypedField("Observable", Observable, postset_hook=lambda inst,value: inst.set_observables([value]))
184+
observable = fields.TypedField("Observable", Observable)
184185
indicator_types = VocabField("Type", IndicatorType, multiple=True, key_name="indicator_types")
185186
confidence = fields.TypedField("Confidence", Confidence)
186187
indicated_ttps = fields.TypedField("Indicated_TTP", RelatedTTP, multiple=True, key_name="indicated_ttps")
@@ -209,7 +210,7 @@ def __init__(self, id_=None, idref=None, timestamp=None, title=None,
209210
short_description=short_description
210211
)
211212

212-
self.observables = []
213+
self.observable = None
213214
self.indicator_types = IndicatorTypes()
214215
self.test_mechanisms = TestMechanisms()
215216
self.alternative_id = None
@@ -228,51 +229,95 @@ def observables(self):
228229
a single object instance or a list of objects.
229230
230231
Note:
231-
If the input value or values are not instance(s) of
232-
``cybox.core.Observable``, an attempt will be made to
233-
convert the value to an instance of ``cybox.core.Observable``.
232+
If only one Observable is set, this property will return a list
233+
with the ``observable`` property.
234234
235-
Default Value: Empty ``list``
235+
If multiple ``cybox.core.Observable`` this property will return
236+
Observables under the ``cybox.core.ObservableComposition``.
237+
238+
Access to the top level ``cybox.core.Observable`` is made via
239+
``observable`` property.
240+
241+
Default Value:
242+
Empty ``list``.
236243
237244
Returns:
238245
A ``list`` of ``cybox.core.Observable`` instances.
239246
247+
"""
248+
if not self.observable:
249+
return []
250+
elif self.observable.observable_composition:
251+
return self.observable.observable_composition.observables
252+
253+
return []
254+
255+
@observables.setter
256+
def observables(self, value):
257+
"""
258+
The method will automatically create a top ``cybox.core.Observable`` and
259+
append all ``cybox.core.Observable`` using ``observable_composition``
260+
property when a ``list`` is given with length greater than 1.
261+
262+
Note:
263+
The top level ``cybox.core.Observable`` will set the ``operator``
264+
property for the ``cybox.core.ObservableComposition`` via the
265+
``observable_composition_operator`` property. The value of
266+
``operator`` can be changed via ``observable_composition_operator``
267+
property. By default, the composition layer will be set to ``"OR"``.
268+
269+
Args:
270+
value: A ``list`` of ``cybox.core.Observable`` instances or a single
271+
``cybox.core.Observable`` instance.
272+
240273
Raises:
241274
ValueError: If set to a value that cannot be converted to an
242275
instance of ``cybox.core.Observable``.
243-
244276
"""
245-
return self._observables
277+
if not value:
278+
return
246279

247-
@observables.setter
248-
def observables(self, value):
249-
# this code causes infinite recursion; observables sets observable which sets obesrvables...
250-
#try:
251-
# self.observable = value[0]
252-
#except TypeError:
253-
# self.observable = value
254-
self._observables = _Observables(value)
280+
if isinstance(value, Observable):
281+
self.observable = value
282+
283+
elif utils.is_sequence(value):
284+
if len(value) == 1:
285+
self.observable = value
286+
return
287+
288+
observable_comp = ObservableComposition()
289+
observable_comp.operator = self.observable_composition_operator
290+
291+
for element in value:
292+
observable_comp.add(element)
293+
294+
self.observable = Observable()
295+
self.observable.observable_composition = observable_comp
255296

256297
def set_observables(self, value):
257298
self.observables = value
258299

259300
def add_observable(self, observable):
260-
"""Adds an observable to the ``observables`` list property of the
301+
"""Adds an observable to the ``observable`` property of the
261302
:class:`Indicator`.
262303
263304
If the `observable` parameter is ``None``, no item will be added
264-
to the ``observables`` list.
305+
to the ``observable`` property.
265306
266307
Note:
267308
The STIX Language dictates that an :class:`Indicator` can have only
268-
one ``Observable`` under it. Because of this, the ``to_xml()``
269-
method will convert the ``observables`` list into an
270-
``cybox.core.ObservableComposition`` instance, in which each item
271-
in the ``observables`` list will be added to the composition. By
309+
one ``Observable`` under it. Because of this, when a user adds
310+
another ``Observable`` a new, empty ``Observable`` will be crated
311+
and append the existing and new ``observable`` using the
312+
``ObservableComposition`` property. To access the top level
313+
``Observable`` can be achieved by the ``observable`` property .By
272314
default, the ``operator`` of the composition layer will be set to
273315
``"OR"``. The ``operator`` value can be changed via the
274316
``observable_composition_operator`` property.
275317
318+
Setting ``observable`` or ``observables`` with re-initialize the
319+
property and lose all ``Observable`` in the composition layer.
320+
276321
Args:
277322
observable: An instance of ``cybox.core.Observable`` or an object
278323
type that can be converted into one.
@@ -283,8 +328,29 @@ def add_observable(self, observable):
283328
instance of ``cybox.core.Observable``.
284329
285330
"""
286-
self.observables.append(observable)
287-
331+
if not observable:
332+
return
333+
334+
# Sets the first observable.
335+
elif not self.observable:
336+
self.observable = observable
337+
338+
# When another is inserted. A "root" Observable is created and the
339+
# user's Observables are appended to the composition.
340+
elif not self.observable.observable_composition:
341+
observable_comp = ObservableComposition()
342+
observable_comp.operator = self.observable_composition_operator
343+
344+
observable_comp.add(self.observable)
345+
observable_comp.add(observable)
346+
347+
self.observable = Observable()
348+
self.observable.observable_composition = observable_comp
349+
350+
# Keep appending to "root" Observable.
351+
else:
352+
self.observable.observable_composition.add(observable)
353+
288354
def add_alternative_id(self, value):
289355
"""Adds an alternative id to the ``alternative_id`` list property.
290356
@@ -300,7 +366,7 @@ def add_alternative_id(self, value):
300366
return
301367

302368
self.alternative_id.append(value)
303-
369+
304370
def add_valid_time_position(self, value):
305371
"""Adds an valid time position to the ``valid_time_positions`` property
306372
list.
@@ -340,7 +406,6 @@ def add_indicator_type(self, value):
340406
"""
341407
self.indicator_types.append(value)
342408

343-
344409
def add_indicated_ttp(self, v):
345410
"""Adds an Indicated TTP to the ``indicated_ttps`` list property
346411
of this :class:`Indicator`.
@@ -367,7 +432,6 @@ def add_indicated_ttp(self, v):
367432
"""
368433
self.indicated_ttps.append(v)
369434

370-
371435
def add_test_mechanism(self, tm):
372436
"""Adds an Test Mechanism to the ``test_mechanisms`` list property
373437
of this :class:`Indicator`.
@@ -467,6 +531,10 @@ def observable_composition_operator(self):
467531
def observable_composition_operator(self, value):
468532
if value in self._ALLOWED_COMPOSITION_OPERATORS:
469533
self._observable_composition_operator = value
534+
535+
if self.observable and self.observable.observable_composition:
536+
self.observable.observable_composition.operator = value
537+
470538
return
471539

472540
error = "observable_composition_operator must one of {0}"
@@ -655,37 +723,7 @@ def add_object(self, object_):
655723

656724
observable = Observable(object_)
657725
self.add_observable(observable)
658-
659-
def to_obj(self, ns_info=None):
660-
obj = super(Indicator, self).to_obj(ns_info=ns_info)
661-
662-
if self.observables:
663-
if len(self.observables) > 1:
664-
root_observable = self._merge_observables(self.observables)
665-
else:
666-
root_observable = self.observables[0]
667-
obj.Observable = root_observable.to_obj(ns_info=ns_info)
668-
669-
return obj
670-
671-
def to_dict(self):
672-
keys = ('observables', 'observable_composition_operator', 'negate')
673-
#d = utils.to_dict(self, skip=keys)
674-
675-
d = super(Indicator, self).to_dict()
676-
677-
if not self.negate:
678-
d.pop("negate", None)
679-
680-
if self.observables:
681-
if len(self.observables) == 1:
682-
d['observable'] = self.observables[0].to_dict()
683-
else:
684-
composite_observable = self._merge_observables(self.observables)
685-
d['observable'] = composite_observable.to_dict()
686-
687-
return d
688-
726+
689727

690728
def check_operator(composite_indicator_exp, value):
691729
allowed = CompositeIndicatorExpression.OPERATORS
@@ -770,8 +808,6 @@ def _fix_value(self, value):
770808

771809
if isinstance(value, Campaign) and value.id_:
772810
return RelatedCampaignRef(CampaignRef(idref=value.id_))
773-
elif isinstance(value, CampaignRef):
774-
return RelatedCampaignRef(value)
775811

776812
msg = "Cannot insert object of type '%s' into '%s'"
777813
msg = msg % (type(value), self.__class__.__name__)

0 commit comments

Comments
 (0)