SearchIndicatorRelationships

This automation outputs the indicator relationships to context according to the provided query, using the entities, entityTypes, and relationships arguments. All arguments will use the AND operator. For example, using the following arguments entities=8.8.8.8 entities_types=Domain will provide only relationships that the 8.8.8.8 indicator has with indicators of type domain.

python · Base

Details

IDSearchIndicatorRelationships
Languagepython
From Version6.2.0
Docker Imagedemisto/python3:3.12.13.10404775
Tagsbasescript

README

This automation outputs the indicator relationships to context according to the provided query, using the entities, entityTypes, and relationships arguments. All arguments will use the AND operator. For example, using the following arguments entities=8.8.8.8 entities_types=Domain will provide only relationships that the 8.8.8.8 indicator has with indicators of type domain.

Script Data


Name Description
Script Type python3
Tags basescript
Cortex XSOAR Version 6.2.0

Inputs


Argument Name Description
entities A comma-separated list of entities for which to search for relationships. For example: 192.168.1.1,192.168.1.2. The search applies to both entity A or entity B values. This argument can be used in conjunction with the entityType and the relationship arguments and all arguments will be treated with the AND operator.
entities_types A comma-separated list of entity types for which to search for relationships. For example: IP,URL. This argument can be used in conjunction with the entities and the relationship arguments and all arguments will be treated with the AND operator.
relationships A comma-separated list of relationship types for which to search for relationships. For example: related-to,contains. This argument can be used in conjunction with the entities and the entitiesTypes arguments and all arguments will be treated with the AND operator.
limit The number of results to return. Default is 20.
verbose Whether all of the relationships attributes will be returned or just the basic attributes. Default is false and the returned values will be name, entity A value, entity A type, entity B value, entity B type, relationships type. If true, all attributes will be returned.
revoked The status of the relationships to return. Default is false.
searchAfter Use the searchAfter token from the preceding response to indicate the starting point for retrieving the subsequent batch of relationships.

Outputs


Path Description Type
Relationships.EntityA The source of the relationship. String
Relationships.EntityB The destination of the relationship. string
Relationships.Relationship The name of the relationship. string
Relationships.Reverse The name of the reverse relationship. string
Relationships.EntityAType The type of the source of the relationship. string
Relationships.EntityBType The type of the destination of the relationship. string
Relationships.ID The ID of the relationship. string
Relationships.Reliability The reliability of the relationship. string
Relationships.Brand The brand of the relationship. string
Relationships.Revoked True if the relationship is revoked. string
Relationships.FirstSeenBySource The first time seen by the source of the relationship. string
Relationships.LastSeenBySource The last time seen by the source of the relationship. string
Relationships.Description The description of the relationship. string
Relationships.Type The type of the relationship. string
RelationshipsPagination The searchAfter token for retrieving the next batch of relationships. string

Script Examples

Example command

!SearchIndicatorRelationships entities=google.com entities_types=IP

Context Example

{
    "Relationships": [
        {
            "EntityA": "4.4.4.4",
            "EntityAType": "IP",
            "EntityB": "google.com",
            "EntityBType": "Domain",
            "ID": "31",
            "Relationship": "related-to",
            "Reverse": "related-to"
        },
        {
            "EntityA": "8.8.8.8",
            "EntityAType": "IP",
            "EntityB": "google.com",
            "EntityBType": "Domain",
            "ID": "30",
            "Relationship": "related-to",
            "Reverse": "related-to"
        }
    ],
    "RelationshipsPagination": [
    [
      "1766338283557",
      "8f8a88336e02a52ef31b3827b3b25d85"
    ]
  ]
}

Human Readable Output

Relationships

Entity A Entity A Type Entity B Entity B Type Relationship
4.4.4.4 IP google.com Domain related-to
8.8.8.8 IP google.com Domain related-to
import json

import demistomock as demisto
import pytest
from CommonServerPython import get_demisto_version
from SearchIndicatorRelationships import handle_stix_types, search_relationships, to_context


def util_load_json(path):
    with open(path, encoding="utf-8") as f:
        return json.loads(f.read())


def test_to_context_verbose_false():
    """
    Given:
    - the data section of the contents of the servers response to the SearchRelationships command.

    When:
    - running to_context function with verbose false.

    Then:
    - Ensure that the context is as expected.
    """
    mock_response = util_load_json("test_data/searchRelationships-response.json")
    response = to_context(mock_response, False)
    expected = util_load_json("test_data/verbose_false_expected.json")
    assert expected == response


def test_to_context_verbose_true():
    """
    Given:
    - the data section of the contents of the servers response to the SearchRelationships command.

    When:
    - running to_context function with verbose true.

    Then:
    - Ensure that the context is as expected.
    """
    mock_response = util_load_json("test_data/searchRelationships-response.json")
    response = to_context(mock_response, True)
    expected = util_load_json("test_data/verbose_true_expected.json")
    assert expected == response


def test_handle_stix_types(mocker):
    mocker.patch.object(demisto, "demistoVersion", return_value={"version": "6.1.0"})

    entity_types = "STIX Malware,STIX Attack Pattern,STIX Threat Actor,STIX Tool"
    entity_types = handle_stix_types(entity_types)
    assert entity_types == "STIX Malware,STIX Attack Pattern,STIX Threat Actor,STIX Tool"


@pytest.mark.parametrize(
    "demisto_version, expected_result", [("6.5.0", ["mock_result_1"]), ("6.6.0", ["mock_result_2"]), ("7.0.0", ["mock_result_3"])]
)
def test_search_relationship_command_args_by_demisto_version(mocker, demisto_version, expected_result):
    """
    Given:
        XSOAR versions:
        1. 6.5.0
        2. 6.6.0
        3. 7.0.0
    When:
        Calling search_relationships method.
    Then:
        Make sure that for each version, the correct implementation of searchRelationships server script is called:
        - For version 6.5.0:
          - The command is called using `executeCommand`.
          - The payload is sent in the expected `searchRelationships` format.
          - An XSOAR entry is returned.
        - For versions 6.6.0 and 7.0.0:
          - the command is called using `demisto.searchRelationships`,
          - The payload is sent in a RelationshipFilter structure.
          - The data is returned in a RelationshipSearchResponse format.
    """
    get_demisto_version._version = None  # clear cache between runs of the test

    def searchRelationships(args):
        assert demisto_version >= "6.6.0"
        assert isinstance(args.get("entities"), list)
        return {"data": expected_result}

    def executeCommand(command_name, args):
        assert command_name == "searchRelationships"
        assert demisto_version < "6.6.0"
        assert isinstance(args.get("entities"), str)
        return [{"Contents": {"data": expected_result}, "Type": "not_error"}]

    mocker.patch.object(demisto, "demistoVersion", return_value={"version": demisto_version})
    mocker.patch.object(demisto, "executeCommand", side_effect=executeCommand)
    mocker.patch.object(demisto, "searchRelationships", side_effect=searchRelationships)
    result = search_relationships(entities="1.1.1.1,8.8.8.8")
    result = result.get("data", [])  # handle both old and new response formats
    assert result == expected_result


@pytest.mark.parametrize("search_after, expected_type", [(["timestamp1", "id1"], dict), (None, dict), ([], dict)])
def test_search_relationships_with_search_after(mocker, search_after, expected_type):
    """
    Given:
        Different searchAfter parameter values:
        1. searchAfter as list ["timestamp1", "id1"]
        2. searchAfter as None
        3. searchAfter as empty list []
    When:
        Calling search_relationships method with searchAfter parameter.
    Then:
        Make sure that search_relationships returns a dict for all searchAfter parameter variations.
    """
    mocker.patch.object(demisto, "demistoVersion", return_value={"version": "6.6.0"})
    mocker.patch.object(demisto, "searchRelationships", return_value={"data": []})

    result = search_relationships(searchAfter=search_after)
    assert isinstance(result, expected_type), "search_relationships should return a dict"


@pytest.mark.parametrize(
    "search_after, expected_pagination", [(["test_timestamp", "test_id"], [["test_timestamp", "test_id"]]), (None, [])]
)
def test_to_context_with_search_after(search_after, expected_pagination):
    """
    Given:
        Mock relationships data with different SearchAfter values:
        1. SearchAfter as list ["test_timestamp", "test_id"]
        2. SearchAfter as None
    When:
        Calling to_context method with the mock relationships data.
    Then:
        Make sure that:
        - Context contains RelationshipsPagination key
        - RelationshipsPagination contains the expected value based on SearchAfter
        - When SearchAfter is None, RelationshipsPagination should be empty
    """
    mock_relationships_data = {"SearchAfter": search_after, "data": []}
    context = to_context(mock_relationships_data, False)
    assert "RelationshipsPagination" in context, "Context should contain RelationshipsPagination"
    assert context["RelationshipsPagination"] == expected_pagination