<commit_before>import json, datetime, base64
from pymisp import MISPEvent
from collections import defaultdict, Counter

misperrors = {'error': 'Error'}
moduleinfo = {'version': '1', 'author': '<NAME>',
              'description': 'Export to GoAML',
              'module-type': ['export'],
              'require_standard_format': True}
moduleconfig = ['rentity_id']
mispattributes = {'input': ['MISPEvent'], 'output': ['xml file']}
outputFileExtension = "xml"
responseType = "application/xml"

objects_to_parse = ['transaction', 'bank-account', 'person', 'entity', 'geolocation']

goAMLmapping = {'bank-account': 't_account', 'institution-code': 'institution_code', 'iban': 'iban',
                'swift': 'swift', 'branch': 'branch', 'non-banking-institution': 'non_bank_institution',
                'account': 'account', 'currency-code': 'currency_code', 'account-name': 'account_name',
                'client-number': 'client_number', 'personal-account-type': 'personal_account_type',
                'opened': 'opened', 'closed': 'closed', 'balance': 'balance', 'status-code': 'status_code',
                'beneficiary': 'beneficiary', 'beneficiary-comment': 'beneficiary_comment', 'comments': 'comments',
                'person': 't_person', 'text': 'comments', 'first-name': 'first_name', 'middle-name': 'middle_name',
                'last-name': 'last_name', 'mothers-name': 'mothers_name', 'title': 'title', 'alias': 'alias',
                'date-of-birth': 'birthdate', 'place-of-birth': 'birth_place', 'gender': 'gender',
                'passport-number': 'passport_number', 'passport-country': 'passport_country',
                'social-security-number': 'ssn', 'nationality': 'nationality1', 'identity-card-number': 'id_number',
                'geolocation': 'location', 'city': 'city', 'region': 'state', 'country': 'country-code',
                'address': 'address', 'zipcode': 'zip',
                'transaction': 'transaction', 'transaction-number': 'transactionnumber', 'date': 'date_transaction',
                'location': 'transaction_location', 'transmode-code': 'transmode_code', 'amount': 'amount_local',
                'transmode-comment': 'transmode_comment', 'date-posting': 'date_posting', 'teller': 'teller',
                'authorized': 'authorized',
                'legal-entity': 'entity', 'name': 'name', 'commercial-name': 'commercial_name', 'business': 'business',
                'legal-form': 'incorporation_legal_form', 'registration-number': 'incorporation_number',
                'phone-number': 'phone'}

referencesMapping = {'bank-account': {'aml_type': '{}_account', 'bracket': 't_{}'},
                     'person': {'transaction': {'aml_type': '{}_person', 'bracket': 't_{}'}, 'bank-account': {'aml_type': 't_person', 'bracket': 'signatory'}},
                     'legal-entity': {'transaction': {'aml_type': '{}_entity', 'bracket': 't_{}'}, 'bank-account': {'aml_type': 'entity'}},
                     'geolocation': {'aml_type': 'address', 'bracket': 'addresses'}}

class GoAmlGeneration(object):
    def __init__(self, config):
        self.config = config
        self.parsed_uuids = defaultdict(list)

    def from_event(self, event):
        self.misp_event = MISPEvent()
        self.misp_event.load(event)

    def parse_objects(self):
        uuids = defaultdict(list)
        report_code = []
        currency_code = []
        for obj in self.misp_event.objects:
            obj_type = obj.name
            uuids[obj_type].append(obj.uuid)
            if obj_type == 'bank-account':
                try:
                    report_code.append(obj.get_attributes_by_relation('report-code')[0].value.split(' ')[0])
                    currency_code.append(obj.get_attributes_by_relation('currency-code')[0].value)
                except:
                    print('report_code or currency_code error')
        self.uuids, self.report_codes, self.currency_codes = uuids, report_code, currency_code

    def build_xml(self):
        self.xml = {'header': "<report><rentity_id>{}</rentity_id><submission_code>E</submission_code>".format(self.config),
                    'data': ""}
        if "STR" in self.report_codes:
            report_code = "STR"
        else:
            report_code = Counter(self.report_codes).most_common(1)[0][0]
        self.xml['header'] += "<report_code>{}</report_code>".format(report_code)
        submission_date = str(self.misp_event.timestamp).replace(' ', 'T')
        self.xml['header'] += "<submission_date>{}</submission_date>".format(submission_date)
        self.xml['header'] += "<currency_code_local>{}</currency_code_local>".format(Counter(self.currency_codes).most_common(1)[0][0])
        for trans_uuid in self.uuids.get('transaction'):
            self.itterate('transaction', 'transaction', trans_uuid, 'data')
        person_to_parse = [person_uuid for person_uuid in self.uuids.get('person') if person_uuid not in self.parsed_uuids.get('person')]
        if len(person_to_parse) == 1:
            self.itterate('person', 'reporting_person', person_to_parse[0], 'header')
        location_to_parse = [location_uuid for location_uuid in self.uuids.get('geolocation') if location_uuid not in self.parsed_uuids.get('geolocation')]
        if len(location_to_parse) == 1:
            self.itterate('geolocation', 'location', location_to_parse[0], 'header')
        self.xml['data'] += "</report>"

    def itterate(self, object_type, aml_type, uuid, xml_part):
        obj = self.misp_event.get_object_by_uuid(uuid)
        if object_type == 'transaction':
            self.xml[xml_part] += "<{}>".format(aml_type)
            self.fill_xml_transaction(obj.attributes, xml_part)
            self.parsed_uuids[object_type].append(uuid)
            if obj.ObjectReference:
                self.parseObjectReferences(object_type, xml_part, obj.ObjectReference)
            self.xml[xml_part] += "</{}>".format(aml_type)
        else:
            if 'to_' in aml_type or 'from_' in aml_type:
                relation_type = aml_type.split('_')[0]
                self.xml[xml_part] += "<{0}_funds_code>{1}</{0}_funds_code>".format(relation_type, self.from_and_to_fields[relation_type]['funds'].split(' ')[0])
                self.itterate_normal_case(object_type, obj, aml_type, uuid, xml_part)
                self.xml[xml_part] += "<{0}_country>{1}</{0}_country>".format(relation_type, self.from_and_to_fields[relation_type]['country'])
            else:
                self.itterate_normal_case(object_type, obj, aml_type, uuid, xml_part)

    def itterate_normal_case(self, object_type, obj, aml_type, uuid, xml_part):
        self.xml[xml_part] += "<{}>".format(aml_type)
        self.fill_xml(obj, xml_part)
        self.parsed_uuids[object_type].append(uuid)
        if obj.ObjectReference:
            self.parseObjectReferences(object_type, xml_part, obj.ObjectReference)
        self.xml[xml_part] += "</{}>".format(aml_type)

    def parseObjectReferences(self, object_type, xml_part, references):
        for ref in references:
            next_uuid = ref.referenced_uuid
            next_object_type = ref.Object.get('name')
            relationship_type = ref.relationship_type
            self.parse_references(object_type, next_object_type, next_uuid, relationship_type, xml_part)

    def fill_xml_transaction(self, attributes, xml_part):
        from_and_to_fields = {'from': {}, 'to': {}}
        for attribute in attributes:
            object_relation = attribute.object_relation
            attribute_value = attribute.value
            if object_relation == 'date-posting':
                self.xml[xml_part] += "<late_deposit>True</late_deposit>"
            elif object_relation in ('from-funds-code', 'to-funds-code'):
                relation_type, field, _ = object_relation.split('-')
                from_and_to_fields[relation_type][field] = attribute_value
                continue
            elif object_relation in ('from-country', 'to-country'):
                relation_type, field = object_relation.split('-')
                from_and_to_fields[relation_type][field] = attribute_value
                continue
            try:
                self.xml[xml_part] += "<{0}>{1}</{0}>".format(goAMLmapping[object_relation], attribute_value)
            except KeyError:
                pass
        self.from_and_to_fields = from_and_to_fields

    def fill_xml(self, obj, xml_part):
        if obj.name == 'bank-account':
            for attribute in obj.attributes:
                if attribute.object_relation in ('personal-account-type', 'status-code'):
                    attribute_value = attribute.value.split(' - ')[0]
                else:
                    attribute_value = attribute.value
                try:
                    self.xml[xml_part] += "<{0}>{1}</{0}>".format(goAMLmapping[attribute.object_relation], attribute_value)
                except KeyError:
                    pass
        else:
            for attribute in obj.attributes:
                try:
                    self.xml[xml_part] += "<{0}>{1}</{0}>".format(goAMLmapping[attribute.object_relation], attribute.value)
                except KeyError:
                    pass

    def parse_references(self, object_type, next_object_type, uuid, relationship_type, xml_part):
        reference = referencesMapping[next_object_type]
        try:
            next_aml_type = reference[object_type].get('aml_type').format(relationship_type.split('_')[0])
            try:
                bracket = reference[object_type].get('bracket').format(relationship_type)
                self.xml[xml_part] += "<{}>".format(bracket)
                self.itterate(next_object_type, next_aml_type, uuid, xml_part)<commit_msg>chg: Modified the mapping dictionary to support misp-objects updates<commit_after>import json, datetime, base64
from pymisp import MISPEvent
from collections import defaultdict, Counter

misperrors = {'error': 'Error'}
moduleinfo = {'version': '1', 'author': '<NAME>',
              'description': 'Export to GoAML',
              'module-type': ['export'],
              'require_standard_format': True}
moduleconfig = ['rentity_id']
mispattributes = {'input': ['MISPEvent'], 'output': ['xml file']}
outputFileExtension = "xml"
responseType = "application/xml"

objects_to_parse = ['transaction', 'bank-account', 'person', 'entity', 'geolocation']

goAMLmapping = {'bank-account': {'bank-account': 't_account', 'institution-name': 'institution_name',
                                 'institution-code': 'institution_code', 'iban': 'iban', 'swift': 'swift',
                                 'branch': 'branch', 'non-banking-institution': 'non_bank_institution',
                                 'account': 'account', 'currency-code': 'currency_code',
                                 'account-name': 'account_name', 'client-number': 'client_number',
                                 'personal-account-type': 'personal_account_type', 'opened': 'opened',
                                 'closed': 'closed', 'balance': 'balance', 'status-code': 'status_code',
                                 'beneficiary': 'beneficiary', 'beneficiary-comment': 'beneficiary_comment',
                                 'comments': 'comments'},
                'person': {'person': 't_person', 'text': 'comments', 'first-name': 'first_name',
                           'middle-name': 'middle_name', 'last-name': 'last_name', 'title': 'title',
                           'mothers-name': 'mothers_name', 'alias': 'alias', 'date-of-birth': 'birthdate',
                           'place-of-birth': 'birth_place', 'gender': 'gender','nationality': 'nationality1',
                           'passport-number': 'passport_number', 'passport-country': 'passport_country',
                           'social-security-number': 'ssn', 'identity-card-number': 'id_number'},
                'geolocation': {'geolocation': 'location', 'city': 'city', 'region': 'state',
                                'country': 'country-code', 'address': 'address', 'zipcode': 'zip'},
                'transaction': {'transaction': 'transaction', 'transaction-number': 'transactionnumber',
                                'date': 'date_transaction', 'location': 'transaction_location',
                                'transmode-code': 'transmode_code', 'amount': 'amount_local',
                                'transmode-comment': 'transmode_comment', 'date-posting': 'date_posting',
                                'teller': 'teller', 'authorized': 'authorized',
                                'text': 'transaction_description'},
                'legal-enitty': {'legal-entity': 'entity', 'name': 'name', 'business': 'business',
                                 'commercial-name': 'commercial_name', 'phone-number': 'phone',
                                 'legal-form': 'incorporation_legal_form',
                                 'registration-number': 'incorporation_number'}}

referencesMapping = {'bank-account': {'aml_type': '{}_account', 'bracket': 't_{}'},
                     'person': {'transaction': {'aml_type': '{}_person', 'bracket': 't_{}'}, 'bank-account': {'aml_type': 't_person', 'bracket': 'signatory'}},
                     'legal-entity': {'transaction': {'aml_type': '{}_entity', 'bracket': 't_{}'}, 'bank-account': {'aml_type': 'entity'}},
                     'geolocation': {'aml_type': 'address', 'bracket': 'addresses'}}

class GoAmlGeneration(object):
    def __init__(self, config):
        self.config = config
        self.parsed_uuids = defaultdict(list)

    def from_event(self, event):
        self.misp_event = MISPEvent()
        self.misp_event.load(event)

    def parse_objects(self):
        uuids = defaultdict(list)
        report_code = []
        currency_code = []
        for obj in self.misp_event.objects:
            obj_type = obj.name
            uuids[obj_type].append(obj.uuid)
            if obj_type == 'bank-account':
                try:
                    report_code.append(obj.get_attributes_by_relation('report-code')[0].value.split(' ')[0])
                    currency_code.append(obj.get_attributes_by_relation('currency-code')[0].value)
                except:
                    print('report_code or currency_code error')
        self.uuids, self.report_codes, self.currency_codes = uuids, report_code, currency_code

    def build_xml(self):
        self.xml = {'header': "<report><rentity_id>{}</rentity_id><submission_code>E</submission_code>".format(self.config),
                    'data': ""}
        if "STR" in self.report_codes:
            report_code = "STR"
        else:
            report_code = Counter(self.report_codes).most_common(1)[0][0]
        self.xml['header'] += "<report_code>{}</report_code>".format(report_code)
        submission_date = str(self.misp_event.timestamp).replace(' ', 'T')
        self.xml['header'] += "<submission_date>{}</submission_date>".format(submission_date)
        self.xml['header'] += "<currency_code_local>{}</currency_code_local>".format(Counter(self.currency_codes).most_common(1)[0][0])
        for trans_uuid in self.uuids.get('transaction'):
            self.itterate('transaction', 'transaction', trans_uuid, 'data')
        person_to_parse = [person_uuid for person_uuid in self.uuids.get('person') if person_uuid not in self.parsed_uuids.get('person')]
        if len(person_to_parse) == 1:
            self.itterate('person', 'reporting_person', person_to_parse[0], 'header')
        location_to_parse = [location_uuid for location_uuid in self.uuids.get('geolocation') if location_uuid not in self.parsed_uuids.get('geolocation')]
        if len(location_to_parse) == 1:
            self.itterate('geolocation', 'location', location_to_parse[0], 'header')
        self.xml['data'] += "</report>"

    def itterate(self, object_type, aml_type, uuid, xml_part):
        obj = self.misp_event.get_object_by_uuid(uuid)
        if object_type == 'transaction':
            self.xml[xml_part] += "<{}>".format(aml_type)
            self.fill_xml_transaction(object_type, obj.attributes, xml_part)
            self.parsed_uuids[object_type].append(uuid)
            if obj.ObjectReference:
                self.parseObjectReferences(object_type, xml_part, obj.ObjectReference)
            self.xml[xml_part] += "</{}>".format(aml_type)
        else:
            if 'to_' in aml_type or 'from_' in aml_type:
                relation_type = aml_type.split('_')[0]
                self.xml[xml_part] += "<{0}_funds_code>{1}</{0}_funds_code>".format(relation_type, self.from_and_to_fields[relation_type]['funds'].split(' ')[0])
                self.itterate_normal_case(object_type, obj, aml_type, uuid, xml_part)
                self.xml[xml_part] += "<{0}_country>{1}</{0}_country>".format(relation_type, self.from_and_to_fields[relation_type]['country'])
            else:
                self.itterate_normal_case(object_type, obj, aml_type, uuid, xml_part)

    def itterate_normal_case(self, object_type, obj, aml_type, uuid, xml_part):
        self.xml[xml_part] += "<{}>".format(aml_type)
        self.fill_xml(object_type, obj, xml_part)
        self.parsed_uuids[object_type].append(uuid)
        if obj.ObjectReference:
            self.parseObjectReferences(object_type, xml_part, obj.ObjectReference)
        self.xml[xml_part] += "</{}>".format(aml_type)

    def parseObjectReferences(self, object_type, xml_part, references):
        for ref in references:
            next_uuid = ref.referenced_uuid
            next_object_type = ref.Object.get('name')
            relationship_type = ref.relationship_type
            self.parse_references(object_type, next_object_type, next_uuid, relationship_type, xml_part)

    def fill_xml_transaction(self, object_type, attributes, xml_part):
        from_and_to_fields = {'from': {}, 'to': {}}
        for attribute in attributes:
            object_relation = attribute.object_relation
            attribute_value = attribute.value
            if object_relation == 'date-posting':
                self.xml[xml_part] += "<late_deposit>True</late_deposit>"
            elif object_relation in ('from-funds-code', 'to-funds-code'):
                relation_type, field, _ = object_relation.split('-')
                from_and_to_fields[relation_type][field] = attribute_value
                continue
            elif object_relation in ('from-country', 'to-country'):
                relation_type, field = object_relation.split('-')
                from_and_to_fields[relation_type][field] = attribute_value
                continue
            try:
                self.xml[xml_part] += "<{0}>{1}</{0}>".format(goAMLmapping[object_type][object_relation], attribute_value)
            except KeyError:
                pass
        self.from_and_to_fields = from_and_to_fields

    def fill_xml(self, object_type, obj, xml_part):
        if obj.name == 'bank-account':
            for attribute in obj.attributes:
                if attribute.object_relation in ('personal-account-type', 'status-code'):
                    attribute_value = attribute.value.split(' - ')[0]
                else:
                    attribute_value = attribute.value
                try:
                    self.xml[xml_part] += "<{0}>{1}</{0}>".format(goAMLmapping[object_type][attribute.object_relation], attribute_value)
                except KeyError:
                    pass
        else:
            for attribute in obj.attributes:
                try:
                    self.xml[xml_part] += "<{0}>{1}</{0}>".format(goAMLmapping[object_type][attribute.object_relation], attribute.value)
                except KeyError:
                    pass

    def parse_references(self, object_type, next_object_type, uuid, relationship_type, xml_part):
        reference = referencesMapping[next_object_type]
        try:
            next_aml_type = reference[object_type].get('aml_type').format(relationship_type.split('_')[0])
            try:
                bracket = reference[object_type].get('bracket').format(relationship_type)
                self.xml[xml_part] += "<{}>".format(bracket)
                self.itterate(next_object_type, next_aml_type, uuid, xml_part)
{'fraction_non_alphanumeric': 0.14569144210838023, 'fraction_numerical': 0.0035534498075214687, 'mean_word_length': 2.6622085667811315, 'pattern_counts': {'":': 0, '<': 63, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 98}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6880068', 'n_tokens_mistral': 5981, 'n_tokens_neox': 5798, 'n_words': 1045}