chg: Update documentation, cleanup

pull/160/head
Raphaël Vinot 2017-12-22 14:49:14 +01:00
parent efb6ca974c
commit a497613a85
4 changed files with 186 additions and 145 deletions

View File

@ -1,10 +1,31 @@
__version__ = '2.4.84' __version__ = '2.4.84'
import sys import sys
import logging import logging
import functools
import warnings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
FORMAT = "%(levelname)s [%(filename)s:%(lineno)s - %(funcName)s() ] %(message)s" FORMAT = "%(levelname)s [%(filename)s:%(lineno)s - %(funcName)s() ] %(message)s"
logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format=FORMAT) logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format=FORMAT)
def deprecated(func):
'''This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted
when the function is used.'''
@functools.wraps(func)
def new_func(*args, **kwargs):
warnings.showwarning(
"Call to deprecated function {}.".format(func.__name__),
category=DeprecationWarning,
filename=func.__code__.co_filename,
lineno=func.__code__.co_firstlineno + 1
)
return func(*args, **kwargs)
return new_func
try: try:
from .exceptions import PyMISPError, NewEventError, NewAttributeError, MissingDependency, NoURL, NoKey, InvalidMISPObject, UnknownMISPObjectTemplate # noqa from .exceptions import PyMISPError, NewEventError, NewAttributeError, MissingDependency, NoURL, NoKey, InvalidMISPObject, UnknownMISPObjectTemplate # noqa
from .api import PyMISP # noqa from .api import PyMISP # noqa

View File

@ -30,11 +30,15 @@ class AbstractMISP(collections.MutableMapping):
__not_jsonable = [] __not_jsonable = []
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Abstract class for all the MISP objects"""
super(AbstractMISP, self).__init__() super(AbstractMISP, self).__init__()
self.__edited = True self.__edited = True # As we create a new object, we assume it is edited
@property @property
def properties(self): def properties(self):
"""All the class public properties that will be dumped in the dictionary, and the JSON export.
Note: all the properties starting with a `_` (private), or listed in __not_jsonable will be skipped.
"""
to_return = [] to_return = []
for prop, value in vars(self).items(): for prop, value in vars(self).items():
if prop.startswith('_') or prop in self.__not_jsonable: if prop.startswith('_') or prop in self.__not_jsonable:
@ -43,6 +47,11 @@ class AbstractMISP(collections.MutableMapping):
return to_return return to_return
def from_dict(self, **kwargs): def from_dict(self, **kwargs):
"""Loading all the parameters as class properties, if they aren't `None`.
This method aims to be called when all the properties requiring a special
treatment are processed.
Note: This method is used when you initialize an object with existing data so by default,
the class is flaged as not edited."""
for prop, value in kwargs.items(): for prop, value in kwargs.items():
if value is None: if value is None:
continue continue
@ -51,9 +60,11 @@ class AbstractMISP(collections.MutableMapping):
self.__edited = False self.__edited = False
def update_not_jsonable(self, *args): def update_not_jsonable(self, *args):
"""Add entries to the __not_jsonable list"""
self.__not_jsonable += args self.__not_jsonable += args
def set_not_jsonable(self, *args): def set_not_jsonable(self, *args):
"""Set __not_jsonable to a new list"""
self.__not_jsonable = args self.__not_jsonable = args
def from_json(self, json_string): def from_json(self, json_string):
@ -61,6 +72,9 @@ class AbstractMISP(collections.MutableMapping):
self.from_dict(json.loads(json_string)) self.from_dict(json.loads(json_string))
def to_dict(self): def to_dict(self):
"""Dump the lass to a dictionary.
This method automatically removes the timestamp recursively in every object
that has been edited is order to let MISP update the event accordingly."""
to_return = {} to_return = {}
for attribute in self.properties: for attribute in self.properties:
val = getattr(self, attribute, None) val = getattr(self, attribute, None)
@ -79,9 +93,11 @@ class AbstractMISP(collections.MutableMapping):
return to_return return to_return
def jsonable(self): def jsonable(self):
"""This method is used by the JSON encoder"""
return self.to_dict() return self.to_dict()
def to_json(self): def to_json(self):
"""Dump recursively any class of type MISPAbstract to a json string"""
return json.dumps(self, cls=MISPEncode) return json.dumps(self, cls=MISPEncode)
def __getitem__(self, key): def __getitem__(self, key):
@ -105,6 +121,8 @@ class AbstractMISP(collections.MutableMapping):
@property @property
def edited(self): def edited(self):
"""Recursively check if an object has been edited and update the flag accordingly
to the parent objects"""
if self.__edited: if self.__edited:
return self.__edited return self.__edited
for p in self.properties: for p in self.properties:
@ -119,6 +137,7 @@ class AbstractMISP(collections.MutableMapping):
@edited.setter @edited.setter
def edited(self, val): def edited(self, val):
"""Set the edit flag"""
if isinstance(val, bool): if isinstance(val, bool):
self.__edited = val self.__edited = val
else: else:
@ -130,6 +149,7 @@ class AbstractMISP(collections.MutableMapping):
super(AbstractMISP, self).__setattr__(name, value) super(AbstractMISP, self).__setattr__(name, value)
def _datetime_to_timestamp(self, d): def _datetime_to_timestamp(self, d):
"""Convert a datetime.datetime object to a timestamp (int)"""
if isinstance(d, (int, str)): if isinstance(d, (int, str)):
# Assume we already have a timestamp # Assume we already have a timestamp
return d return d

View File

@ -10,13 +10,11 @@ from dateutil.parser import parse
import os import os
import base64 import base64
import re import re
import functools
import logging import logging
import warnings
from io import BytesIO, open from io import BytesIO, open
import zipfile import zipfile
from . import __version__ from . import __version__, deprecated
from .exceptions import PyMISPError, SearchError, NoURL, NoKey from .exceptions import PyMISPError, SearchError, NoURL, NoKey
from .mispevent import MISPEvent, MISPAttribute, MISPUser, MISPOrganisation from .mispevent import MISPEvent, MISPAttribute, MISPUser, MISPOrganisation
from .abstract import MISPEncode from .abstract import MISPEncode
@ -46,23 +44,6 @@ except ImportError:
ASYNC_OK = False ASYNC_OK = False
def deprecated(func):
'''This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted
when the function is used.'''
@functools.wraps(func)
def new_func(*args, **kwargs):
warnings.showwarning(
"Call to deprecated function {}.".format(func.__name__),
category=DeprecationWarning,
filename=func.__code__.co_filename,
lineno=func.__code__.co_firstlineno + 1
)
return func(*args, **kwargs)
return new_func
class PyMISP(object): class PyMISP(object):
"""Python API for MISP """Python API for MISP
@ -282,7 +263,7 @@ class PyMISP(object):
def _prepare_full_event(self, distribution, threat_level_id, analysis, info, date=None, published=False, orgc_id=None, org_id=None, sharing_group_id=None): def _prepare_full_event(self, distribution, threat_level_id, analysis, info, date=None, published=False, orgc_id=None, org_id=None, sharing_group_id=None):
"""Initialize a new MISPEvent from scratch""" """Initialize a new MISPEvent from scratch"""
misp_event = MISPEvent(self.describe_types) misp_event = MISPEvent(self.describe_types)
misp_event.set_all_values(info=info, distribution=distribution, threat_level_id=threat_level_id, misp_event.from_dict(info=info, distribution=distribution, threat_level_id=threat_level_id,
analysis=analysis, date=date, orgc_id=orgc_id, org_id=org_id, sharing_group_id=sharing_group_id) analysis=analysis, date=date, orgc_id=orgc_id, org_id=org_id, sharing_group_id=sharing_group_id)
if published: if published:
misp_event.publish() misp_event.publish()
@ -291,7 +272,7 @@ class PyMISP(object):
def _prepare_full_attribute(self, category, type_value, value, to_ids, comment=None, distribution=None, **kwargs): def _prepare_full_attribute(self, category, type_value, value, to_ids, comment=None, distribution=None, **kwargs):
"""Initialize a new MISPAttribute from scratch""" """Initialize a new MISPAttribute from scratch"""
misp_attribute = MISPAttribute(self.describe_types) misp_attribute = MISPAttribute(self.describe_types)
misp_attribute.set_all_values(type=type_value, value=value, category=category, misp_attribute.from_dict(type=type_value, value=value, category=category,
to_ids=to_ids, comment=comment, distribution=distribution, **kwargs) to_ids=to_ids, comment=comment, distribution=distribution, **kwargs)
return misp_attribute return misp_attribute

View File

@ -12,6 +12,7 @@ import sys
import uuid import uuid
from collections import Counter from collections import Counter
from . import deprecated
from .abstract import AbstractMISP from .abstract import AbstractMISP
from .exceptions import UnknownMISPObjectTemplate, InvalidMISPObject, PyMISPError, NewEventError, NewAttributeError from .exceptions import UnknownMISPObjectTemplate, InvalidMISPObject, PyMISPError, NewEventError, NewAttributeError
@ -102,9 +103,11 @@ class MISPAttribute(AbstractMISP):
raise PyMISPError('All the attributes have to be of type MISPAttribute.') raise PyMISPError('All the attributes have to be of type MISPAttribute.')
def delete(self): def delete(self):
"""Mark the attribute as deleted (soft delete)"""
self.deleted = True self.deleted = True
def add_tag(self, tag): def add_tag(self, tag):
"""Add a tag to the attribute (by name or a MISPTag object)"""
misp_tag = MISPTag() misp_tag = MISPTag()
if isinstance(tag, str): if isinstance(tag, str):
misp_tag.from_dict(name=tag) misp_tag.from_dict(name=tag)
@ -115,11 +118,6 @@ class MISPAttribute(AbstractMISP):
self.tags.append(misp_tag) self.tags.append(misp_tag)
self.edited = True self.edited = True
def __repr__(self):
if hasattr(self, 'value'):
return '<{self.__class__.__name__}(type={self.type}, value={self.value})'.format(self=self)
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
def from_dict(self, **kwargs): def from_dict(self, **kwargs):
if kwargs.get('type') and kwargs.get('category'): if kwargs.get('type') and kwargs.get('category'):
if kwargs['type'] not in self.__category_type_mapping[kwargs['category']]: if kwargs['type'] not in self.__category_type_mapping[kwargs['category']]:
@ -129,7 +127,7 @@ class MISPAttribute(AbstractMISP):
self.type = kwargs.pop('type', None) # Required self.type = kwargs.pop('type', None) # Required
if self.type is None: if self.type is None:
raise NewAttributeError('The type of the attribute is required.') raise NewAttributeError('The type of the attribute is required.')
if self.type not in self.get_known_types(): if self.type not in self.known_types:
raise NewAttributeError('{} is invalid, type has to be in {}'.format(self.type, (', '.join(self._types)))) raise NewAttributeError('{} is invalid, type has to be in {}'.format(self.type, (', '.join(self._types))))
type_defaults = self.__sane_default[self.type] type_defaults = self.__sane_default[self.type]
@ -184,6 +182,13 @@ class MISPAttribute(AbstractMISP):
super(MISPAttribute, self).from_dict(**kwargs) super(MISPAttribute, self).from_dict(**kwargs)
def to_dict(self):
to_return = super(MISPAttribute, self).to_dict()
if to_return.get('data'):
to_return['data'] = base64.b64encode(self.data.getvalue()).decode()
to_return = _int_to_str(to_return)
return to_return
def _prepare_new_malware_sample(self): def _prepare_new_malware_sample(self):
if '|' in self.value: if '|' in self.value:
# Get the filename, ignore the md5, because humans. # Get the filename, ignore the md5, because humans.
@ -233,12 +238,10 @@ class MISPAttribute(AbstractMISP):
# not a encrypted zip file, assuming it is a new malware sample # not a encrypted zip file, assuming it is a new malware sample
self._prepare_new_malware_sample() self._prepare_new_malware_sample()
def to_dict(self): def __repr__(self):
to_return = super(MISPAttribute, self).to_dict() if hasattr(self, 'value'):
if to_return.get('data'): return '<{self.__class__.__name__}(type={self.type}, value={self.value})'.format(self=self)
to_return['data'] = base64.b64encode(self.data.getvalue()).decode() return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
to_return = _int_to_str(to_return)
return to_return
def verify(self, gpg_uid): def verify(self, gpg_uid):
# Not used # Not used
@ -272,24 +275,24 @@ class MISPAttribute(AbstractMISP):
signed, _ = c.sign(to_sign, mode=mode.DETACH) signed, _ = c.sign(to_sign, mode=mode.DETACH)
self.sig = base64.b64encode(signed).decode() self.sig = base64.b64encode(signed).decode()
@deprecated
def get_known_types(self): def get_known_types(self):
# Deprecated
return self.known_types return self.known_types
@deprecated
def get_malware_binary(self): def get_malware_binary(self):
# Deprecated
return self.malware_binary return self.malware_binary
@deprecated
def _json(self): def _json(self):
# DEPRECATED
return self.to_dict() return self.to_dict()
@deprecated
def _json_full(self): def _json_full(self):
# Deprecated
return self.to_dict() return self.to_dict()
@deprecated
def set_all_values(self, **kwargs): def set_all_values(self, **kwargs):
# Deprecated
self.from_dict(**kwargs) self.from_dict(**kwargs)
@ -319,13 +322,38 @@ class MISPEvent(AbstractMISP):
def known_types(self): def known_types(self):
return self._types return self._types
@property
def attributes(self):
return self.Attribute
@attributes.setter
def attributes(self, attributes):
if all(isinstance(x, MISPAttribute) for x in attributes):
self.Attribute = attributes
else:
raise PyMISPError('All the attributes have to be of type MISPAttribute.')
@property
def related_events(self):
return self.RelatedEvent
@property
def objects(self):
return self.Object
@property
def tags(self):
return self.Tag
def load_file(self, event_path): def load_file(self, event_path):
"""Load a JSON dump from a file on the disk"""
if not os.path.exists(event_path): if not os.path.exists(event_path):
raise PyMISPError('Invalid path, unable to load the event.') raise PyMISPError('Invalid path, unable to load the event.')
with open(event_path, 'r') as f: with open(event_path, 'r') as f:
self.load(f) self.load(f)
def load(self, json_event): def load(self, json_event):
"""Load a JSON dump from a pseudo file or a JSON string"""
if hasattr(json_event, 'read'): if hasattr(json_event, 'read'):
# python2 and python3 compatible to find if we have a file # python2 and python3 compatible to find if we have a file
json_event = json_event.read() json_event = json_event.read()
@ -342,9 +370,10 @@ class MISPEvent(AbstractMISP):
event['Event']['attribute_count'] = '0' event['Event']['attribute_count'] = '0'
jsonschema.validate(event, self.__json_schema) jsonschema.validate(event, self.__json_schema)
e = event.get('Event') e = event.get('Event')
self.set_all_values(**e) self.from_dict(**e)
def set_date(self, date, ignore_invalid=False): def set_date(self, date, ignore_invalid=False):
"""Set a date for the event (string, datetime, or date object)"""
if isinstance(date, basestring) or isinstance(date, unicode): if isinstance(date, basestring) or isinstance(date, unicode):
self.date = parse(date).date() self.date = parse(date).date()
elif isinstance(date, datetime.datetime): elif isinstance(date, datetime.datetime):
@ -357,11 +386,6 @@ class MISPEvent(AbstractMISP):
else: else:
raise NewEventError('Invalid format for the date: {} - {}'.format(date, type(date))) raise NewEventError('Invalid format for the date: {} - {}'.format(date, type(date)))
def __repr__(self):
if hasattr(self, 'info'):
return '<{self.__class__.__name__}(info={self.info})'.format(self=self)
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
def from_dict(self, **kwargs): def from_dict(self, **kwargs):
# Required value # Required value
self.info = kwargs.pop('info', None) self.info = kwargs.pop('info', None)
@ -396,7 +420,7 @@ class MISPEvent(AbstractMISP):
if kwargs.get('Attribute'): if kwargs.get('Attribute'):
for a in kwargs.pop('Attribute'): for a in kwargs.pop('Attribute'):
attribute = MISPAttribute() attribute = MISPAttribute()
attribute.set_all_values(**a) attribute.from_dict(**a)
if not hasattr(self, 'Attribute'): if not hasattr(self, 'Attribute'):
self.Attribute = [] self.Attribute = []
self.Attribute.append(attribute) self.Attribute.append(attribute)
@ -448,6 +472,7 @@ class MISPEvent(AbstractMISP):
return to_return return to_return
def add_tag(self, tag): def add_tag(self, tag):
"""Add a tag to the attribute (by name or a MISPTag object)"""
misp_tag = MISPTag() misp_tag = MISPTag()
if isinstance(tag, str): if isinstance(tag, str):
misp_tag.from_dict(name=tag) misp_tag.from_dict(name=tag)
@ -490,12 +515,15 @@ class MISPEvent(AbstractMISP):
return attributes return attributes
def publish(self): def publish(self):
"""Mark the attribute as published"""
self.published = True self.published = True
def unpublish(self): def unpublish(self):
"""Mark the attribute as un-published (set publish flag to false)"""
self.published = False self.published = False
def delete_attribute(self, attribute_id): def delete_attribute(self, attribute_id):
"""Delete an attribute, you can search by ID or UUID"""
found = False found = False
for a in self.attributes: for a in self.attributes:
if ((hasattr(a, 'id') and a.id == attribute_id) or if ((hasattr(a, 'id') and a.id == attribute_id) or
@ -507,47 +535,28 @@ class MISPEvent(AbstractMISP):
raise Exception('No attribute with UUID/ID {} found.'.format(attribute_id)) raise Exception('No attribute with UUID/ID {} found.'.format(attribute_id))
def add_attribute(self, type, value, **kwargs): def add_attribute(self, type, value, **kwargs):
"""Add an attribute. type and value are required but you can pass all
other parameters supported by MISPAttribute"""
if isinstance(value, list): if isinstance(value, list):
for a in value: for a in value:
self.add_attribute(type=type, value=a, **kwargs) self.add_attribute(type=type, value=a, **kwargs)
else: else:
attribute = MISPAttribute() attribute = MISPAttribute()
attribute.set_all_values(type=type, value=value, **kwargs) attribute.from_dict(type=type, value=value, **kwargs)
if not hasattr(self, 'attributes'): if not hasattr(self, 'attributes'):
self.attributes = [] self.attributes = []
self.attributes.append(attribute) self.attributes.append(attribute)
self.edited = True self.edited = True
@property
def attributes(self):
return self.Attribute
@attributes.setter
def attributes(self, attributes):
if all(isinstance(x, MISPAttribute) for x in attributes):
self.Attribute = attributes
else:
raise PyMISPError('All the attributes have to be of type MISPAttribute.')
@property
def related_events(self):
return self.RelatedEvent
@property
def objects(self):
return self.Object
@property
def tags(self):
return self.Tag
def get_object_by_id(self, object_id): def get_object_by_id(self, object_id):
"""Get an object by ID (the ID is the one set by the server when creating the new object)"""
for obj in self.objects: for obj in self.objects:
if hasattr(obj, 'id') and obj.id == object_id: if hasattr(obj, 'id') and obj.id == object_id:
return obj return obj
raise InvalidMISPObject('Object with {} does not exists in ths event'.format(object_id)) raise InvalidMISPObject('Object with {} does not exists in ths event'.format(object_id))
def add_object(self, obj): def add_object(self, obj):
"""Add an object to the Event, either by passing a MISPObject, or a dictionary"""
if isinstance(obj, MISPObject): if isinstance(obj, MISPObject):
self.Object.append(obj) self.Object.append(obj)
elif isinstance(obj, dict): elif isinstance(obj, dict):
@ -558,18 +567,25 @@ class MISPEvent(AbstractMISP):
raise InvalidMISPObject("An object to add to an existing Event needs to be either a MISPObject, or a plain python dictionary") raise InvalidMISPObject("An object to add to an existing Event needs to be either a MISPObject, or a plain python dictionary")
self.edited = True self.edited = True
def __repr__(self):
if hasattr(self, 'info'):
return '<{self.__class__.__name__}(info={self.info})'.format(self=self)
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
def _serialize(self): def _serialize(self):
return '{date}{threat_level_id}{info}{uuid}{analysis}{timestamp}'.format( return '{date}{threat_level_id}{info}{uuid}{analysis}{timestamp}'.format(
date=self.date, threat_level_id=self.threat_level_id, info=self.info, date=self.date, threat_level_id=self.threat_level_id, info=self.info,
uuid=self.uuid, analysis=self.analysis, timestamp=self.timestamp).encode() uuid=self.uuid, analysis=self.analysis, timestamp=self.timestamp).encode()
def _serialize_sigs(self): def _serialize_sigs(self):
# Not used
all_sigs = self.sig all_sigs = self.sig
for a in self.attributes: for a in self.attributes:
all_sigs += a.sig all_sigs += a.sig
return all_sigs.encode() return all_sigs.encode()
def sign(self, gpg_uid, passphrase=None): def sign(self, gpg_uid, passphrase=None):
# Not used
if not has_pyme: if not has_pyme:
raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.')
to_sign = self._serialize() to_sign = self._serialize()
@ -592,6 +608,7 @@ class MISPEvent(AbstractMISP):
self.global_sig = base64.b64encode(signed).decode() self.global_sig = base64.b64encode(signed).decode()
def verify(self, gpg_uid): def verify(self, gpg_uid):
# Not used
if not has_pyme: if not has_pyme:
raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.')
to_return = {} to_return = {}
@ -615,16 +632,16 @@ class MISPEvent(AbstractMISP):
to_return['global'] = False to_return['global'] = False
return to_return return to_return
@deprecated
def get_known_types(self): def get_known_types(self):
# Deprecated
return self.known_types return self.known_types
@deprecated
def set_all_values(self, **kwargs): def set_all_values(self, **kwargs):
# Deprecated
self.from_dict(**kwargs) self.from_dict(**kwargs)
@deprecated
def _json(self): def _json(self):
# DEPTECATED
return self.to_dict() return self.to_dict()
@ -632,26 +649,21 @@ class MISPTag(AbstractMISP):
def __init__(self): def __init__(self):
super(MISPTag, self).__init__() super(MISPTag, self).__init__()
def from_dict(self, name, **kwargs):
self.name = name
super(MISPTag, self).from_dict(**kwargs)
def __repr__(self): def __repr__(self):
if hasattr(self, 'name'): if hasattr(self, 'name'):
return '<{self.__class__.__name__}(name={self.name})'.format(self=self) return '<{self.__class__.__name__}(name={self.name})'.format(self=self)
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self) return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
def from_dict(self, name, **kwargs):
self.name = name
super(MISPTag, self).from_dict(**kwargs)
class MISPObjectReference(AbstractMISP): class MISPObjectReference(AbstractMISP):
def __init__(self): def __init__(self):
super(MISPObjectReference, self).__init__() super(MISPObjectReference, self).__init__()
def __repr__(self):
if hasattr(self, 'referenced_uuid'):
return '<{self.__class__.__name__}(object_uuid={self.object_uuid}, referenced_uuid={self.referenced_uuid}, relationship_type={self.relationship_type})'.format(self=self)
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
def from_dict(self, object_uuid, referenced_uuid, relationship_type, comment=None, **kwargs): def from_dict(self, object_uuid, referenced_uuid, relationship_type, comment=None, **kwargs):
self.object_uuid = object_uuid self.object_uuid = object_uuid
self.referenced_uuid = referenced_uuid self.referenced_uuid = referenced_uuid
@ -659,6 +671,11 @@ class MISPObjectReference(AbstractMISP):
self.comment = comment self.comment = comment
super(MISPObjectReference, self).from_dict(**kwargs) super(MISPObjectReference, self).from_dict(**kwargs)
def __repr__(self):
if hasattr(self, 'referenced_uuid'):
return '<{self.__class__.__name__}(object_uuid={self.object_uuid}, referenced_uuid={self.referenced_uuid}, relationship_type={self.relationship_type})'.format(self=self)
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
class MISPUser(AbstractMISP): class MISPUser(AbstractMISP):
@ -678,11 +695,6 @@ class MISPObjectAttribute(MISPAttribute):
super(MISPObjectAttribute, self).__init__() super(MISPObjectAttribute, self).__init__()
self.__definition = definition self.__definition = definition
def __repr__(self):
if hasattr(self, 'value'):
return '<{self.__class__.__name__}(object_relation={self.object_relation}, value={self.value})'.format(self=self)
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
def from_dict(self, object_relation, value, **kwargs): def from_dict(self, object_relation, value, **kwargs):
self.object_relation = object_relation self.object_relation = object_relation
self.value = value self.value = value
@ -703,6 +715,11 @@ class MISPObjectAttribute(MISPAttribute):
kwargs.update(**self) kwargs.update(**self)
super(MISPObjectAttribute, self).from_dict(**kwargs) super(MISPObjectAttribute, self).from_dict(**kwargs)
def __repr__(self):
if hasattr(self, 'value'):
return '<{self.__class__.__name__}(object_relation={self.object_relation}, value={self.value})'.format(self=self)
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
class MISPObject(AbstractMISP): class MISPObject(AbstractMISP):
@ -760,7 +777,7 @@ class MISPObject(AbstractMISP):
self.distribution = self._default_attributes_parameters.distribution self.distribution = self._default_attributes_parameters.distribution
self.sharing_group_id = self._default_attributes_parameters.sharing_group_id self.sharing_group_id = self._default_attributes_parameters.sharing_group_id
else: else:
self.distribution = 3 self.distribution = 5 # Default to inherit
self.sharing_group_id = None self.sharing_group_id = None
self.ObjectReference = [] self.ObjectReference = []
self._standalone = standalone self._standalone = standalone
@ -768,10 +785,13 @@ class MISPObject(AbstractMISP):
# Mark as non_jsonable because we need to add the references manually after the object(s) have been created # Mark as non_jsonable because we need to add the references manually after the object(s) have been created
self.update_not_jsonable('ObjectReference') self.update_not_jsonable('ObjectReference')
def __repr__(self): @property
if hasattr(self, 'name'): def attributes(self):
return '<{self.__class__.__name__}(name={self.name})'.format(self=self) return self.Attribute
return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
@property
def references(self):
return self.ObjectReference
def from_dict(self, **kwargs): def from_dict(self, **kwargs):
if self.__known_template: if self.__known_template:
@ -795,6 +815,51 @@ class MISPObject(AbstractMISP):
super(MISPObject, self).from_dict(**kwargs) super(MISPObject, self).from_dict(**kwargs)
def add_reference(self, referenced_uuid, relationship_type, comment=None, **kwargs):
"""Add a link (uuid) to an other object"""
if kwargs.get('object_uuid'):
# Load existing object
object_uuid = kwargs.pop('object_uuid')
else:
# New reference
object_uuid = self.uuid
reference = MISPObjectReference()
reference.from_dict(object_uuid=object_uuid, referenced_uuid=referenced_uuid,
relationship_type=relationship_type, comment=comment, **kwargs)
self.ObjectReference.append(reference)
self.edited = True
def get_attributes_by_relation(self, object_relation):
'''Returns the list of attributes with the given object relation in the object'''
return self.__fast_attribute_access.get(object_relation, [])
def has_attributes_by_relation(self, list_of_relations):
'''True if all the relations in the list are defined in the object'''
return all(relation in self.__fast_attribute_access for relation in list_of_relations)
def add_attribute(self, object_relation, **value):
"""Add an attribute. object_relation is required and the value key is a
dictionary with all the keys supported by MISPAttribute"""
if value.get('value') is None:
return None
if self.__known_template:
if self.__definition['attributes'].get(object_relation):
attribute = MISPObjectAttribute(self.__definition['attributes'][object_relation])
else:
# Woopsie, this object_relation is unknown, no sane defaults for you.
logger.warning("The template ({}) doesn't have the object_relation ({}) you're trying to add.".format(self.name, object_relation))
attribute = MISPObjectAttribute({})
else:
attribute = MISPObjectAttribute({})
# Overwrite the parameters of self._default_attributes_parameters with the ones of value
attribute.from_dict(object_relation=object_relation, **dict(self._default_attributes_parameters, **value))
if not self.__fast_attribute_access.get(object_relation):
self.__fast_attribute_access[object_relation] = []
self.Attribute.append(attribute)
self.__fast_attribute_access[object_relation].append(attribute)
self.edited = True
return attribute
def to_dict(self, strict=False): def to_dict(self, strict=False):
if strict or self.__strict and self.__known_template: if strict or self.__strict and self.__known_template:
self._validate() self._validate()
@ -826,53 +891,7 @@ class MISPObject(AbstractMISP):
raise InvalidMISPObject('{} is required'.format(r)) raise InvalidMISPObject('{} is required'.format(r))
return True return True
def add_reference(self, referenced_uuid, relationship_type, comment=None, **kwargs): def __repr__(self):
"""Add a link (uuid) to an other object""" if hasattr(self, 'name'):
if kwargs.get('object_uuid'): return '<{self.__class__.__name__}(name={self.name})'.format(self=self)
# Load existing object return '<{self.__class__.__name__}(NotInitialized)'.format(self=self)
object_uuid = kwargs.pop('object_uuid')
else:
# New reference
object_uuid = self.uuid
reference = MISPObjectReference()
reference.from_dict(object_uuid=object_uuid, referenced_uuid=referenced_uuid,
relationship_type=relationship_type, comment=comment, **kwargs)
self.ObjectReference.append(reference)
self.edited = True
def get_attributes_by_relation(self, object_relation):
'''Returns the list of attributes with the given object relation in the object'''
return self.__fast_attribute_access.get(object_relation, [])
def has_attributes_by_relation(self, list_of_relations):
'''True if all the relations in the list are defined in the object'''
return all(relation in self.__fast_attribute_access for relation in list_of_relations)
@property
def attributes(self):
return self.Attribute
@property
def references(self):
return self.ObjectReference
def add_attribute(self, object_relation, **value):
if value.get('value') is None:
return None
if self.__known_template:
if self.__definition['attributes'].get(object_relation):
attribute = MISPObjectAttribute(self.__definition['attributes'][object_relation])
else:
# Woopsie, this object_relation is unknown, no sane defaults for you.
logger.warning("The template ({}) doesn't have the object_relation ({}) you're trying to add.".format(self.name, object_relation))
attribute = MISPObjectAttribute({})
else:
attribute = MISPObjectAttribute({})
# Overwrite the parameters of self._default_attributes_parameters with the ones of value
attribute.from_dict(object_relation=object_relation, **dict(self._default_attributes_parameters, **value))
if not self.__fast_attribute_access.get(object_relation):
self.__fast_attribute_access[object_relation] = []
self.Attribute.append(attribute)
self.__fast_attribute_access[object_relation].append(attribute)
self.edited = True
return attribute