1919 RelatedIndicator , RelatedCampaignRef , RelatedPackageRefs )
2020from stix .common .vocabs import VocabField , IndicatorType
2121from stix .common .kill_chains import KillChainPhasesReference
22+ from stix import utils
2223import 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
690728def 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