예제 #1
0
class Ce1susAdapter(BaseController):
    def __init__(self, config, session=None):
        self.server_details = None
        self.proxies = {}
        self.verify_ssl = False
        self.ssl_cert = None
        self.session = requests.session()
        self.assembler = Assembler(config, session)
        self.event_controller = EventController(config, session)
        self.process_controller = ProcessController(config, session)

    @property
    def apiUrl(self):
        return '{0}/REST/0.3.0'.format(self.server_details.baseurl)

    @property
    def apiKey(self):
        return self.server_details.user.api_key

    def __set_complete_inflated(self, url, complete=False, inflated=False):
        if complete and not inflated:
            url = '{0}?complete=true'.format(url)
        if not complete and inflated:
            url = '{0}?inflated=true'.format(url)
        if complete and inflated:
            url = '{0}?complete=true&inflated=true'.format(url)
        return url

    def __extract_message(self, error):
        reason = error.message
        message = error.response.text
        code = error.response.status_code
        # "<p>An event with uuid "54f63b0f-0c98-4e74-ab95-60c718689696" already exists</p>
        try:
            pos = message.index('<p>') + 3
            message = message[pos:]
            pos = message.index('</p>')
            message = message[:pos]
        except ValueError:
            # In case the message is not parsable
            pass
        return code, reason, message

    def __handle_exception(self, request):
        try:
            request.raise_for_status()
        except requests.exceptions.HTTPError as error:
            code, reason, message = self.__extract_message(error)
            message = u'{0} ({1})'.format(reason, message)
            if code == 403:
                raise Ce1susAdapterForbiddenException(message)
            elif code == 404:
                raise Ce1susAdapterNothingFoundException(message)
            else:
                raise Ce1susAdapterException(message)

    def __request(self, path, method, data=None, extra_headers=None):
        try:
            url = '{0}/{1}'.format(self.apiUrl, path)
            headers = {
                'Content-Type': 'application/json; charset=utf-8',
                'User-Agent': 'Ce1sus API server client {0}'.format(APP_REL),
                'key': self.apiKey
            }
            if extra_headers:
                for key, value in extra_headers.items():
                    headers[key] = value

            if method == 'GET':
                request = self.session.get(url,
                                           headers=headers,
                                           proxies=self.proxies,
                                           verify=self.verify_ssl,
                                           cert=self.ssl_cert,
                                           cookies=self.session.cookies)
            elif method == 'PUT':
                request = self.session.put(url,
                                           json.dumps(data),
                                           headers=headers,
                                           proxies=self.proxies,
                                           verify=self.verify_ssl,
                                           cert=self.ssl_cert,
                                           cookies=self.session.cookies)
            elif method == 'DELETE':
                request = self.session.delete(url,
                                              headers=headers,
                                              proxies=self.proxies,
                                              verify=self.verify_ssl,
                                              cert=self.ssl_cert,
                                              cookies=self.session.cookies)
            elif method == 'POST':
                request = self.session.post(url,
                                            json.dumps(data),
                                            headers=headers,
                                            proxies=self.proxies,
                                            verify=self.verify_ssl,
                                            cert=self.ssl_cert,
                                            cookies=self.session.cookies)
            else:
                raise UnkownMethodException(
                    u'Mehtod {0} is not specified can only be GET,POST,PUT or DELETE'
                )

            if request.status_code == requests.codes.ok:
                return json.loads(request.text)
            else:
                self.__handle_exception(request)
        except requests.exceptions.RequestException as error:
            raise Ce1susAdapterException(error)
        except requests.ConnectionError as error:
            raise Ce1susAdapterConnectionException('{0}'.format(error.message))

    def login(self):
        text = self.__request('/login', 'POST', None)
        return text

    def logout(self):
        text = self.__request(
            '/logout',
            'GET',
        )
        if text == 'User logged out':
            return True
        else:
            return False

    def get_event_by_uuid(self,
                          uuid,
                          complete=False,
                          inflated=False,
                          poponly=True,
                          json=False):
        url = '/event/{0}'.format(uuid)
        url = self.__set_complete_inflated(url, complete, inflated)
        json = self.__request(url, 'GET', None)
        if json:
            return json
        else:
            event = self.assembler.assemble_event(json,
                                                  self.server_details.user,
                                                  False, True, poponly)
            return event

    def insert_event(self, event, complete=False, inflated=False):
        url = '/event'
        url = self.__set_complete_inflated(url, complete, inflated)
        event_permissions = self.event_controller.get_event_user_permissions(
            event, self.server_details.user)
        json = self.__request(url,
                              'POST',
                              data=event.to_dict(True, True, event_permissions,
                                                 self.server_details.user))
        owner = is_event_owner(event, self.server_details.user)
        event = self.assembler.update_event(event, json,
                                            self.server_details.user, owner,
                                            True)
        return event

    def update_event(self, event, complete=False, inflated=False):
        url = '/event/{0}'.format(event.uuid)
        url = self.__set_complete_inflated(url, complete, inflated)
        event_permissions = self.event_controller.get_event_user_permissions(
            event, self.server_details.user)
        json = self.__request(url,
                              'PUT',
                              data=event.to_dict(True, True, event_permissions,
                                                 self.server_details.user))
        owner = is_event_owner(event, self.server_details.user)
        event = self.assembler.update_event(event, json,
                                            self.server_details.user, owner,
                                            True)
        return event

    def get_index(self, server_details=None):
        url = '/events'
        if server_details:
            self.server_details = server_details
        url = self.__set_complete_inflated(url, False, False)
        json = self.__request(url, 'GET', None)
        events_json = json.get('data', list())
        result = list()
        for event_json in events_json:
            event = self.assembler.assemble_event(event_json,
                                                  self.server_details.user,
                                                  False, True, True)
            result.append(event)
        return result

    def push(self, server_details):
        try:
            self.server_details = server_details
            self.login()
            user_events = self.event_controller.get_all_for_user(
                server_details.user)
            # get the remote ones
            rem_events = self.get_index(server_details)
            rem_events_dict = dict()
            for rem_event in rem_events:
                rem_events_dict[rem_event.uuid] = rem_event

            uuids_to_push = list()
            for event in user_events:
                rem_event = rem_events_dict.get(event.uuid, None)
                if rem_event:
                    if event.last_publish_date and rem_event.last_publish_date:
                        if event.last_publish_date > rem_event.last_publish_date:
                            uuids_to_push.append(event.uuid)
                else:
                    uuids_to_push.append(event.uuid)

            # pass
            for uuid_to_push in uuids_to_push:
                self.process_controller.create_new_process(
                    ProcessType.PUSH, uuid_to_push, self.server_details.user,
                    server_details, True)
        except Ce1susAdapterException as error:
            self.logout()
            raise Ce1susAdapterException(error)
        self.logout()

    def pull(self, server_details):
        try:
            self.server_details = server_details
            self.login()
            events = self.get_index(server_details)
            event_uuids = dict()
            for event in events:
                event_uuids[event.uuid] = event

            local_events = self.event_controller.get_event_by_uuids(
                event_uuids.keys())

            items_to_remove = list()
            for local_event in local_events:
                rem_event = event_uuids[local_event.uuid]
                if local_event.last_publish_date and rem_event.last_publish_date:
                    if rem_event.last_publish_date <= local_event.last_publish_date:
                        items_to_remove.append(local_event.uuid)
                else:
                    items_to_remove.append(local_event.uuid)

            for item_to_remove in items_to_remove:
                del event_uuids[item_to_remove]

            for rem_event in event_uuids.itervalues():
                self.process_controller.create_new_process(
                    ProcessType.PULL, rem_event.uuid, server_details.user,
                    server_details, True)
            self.logout()
            return 'OK'
        except Ce1susAdapterException as error:
            self.logout()
            raise Ce1susAdapterException(error)
예제 #2
0
파일: misp.py 프로젝트: tsmolka/ce1sus
class MISPAdapter(BaseView):
    def __init__(self, config, session=None):
        BaseView.__init__(self, config)
        self.event_controller = EventController(config, session)
        self.server_broker = self.event_controller.broker_factory(
            SyncServerBroker)
        self.ce1sus_to_misp = Ce1susMISP(config, session)
        self.login_handler = LoginHandler(config)
        self.logout_handler = LogoutHandler(config)
        self.misp_converter = MispConverter(config, None, None, None, session)
        dump = config.get('MISPAdapter', 'dump', False)
        file_loc = config.get('MISPAdapter', 'file', None)
        self.misp_converter.dump = dump
        self.misp_converter.file_location = file_loc
        self.merger = Merger(config, session)
        self.process_controller = ProcessController(config, session)

    @cherrypy.expose
    @cherrypy.tools.allow(methods=['POST'])
    @require()
    def upload_xml(self, *vpath, **params):
        try:
            input_json = self.get_json()
            filename = input_json['name']
            data = input_json['data']['data']
            xml_string = base64.b64decode(data)
            complete = params.get('complete', False)
            inflated = params.get('inflated', False)
            user = self.get_user()
            user = self.user_controller.get_user_by_username(user.username)
            if 'XML' in filename or 'xml' in filename:
                self.logger.info(
                    'Starting to import xml form file {0}'.format(filename))
                self.misp_converter.user = user
                event_uuid = self.misp_converter.get_uuid_from_event_xml(
                    xml_string)
                try:
                    event = self.misp_converter.get_event_from_xml(
                        xml_string, None)
                    self.logger.info('Received Event {0}'.format(event.uuid))
                    self.event_controller.insert_event(user, event, True, True)
                except ControllerIntegrityException as error:
                    local_event = self.event_controller.get_event_by_uuid(
                        event_uuid)
                    event = self.misp_converter.get_event_from_xml(
                        xml_string, local_event)
                    # merge event with existing event

                    if self.is_event_viewable(local_event, user):
                        event_permissions = self.get_event_user_permissions(
                            event, user)
                        try:
                            merged_event = self.merger.merge_event(
                                local_event, event, user, event_permissions)
                        except MergingException:
                            raise MISPAdapterException405()
                            # raise cherrypy.HTTPError(405)
                    else:
                        # TODO log the changes
                        self.logger.warning(
                            'user {0} tried to change event {1} but does not have the right to see it'
                            .format(user.username, event.identifer))
                        raise HTTPError(403, 'Not authorized')

                    if merged_event:
                        self.logger.info('Received Event {0} updates'.format(
                            merged_event.uuid))
                        self.event_controller.update_event(
                            user, merged_event, True, True)
                        event = merged_event
                    else:
                        self.logger.info(
                            'Received Event {0} did not need to update as it is up to date'
                            .format(event.uuid))
                        # Log errors however
                        self.event_controller.event_broker.do_commit()
                event_permissions = self.event_controller.get_event_user_permissions(
                    event, user)
                cherrypy.response.headers[
                    'Content-Type'] = 'application/json; charset=UTF-8'
                return event.to_dict(complete, inflated, event_permissions,
                                     user)
            else:
                raise HTTPError(409, 'File does not end in xml or XML')
        except MispConverterException as error:
            self.logger.error(error)
            raise HTTPError(409, 'File is not a MISP XML')
        except ControllerException as error:
            self.logger.error(error)
            raise HTTPError(400, '{0}'.format(error.message))
        except Exception as error:
            self.logger.critical(error)
            raise HTTPError(500, '{0}'.format(error.message))

    @cherrypy.expose
    @cherrypy.tools.allow(methods=['GET'])
    def shadow_attributes(self, *vpath, **params):
        # this is called from the misp server to see it his know events have new proposals
        # TODO: Proposal for misp
        raise cherrypy.HTTPError(404)

    def get_all_events(self, server_user):
        events = self.event_controller.get_all()
        result = list()
        for event in events:
            if event.properties.is_validated:
                if self.is_event_viewable(event, server_user):
                    result.append(event)
            else:
                if event.originating_group.identifier == server_user.group.identifier:
                    result.append(event)
        return result

    def make_index(self, server_user):
        result = self.get_all_events(server_user)
        return self.ce1sus_to_misp.make_index(result)

    def pull(self, server_details):
        # use the logged in user for these request
        if server_details.type != 'MISP':
            raise MISPAdapterException(
                'Server {0} is not a MISP server'.format(
                    server_details.identifier))

        self.misp_converter.api_key = server_details.user.api_key
        self.misp_converter.api_url = server_details.baseurl
        self.misp_converter.tag = server_details.name
        # TODO check if it is a pull server
        user = self.event_controller.user_broker.get_by_id(
            self.get_user().identifier)
        self.misp_converter.user = user

        recent_events = self.misp_converter.get_index()
        local_events = self.event_controller.get_event_by_uuids(
            recent_events.keys())
        items_to_remove = list()

        # find the ones do not need to be updated
        for local_event in local_events:
            values = recent_events[local_event.uuid]
            if values[1] <= local_event.last_publish_date:
                items_to_remove.append(local_event.uuid)

        for item_to_remove in items_to_remove:
            del recent_events[item_to_remove]

        for value in recent_events.itervalues():
            self.process_controller.create_new_process(ProcessType.PULL,
                                                       value[0], user,
                                                       server_details)

            # fetch one event from misp
            # misp_event_xml = self.misp_converter.get_xml_event(value[0])
            # make insert/merge
            # try:
            #  self.ins_merg_event(server_details, misp_event_xml)
            # except BrokerException as error:
            #  self.logger.error(error)
            # TODO dump xml or log it in browser

        return 'OK'

    def push(self, server_details):
        if server_details.type != 'MISP':
            raise MISPAdapterException(
                'Server {0} is not a MISP server'.format(
                    server_details.identifier))
        self.misp_converter.api_key = server_details.user.api_key
        self.misp_converter.api_url = server_details.baseurl
        self.misp_converter.tag = server_details.name
        # TODO check if it is a pull server
        user = self.event_controller.user_broker.get_by_id(
            self.get_user().identifier)
        self.misp_converter.user = user

        # everything is handled inside here
        return self.misp_converter.filter_event_push(self, server_details)

    def make_misp_xml(self, event, server_user):

        flat_attribtues = self.ce1sus_to_misp.relations_controller.get_flat_attributes_for_event(
            event)
        result = list()
        for flat_attribtue in flat_attribtues:
            if self.is_item_viewable(
                    event, flat_attribtue,
                    server_user) and flat_attribtue.properties.is_shareable:
                result.append(flat_attribtue)

        references = list()
        for report in event.reports:
            for reference in report.references:
                if self.is_item_viewable(
                        event, reference,
                        server_user) and reference.properties.is_shareable:
                    references.append(reference)
        result = self.ce1sus_to_misp.create_event_xml(event, result,
                                                      references)
        return result

    @cherrypy.expose
    @cherrypy.tools.allow(methods=['GET', 'POST'])
    def events(self, *vpath, **params):

        headers = cherrypy.request.headers
        authkey = headers.get('Authorization')

        # reset key
        headers['key'] = authkey
        self.login_handler.login(headers=headers)
        # does not handle sessions well :P
        try:
            server_user = self.event_controller.user_broker.get_user_by_api_key(
                authkey)
        except BrokerException as error:
            self.logger.error(error)
            raise cherrypy.HTTPError(403)

        rawbody = ''
        path = vpath
        method = cherrypy.request.method
        cl = headers.get('Content-Length', None)
        if cl:
            rawbody = cherrypy.request.body.read(int(cl))

        content_type = headers.get('Content-Type', 'application/json')
        if content_type == 'application/json':
            body = json.loads(rawbody)
        elif content_type == 'application/xml':
            body = rawbody
        else:
            raise
        # check aut else a 405 exception

        if len(path) > 0:
            detected = False
            if path[0] == 'filterEventIdsForPush':
                if method == 'POST':
                    return_message = self.perform_push(body)
                    detected = True
            elif path[0] == 'index':
                if method == 'GET':
                    cherrypy.response.headers[
                        'Content-Type'] = 'application/xml; charset=UTF-8'
                    return_message = self.make_index(server_user)
                    detected = True
            if not detected:
                try:
                    event_id = int(path[0])
                    event = self.event_controller.get_event_by_id(event_id)
                    self.is_event_viewable(event, server_user)
                    # TODO check if user can view event
                    # convert event to misp event
                    cherrypy.response.headers[
                        'Content-Type'] = 'application/xml; charset=UTF-8'
                    misp_event = self.make_misp_xml(event, server_user)
                    return_message = misp_event
                except ValueError:
                    raise cherrypy.HTTPError(404)
        else:
            # it is an insert of an event
            if method == 'POST':
                cherrypy.response.headers[
                    'Content-Type'] = 'application/xml; charset=UTF-8'
                return_message = self.pushed_event(body, server_user)

        self.logout_handler.logout()
        return return_message

    def ins_merg_event(self,
                       server_details,
                       xml_string,
                       server_user=None,
                       user=None):
        if server_details.type == 'MISP':
            if user:
                pass
            else:
                user = self.event_controller.user_broker.get_by_id(
                    self.get_user().identifier)
            self.misp_converter.api_key = server_details.user.api_key
            self.misp_converter.api_url = server_details.baseurl
            self.misp_converter.tag = server_details.name
            self.misp_converter.user = user
            merged_event = None
            try:
                event_uuid = self.misp_converter.get_uuid_from_event_xml(
                    xml_string)
                try:
                    event = self.misp_converter.get_event_from_xml(
                        xml_string, None)
                    self.logger.info('Received Event {0}'.format(event.uuid))
                    event.properties.is_validated = False
                    self.event_controller.insert_event(user, event, True, True)
                except ControllerIntegrityException as error:
                    local_event = self.event_controller.get_event_by_uuid(
                        event_uuid)
                    event = self.misp_converter.get_event_from_xml(
                        xml_string, local_event)
                    # merge event with existing event

                    if self.is_event_viewable(local_event, server_user):
                        event_permissions = self.get_event_user_permissions(
                            event, user)
                        try:
                            merged_event = self.merger.merge_event(
                                local_event, event, user, event_permissions)
                        except MergingException:
                            raise MISPAdapterException405()
                            # raise cherrypy.HTTPError(405)
                    else:
                        # TODO log the changes
                        self.logger.warning(
                            'user {0} tried to change event {1} but does not have the right to see it'
                            .format(user.username, event.identifer))
                    if merged_event:
                        self.logger.info('Received Event {0} updates'.format(
                            merged_event.uuid))
                        self.event_controller.update_event(
                            user, merged_event, True, True)
                        event = merged_event
                    else:
                        self.logger.info(
                            'Received Event {0} did not need to update as it is up to date'
                            .format(event.uuid))
                        # Log errors however
                        self.event_controller.event_broker.do_commit()

                return self.make_misp_xml(event, server_user)
            except BrokerException as error:
                self.logger.error('Received a MISP Event which caused errors')
                self.logger.error(error)
                # TODO Dump xml
                raise MISPAdapterException500()
                # raise cherrypy.HTTPError(500)
        else:
            raise MISPAdapterException409('Server is not a MISP Server')
            # raise cherrypy.HTTPError(409, 'Server is not a MISP Server')

    def pushed_event(self, xml_string, server_user):
        # instantiate misp converter
        user = self.get_user()
        try:
            server_details = self.server_broker.get_server_by_user_id(
                user.identifier)
            if server_details.type != 'MISP':
                raise cherrypy.HTTPError(409, 'Server is not a MISP Server')
            # check if it is a misp server else raise
            return self.ins_merg_event(server_details, xml_string, server_user)
        except BrokerException as error:
            self.logger.error(error)
            raise cherrypy.HTTPError(404)
        except MISPAdapterException405 as error:
            raise cherrypy.HTTPError(405)
        except MISPAdapterException409 as error:
            raise cherrypy.HTTPError(409)
        except MISPAdapterException500 as error:
            raise cherrypy.HTTPError(500)

    def perform_push(self, send_events):
        incomming_events = dict()

        for send_event in send_events:
            uuid = send_event['Event']['uuid']

            incomming_events[uuid] = send_event['Event']['timestamp']
        remove = list()
        # find all events matching the uuids
        local_events = self.event_controller.get_event_by_uuids(
            incomming_events.keys())
        for local_event in local_events:
            # check if the local events were not modified
            date = incomming_events[local_event.uuid]
            datetime_from_string = datetime.utcfromtimestamp(int(date))

            if local_event.last_publish_date >= datetime_from_string:
                # remove the ones which are either new or
                remove.append(local_event.uuid)
            # check if the local event is not locked -> does not exist in ours
            # TODO: implement a lock?

        result = list()

        # create array of uuids
        for key in incomming_events.iterkeys():
            if key not in remove:
                result.append(key)
        cherrypy.response.headers[
            'Content-Type'] = 'application/json; charset=UTF-8'
        return json.dumps(result)

    @cherrypy.expose
    @cherrypy.tools.allow(methods=['GET'])
    @require()
    def event(self, *vpath, **params):
        if len(vpath) > 0:
            uuid_string = vpath[0]
            # check if it is a uuid
            # check the mode
            make_file = params.get('file', None)
            if make_file == '':
                cherrypy.response.headers[
                    'Content-Type'] = 'application/x-download'
                cherrypy.response.headers[
                    "Content-Disposition"] = 'attachment; filename=Event_{0}_MISP.xml'.format(
                        uuid_string)
            else:
                cherrypy.response.headers['Content-Type'] = 'text/xml'
            try:
                UUID(uuid_string, version=4)
            except ValueError as error:
                print error
                raise HTTPError(
                    400,
                    'The provided uuid "{0}" is not valid'.format(uuid_string))
            try:
                event = self.event_controller.get_event_by_uuid(uuid_string)
                return self.make_misp_xml(event, self.get_user())
            except ControllerNothingFoundException as error:
                raise HTTPError(404, '{0}'.format(error.message))
        else:
            raise HTTPError(400, 'Cannot be called without a uuid')