2017-02-10 22:35:02 +01:00
|
|
|
"""Base class for type definitions in the stix2 library."""
|
|
|
|
|
|
|
|
import collections
|
2017-05-02 20:06:42 +02:00
|
|
|
import copy
|
2017-02-10 22:35:02 +01:00
|
|
|
import datetime as dt
|
2017-08-15 19:40:47 +02:00
|
|
|
|
2017-08-11 21:10:44 +02:00
|
|
|
import simplejson as json
|
2017-02-10 22:35:02 +01:00
|
|
|
|
2017-06-01 22:47:56 +02:00
|
|
|
from .exceptions import (AtLeastOnePropertyError, DependentPropertiesError,
|
|
|
|
ExtraPropertiesError, ImmutableError,
|
|
|
|
InvalidObjRefError, InvalidValueError,
|
|
|
|
MissingPropertiesError,
|
2017-08-31 18:28:07 +02:00
|
|
|
MutuallyExclusivePropertiesError)
|
2017-06-12 14:06:37 +02:00
|
|
|
from .markings.utils import validate
|
2017-08-31 22:36:59 +02:00
|
|
|
from .utils import NOW, find_property_index, format_datetime, get_timestamp
|
2017-08-31 18:28:07 +02:00
|
|
|
from .utils import new_version as _new_version
|
|
|
|
from .utils import revoke as _revoke
|
2017-02-10 22:35:02 +01:00
|
|
|
|
|
|
|
__all__ = ['STIXJSONEncoder', '_STIXBase']
|
|
|
|
|
2017-05-16 18:27:30 +02:00
|
|
|
DEFAULT_ERROR = "{type} must have {property}='{expected}'."
|
2017-02-10 22:35:02 +01:00
|
|
|
|
|
|
|
|
|
|
|
class STIXJSONEncoder(json.JSONEncoder):
|
|
|
|
|
|
|
|
def default(self, obj):
|
|
|
|
if isinstance(obj, (dt.date, dt.datetime)):
|
|
|
|
return format_datetime(obj)
|
|
|
|
elif isinstance(obj, _STIXBase):
|
|
|
|
return dict(obj)
|
|
|
|
else:
|
|
|
|
return super(STIXJSONEncoder, self).default(obj)
|
|
|
|
|
|
|
|
|
2017-02-24 16:28:53 +01:00
|
|
|
def get_required_properties(properties):
|
2017-03-22 14:26:13 +01:00
|
|
|
return (k for k, v in properties.items() if v.required)
|
2017-02-24 16:28:53 +01:00
|
|
|
|
|
|
|
|
2017-02-10 22:35:02 +01:00
|
|
|
class _STIXBase(collections.Mapping):
|
|
|
|
"""Base class for STIX object types"""
|
|
|
|
|
2017-08-29 21:08:26 +02:00
|
|
|
def object_properties(self):
|
2017-08-11 21:10:44 +02:00
|
|
|
return list(self._properties.keys())
|
|
|
|
|
2017-02-24 17:20:24 +01:00
|
|
|
def _check_property(self, prop_name, prop, kwargs):
|
|
|
|
if prop_name not in kwargs:
|
2017-02-24 20:07:54 +01:00
|
|
|
if hasattr(prop, 'default'):
|
2017-03-22 01:15:06 +01:00
|
|
|
value = prop.default()
|
|
|
|
if value == NOW:
|
|
|
|
value = self.__now
|
|
|
|
kwargs[prop_name] = value
|
2017-02-24 20:07:54 +01:00
|
|
|
|
|
|
|
if prop_name in kwargs:
|
|
|
|
try:
|
2017-04-17 21:13:11 +02:00
|
|
|
kwargs[prop_name] = prop.clean(kwargs[prop_name])
|
2017-02-24 20:07:54 +01:00
|
|
|
except ValueError as exc:
|
2017-04-18 21:42:59 +02:00
|
|
|
raise InvalidValueError(self.__class__, prop_name, reason=str(exc))
|
2017-02-24 17:20:24 +01:00
|
|
|
|
Changes so File object creation doesn't violate on of the MUSTs
Added three new exceptions: DependentPropertiestError, AtLeastOnePropertyError, MutuallyExclusivePropertiesError
Added tests for NetworkTraffic, Process, URL, WindowsRegistryKey and X509Certificate
Added error tests for EmailMessage, NetworkTraffic, Artifact,
Added interproperty checker methods to the base class: _check_mutually_exclusive_properties, _check_at_least_one_property and _check_properties_dependency
Added interproperty checkers to Artifact, EmailMIMEComponent, EmailMessage, NetworkTraffic
Made NetworkTraffic.protocols required
Added X509V3ExtenstionsType class
Use EmbeddedObjectProperty for X509Certificate.x509_v3_extensions
2017-05-11 21:22:46 +02:00
|
|
|
# interproperty constraint methods
|
|
|
|
|
|
|
|
def _check_mutually_exclusive_properties(self, list_of_properties, at_least_one=True):
|
|
|
|
current_properties = self.properties_populated()
|
2017-05-12 19:18:02 +02:00
|
|
|
count = len(set(list_of_properties).intersection(current_properties))
|
Changes so File object creation doesn't violate on of the MUSTs
Added three new exceptions: DependentPropertiestError, AtLeastOnePropertyError, MutuallyExclusivePropertiesError
Added tests for NetworkTraffic, Process, URL, WindowsRegistryKey and X509Certificate
Added error tests for EmailMessage, NetworkTraffic, Artifact,
Added interproperty checker methods to the base class: _check_mutually_exclusive_properties, _check_at_least_one_property and _check_properties_dependency
Added interproperty checkers to Artifact, EmailMIMEComponent, EmailMessage, NetworkTraffic
Made NetworkTraffic.protocols required
Added X509V3ExtenstionsType class
Use EmbeddedObjectProperty for X509Certificate.x509_v3_extensions
2017-05-11 21:22:46 +02:00
|
|
|
# at_least_one allows for xor to be checked
|
|
|
|
if count > 1 or (at_least_one and count == 0):
|
|
|
|
raise MutuallyExclusivePropertiesError(self.__class__, list_of_properties)
|
|
|
|
|
2017-05-17 21:33:28 +02:00
|
|
|
def _check_at_least_one_property(self, list_of_properties=None):
|
|
|
|
if not list_of_properties:
|
|
|
|
list_of_properties = sorted(list(self.__class__._properties.keys()))
|
|
|
|
if "type" in list_of_properties:
|
|
|
|
list_of_properties.remove("type")
|
Changes so File object creation doesn't violate on of the MUSTs
Added three new exceptions: DependentPropertiestError, AtLeastOnePropertyError, MutuallyExclusivePropertiesError
Added tests for NetworkTraffic, Process, URL, WindowsRegistryKey and X509Certificate
Added error tests for EmailMessage, NetworkTraffic, Artifact,
Added interproperty checker methods to the base class: _check_mutually_exclusive_properties, _check_at_least_one_property and _check_properties_dependency
Added interproperty checkers to Artifact, EmailMIMEComponent, EmailMessage, NetworkTraffic
Made NetworkTraffic.protocols required
Added X509V3ExtenstionsType class
Use EmbeddedObjectProperty for X509Certificate.x509_v3_extensions
2017-05-11 21:22:46 +02:00
|
|
|
current_properties = self.properties_populated()
|
2017-05-17 21:33:28 +02:00
|
|
|
list_of_properties_populated = set(list_of_properties).intersection(current_properties)
|
2017-05-17 21:51:07 +02:00
|
|
|
if list_of_properties and (not list_of_properties_populated or list_of_properties_populated == set(["extensions"])):
|
2017-05-12 19:18:02 +02:00
|
|
|
raise AtLeastOnePropertyError(self.__class__, list_of_properties)
|
Changes so File object creation doesn't violate on of the MUSTs
Added three new exceptions: DependentPropertiestError, AtLeastOnePropertyError, MutuallyExclusivePropertiesError
Added tests for NetworkTraffic, Process, URL, WindowsRegistryKey and X509Certificate
Added error tests for EmailMessage, NetworkTraffic, Artifact,
Added interproperty checker methods to the base class: _check_mutually_exclusive_properties, _check_at_least_one_property and _check_properties_dependency
Added interproperty checkers to Artifact, EmailMIMEComponent, EmailMessage, NetworkTraffic
Made NetworkTraffic.protocols required
Added X509V3ExtenstionsType class
Use EmbeddedObjectProperty for X509Certificate.x509_v3_extensions
2017-05-11 21:22:46 +02:00
|
|
|
|
2017-06-02 19:48:44 +02:00
|
|
|
def _check_properties_dependency(self, list_of_properties, list_of_dependent_properties):
|
Changes so File object creation doesn't violate on of the MUSTs
Added three new exceptions: DependentPropertiestError, AtLeastOnePropertyError, MutuallyExclusivePropertiesError
Added tests for NetworkTraffic, Process, URL, WindowsRegistryKey and X509Certificate
Added error tests for EmailMessage, NetworkTraffic, Artifact,
Added interproperty checker methods to the base class: _check_mutually_exclusive_properties, _check_at_least_one_property and _check_properties_dependency
Added interproperty checkers to Artifact, EmailMIMEComponent, EmailMessage, NetworkTraffic
Made NetworkTraffic.protocols required
Added X509V3ExtenstionsType class
Use EmbeddedObjectProperty for X509Certificate.x509_v3_extensions
2017-05-11 21:22:46 +02:00
|
|
|
failed_dependency_pairs = []
|
|
|
|
for p in list_of_properties:
|
|
|
|
for dp in list_of_dependent_properties:
|
2017-06-08 14:42:32 +02:00
|
|
|
if not self.get(p) and self.get(dp):
|
Changes so File object creation doesn't violate on of the MUSTs
Added three new exceptions: DependentPropertiestError, AtLeastOnePropertyError, MutuallyExclusivePropertiesError
Added tests for NetworkTraffic, Process, URL, WindowsRegistryKey and X509Certificate
Added error tests for EmailMessage, NetworkTraffic, Artifact,
Added interproperty checker methods to the base class: _check_mutually_exclusive_properties, _check_at_least_one_property and _check_properties_dependency
Added interproperty checkers to Artifact, EmailMIMEComponent, EmailMessage, NetworkTraffic
Made NetworkTraffic.protocols required
Added X509V3ExtenstionsType class
Use EmbeddedObjectProperty for X509Certificate.x509_v3_extensions
2017-05-11 21:22:46 +02:00
|
|
|
failed_dependency_pairs.append((p, dp))
|
|
|
|
if failed_dependency_pairs:
|
2017-05-19 19:51:59 +02:00
|
|
|
raise DependentPropertiesError(self.__class__, failed_dependency_pairs)
|
Changes so File object creation doesn't violate on of the MUSTs
Added three new exceptions: DependentPropertiestError, AtLeastOnePropertyError, MutuallyExclusivePropertiesError
Added tests for NetworkTraffic, Process, URL, WindowsRegistryKey and X509Certificate
Added error tests for EmailMessage, NetworkTraffic, Artifact,
Added interproperty checker methods to the base class: _check_mutually_exclusive_properties, _check_at_least_one_property and _check_properties_dependency
Added interproperty checkers to Artifact, EmailMIMEComponent, EmailMessage, NetworkTraffic
Made NetworkTraffic.protocols required
Added X509V3ExtenstionsType class
Use EmbeddedObjectProperty for X509Certificate.x509_v3_extensions
2017-05-11 21:22:46 +02:00
|
|
|
|
2017-05-18 15:48:01 +02:00
|
|
|
def _check_object_constraints(self):
|
2017-06-02 16:10:50 +02:00
|
|
|
for m in self.get("granular_markings", []):
|
2017-08-24 18:47:14 +02:00
|
|
|
validate(self, m.get("selectors"))
|
2017-05-09 21:28:32 +02:00
|
|
|
|
2017-06-12 18:54:05 +02:00
|
|
|
def __init__(self, allow_custom=False, **kwargs):
|
2017-02-10 22:35:02 +01:00
|
|
|
cls = self.__class__
|
|
|
|
|
|
|
|
# Use the same timestamp for any auto-generated datetimes
|
2017-02-24 16:28:53 +01:00
|
|
|
self.__now = get_timestamp()
|
2017-02-10 22:35:02 +01:00
|
|
|
|
|
|
|
# Detect any keyword arguments not allowed for a specific type
|
2017-06-09 18:20:40 +02:00
|
|
|
custom_props = kwargs.pop('custom_properties', {})
|
|
|
|
if custom_props and not isinstance(custom_props, dict):
|
|
|
|
raise ValueError("'custom_properties' must be a dictionary")
|
2017-06-12 18:54:05 +02:00
|
|
|
if not allow_custom:
|
|
|
|
extra_kwargs = list(set(kwargs) - set(cls._properties))
|
|
|
|
if extra_kwargs:
|
|
|
|
raise ExtraPropertiesError(cls, extra_kwargs)
|
2017-02-10 22:35:02 +01:00
|
|
|
|
2017-05-04 22:34:08 +02:00
|
|
|
# Remove any keyword arguments whose value is None
|
|
|
|
setting_kwargs = {}
|
2017-06-09 18:20:40 +02:00
|
|
|
props = kwargs.copy()
|
|
|
|
props.update(custom_props)
|
|
|
|
for prop_name, prop_value in props.items():
|
2017-06-01 21:25:03 +02:00
|
|
|
if prop_value is not None:
|
2017-05-04 22:34:08 +02:00
|
|
|
setting_kwargs[prop_name] = prop_value
|
|
|
|
|
2017-05-16 18:27:30 +02:00
|
|
|
# Detect any missing required properties
|
|
|
|
required_properties = get_required_properties(cls._properties)
|
|
|
|
missing_kwargs = set(required_properties) - set(setting_kwargs)
|
2017-02-10 22:35:02 +01:00
|
|
|
if missing_kwargs:
|
2017-05-19 19:51:59 +02:00
|
|
|
raise MissingPropertiesError(cls, missing_kwargs)
|
2017-02-10 22:35:02 +01:00
|
|
|
|
|
|
|
for prop_name, prop_metadata in cls._properties.items():
|
2017-05-04 22:34:08 +02:00
|
|
|
self._check_property(prop_name, prop_metadata, setting_kwargs)
|
2017-02-10 22:35:02 +01:00
|
|
|
|
2017-05-04 22:34:08 +02:00
|
|
|
self._inner = setting_kwargs
|
2017-02-10 22:35:02 +01:00
|
|
|
|
2017-05-18 15:48:01 +02:00
|
|
|
self._check_object_constraints()
|
2017-03-31 21:52:27 +02:00
|
|
|
|
2017-02-10 22:35:02 +01:00
|
|
|
def __getitem__(self, key):
|
|
|
|
return self._inner[key]
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return iter(self._inner)
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self._inner)
|
|
|
|
|
|
|
|
# Handle attribute access just like key access
|
|
|
|
def __getattr__(self, name):
|
2017-06-08 14:42:32 +02:00
|
|
|
if name in self:
|
2017-06-02 16:10:50 +02:00
|
|
|
return self.__getitem__(name)
|
2017-06-08 14:42:32 +02:00
|
|
|
raise AttributeError("'%s' object has no attribute '%s'" %
|
|
|
|
(self.__class__.__name__, name))
|
2017-02-10 22:35:02 +01:00
|
|
|
|
|
|
|
def __setattr__(self, name, value):
|
2017-02-24 16:28:53 +01:00
|
|
|
if name != '_inner' and not name.startswith("_STIXBase__"):
|
2017-06-01 18:43:06 +02:00
|
|
|
raise ImmutableError(self.__class__, name)
|
2017-02-10 22:35:02 +01:00
|
|
|
super(_STIXBase, self).__setattr__(name, value)
|
|
|
|
|
|
|
|
def __str__(self):
|
2017-08-29 21:08:26 +02:00
|
|
|
properties = self.object_properties()
|
2017-08-15 19:40:47 +02:00
|
|
|
|
|
|
|
def sort_by(element):
|
|
|
|
return find_property_index(self, properties, element)
|
|
|
|
|
2017-08-11 21:10:44 +02:00
|
|
|
# separators kwarg -> don't include spaces after commas.
|
|
|
|
return json.dumps(self, indent=4, cls=STIXJSONEncoder,
|
2017-08-15 19:40:47 +02:00
|
|
|
item_sort_key=sort_by,
|
2017-08-11 21:10:44 +02:00
|
|
|
separators=(",", ": "))
|
2017-02-10 22:58:17 +01:00
|
|
|
|
|
|
|
def __repr__(self):
|
2017-08-29 21:08:26 +02:00
|
|
|
props = [(k, self[k]) for k in self.object_properties() if self.get(k)]
|
2017-02-10 22:58:17 +01:00
|
|
|
return "{0}({1})".format(self.__class__.__name__,
|
|
|
|
", ".join(["{0!s}={1!r}".format(k, v) for k, v in props]))
|
2017-05-03 20:10:10 +02:00
|
|
|
|
2017-05-03 18:14:09 +02:00
|
|
|
def __deepcopy__(self, memo):
|
|
|
|
# Assumption: we can ignore the memo argument, because no object will ever contain the same sub-object multiple times.
|
|
|
|
new_inner = copy.deepcopy(self._inner, memo)
|
|
|
|
cls = type(self)
|
|
|
|
return cls(**new_inner)
|
|
|
|
|
2017-05-09 21:28:32 +02:00
|
|
|
def properties_populated(self):
|
|
|
|
return list(self._inner.keys())
|
|
|
|
|
2017-05-02 20:06:42 +02:00
|
|
|
# Versioning API
|
|
|
|
|
|
|
|
def new_version(self, **kwargs):
|
2017-08-31 18:28:07 +02:00
|
|
|
return _new_version(self, **kwargs)
|
2017-05-02 20:06:42 +02:00
|
|
|
|
|
|
|
def revoke(self):
|
2017-08-31 18:28:07 +02:00
|
|
|
return _revoke(self)
|
2017-05-08 17:11:56 +02:00
|
|
|
|
2017-05-03 20:10:10 +02:00
|
|
|
|
2017-05-10 00:03:46 +02:00
|
|
|
class _Observable(_STIXBase):
|
2017-05-05 18:32:02 +02:00
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
2017-05-09 03:03:15 +02:00
|
|
|
# the constructor might be called independently of an observed data object
|
|
|
|
if '_valid_refs' in kwargs:
|
|
|
|
self._STIXBase__valid_refs = kwargs.pop('_valid_refs')
|
|
|
|
else:
|
|
|
|
self._STIXBase__valid_refs = []
|
2017-05-10 00:03:46 +02:00
|
|
|
super(_Observable, self).__init__(**kwargs)
|
2017-05-05 18:32:02 +02:00
|
|
|
|
2017-05-17 21:21:02 +02:00
|
|
|
def _check_ref(self, ref, prop, prop_name):
|
|
|
|
if ref not in self._STIXBase__valid_refs:
|
|
|
|
raise InvalidObjRefError(self.__class__, prop_name, "'%s' is not a valid object in local scope" % ref)
|
|
|
|
|
|
|
|
try:
|
|
|
|
allowed_types = prop.contained.valid_types
|
|
|
|
except AttributeError:
|
2017-08-30 21:33:28 +02:00
|
|
|
allowed_types = prop.valid_types
|
|
|
|
|
|
|
|
try:
|
|
|
|
ref_type = self._STIXBase__valid_refs[ref]
|
|
|
|
except TypeError:
|
|
|
|
raise ValueError("'%s' must be created with _valid_refs as a dict, not a list." % self.__class__.__name__)
|
2017-05-17 21:21:02 +02:00
|
|
|
|
|
|
|
if allowed_types:
|
|
|
|
if ref_type not in allowed_types:
|
|
|
|
raise InvalidObjRefError(self.__class__, prop_name, "object reference '%s' is of an invalid type '%s'" % (ref, ref_type))
|
|
|
|
|
2017-05-05 18:32:02 +02:00
|
|
|
def _check_property(self, prop_name, prop, kwargs):
|
2017-05-10 00:03:46 +02:00
|
|
|
super(_Observable, self)._check_property(prop_name, prop, kwargs)
|
2017-05-17 21:21:02 +02:00
|
|
|
if prop_name not in kwargs:
|
|
|
|
return
|
|
|
|
|
|
|
|
if prop_name.endswith('_ref'):
|
2017-05-09 17:03:19 +02:00
|
|
|
ref = kwargs[prop_name]
|
2017-05-17 21:21:02 +02:00
|
|
|
self._check_ref(ref, prop, prop_name)
|
|
|
|
elif prop_name.endswith('_refs'):
|
2017-05-09 17:03:19 +02:00
|
|
|
for ref in kwargs[prop_name]:
|
2017-05-17 21:21:02 +02:00
|
|
|
self._check_ref(ref, prop, prop_name)
|
2017-05-18 20:04:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
class _Extension(_STIXBase):
|
|
|
|
|
|
|
|
def _check_object_constraints(self):
|
|
|
|
super(_Extension, self)._check_object_constraints()
|
|
|
|
self._check_at_least_one_property()
|