# Copyright (c) 2017, The MITRE Corporation. All rights reserved.
# See LICENSE.txt for complete terms.
from mixbox import entities
from mixbox import fields
from mixbox.vendor import six
import cybox.bindings.cybox_common as common_binding
from cybox.common.attribute_groups import PatternFieldGroup
from cybox.common.datetimewithprecision import (validate_date_precision,
validate_time_precision, validate_datetime_precision)
from cybox.utils import normalize_to_xml, denormalize_from_xml
DATE_PRECISION_VALUES = ("year", "month", "day")
TIME_PRECISION_VALUES = ("hour", "minute", "second")
DATETIME_PRECISION_VALUES = DATE_PRECISION_VALUES + TIME_PRECISION_VALUES
def validate_string_type(instance, value):
if value is None:
return
elif isinstance(value, six.string_types):
return
elif isinstance(value, list):
for x in value:
validate_string_type(instance, x)
else:
raise ValueError("Value must be a string. Received %r" % value)
class ListFieldMixin(object):
"""Mixin that allows a TypedField to be set to a list of values or a single
value. If a list of values are passed in, each item in the list will be
passed through the _clean() method.
"""
def check_type(self, value):
func_check = super(ListFieldMixin, self).check_type
if isinstance(value, list):
return all(func_check(x) for x in value)
return func_check(value)
def _clean(self, value):
func_clean = super(ListFieldMixin, self)._clean
if isinstance(value, list):
return [func_clean(x) for x in value]
return func_clean(value)
def binding_value(self, value):
func_binding = super(ListFieldMixin, self).binding_value
if isinstance(value, list):
return [func_binding(x) for x in value]
return func_binding(value)
def dict_value(self, value):
func_dict = super(ListFieldMixin, self).dict_value
if isinstance(value, list):
return [func_dict(x) for x in value]
return func_dict(value)
# Field types that can accept lists or scalar values.
class ListTypedField(ListFieldMixin, fields.TypedField): pass
class ListDateField(ListFieldMixin, fields.DateField): pass
class ListDateTimeField(ListFieldMixin, fields.DateTimeField): pass
class ListFloatField(ListFieldMixin, fields.FloatField): pass
class ListIntegerField(ListFieldMixin, fields.IntegerField): pass
class ListLongField(ListFieldMixin, fields.LongField): pass
@six.python_2_unicode_compatible
[docs]class BaseProperty(PatternFieldGroup, entities.Entity):
__hash__ = entities.Entity.__hash__
# Most Properties are defined in the "common" binding, so we'll just set
# that here. Some BaseProperty subclasses might have to override this.
_binding = common_binding
_binding_class = _binding.BaseObjectPropertyType
_namespace = 'http://cybox.mitre.org/common-2'
# If `True`, force the "datatype" attribute to be output. This is
# necessary in some cases
_force_datatype = False
default_datatype = 'string'
# BaseObjectProperty Group
id_ = fields.IdField("id")
idref = fields.IdrefField("idref")
value = ListTypedField("valueOf_", key_name="value")
datatype = fields.TypedField("datatype")
appears_random = fields.TypedField("appears_random")
is_obfuscated = fields.TypedField("is_obfuscated")
obfuscation_algorithm_ref = fields.TypedField("obfuscation_algorithm_ref")
is_defanged = fields.TypedField("is_defanged")
defanging_algorithm_ref = fields.TypedField("defanging_algorithm_ref")
refanging_transform_type = fields.TypedField("refanging_transform_type")
refanging_transform = fields.TypedField("refanging_transform")
observed_encoding = fields.TypedField("observed_encoding")
def __init__(self, value=None):
super(BaseProperty, self).__init__()
self._force_datatype = False
self.value = value
self.datatype = self.default_datatype
def __str__(self):
return six.text_type(self.value)
def __int__(self):
return int(self.value)
@property
def serialized_value(self):
return self.value
@property
def values(self):
"""Allow uniform access to `value` as a list.
This allows code like the following to always work where `obj` is a
CybOX entity and `prop` is any BaseProperty subclass:
```
for x in obj.prop.values():
do_something(x)
```
If `value` is None, this returns an empty list ([])
If `value` is a single non-list value, it returns a single-item list.
If `value` is a list, `values` is identical to `value`.
NOTE: This property cannot be set. Use the `value` setter for this.
"""
if self.value is None:
return []
elif isinstance(self.value, list):
return self.value
else:
return [self.value]
def __eq__(self, other):
# None-type checking
if not other:
return False
# It is possible to compare a Property to a single value if
# the Property defines only the "value" property.
if not isinstance(other, BaseProperty) and self.is_plain():
return self.value == other
return (
self.value == other.value and
self.id_ == other.id_ and
self.idref == other.idref and
self.datatype == other.datatype and
self.appears_random == other.appears_random and
self.is_obfuscated == other.is_obfuscated and
self.obfuscation_algorithm_ref == other.obfuscation_algorithm_ref and
self.is_defanged == other.is_defanged and
self.defanging_algorithm_ref == other.defanging_algorithm_ref and
self.refanging_transform_type == other.refanging_transform_type and
self.refanging_transform == other.refanging_transform and
self.observed_encoding == other.observed_encoding and
PatternFieldGroup._conditions_equal(self, other) and
self.bit_mask == other.bit_mask and
self.pattern_type == other.pattern_type and
self.regex_syntax == other.regex_syntax and
self.is_case_sensitive == other.is_case_sensitive and
self.has_changed == other.has_changed and
self.trend == other.trend
)
def __ne__(self, other):
return not self == other
[docs] def is_plain(self):
"""Whether the Property can be represented as a single value.
The `datatype` can be inferred by the particular BaseProperty subclass,
so if `datatype` and `value` are the only non-None properties, the
BaseProperty 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
self.id_ is None and
self.idref is None and
# ignore datatype
self.appears_random is None and
self.is_obfuscated is None and
self.obfuscation_algorithm_ref is None and
self.is_defanged is None and
self.defanging_algorithm_ref is None and
self.refanging_transform_type is None and
self.refanging_transform is None and
self.observed_encoding is None and
super(BaseProperty, self).is_plain()
)
def __nonzero__(self):
return (not self.is_plain()) or (self.value is not None)
__bool__ = __nonzero__
def _datatype_serialized_value(self):
if self._force_datatype:
return self.datatype
elif self.datatype != self.default_datatype:
return self.datatype
else:
return None
def to_obj(self, ns_info=None):
attr_obj = super(BaseProperty, self).to_obj(ns_info=ns_info)
attr_obj.datatype = self._datatype_serialized_value()
attr_obj.valueOf_ = normalize_to_xml(self.serialized_value,
self.delimiter)
return attr_obj
def to_dict(self):
if self.is_plain():
return self.serialized_value
attr_dict = super(BaseProperty, self).to_dict()
attr_dict.pop("datatype", None)
if self._datatype_serialized_value():
attr_dict['datatype'] = self._datatype_serialized_value()
if self.value is not None:
attr_dict['value'] = self.serialized_value
return attr_dict
@classmethod
def from_obj(cls, cls_obj):
# Use the subclass this was called on to initialize the object
if not cls_obj:
return None
# split delimited values now, before converting from bindings object to API object
cls_obj.valueOf_ = denormalize_from_xml(cls_obj.valueOf_, cls_obj.delimiter)
attr = super(BaseProperty, cls).from_obj(cls_obj)
attr.datatype = cls_obj.datatype or cls.default_datatype
return attr
@classmethod
def from_dict(cls, cls_dict):
attr = super(BaseProperty, cls).from_dict(cls_dict)
if isinstance(cls_dict, dict):
attr.datatype = cls_dict.get('datatype', cls.default_datatype)
return attr
[docs]class String(BaseProperty):
_binding_class = common_binding.StringObjectPropertyType
default_datatype = "string"
value = ListTypedField("valueOf_", key_name="value", preset_hook=validate_string_type)
class _IntegerBase(BaseProperty):
"""Define a common _parse_value function for all Integer types"""
value = ListIntegerField("valueOf_", key_name="value")
[docs]class Integer(_IntegerBase):
_binding_class = common_binding.IntegerObjectPropertyType
default_datatype = "int"
[docs]class PositiveInteger(_IntegerBase):
_binding_class = common_binding.PositiveIntegerObjectPropertyType
default_datatype = "positiveInteger"
[docs]class UnsignedInteger(_IntegerBase):
_binding_class = common_binding.UnsignedIntegerObjectPropertyType
default_datatype = "unsignedInt"
[docs]class NonNegativeInteger(_IntegerBase):
_binding_class = common_binding.NonNegativeIntegerObjectPropertyType
default_datatype = "nonNegativeInteger"
[docs]class AnyURI(BaseProperty):
_binding_class = common_binding.AnyURIObjectPropertyType
default_datatype = "anyURI"
[docs]class HexBinary(BaseProperty):
_binding_class = common_binding.HexBinaryObjectPropertyType
default_datatype = "hexBinary"
[docs]class Base64Binary(BaseProperty):
_binding_class = common_binding.Base64BinaryObjectPropertyType
default_datatype = "base64Binary"
[docs]class Duration(BaseProperty):
_binding_class = common_binding.DurationObjectPropertyType
default_datatype = "duration"
[docs]class Time(BaseProperty):
_binding_class = common_binding.TimeObjectPropertyType
default_datatype = "time"
precision = fields.TypedField("precision", preset_hook=validate_time_precision)
def __init__(self, value=None, precision='second'):
super(Time, self).__init__(value=value)
self.precision = precision
@six.python_2_unicode_compatible
[docs]class Date(BaseProperty):
_binding_class = common_binding.DateObjectPropertyType
default_datatype = "date"
value = ListDateField("valueOf_", key_name="value")
precision = fields.TypedField("precision", preset_hook=validate_date_precision)
def __init__(self, value=None, precision='day'):
super(Date, self).__init__(value=value)
self.precision = precision
def __str__(self):
if self.value:
return self.value.isoformat()
return super(BaseProperty, self).__str__()
@property
def serialized_value(self):
if isinstance(self.value, list):
return [x.isoformat() for x in self.value]
elif self.value is None:
return None
else:
return self.value.isoformat()
@six.python_2_unicode_compatible
[docs]class DateTime(BaseProperty):
_binding_class = common_binding.DateTimeObjectPropertyType
default_datatype = "dateTime"
value = ListDateTimeField("valueOf_", key_name="value")
precision = fields.TypedField("precision", preset_hook=validate_datetime_precision)
def __init__(self, value=None, precision='second'):
super(DateTime, self).__init__(value=value)
self.precision = precision
def __str__(self):
if self.value:
return self.value.isoformat()
return super(BaseProperty, self).__str__()
@property
def serialized_value(self):
if isinstance(self.value, list):
return [x.isoformat() for x in self.value]
elif self.value is None:
return None
else:
return self.value.isoformat()
class _FloatBase(BaseProperty):
"""Define a common _parse_value function for Float and Double types."""
value = ListFloatField("valueOf_", key_name="value")
[docs]class Double(_FloatBase):
_binding_class = common_binding.DoubleObjectPropertyType
default_datatype = "double"
[docs]class Float(_FloatBase):
_binding_class = common_binding.FloatObjectPropertyType
default_datatype = "float"
class _LongBase(BaseProperty):
"""Define a common _parse_value function for all Long types."""
value = ListLongField("valueOf_", key_name="value")
[docs]class Long(_LongBase):
_binding_class = common_binding.LongObjectPropertyType
default_datatype = "long"
[docs]class UnsignedLong(_LongBase):
_binding_class = common_binding.UnsignedLongObjectPropertyType
default_datatype = "unsignedLong"
[docs]class Name(BaseProperty):
_binding_class = common_binding.NameObjectPropertyType
default_datatype = "name"
# Mapping of binding classes to the corresponding BaseProperty subclass
BINDING_CLASS_MAPPING = {
common_binding.StringObjectPropertyType: String,
common_binding.IntegerObjectPropertyType: Integer,
common_binding.PositiveIntegerObjectPropertyType: PositiveInteger,
common_binding.UnsignedIntegerObjectPropertyType: UnsignedInteger,
common_binding.UnsignedLongObjectPropertyType: UnsignedLong,
common_binding.AnyURIObjectPropertyType: AnyURI,
common_binding.HexBinaryObjectPropertyType: HexBinary,
common_binding.DateTimeObjectPropertyType: DateTime,
common_binding.DateObjectPropertyType: Date,
common_binding.TimeObjectPropertyType: Time,
common_binding.DurationObjectPropertyType: Duration,
common_binding.NonNegativeIntegerObjectPropertyType: NonNegativeInteger,
common_binding.FloatObjectPropertyType: Float,
common_binding.DoubleObjectPropertyType: Double,
common_binding.LongObjectPropertyType: Long,
# This shouldn't be needed anymore, but we'll leave it here to be safe.
common_binding.SimpleHashValueType: HexBinary,
# common_binding.HashNameType: HashName,
}