# Copyright (c) 2015, The MITRE Corporation. All rights reserved.
# See LICENSE.txt for complete terms.
from datetime import datetime
import dateutil.parser
from mixbox import entities
from mixbox.vendor import six
from cybox.compat import long
import cybox.bindings.cybox_common as common_binding
from cybox.common import PatternFieldGroup
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
@six.python_2_unicode_compatible
[docs]class BaseProperty(PatternFieldGroup, entities.Entity):
# 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
_namespace = 'http://cybox.mitre.org/common-2'
default_datatype = 'string'
def __init__(self, value=None):
super(BaseProperty, self).__init__()
self.value = value
# If `True`, force the "datatype" attribute to be output. This is
# necessary in some cases
self._force_datatype = False
# BaseObjectProperty Group
self.id_ = None
self.idref = None
# ``datatype`` is a class-level variable
self.appears_random = None
self.is_obfuscated = None
self.obfuscation_algorithm_ref = None
self.is_defanged = None
self.defanging_algorithm_ref = None
self.refanging_transform_type = None
self.refanging_transform = None
self.observed_encoding = None
def __str__(self):
return six.text_type(self.serialized_value)
def __int__(self):
return int(self.serialized_value)
@property
def value(self):
return self._value
@value.setter
def value(self, value_):
# This is done here, so the value is always parsed, regardless of
# whether it is set via the __init__() function, via the from_*
# static methods, or on an instance of the class after it has been
# created.
if isinstance(value_, list):
self._value = list(map(self._parse_value, value_))
else:
self._value = self._parse_value(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]
@staticmethod
def _parse_value(value):
"""Parse a user-supplied value into the internal representation.
For most Property types, this does not modify `value`. However,
some attributes may have a more specific representation.
"""
return value
@property
def serialized_value(self):
if isinstance(self.value, list):
return list(map(self._serialize_value, self.value))
else:
return self.__class__._serialize_value(self.value)
@staticmethod
def _serialize_value(value):
"""Format the `value` for serialization (XML, JSON).
For most attribute types, this will return the `value` unmodified.
However, some attributes types may need additional formatting.
"""
return 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
PatternFieldGroup.is_plain(self)
)
def __nonzero__(self):
return (not self.is_plain()) or (self.value is not None)
__bool__ = __nonzero__
def to_obj(self, return_obj=None, ns_info=None):
self._collect_ns_info(ns_info)
attr_obj = self._binding_class()
attr_obj.set_valueOf_(normalize_to_xml(self.serialized_value,
self.delimiter))
# For now, don't output the datatype, as it is not required and is
# usually not needed, as it can be inferred from the context.
#attr_obj.datatype = self.datatype
if self.id_ is not None:
attr_obj.id = self.id_
if self.idref is not None:
attr_obj.idref = self.idref
if self.appears_random is not None:
attr_obj.appears_random = self.appears_random
if self.is_obfuscated is not None:
attr_obj.is_obfuscated = self.is_obfuscated
if self.obfuscation_algorithm_ref is not None:
attr_obj.obfuscation_algorithm_ref = self.obfuscation_algorithm_ref
if self.is_defanged is not None:
attr_obj.is_defanged = self.is_defanged
if self.defanging_algorithm_ref is not None:
attr_obj.defanging_algorithm_ref = self.defanging_algorithm_ref
if self.refanging_transform_type is not None:
attr_obj.refanging_transform_type = self.refanging_transform_type
if self.refanging_transform is not None:
attr_obj.refanging_transform = self.refanging_transform
if self.observed_encoding is not None:
attr_obj.observed_encoding = self.observed_encoding
#Datatype output logic
if self._force_datatype or (self.datatype != self.default_datatype):
attr_obj.datatype = self.datatype
else:
attr_obj.datatype = None
PatternFieldGroup.to_obj(self, return_obj=attr_obj, ns_info=ns_info)
return attr_obj
def to_dict(self):
if self.is_plain():
return self.serialized_value
attr_dict = {}
if self.value is not None:
attr_dict['value'] = self.serialized_value
if self.datatype is not None and (self.datatype != self.default_datatype):
attr_dict['datatype'] = self.datatype
if self.id_ is not None:
attr_dict['id'] = self.id_
if self.idref is not None:
attr_dict['idref'] = self.idref
if self.appears_random is not None:
attr_dict['appears_random'] = self.appears_random
if self.is_obfuscated is not None:
attr_dict['is_obfuscated'] = self.is_obfuscated
if self.obfuscation_algorithm_ref is not None:
attr_dict['obfuscation_algorithm_ref'] = self.obfuscation_algorithm_ref
if self.is_defanged is not None:
attr_dict['is_defanged'] = self.is_defanged
if self.defanging_algorithm_ref is not None:
attr_dict['defanging_algorithm_ref'] = self.defanging_algorithm_ref
if self.refanging_transform_type is not None:
attr_dict['refanging_transform_type'] = self.refanging_transform_type
if self.refanging_transform is not None:
attr_dict['refanging_transform'] = self.refanging_transform
if self.observed_encoding is not None:
attr_dict['observed_encoding'] = self.observed_encoding
PatternFieldGroup.to_dict(self, attr_dict)
return attr_dict
@classmethod
def from_obj(cls, attr_obj):
# Subclasses with additional fields should override this method
# and use _populate_from_obj as necessary.
# Use the subclass this was called on to initialize the object
if not attr_obj:
return None
attr = cls()
attr._populate_from_obj(attr_obj)
return attr
def _populate_from_obj(self, attr_obj):
self.id_ = attr_obj.id
self.idref = attr_obj.idref
self.datatype = attr_obj.datatype
self.appears_random = attr_obj.appears_random
self.is_obfuscated = attr_obj.is_obfuscated
self.obfuscation_algorithm_ref = attr_obj.obfuscation_algorithm_ref
self.is_defanged = attr_obj.is_defanged
self.defanging_algorithm_ref = attr_obj.defanging_algorithm_ref
self.refanging_transform_type = attr_obj.refanging_transform_type
self.refanging_transform = attr_obj.refanging_transform
self.observed_encoding = attr_obj.observed_encoding
PatternFieldGroup.from_obj(attr_obj, self)
# We need to check for a non-default delimiter before trying to parse
# the value.
self.value = denormalize_from_xml(attr_obj.valueOf_,
self.delimiter)
@classmethod
def from_dict(cls, attr_dict):
# Subclasses with additional fields should override this method
# and use _populate_from_dict as necessary.
if attr_dict is None:
return None
# Use the subclass this was called on to initialize the object.
attr = cls()
attr._populate_from_dict(attr_dict)
return attr
def _populate_from_dict(self, attr_dict):
# If this attribute is "plain", use it as the value and assume the
# datatype was set correctly by the constructor of the particular
# BaseProperty Subclass.
if not isinstance(attr_dict, dict):
self.value = attr_dict
else:
# This key should always be present
self.value = attr_dict.get('value')
# This defaults to False if missing
self._force_datatype = attr_dict.get('force_datatype', False)
# 'None' is fine if these keys are missing
self.id_ = attr_dict.get('id')
self.idref = attr_dict.get('idref')
self.appears_random = attr_dict.get('appears_random')
self.datatype = attr_dict.get('datatype')
self.is_obfuscated = attr_dict.get('is_obfuscated')
self.obfuscation_algorithm_ref = attr_dict.get('obfuscation_algorithm_ref')
self.is_defanged = attr_dict.get('is_defanged')
self.defanging_algorithm_ref = attr_dict.get('defanging_algorithm_ref')
self.refanging_transform_type = attr_dict.get('refanging_transform_type')
self.refanging_transform = attr_dict.get('refanging_transform')
self.observed_encoding = attr_dict.get('observed_encoding')
PatternFieldGroup.from_dict(attr_dict, self)
[docs]class String(BaseProperty):
_binding_class = common_binding.StringObjectPropertyType
datatype = "string"
default_datatype = "string"
@staticmethod
def _parse_value(value):
if value is not None and not isinstance(value, six.string_types):
raise ValueError("Cannot set String type to non-string value")
return value
class _IntegerBase(BaseProperty):
'''Define a common _parse_value function for all Integer types'''
@staticmethod
def _parse_value(value):
if value is None or value == '':
return None
if isinstance(value, six.string_types):
return int(value, 0)
else:
return int(value)
[docs]class Integer(_IntegerBase):
_binding_class = common_binding.IntegerObjectPropertyType
datatype = "integer"
default_datatype = "integer"
[docs]class PositiveInteger(_IntegerBase):
_binding_class = common_binding.PositiveIntegerObjectPropertyType
datatype = "positiveInteger"
default_datatype = "positiveInteger"
[docs]class UnsignedInteger(_IntegerBase):
_binding_class = common_binding.UnsignedIntegerObjectPropertyType
datatype = "unsignedInt"
default_datatype = "unsignedInt"
[docs]class NonNegativeInteger(_IntegerBase):
_binding_class = common_binding.NonNegativeIntegerObjectPropertyType
datatype = "nonNegativeInteger"
default_datatype = "nonNegativeInteger"
[docs]class AnyURI(BaseProperty):
_binding_class = common_binding.AnyURIObjectPropertyType
datatype = "anyURI"
default_datatype = "anyURI"
[docs]class HexBinary(BaseProperty):
_binding_class = common_binding.HexBinaryObjectPropertyType
datatype = "hexBinary"
default_datatype = "hexBinary"
[docs]class Base64Binary(BaseProperty):
_binding_class = common_binding.Base64BinaryObjectPropertyType
datatype = "base64Binary"
default_datatype = "base64Binary"
[docs]class Duration(BaseProperty):
_binding_class = common_binding.DurationObjectPropertyType
datatype = "duration"
default_datatype = "duration"
[docs]class Time(BaseProperty):
_binding_class = common_binding.TimeObjectPropertyType
datatype = "time"
default_datatype = "time"
def __init__(self, value=None, precision='second'):
super(Time, self).__init__(value=value)
self.precision = precision
@property
def precision(self):
return self._precision
@precision.setter
def precision(self, value):
if value not in TIME_PRECISION_VALUES:
raise ValueError("value must be one of [%s]" % ", ".join(x for x in TIME_PRECISION_VALUES))
self._precision = value
[docs]class Date(BaseProperty):
_binding_class = common_binding.DateObjectPropertyType
datatype = "date"
default_datatype = "date"
def __init__(self, value=None, precision='day'):
super(Date, self).__init__(value=value)
self.precision = precision
@property
def precision(self):
return self._precision
@precision.setter
def precision(self, value):
if value not in DATE_PRECISION_VALUES:
raise ValueError("value must be one of [%s]" % ", ".join(x for x in DATE_PRECISION_VALUES))
self._precision = value
[docs]class DateTime(BaseProperty):
_binding_class = common_binding.DateTimeObjectPropertyType
datatype = "dateTime"
default_datatype = "dateTime"
def __init__(self, value=None, precision='second'):
super(DateTime, self).__init__(value=value)
self.precision = precision
@property
def precision(self):
return self._precision
@precision.setter
def precision(self, value):
if value not in DATETIME_PRECISION_VALUES:
raise ValueError("value must be one of [%s]" % ", ".join(x for x in DATETIME_PRECISION_VALUES))
self._precision = value
@staticmethod
def _parse_value(value):
if not value:
return None
elif isinstance(value, datetime):
return value
return dateutil.parser.parse(value)
@staticmethod
def _serialize_value(value):
if not value:
return None
return value.isoformat()
class _FloatBase(BaseProperty):
'''Define a common _parse_value function for Float and Double types'''
@staticmethod
def _parse_value(value):
if value is None or value == '':
return None
else:
return float(value)
[docs]class Double(_FloatBase):
_binding_class = common_binding.DoubleObjectPropertyType
datatype = "double"
default_datatype = "double"
[docs]class Float(_FloatBase):
_binding_class = common_binding.FloatObjectPropertyType
datatype = "float"
default_datatype = "float"
class _LongBase(BaseProperty):
'''Define a common _parse_value function for all Long types'''
@staticmethod
def _parse_value(value):
if value is None or value == '':
return None
if isinstance(value, six.string_types):
return long(value, 0)
else:
return long(value)
[docs]class Long(_LongBase):
_binding_class = common_binding.LongObjectPropertyType
datatype = "long"
default_datatype = "long"
[docs]class UnsignedLong(_LongBase):
_binding_class = common_binding.UnsignedLongObjectPropertyType
datatype = "unsignedLong"
default_datatype = "unsignedLong"
[docs]class Name(BaseProperty):
_binding_class = common_binding.NameObjectPropertyType
datatype = "name"
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,
common_binding.UnsignedLongObjectPropertyType: UnsignedLong,
# This shouldn't be needed anymore, but we'll leave it here to be safe.
common_binding.SimpleHashValueType: HexBinary,
# common_binding.HashNameType: HashName,
}