ProofpointIsolationEventCollector

Proofpoint Isolation is an integration that supports fetching Browser and Email Isolation logs events.

Analytics & SIEM · Proofpoint Isolation

Details

IDProofpointIsolationEventCollector
ProviderThoma Bravo
CategoryAnalytics & SIEM
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10404775
Supported ModulesXSIAM

README

Proofpoint Isolation is an integration that supports fetching Browser and Email Isolation logs events within Cortex XSIAM.
This integration was integrated and tested with version V2 of ProofpointIsolation.

Configure Proofpoint Isolation in Cortex

Parameter Description Required
Server URL The endpoint URL. True
API Key The API Key to use for connection True
Maximum number of events per fetch Defines The maximum number of browser and email isolation events per fetch cycle. Default value: 50000. True
Trust any certificate (not secure)   False
Use system proxy settings   False

How to Access Reporting API

  1. In Proofpoint Isolation, navigate to Product Settings > Reporting API. Proofpoint Isolation’s Reporting API tools and documentation display in the Console’s main viewing panel.
  2. Copy the reporting API key.

image

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

proofpoint-isolation-get-events


Retrieves a list of events from the Proofpoint Isolation instance.

Base Command

proofpoint-isolation-get-events

Input

Argument Name Description Required
should_push_events Set this argument to True in order to create events, otherwise it will only display them. Possible values are: true, false. Default is false. Required
limit Maximum number of events to return. Value range: 1-50000. Required
start_date From which date to fetch the events. Required
end_date Until which date to fetch the events. Required

Context Output

Path Type Description
ProofpointIsolationEventCollector List The list of events.

Command example

!proofpoint-isolation-get-events should_push_events=false limit=10 end_date=2025-01-12 start_date=2025-01-11T11:27:08

Configuration parameters

  • base_url — Server URL (required)
  • credentials — (required)
  • max_events_per_fetch — Maximum number of events per fetch (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • proofpoint-isolation-get-events

    Retrieves a list of events from the Proofpoint Isolation instance.

import json

DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
MOCK_BASEURL = "https://example.com"
MOCK_API_KEY = "API_KEY"


def create_client():
    from ProofpointIsolationEventCollector import Client

    return Client(base_url=MOCK_BASEURL, verify=False, api_key=MOCK_API_KEY)


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


def test_get_and_reorganize_events(mocker):
    """
    Given: A mock Proofpoint client with a set of raw events and a set of event IDs.
    When: Reorganizing events based on date and excluding the last event ID.
    Then:
        - Ensure the events are sorted in chronological order.
        - The number of reorganized events matches the expected count.
        - The last event is excluded because it is in ids set.

    """
    from ProofpointIsolationEventCollector import get_and_reorganize_events, hash_user_name_and_url

    mocked_events = util_load_json("test_data/get_events_raw_response.json")
    mocker.patch("ProofpointIsolationEventCollector.Client.get_events", return_value=mocked_events)

    events = mocked_events["data"]
    client = create_client()
    hashed_id_last_event = hash_user_name_and_url(events[-1])
    ids = {hashed_id_last_event}

    organized_events = get_and_reorganize_events(client, "2025-01-01T19:44:35Z", "2025-01-12", ids)

    assert all(organized_events[i]["date"] <= organized_events[i + 1]["date"] for i in range(len(organized_events) - 1))

    assert len(organized_events) == len(events) - 1

    organized_ids = {hash_user_name_and_url(event) for event in organized_events}
    assert hashed_id_last_event not in organized_ids

    assert organized_events[0]["date"] == "2025-01-01T19:44:35.000+0000"
    assert organized_events[-1]["date"] == "2025-01-09T19:44:35.000+0000"


def test_remove_duplicate_events():
    """
    Given: A list of events sorted by date, with a specified start date and event IDs.
    When: Removing duplicate events for a given start date.
    Then: Ensure the number of events is reduced by the expected amount,
     and verify that no event with the same ID and start date remains after duplicates are removed.
    """
    from ProofpointIsolationEventCollector import (
        get_and_parse_date,
        hash_user_name_and_url,
        remove_duplicate_events,
        sort_events_by_date,
    )

    mocked_events = util_load_json("test_data/get_events_raw_response.json")

    events = sort_events_by_date(mocked_events["data"])
    start_date = "2025-01-01T19:44:35Z"

    ids = {hash_user_name_and_url(event) for event in events if get_and_parse_date(event) == start_date}

    remove_duplicate_events(start_date, ids, events)

    expected_event_count = len(mocked_events["data"]) - 5
    assert len(events) == expected_event_count

    for event in events:
        assert hash_user_name_and_url(event) not in ids or get_and_parse_date(event) != start_date


def test_get_and_parse_date():
    """
    Given: A valid event with a date in a specific format.
    When: Parsing the event's date using the `get_and_parse_date` function.
    Then: Ensure the date is correctly parsed and returned in the expected format.
    """
    from ProofpointIsolationEventCollector import get_and_parse_date

    valid_event = {
        "date": "2025-01-01T19:44:35.000+0000",
        "userName": "user@example.com",
    }

    parsed_date = get_and_parse_date(valid_event)
    assert parsed_date == "2025-01-01T19:44:35Z"


def test_hash_user_name_and_url():
    """
    Given: A dictionary containing event data with 'url' and 'userName' fields.
    When: The function hash_user_name_and_url is called.
    Then: Ensure the output matches the expected '<url>&<userName>' format.
    """
    from ProofpointIsolationEventCollector import hash_user_name_and_url

    event = {"url": "example.com", "userName": "testUser", "extraField": "extraValue"}
    result = hash_user_name_and_url(event)
    assert result == "example.com&testUser"


def test_sort_events_by_date():
    """
    Given: A list of events, each containing a 'date' field in ISO 8601 format.
    When: The function is called to sort the events by their 'date' field.
    Then: Ensure the events are sorted in ascending order by date.
    """
    from ProofpointIsolationEventCollector import sort_events_by_date

    events = [
        {"date": "2025-01-14T12:00:00.000+0000", "event_id": 1},
        {"date": "2025-01-13T12:00:00.000+0000", "event_id": 2},
        {"date": "2025-01-15T12:00:00.000+0000", "event_id": 3},
    ]
    sorted_events = sort_events_by_date(events)
    assert sorted_events[0]["event_id"] == 2
    assert sorted_events[1]["event_id"] == 1
    assert sorted_events[2]["event_id"] == 3

    events = []
    sorted_events = sort_events_by_date(events)
    assert sorted_events == []


def test_no_more_events_after_second_call(mocker):
    """
    Given: A mock Proofpoint client with event data and a last run timestamp.
    When: Fetching events with a specified limit across multiple calls.
    Then: Ensure the correct number of events are fetched on the first call,
     and no events are returned on the second call when there are no more events to fetch.
    """
    from ProofpointIsolationEventCollector import fetch_events

    client = create_client()
    mocked_events = util_load_json("test_data/get_events_raw_response.json")
    last_event = {"data": [mocked_events.get("data")[-2]]}
    mocker.patch("ProofpointIsolationEventCollector.Client.get_events", side_effect=[mocked_events, last_event, {"data": []}])

    last_run_mock = {"start_date": "2025-01-09T11:27:08"}
    mocker.patch("ProofpointIsolationEventCollector.demisto.getLastRun", return_value=last_run_mock)

    limit = 10

    events, new_last_run = fetch_events(client, limit)

    assert len(events) == limit
    assert new_last_run["ids"]

    mocker.patch("ProofpointIsolationEventCollector.demisto.getLastRun", return_value=new_last_run)
    events, new_last_run = fetch_events(client, limit)
    assert len(events) == 1


def test_fetch_events(mocker):
    """
    Given: A mock Proofpoint client with event data and no previous run data.
    When: Fetching events multiple times with a specified limit.
    Then: Ensure correct number of events are fetched, and `lastRun` updates correctly with unique event IDs.
          Also ensure the first fetch initializes `lastRun` properly.
    """
    from ProofpointIsolationEventCollector import fetch_events

    client = create_client()
    mocked_events = util_load_json("test_data/get_events_raw_response.json")
    return_values_events = [mocked_events, {"data": mocked_events.get("data")[4:]}]

    mocker.patch("ProofpointIsolationEventCollector.Client.get_events", side_effect=return_values_events)

    mocker.patch("ProofpointIsolationEventCollector.demisto.getLastRun", return_value={})
    limit = 5

    events, new_last_run = fetch_events(client, limit)
    assert len(events) == limit
    assert "ids" in new_last_run
    assert "https://exmaple.k1.com/&user9@example.com" in new_last_run["ids"]
    assert "https://exmaple.k1.com/&user10@example.com" in new_last_run["ids"]
    assert "https://exmaple.k1.com/&user7@example.com" in new_last_run["ids"]
    assert "https://exmaple.k10.com/&user0@example.com" in new_last_run["ids"]
    assert "https://exmaple.k1.com/&user8@example.com" in new_last_run["ids"]
    assert new_last_run.get("start_date") == "2025-01-01T19:44:35Z"

    mocker.patch("ProofpointIsolationEventCollector.demisto.getLastRun", return_value=new_last_run)

    events, new_last_run = fetch_events(client, limit)
    assert len(events) == limit
    assert "ids" in new_last_run
    assert len(new_last_run.get("ids")) == 1
    assert new_last_run.get("start_date") == "2025-01-06T19:44:35Z"


def test_fetch_events_ids_reset_when_no_more_events(mocker):
    """
    Given: A mock Proofpoint client where the first API call returns events and the second returns none,
           and a last_run containing stale IDs from a previous fetch cycle.
    When: Fetching events with a limit higher than the available event count (so the loop exhausts all events).
    Then: Ensure the returned last_run has an empty 'ids' list, confirming stale dedup IDs are cleared
          when the pagination loop ends with no more events.
          Also ensure start_date advances to the end date so the next fetch cycle starts from the correct point.
    """
    from ProofpointIsolationEventCollector import fetch_events

    client = create_client()
    mocked_events = util_load_json("test_data/get_events_raw_response.json")

    end_date = "2025-02-01T00:00:00Z"
    # First call returns events, second call returns empty — simulating end of pagination.
    mocker.patch(
        "ProofpointIsolationEventCollector.Client.get_events",
        side_effect=[mocked_events, {"data": []}],
    )

    stale_ids = ["https://stale.url/&staleUser@example.com"]
    last_run_mock = {"start_date": "2025-01-01T19:44:35Z", "ids": stale_ids}
    mocker.patch("ProofpointIsolationEventCollector.demisto.getLastRun", return_value=last_run_mock)
    mocker.patch(
        "ProofpointIsolationEventCollector.get_current_time",
        return_value=__import__("datetime").datetime.strptime(end_date, DATE_FORMAT),
    )

    # Set limit higher than available events so the loop exhausts and hits the empty-response branch.
    limit = 100

    events, new_last_run = fetch_events(client, limit)

    assert len(events) == len(mocked_events["data"])
    # The critical assertion: when no more events are found, ids must be reset to an empty list.
    assert new_last_run["ids"] == []
    # Verify the cursor advanced to the end date so the next cycle doesn't re-fetch old events.
    assert new_last_run["start_date"] == end_date


def test_fetch_events_stops_at_limit(mocker):
    """
    Given: A mock Proofpoint client that returns more events than the fetch limit allows.
    When: Fetching events with a limit smaller than the total available events.
    Then:
        - The loop exits via the limit branch (not the empty-response branch).
        - Exactly fetch_limit events are returned.
        - new_last_run is set so the next fetch cycle can continue from where it stopped.
    """
    from ProofpointIsolationEventCollector import fetch_events

    client = create_client()
    mocked_events = util_load_json("test_data/get_events_raw_response.json")
    total_available = len(mocked_events["data"])  # 13 events

    # The API keeps returning the same batch — the loop must stop at the limit, not keep going.
    mocker.patch(
        "ProofpointIsolationEventCollector.Client.get_events",
        return_value=mocked_events,
    )

    mocker.patch("ProofpointIsolationEventCollector.demisto.getLastRun", return_value={})
    mocker.patch(
        "ProofpointIsolationEventCollector.get_current_time",
        return_value=__import__("datetime").datetime(2025, 2, 1, 0, 0, 0),
    )

    limit = 3
    assert limit < total_available, "Test requires limit < total events to verify the limit branch"

    events, new_last_run = fetch_events(client, limit)

    # Exactly fetch_limit events should be returned.
    assert len(events) == limit
    # new_last_run must have a start_date so the next cycle continues from the correct point.
    assert new_last_run["start_date"]
    # new_last_run must have ids so the next cycle can deduplicate.
    assert new_last_run["ids"]
    # The start_date should NOT be the end date — it should be the date of the last processed event.
    assert new_last_run["start_date"] != "2025-02-01T00:00:00Z"