Skip to content

XSUP-51378 fix #40173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 10, 2025
Merged
59 changes: 49 additions & 10 deletions Packs/Box/Integrations/BoxV2/BoxV2.py
Original file line number Diff line number Diff line change
@@ -143,12 +143,13 @@ def __init__(self, raw_input):
# Created at time is stored in either or two locations, never both.
demisto.debug(f"Event init: created_at={raw_input.get('created_at')}, source exists= {bool(raw_input.get('source'))}")
created_at = raw_input.get("created_at")
_created_at = raw_input.get("source", {}).get("created_at") if raw_input.get("source") else None
_created_at = raw_input.get("source", {}).get("created_at") if raw_input.get("source") else ""
self.created_at = created_at if created_at is not None else _created_at
demisto.debug(f"Event init:{self.created_at=}")
self.event_id = raw_input.get("event_id")
self.event_type = raw_input.get("event_type")
self.labels = raw_input
demisto.debug(f"Useful current event info {self.event_id=} {self.event_type=} {raw_input=}")

def format_incident(self):
incident = {
@@ -682,7 +683,15 @@ def trashed_item_permanently_delete(self, item_id: str, type: str, as_user: str)
self._headers.update({"As-User": validated_as_user})
return self._http_request(method="DELETE", url_suffix=url_suffix, return_empty_response=True)

def list_events(self, as_user: str, stream_type: str, created_after: str = None, limit: int = None):
def list_events(
self,
as_user: str,
stream_type: str,
created_after: str = None,
limit: int = None,
event_type: str = None,
next_stream_position: str = None,
):
"""
Lists the events which have occurred given the as_user argument/parameter. Same endpoint is
used to also handle the enterprise logs as well.
@@ -691,6 +700,8 @@ def list_events(self, as_user: str, stream_type: str, created_after: str = None,
:param stream_type: str - Indicates the type of logs to be retrieved.
:param created_after: str - Is used the return only events created after the given time.
:param limit: int - The maximum amount of events to return.
:param event_type: a comma separated list of Event types to return.
:param next_stream_position: The location in the event stream to start receiving events from.
:return: dict - The results for the given logs query.
"""
url_suffix = "/events/"
@@ -701,6 +712,11 @@ def list_events(self, as_user: str, stream_type: str, created_after: str = None,
request_params.update({"created_after": created_after})
if limit:
request_params.update({"limit": limit}) # type:ignore
if event_type:
request_params.update({"event_type": event_type})
if next_stream_position:
request_params.update({"stream_position": next_stream_position})
demisto.debug(f"The command with {url_suffix=}, params are {request_params=}")
return self._http_request(method="GET", url_suffix=url_suffix, params=request_params)

def get_current_user(self, as_user: str):
@@ -1705,33 +1721,53 @@ def test_module(client: Client, params: dict, first_fetch_time: int) -> str:


def fetch_incidents(
client: Client, max_results: int, last_run: dict, first_fetch_time: int, as_user: str
) -> tuple[str, list[dict]]:
client: Client, max_results: int, last_run: dict, first_fetch_time: int, as_user: str, event_type: list = None
) -> tuple[dict, list[dict]]:
"""

:param client:
:param max_results:
:param last_run:
:param first_fetch_time:
:param as_user:
:param event_type:
:return:
"""
created_after = last_run.get("time", None)
next_stream_position = last_run.get("next_stream_position", "")
incidents = []
if not created_after:
created_after = datetime.fromtimestamp(first_fetch_time, tz=UTC).strftime(DATE_FORMAT)
results = client.list_events(stream_type="admin_logs", as_user=as_user, limit=max_results, created_after=created_after)
demisto.debug(f"At the beginning of the fetch, {created_after=} {next_stream_position=}")
event_type_str = ",".join(event_type) if event_type else ""
demisto.debug(f"{event_type_str=}")
results = client.list_events(
stream_type="admin_logs",
as_user=as_user,
limit=max_results,
created_after=created_after,
event_type=event_type_str,
next_stream_position=next_stream_position,
)
raw_incidents = results.get("entries", [])
next_stream_position = results.get("next_stream_position")
demisto.debug(f"Extracted {len(raw_incidents)} raw incidents from the results.")
next_run = datetime.now(tz=UTC).strftime(DATE_FORMAT)
next_run = created_after
for raw_incident in raw_incidents:
event = Event(raw_input=raw_incident)
xsoar_incident = event.format_incident()
incidents.append(xsoar_incident)
if event.created_at > created_after:
next_run = event.created_at # type: ignore[assignment]
utc_event_time = arg_to_datetime(event.created_at)
utc_event_time = utc_event_time.strftime(DATE_FORMAT) if utc_event_time else ""
next_run = arg_to_datetime(next_run)
next_run = next_run.strftime(DATE_FORMAT) if next_run else ""
demisto.debug(f"{utc_event_time=} >? {next_run=}")
if utc_event_time > next_run:
next_run = utc_event_time
last_run = {"time": next_run, "next_stream_position": next_stream_position}
demisto.debug(f"The final {last_run=}")

return next_run, incidents
return last_run, incidents


def main() -> None: # pragma: no cover
@@ -1766,6 +1802,8 @@ def main() -> None: # pragma: no cover

elif demisto.command() == "fetch-incidents":
as_user = demisto.params().get("as_user", None)
event_type = demisto.params().get("event_type")
demisto.debug(f"The {event_type=}")
max_results = arg_to_int(arg=demisto.params().get("max_fetch"), arg_name="max_fetch")
if not max_results or max_results > MAX_INCIDENTS_TO_FETCH:
max_results = MAX_INCIDENTS_TO_FETCH
@@ -1776,8 +1814,9 @@ def main() -> None: # pragma: no cover
last_run=demisto.getLastRun(),
first_fetch_time=first_fetch_time,
as_user=as_user,
event_type=event_type,
)
demisto.setLastRun({"time": next_run})
demisto.setLastRun(next_run)
demisto.incidents(incidents)

elif demisto.command() == "box-create-file-share-link" or demisto.command() == "box-update-file-share-link":
2 changes: 1 addition & 1 deletion Packs/Box/Integrations/BoxV2/BoxV2.yml
Original file line number Diff line number Diff line change
@@ -2511,7 +2511,7 @@ script:
- contextPath: Box.Folder.item_status
description: The status of the parent of the item.
type: String
dockerimage: demisto/auth-utils:1.0.0.3562326
dockerimage: demisto/auth-utils:1.0.0.3609876
isfetch: true
runonce: false
script: '-'
56 changes: 53 additions & 3 deletions Packs/Box/Integrations/BoxV2/BoxV2_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import json

import pytest

import demistomock as demisto
from BoxV2 import Client

@@ -870,7 +873,7 @@ def test_fetch_incidents(requests_mock, mocker):

as_user = "sample_current_user"
max_results = 10
last_run = {"time": "2015-10-21T04:29-8:00"}
last_run = {"time": "2012-12-12T10:50:43-08:00"}
first_fetch_time = 1607935741

mock_response = util_load_json("test_data/events.json")
@@ -883,12 +886,59 @@ def test_fetch_incidents(requests_mock, mocker):
assert requests_mock.request_history[0].headers.get("As-User") == "sample_current_user"
assert requests_mock.request_history[0].headers.get("Authorization") == "Bearer JWT_TOKEN"
assert requests_mock.request_history[0].qs.get("stream_type") == ["admin_logs"]
assert requests_mock.request_history[0].qs.get("created_after") == ["2015-10-21t04:29-8:00"]
assert requests_mock.request_history[0].qs.get("created_after") == ["2012-12-12t10:50:43-08:00"]

assert response[0] > "2015-10-21T04:29-8:00"
assert response[0].get("time") == "2012-12-12T18:53:43+0000"
assert response[0].get("next_stream_position") == 1152922976252290800
assert response[1] == expected_fetch_results


@pytest.mark.parametrize(
argnames="last_run_time, next_stream_position",
argvalues=[("2012-12-12T10:50:43-08:00", ""), ("2012-12-12T18:50:43+0000", "1152922976252290700")],
)
def test_fetch_incidents_event_type(mocker, last_run_time, next_stream_position):
"""
Tests the fetch-incidents function and command.
This unit test checks 2 scenarios:
1. A scenario in which a local time was saved to the last run, and no streaming position was given.
2. A scenario in which the utc time was saved to the last run, and a next_stream_position was given.

Given: A valid last run object and time in the past.
When: Executing the fetch-incidents command.
Then: The correct arguments are being sent in the request, and that the expected last_run object is returned.
1. no stream_position is used in the params to the api call.
2. a stream_position is used in the params to the api call.
"""
from BoxV2 import fetch_incidents

client = ClientTestBox(mocker).client

as_user = "sample_current_user"
max_results = 10
last_run = {"time": last_run_time, "next_stream_position": next_stream_position}
event_type = ["FILE_MARKED_MALICIOUS", "FILE_MARKED_MALICIOUS2"]
first_fetch_time = 1607935741
expected_event_type = "FILE_MARKED_MALICIOUS,FILE_MARKED_MALICIOUS2"
expected_params = {
"created_after": last_run.get("time"),
"limit": max_results,
"event_type": expected_event_type,
"stream_type": "admin_logs",
}
if next_stream_position:
expected_params["stream_position"] = next_stream_position

mock_response = util_load_json("test_data/events2.json")
http_request = mocker.patch.object(client, "_http_request", return_value=mock_response)

last_run, incidents = fetch_incidents(client, max_results, last_run, first_fetch_time, as_user, event_type)

http_request.assert_called_with(method="GET", url_suffix="/events/", params=expected_params)
assert last_run == {"time": "2012-12-12T18:53:44+0000", "next_stream_position": 1152922976252290800}
assert len(incidents) == 2


def test_list_user_events_command(requests_mock, mocker):
"""
Tests the box-list-user-events function and command.
80 changes: 80 additions & 0 deletions Packs/Box/Integrations/BoxV2/test_data/events2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"chunk_size": 2,
"entries": [
{
"type": "event",
"event_id": "2222222222222222222222222222222222222222",
"created_by": {
"id": 11446498,
"type": "user",
"name": "User Name",
"login": "ceo@example.com"
},
"event_type": "FILE_MARKED_MALICIOUS",
"session_id": "70090280850c8d2a1933c1",
"source": {
"id": 11446498,
"type": "user",
"name": "User Name",
"login": "ceo@example.com",
"created_at": "2012-12-12T10:53:43-08:00",
"modified_at": "2012-12-12T10:53:43-08:00",
"language": "en",
"timezone": "Africa/Bujumbura",
"space_amount": 11345156112,
"space_used": 1237009912,
"max_upload_size": 2147483648,
"status": "active",
"job_title": "CEO",
"phone": 2222222222,
"address": "address",
"avatar_url": "https://www.box.com/api/avatar/large/222222222",
"notification_email": {
"email": "notifications@example.com",
"is_confirmed": true
}
},
"additional_details": {
"key": "value"
}
},
{
"type": "event",
"event_id": "1111111111111111111111111111111111111111",
"created_by": {
"id": 11446499,
"type": "user",
"name": "User Name",
"login": "ceo@example.com"
},
"event_type": "FILE_MARKED_MALICIOUS",
"session_id": "70090280850c8d2a1933c1",
"source": {
"id": 11446499,
"type": "user",
"name": "User Name",
"login": "ceo@example.com",
"created_at": "2012-12-12T10:53:44-08:00",
"modified_at": "2012-12-12T10:53:44-08:00",
"language": "en",
"timezone": "Africa/Bujumbura",
"space_amount": 11345156112,
"space_used": 1237009912,
"max_upload_size": 2147483648,
"status": "active",
"job_title": "CEO",
"phone": 1111111111,
"address": "address",
"avatar_url": "https://www.box.com/api/avatar/large/111111111",
"notification_email": {
"email": "notifications@example.com",
"is_confirmed": true
}
},
"additional_details": {
"key": "value"
}
}
],
"next_stream_position": 1152922976252290800
}
8 changes: 8 additions & 0 deletions Packs/Box/ReleaseNotes/3_2_14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

#### Integrations

##### Box v2

- Updated the Docker image to: *demisto/auth-utils:1.0.0.3609876*.
- Fixed an issue with the **event_type** param in the **fetch-incidents** mechanism.
- Fixed an issue where some incidents were duplicated in the **fetch-incidents** mechanism.
2 changes: 1 addition & 1 deletion Packs/Box/pack_metadata.json
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
"name": "Box",
"description": "Manage Box users",
"support": "xsoar",
"currentVersion": "3.2.13",
"currentVersion": "3.2.14",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",