Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions cybox/common/vocabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__()
Expand All @@ -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))
Expand All @@ -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:
Expand Down Expand Up @@ -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')
Expand Down
7 changes: 4 additions & 3 deletions cybox/core/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -79,16 +80,16 @@ 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_.action_references = [ActionReference.from_dict(x) for x in action_relationship_dict.get('action_reference')]
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_

@staticmethod
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_

Expand Down
2 changes: 1 addition & 1 deletion cybox/core/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
13 changes: 12 additions & 1 deletion cybox/test/common/vocab_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 = {
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 23 additions & 3 deletions cybox/test/core/action_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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__":
Expand Down
36 changes: 35 additions & 1 deletion cybox/test/core/object_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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()
Expand All @@ -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()