FindSimilarIncidents Deprecated

Deprecated. Use DBotFindSimilarIncidents instead. Finds similar incidents by common incident keys, labels, custom fields or context keys. It's highly recommended to use incident keys if possible (e.g., "type" for the same incident type). For best performance, it's recommended to avoid using context keys if possible (for example, if the value also appears in a label key, use label). This automation runs using the default Limited User role, unless you explicitly change the permissions. For more information, see the section about permissions here: - For Cortex XSOAR 6 see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/6.x/Cortex-XSOAR-Playbook-Design-Guide/Automations - For Cortex XSOAR 8 Cloud see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8/Cortex-XSOAR-Cloud-Documentation/Create-a-script - For Cortex XSOAR 8.7 On-prem see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8.7/Cortex-XSOAR-On-prem-Documentation/Create-a-script

python · Common Scripts

Details

IDFindSimilarIncidents
Languagepython
From Version5.0.0
Docker Imagedemisto/python3:3.10.13.83255
Tagsdedup duplicate incidents

README

Deprecated. Use DBotFindSimilarIncidents instead.

Finds similar incidents by common incident keys, labels, custom fields or context keys.
It’s highly recommended to use incident keys if possible (e.g., “type” for the same incident type).
For best performance, it’s recommended to avoid using context keys if possible (for example, if the value also appears in a label key, use label).

This automation runs using the default Limited User role, unless you explicitly change the permissions.
For more information, see the section about permissions here: For Cortex XSOAR 6, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/6.x/Cortex-XSOAR-Playbook-Design-Guide/Automations for Cortex XSOAR 8 Cloud, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8/Cortex-XSOAR-Cloud-Documentation/Create-a-script for Cortex XSOAR 8 On-prem, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8.7/Cortex-XSOAR-On-prem-Documentation/Create-a-script.

Script Data


Name Description
Script Type python2
Tags dedup, duplicate, incidents
Cortex XSOAR Version 5.0.0

Used In


This script is used in the following playbooks and scripts.

  • Cortex XDR incident handling v2
  • DeDup incidents
  • Dedup - Generic
  • Dedup - Generic v2
  • Dedup - Generic v3
  • Handle Darktrace Model Breach
  • JOB - Integrations and Incidents Health Check
  • Palo Alto Networks - Endpoint Malware Investigation v2
  • Palo Alto Networks - Endpoint Malware Investigation v3
  • Shift handover

Inputs


Argument Name Description
similarIncidentKeys A comma-separated list of identical incident keys.
similarLabelsKeys A comma-separated list of similar label keys. Comma separated value. Also supports allowing X different words between labels, within the following way: label_name:X, where X is the number of words. X can also be ‘*’ for contains. For example: the value “Email/subject:*” will consider email subject similar, if one is substring of the other.
similarContextKeys A comma-separated list of similar context keys. Also supports allowing X different words between values (see the labels description).
similarCustomFields A comma-separated list of Similar custom fields keys. Also supports allowing X different words between values (see the labels description).
ignoreClosedIncidents Whether to ignore closed incidents as duplicate candidates. Can be “yes” (ignore) or “no” (don’t ignore). The default value is “yes”.
maxNumberOfIncidents Maximum number of incidents to query.
hoursBack Query incidents in the last X hours. Supports float value.
timeField Filter incidents by this time field.
maxResults Maximum number of results to display.
similarIncidentFields A comma-separated list of similar incident fields keys. Also supports allowing X different words between values (see the labels description).
filterQuery Use this query condition when fetching duplicate incidents.
incidentFieldsAppliedCondition The condition to apply between incident fields. Can be “OR” or “AND”. This will apply only for fields with “exact match”.
skipMissingValues Whether to skip the incident if it does not have specific key. Can be “yes” (skip) or “no” (don’t skip). The default value is “yes”. WARNING: if no fields exist in the incident, random incidents might be returned as results due to the empty condition.

Outputs


Path Description Type
similarIncident.rawId Similar incident ID. string
isSimilarIncidentFound Whether a similar incident was found (“true” or “false”). boolean
similarIncident Similar incident. unknown
similarIncident.name Similar incident name. string
import pytest

from CommonServerPython import *
import demistomock as demisto

default_args = {
    'hoursBack': 5,
    'timeField': 'created',
    'ignoreClosedIncidents': 'yes',
    'maxNumberOfIncidents': 3000,
    'maxResults': 10,
    'skipMissingValues': 'yes',
    'incidentFieldsAppliedCondition': 'AND'
}

incident1 = {
    'id': 1,
    'name': 'This is incident1',
    'type': 'Phishing',
    'severity': 0,
    'status': 1,
    'created': '2019-01-02',
    'closed': '0001-01-01T00:00:00Z',
    'labels': [{'type': 'subject', 'value': 'This subject1'}, {'type': 'unique', 'value': 'This subject1'}],
    'attachment': [{'name': 'Test word1 word2'}]
}

context1 = {
    'simpleValue': 'simple',
    'listValue': [{'name': 'test1'}, {'name': 'test2'}],
    'dictListValue': {'test': ['1', '2']},
    'simpleListValue': ['1', '2', '3'],
    'dictListValue2': {'first': ['1', '2', '3'], 'second': ['1']},
    'multipleDictsValue': {
        'test':
            {
                'first':
                    {
                        'word1': '1', 'word2': '2'
                    },
                'second': ['word3', 'word4']
            }
    }
}
context2 = context1

context3 = {
    'listValue': [{'name': 'test1'}, {'name': 'test3'}],
    'dictListValue': {'test': ['2', '1']},
    'simpleListValue': ['2', '1', '3'],
    'dictListValue2': {'first': ['1', '2', '3']},
    'multipleDictsValue': {
        'test':
            {
                'first':
                    {
                        'word1': '4', 'word2': '2'
                    },
                'second': ['word3', 'word4']
            }
    }
}

incident2 = {
    'id': 2,
    'name': 'This is incident2',
    'type': 'Phishing',
    'severity': 0,
    'status': 1,
    'created': '2019-01-02',
    'closed': '0001-01-01T00:00:00Z',
    'labels': [{'type': 'subject', 'value': 'This subject2'}],
    'attachment': [{'name': 'Test word1'}]
}

incident1_dup = {
    'id': 3,
    'name': 'This is incident1',
    'type': 'Phishing',
    'severity': 0,
    'status': 1,
    'created': '2019-01-01',
    'closed': '0001-01-01T00:00:00Z',
    'labels': [{'type': 'subject', 'value': 'This subject1'}],
    'attachment': [{'name': 'Test word1 word2'}]
}

incident1_dup2 = {
    'id': 4,
    'name': 'This is incident1',
    'type': 'Phishing',
    'severity': 0,
    'status': 1,
    'created': '2019-01-01',
    'closed': '0001-01-01T00:00:00Z',
    'labels': [{'type': 'subject', 'value': 'This subject1'}],
    'attachment': [{'name': 'Test word1 word2'}]
}

incident_by_keys = [
    {
        'CustomFields': {},
        'account': '',
        'activated': '0001-01-01T00:00:00Z',
        'attachment': None,
        'autime': 1550670443962164000,
        'canvases': None,
        'category': '',
        'closeNotes': '',
        'closeReason': '',
        'closed': '0001-01-01T00:00:00Z',
        'closingUserId': '',
        'created': '2019-02-20T15:47:23.962164+02:00',
        'details': '',
        'droppedCount': 0,
        'dueDate': '2019-03-02T15:47:23.962164+02:00',
        'hasRole': False,
        'id': '1',
        'investigationId': '1',
        'isPlayground': False,
        'labels': [{'type': 'Instance', 'value': 'test'},
                   {'type': 'Brand', 'value': 'Manual'}],
        'lastOpen': '0001-01-01T00:00:00Z',
        'linkedCount': 0,
        'linkedIncidents': None,
        'modified': '2019-02-20T15:47:27.158969+02:00',
        'name': '1',
        'notifyTime': '2019-02-20T15:47:27.156966+02:00',
        'occurred': '2019-02-20T15:47:23.962163+02:00',
        'openDuration': 0,
        'owner': 'analyst',
        'parent': '',
        'phase': '',
        'playbookId': 'playbook0',
        'previousRoles': None,
        'rawCategory': '',
        'rawCloseReason': '',
        'rawJSON': '',
        'rawName': '1',
        'rawPhase': '',
        'rawType': 'Unclassified',
        'reason': '',
        'reminder': '0001-01-01T00:00:00Z',
        'roles': None,
        'runStatus': 'waiting',
        'severity': 0,
        'sla': 0,
        'sourceBrand': 'Manual',
        'sourceInstance': 'amichay',
        'status': 1,
        'type': 'Unclassified',
        'version': 6
    }
]


@pytest.fixture(autouse=True)
def mock_demistoVersion(mocker):
    mocker.patch.object(demisto, 'demistoVersion', return_value={'platform': 'xsoar'})


def execute_command(command, args=None):
    if command == 'getIncidents':
        entry = {}
        entry['Type'] = entryTypes['note']
        entry['Contents'] = {}
        entry['Contents']['data'] = [incident1_dup, incident2, incident1_dup2]
        return [entry]
    elif command == 'getContext':
        if args['id'] == 1:
            return [{'Contents': {'context': context1}}]
        elif args['id'] == 2:
            return [{'Contents': {'context': context2}}]
        elif args['id'] == 3:
            return [{'Contents': {'context': context3}}]
        else:
            return []
    else:
        return []


def test_get_incidents_by_keys():
    from FindSimilarIncidentsV2 import get_incidents_by_keys
    res = get_incidents_by_keys({u'name': u'\U0001f489'}, 'created', '2020-10-07T19:49:37.392378+03:00', '7137', 72,
                                False, '1000', 'status:Closed', 'AND')
    assert res == incident_by_keys


def test_build_similar_keys_list():
    """
    Given
        - key and value of the incident fields.
    When
        - build_incident_fields_query is called
    Then
        - Ensure that the query matches the type of the incident field: int -> ":=", str -> "=".
    """
    from FindSimilarIncidentsV2 import build_incident_fields_query

    int_res = build_incident_fields_query({u'sla': 0})
    assert int_res == ['sla:=0']

    str_res = build_incident_fields_query({u'employeeid': u'1111'})
    assert str_res == [u'employeeid="1111"']

    list_res = build_incident_fields_query({u'test': [u'name1', 0]})
    assert list_res == [u'test="name1"', 'test:=0']

    list_res = build_incident_fields_query({u'test': []})
    assert list_res == ['test=[]']

    escape_res = build_incident_fields_query({u'test': u'"C:\\test\\escape\\sequence" test'})
    assert escape_res == [u'test="\\"C:\\test\\escape\\sequence\\" test"']


def test_similar_incidents_fields(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarLabelsKeys': 'subject'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])

    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 2
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 3


def test_similar_incidents_fields_with_diff(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarLabelsKeys': 'subject:1'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])

    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 3
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 3
    assert result['EntryContext']['similarIncidentList'][1]['rawId'] == 4
    assert result['EntryContext']['similarIncidentList'][2]['rawId'] == 2


def test_similar_incidents_missing_fields(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name,emailbody', 'similarLabelsKeys': 'emailbody'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)

    with pytest.raises(SystemExit) as err:
        args['skipMissingValues'] = 'no'
        main()
    assert err.type == SystemExit


def test_similar_incidents_list_field(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'attachment.name:1'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 3
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 3
    assert result['EntryContext']['similarIncidentList'][1]['rawId'] == 4
    assert result['EntryContext']['similarIncidentList'][2]['rawId'] == 2


def test_similar_incidents_no_results(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarLabelsKeys': 'unique'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)

    with pytest.raises(SystemExit) as err:
        main()
    assert err.type == SystemExit


def test_similar_incidents_order(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarLabelsKeys': 'subject'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])

    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 2
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 3
    assert result['EntryContext']['similarIncidentList'][1]['rawId'] == 4


def test_similar_context_simple_value(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'simpleValue'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)
    mocker.patch.object(demisto, 'context', return_value=context1)
    mocker.patch.object(demisto, 'dt', side_effect=dt_res)
    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2


def test_similar_context_list_value(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'listValue.name'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)
    mocker.patch.object(demisto, 'context', return_value=context1)
    mocker.patch.object(demisto, 'dt', side_effect=dt_res)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2
    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'listValue'})

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2


def test_similar_context_dict_list_value(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'dictListValue'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)
    mocker.patch.object(demisto, 'context', return_value=context1)
    mocker.patch.object(demisto, 'dt', side_effect=dt_res)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2

    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'dictListValue.test'})
    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 2
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 3
    assert result['EntryContext']['similarIncidentList'][1]['rawId'] == 2


def test_similar_context_dict_list_value2(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'dictListValue2'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)
    mocker.patch.object(demisto, 'context', return_value=context1)
    mocker.patch.object(demisto, 'dt', side_effect=dt_res)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2

    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'dictListValue2.first'})
    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 2
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 3
    assert result['EntryContext']['similarIncidentList'][1]['rawId'] == 2

    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'dictListValue2.second'})
    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2


def test_similar_context_multiple_dicts_value(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'multipleDictsValue'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)
    mocker.patch.object(demisto, 'context', return_value=context1)
    mocker.patch.object(demisto, 'dt', side_effect=dt_res)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2

    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'multipleDictsValue.test.second'})
    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 2
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 3
    assert result['EntryContext']['similarIncidentList'][1]['rawId'] == 2

    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'multipleDictsValue.test'})
    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2

    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'multipleDictsValue.test.first'})
    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 1
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 2


def test_similar_context_simple_list_value(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'similarIncidentFields': 'name', 'similarContextKeys': 'simpleListValue'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)
    mocker.patch.object(demisto, 'context', return_value=context1)
    mocker.patch.object(demisto, 'dt', side_effect=dt_res)

    result = main()
    assert len(result['EntryContext']['similarIncidentList']) == 2
    assert result['EntryContext']['similarIncidentList'][0]['rawId'] == 3
    assert result['EntryContext']['similarIncidentList'][1]['rawId'] == 2


def test_similar_context_missing_key(mocker):
    from FindSimilarIncidentsV2 import main
    args = dict(default_args)
    args.update({'skipMissingValues': 'no', 'similarContextKeys': 'missingKey'})

    mocker.patch.object(demisto, 'args', return_value=args)
    mocker.patch.object(demisto, 'incidents', return_value=[incident1])
    mocker.patch.object(demisto, 'executeCommand', side_effect=execute_command)
    mocker.patch.object(demisto, 'context', return_value=context1)
    mocker.patch.object(demisto, 'dt', side_effect=dt_res)

    with pytest.raises(ValueError, match="Error: Missing context key for incident: missingKey"):
        main()


def test_build_incident_query():
    from FindSimilarIncidentsV2 import build_incident_query
    res = build_incident_query('type="test"', True, '826', 'name:incident1')

    assert res == '(-id:826) and (type="test" and -status:Closed) and (name:incident1)'


def test_build_incident_query_without_similar_keys_query():
    from FindSimilarIncidentsV2 import build_incident_query
    res = build_incident_query('', True, '826', 'name:incident1')

    assert res == '(-id:826) and (-status:Closed) and (name:incident1)'


def test_build_incident_query_without_extra_query():
    from FindSimilarIncidentsV2 import build_incident_query
    res = build_incident_query('', True, '826', '')

    assert res == '(-id:826) and (-status:Closed)'


def test_build_incident_query_without_similar_keys_query_and_ignore_closed():
    """
    Given:
        - Fields for creating query without similar_keys_query and ignore_closed flag.

    When:
        - Building incident query using 'build_incident_query' function.

    Then:
        - Ensure the query was created correctly and that the query string got the values needed
          from the formating.
    """
    from FindSimilarIncidentsV2 import build_incident_query
    res = build_incident_query('', False, '826', '')

    assert res == '(-id:826)'


def dt_res(context, keys_to_search):

    keys_list = keys_to_search.split('.')
    context_key_value = None
    for key in keys_list:
        if context_key_value:
            if isinstance(context_key_value, list):
                list_value = []
                for value in context_key_value:
                    if isinstance(value, dict):
                        if value.get(key):
                            list_value.append(value[key])
                    else:
                        list_value.append(value)
                context_key_value = list_value

            elif isinstance(context_key_value, dict):
                dict_list_value = []
                for dict_key in context_key_value.keys():
                    if dict_key == key:
                        if isinstance(context_key_value[dict_key], list):
                            dict_list_value.append(sorted(context_key_value[dict_key]))
                        else:
                            dict_list_value.append(context_key_value[dict_key])

                context_key_value = dict_list_value

            else:
                context = context_key_value
        else:
            context_key_value = context.get(key)
    return context_key_value