From 06dfe280bb083d29b8f70845ed7b63674ec245ac Mon Sep 17 00:00:00 2001 From: Greg Back Date: Thu, 3 Jul 2014 08:52:01 -0500 Subject: [PATCH 1/5] Add more tests for Related Objects --- cybox/test/core/object_test.py | 36 +++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/cybox/test/core/object_test.py b/cybox/test/core/object_test.py index a2ae46b3..9ef3f47d 100644 --- a/cybox/test/core/object_test.py +++ b/cybox/test/core/object_test.py @@ -7,7 +7,7 @@ from cybox.objects.address_object import Address from cybox.objects.email_message_object import EmailMessage from cybox.objects.uri_object import URI -from cybox.test import EntityTestCase, round_trip +from cybox.test import EntityTestCase, round_trip, round_trip_dict from cybox.utils import CacheMiss, set_id_method @@ -120,6 +120,34 @@ def test_missing_related_object(self): rel_obj = o2.observables[0].object_.related_objects[0] self.assertRaises(CacheMiss, rel_obj.get_properties) + def test_relationship_standard_xsitype(self): + d = { + 'id': "example:Object-1", + 'relationship': u"Created", + } + self._test_round_trip_dict(d) + + def test_relationship_nonstandard_xsitype(self): + d = { + 'id': "example:Object-1", + 'relationship': { + 'value': u"Created", + 'xsi:type': "Foo", + } + } + self._test_round_trip_dict(d) + + def test_relationship_vocabnameref(self): + d = { + 'id': "example:Object-1", + 'relationship': { + 'value': u"Created", + 'vocab_name': "Foo", + 'vocab_reference': "http://example.com/FooVocab", + } + } + self._test_round_trip_dict(d) + def _test_round_trip(self, observables): self.maxDiff = None print observables.to_xml() @@ -138,5 +166,11 @@ def _test_returned_objects(self, o2): # Check that the properties are identical self.assertEqual(self.ip.to_dict(), ip_obj.get_properties().to_dict()) + def _test_round_trip_dict(self, d): + d2 = round_trip_dict(RelatedObject, d) + self.maxDiff = None + self.assertEqual(d, d2) + + if __name__ == "__main__": unittest.main() From c906cf7369ae108cb972cee979ca3531cea7630a Mon Sep 17 00:00:00 2001 From: Greg Back Date: Thu, 3 Jul 2014 08:52:23 -0500 Subject: [PATCH 2/5] Update Vocab Name for object relationship types --- cybox/core/object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cybox/core/object.py b/cybox/core/object.py index f10a9f78..b7ae58d6 100644 --- a/cybox/core/object.py +++ b/cybox/core/object.py @@ -164,7 +164,7 @@ def from_dict(object_dict, obj_class=None): class Relationship(VocabString): - _XSI_TYPE = 'cyboxVocabs:ObjectRelationshipVocab-1.0' + _XSI_TYPE = 'cyboxVocabs:ObjectRelationshipVocab-1.1' class RelatedObject(Object): From aa6490fb72e340886971ee6e634e212692ce6d2e Mon Sep 17 00:00:00 2001 From: Greg Back Date: Thu, 3 Jul 2014 08:53:22 -0500 Subject: [PATCH 3/5] Update is_plain for VocabStrings to check for non-standard XSI types --- cybox/common/vocabs.py | 35 ++++++++++++++++++++++++--------- cybox/test/common/vocab_test.py | 13 +++++++++++- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/cybox/common/vocabs.py b/cybox/common/vocabs.py index 108949c7..5dacaf5f 100644 --- a/cybox/common/vocabs.py +++ b/cybox/common/vocabs.py @@ -6,11 +6,13 @@ from cybox.common import PatternFieldGroup from cybox.utils import normalize_to_xml, denormalize_from_xml +INVALID_XSI_TYPE = "bad_xsi_type" + class VocabString(PatternFieldGroup, cybox.Entity): _namespace = 'http://cybox.mitre.org/default_vocabularies-2' # All subclasses should override this - _XSI_TYPE = "BAD_XSI_TYPE" + _XSI_TYPE = INVALID_XSI_TYPE def __init__(self, value=None): super(VocabString, self).__init__() @@ -33,20 +35,33 @@ def __eq__(self, other): def is_plain(self): """Whether the VocabString can be represented as a single value. - If `xsi:type` and `value` are the only non-None properties, the - VocabString can be represented by a single value rather than a - dictionary. This makes the JSON representation simpler without losing - any data fidelity. + If `value` is the only non-None properties and a custom XSI type has + not been set, the VocabString can be represented by a single value + rather than a dictionary. This makes the JSON representation simpler + without losing any data fidelity. """ return ( - # ignore value and xsi_type + # ignore value self.vocab_name is None and self.vocab_reference is None and + self.xsi_type == self._XSI_TYPE and PatternFieldGroup.is_plain(self) ) + def is_valid(self): + """For a vocab string to be valid, it must have an xsi:type, a + vocab_name, or a vocab_reference.""" + return ( + self.xsi_type != INVALID_XSI_TYPE or + self.vocab_name is not None or + self.vocab_reference is not None + ) + def to_obj(self): + if not self.is_valid(): + raise ValueError("Vocab being used has not been specified") + vocab_obj = common_binding.ControlledVocabularyStringType() vocab_obj.set_valueOf_(normalize_to_xml(self.value, self.delimiter)) @@ -62,15 +77,17 @@ def to_obj(self): return vocab_obj def to_dict(self): + if not self.is_valid(): + raise ValueError("Vocab being used has not been specified") + if self.is_plain(): return self.value vocab_dict = {} if self.value is not None: vocab_dict['value'] = self.value - if self.xsi_type is not None: + if self.xsi_type != self._XSI_TYPE: vocab_dict['xsi:type'] = self.xsi_type - if self.vocab_name is not None: vocab_dict['vocab_name'] = self.vocab_name if self.vocab_reference is not None: @@ -113,7 +130,7 @@ def from_dict(cls, vocab_dict): if not isinstance(vocab_dict, dict): vocab_str.value = vocab_dict else: - vocab_str.xsi_type = vocab_dict.get('xsi:type') + vocab_str.xsi_type = vocab_dict.get('xsi:type', cls._XSI_TYPE) vocab_str.value = vocab_dict.get('value') vocab_str.vocab_name = vocab_dict.get('vocab_name') vocab_str.vocab_reference = vocab_dict.get('vocab_reference') diff --git a/cybox/test/common/vocab_test.py b/cybox/test/common/vocab_test.py index e9b16125..09397bed 100644 --- a/cybox/test/common/vocab_test.py +++ b/cybox/test/common/vocab_test.py @@ -3,7 +3,7 @@ import unittest -from cybox.common import VocabString +from cybox.common import HashName, VocabString import cybox.test from cybox.utils import normalize_to_xml @@ -13,6 +13,16 @@ class TestVocabString(unittest.TestCase): def test_plain(self): a = VocabString("test_value") self.assertTrue(a.is_plain()) + self.assertFalse(a.is_valid()) + + def test_is_plain_hashname(self): + md5 = HashName("MD5") + self.assertTrue(md5.is_plain()) + + def test_is_plain_hashname(self): + md5 = HashName("MD5") + md5.xsi_type = "Some Other xsi:type" + self.assertFalse(md5.is_plain()) def test_round_trip(self): vocab_dict = { @@ -52,6 +62,7 @@ def test_round_trip_list(self): 'value': ['Value1', 'Value2', 'Value3'], 'condition': "Equals", 'apply_condition': "ALL", + 'vocab_name': "Test", } vocab_dict2 = cybox.test.round_trip_dict(VocabString, vocab_dict) From c9f7a1db9a3755cd67aeb902027c22f9754a77be Mon Sep 17 00:00:00 2001 From: Greg Back Date: Thu, 3 Jul 2014 08:56:44 -0500 Subject: [PATCH 4/5] Update Actions to use a specific Vocab String --- cybox/core/action.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cybox/core/action.py b/cybox/core/action.py index 428b0a52..549457b4 100644 --- a/cybox/core/action.py +++ b/cybox/core/action.py @@ -79,7 +79,7 @@ def from_dict(action_relationship_dict): if not action_relationship_dict: return None action_relationship_ = ActionRelationship() - action_relationship_.type = VocabString.from_dict(action_relationship_dict.get('type')) + action_relationship_.type = ActionType.from_dict(action_relationship_dict.get('type')) action_relationship_.action_references = [ActionReference.from_dict(x) for x in action_relationship_dict.get('action_reference')] return action_relationship_ @@ -88,7 +88,7 @@ def from_obj(action_relationship_obj): if not action_relationship_obj: return None action_relationship_ = ActionRelationship() - action_relationship_.type = VocabString.from_obj(action_relationship_obj.get_Type()) + action_relationship_.type = ActionType.from_obj(action_relationship_obj.get_Type()) action_relationship_.action_references = [ActionReference.from_obj(x) for x in action_relationship_obj.get_Action_Reference()] return action_relationship_ From 466a1a9a65b95e289fd5a76e157971537f466af8 Mon Sep 17 00:00:00 2001 From: Greg Back Date: Thu, 3 Jul 2014 09:15:53 -0500 Subject: [PATCH 5/5] Ensure that non-standard vocabs can be used --- cybox/core/action.py | 3 ++- cybox/test/core/action_test.py | 26 +++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/cybox/core/action.py b/cybox/core/action.py index 549457b4..afa757db 100644 --- a/cybox/core/action.py +++ b/cybox/core/action.py @@ -51,6 +51,7 @@ class AssociatedObjects(cybox.EntityList): class ActionRelationship(cybox.Entity): + _binding = core_binding _namespace = 'http://cybox.mitre.org/cybox-2' def __init__(self): @@ -80,7 +81,7 @@ def from_dict(action_relationship_dict): return None action_relationship_ = ActionRelationship() action_relationship_.type = ActionType.from_dict(action_relationship_dict.get('type')) - action_relationship_.action_references = [ActionReference.from_dict(x) for x in action_relationship_dict.get('action_reference')] + action_relationship_.action_references = [ActionReference.from_dict(x) for x in action_relationship_dict.get('action_reference', [])] return action_relationship_ @staticmethod diff --git a/cybox/test/core/action_test.py b/cybox/test/core/action_test.py index fdf36d46..2a6b3615 100644 --- a/cybox/test/core/action_test.py +++ b/cybox/test/core/action_test.py @@ -3,8 +3,8 @@ import unittest -from cybox.core import Action -from cybox.test import EntityTestCase +from cybox.core import Action, ActionRelationship, ActionType +from cybox.test import EntityTestCase, round_trip class TestAction(EntityTestCase, unittest.TestCase): @@ -47,7 +47,27 @@ class TestAction(EntityTestCase, unittest.TestCase): } -#TODO: Test AssociateObjects and ActionRelationships +class TestActionRelationship(EntityTestCase, unittest.TestCase): + klass = ActionRelationship + + _full_dict = { + 'type': u"Add", + 'action_reference': [ + {'action_id': "example:Action-1"}, + {'action_id': "example:Action-3"}, + ] + } + + def test_nonstandard_type_vocab(self): + ar = ActionRelationship() + ar.type = ActionType(u"AddedMultipleTimes") + ar.type.vocab_reference = "http://example.com/action-types/" + ar.type.xsi_type = None + ar2 = round_trip(ar) + self.assertEqual(ar.to_dict(), ar2.to_dict()) + + +#TODO: Test AssociateObjects if __name__ == "__main__":