Skip to content
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

fetch all computers without a date filter. #37219

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -234,91 +234,104 @@ def get_alerts(self, args: dict, next_page: str) -> dict:
variables["next"] = next_page
return self.graphql(query, variables)

def get_computer(self, args: dict, next_page: str) -> dict:
def get_computers(self, args: dict, next_page: str) -> dict:
"""
Fetches computer from the Jamf Protect API.
Fetches a list of computers from the Jamf Protect API, with an optional date filter.

Args:
args (dict): The arguments to be used in the GraphQL query.
It should contain a key "created" with a value representing the creation date of the computer.
next_page (str): The next page token for pagination.
args (dict): A dictionary of arguments for the GraphQL query.
If using a date filter, this should include:
- "use_date_filter" (bool): If True, a creation date filter is applied.
- "created" (str): A date in 'YYYY-MM-DDTHH:MM:SSZ' format, representing the
minimum creation date for filtering the results.
next_page (str): The token for the next page of results, used for pagination.

Returns:
dict: The response from the API.
dict: The API response containing a list of computers and pagination information.
"""
query = """
query listComputers($page_size: Int, $next: String $created:AWSDateTime) {
listComputers( input: {
filter: {
created: {
greaterThan: $created
}
}
pageSize: $page_size next: $next
}
) {
items {
serial
uuid
provisioningUDID
updated
checkin
connectionStatus
lastConnection
lastConnectionIp
lastDisconnection
lastDisconnectionReason
insightsUpdated
insightsStatsFail
insightsStatsPass
insightsStatsUnknown
version
signaturesVersion
installType
plan {
hash
id
name
logLevel
}
scorecard {
uuid
label
section
pass
tags
enabled
}
osMajor
osMinor
osPatch
osString
arch
certid
configHash
created
hostName
kernelVersion
memorySize
modelName
label
webProtectionActive
fullDiskAccess
tags
}
pageInfo {
next
total
date_filter = "$created: AWSDateTime" if args.get('use_date_filter') else ""
filter_clause = """
filter: {
created: {
greaterThan: $created
}
}
}
""" if args.get('use_date_filter') else ""

query = f"""
query listComputers($page_size: Int, $next: String {date_filter}) {{
listComputers( input: {{
{filter_clause}
pageSize: $page_size
next: $next
}}) {{
items {{
serial
uuid
provisioningUDID
updated
checkin
connectionStatus
lastConnection
lastConnectionIp
lastDisconnection
lastDisconnectionReason
insightsUpdated
insightsStatsFail
insightsStatsPass
insightsStatsUnknown
version
signaturesVersion
installType
plan {{
hash
id
name
logLevel
}}
scorecard {{
uuid
label
section
pass
tags
enabled
}}
osMajor
osMinor
osPatch
osString
arch
certid
configHash
created
hostName
kernelVersion
memorySize
modelName
label
webProtectionActive
fullDiskAccess
tags
}}
pageInfo {{
next
total
}}
}}
}}
"""
yedidyacohenpalo marked this conversation as resolved.
Show resolved Hide resolved
variables = {
"created": args.get("created"),

variables: dict[str, Any] = {
"page_size": COMPUTER_PAGE_SIZE
}

if args.get('use_date_filter'):
variables["created"] = args.get("created")

if next_page:
variables["next"] = next_page

return self.graphql(query, variables)

def get_audits(self, args: dict, next_page: str) -> dict:
Expand Down Expand Up @@ -382,7 +395,7 @@ def test_module(client: Client) -> str:
Returns:
str: Returns "ok" if the client is able to interact with the API successfully, raises an exception otherwise.
"""
fetch_events(client, max_fetch_audits=1, max_fetch_alerts=1, max_fetch_computer=1)
fetch_events(client, max_fetch_audits=1, max_fetch_alerts=1, max_fetch_computer=1, fetch_all_computers=False)
return "ok"


Expand Down Expand Up @@ -451,7 +464,9 @@ def get_events_alert_type(client: Client, start_date: str, max_fetch: int, last_
return events, new_last_run_without_next_page


def get_events_computer_type(client: Client, start_date: str, max_fetch: int, last_run: dict) -> tuple[list[dict], dict]:
def get_events_computer_type(
client: Client, start_date: str, max_fetch: int, last_run: dict, fetch_all_computers: bool
) -> tuple[list[dict], dict]:
"""
Fetches computer type events from the Jamf Protect API within a specified date range.

Expand All @@ -472,11 +487,13 @@ def get_events_computer_type(client: Client, start_date: str, max_fetch: int, la
the end date of the fetched events and a continuance token if the fetched reached the max limit.
"""
created, current_date = calculate_fetch_dates(start_date, last_run=last_run, last_run_key="computer")
command_args = {"created": created}
client_event_type_func = client.get_computer
command_args = {"created": created, 'use_date_filter': bool(last_run or not fetch_all_computers)}
next_page = last_run.get("computer", {}).get("next_page", "")

demisto.debug(f"Fetching computers since {created}")
debug_message = "Fetching all computers" if fetch_all_computers and not last_run else f"Fetching computers since {created}"
demisto.debug(debug_message)

client_event_type_func = client.get_computers
events, next_page = get_events(command_args, client_event_type_func, max_fetch, next_page)
for event in events:
event["source_log_type"] = "computers"
Expand Down Expand Up @@ -606,8 +623,8 @@ def calculate_fetch_dates(start_date: str, last_run_key: str, last_run: dict, en
return start_date, end_date


def fetch_events(client: Client, max_fetch_alerts: int, max_fetch_audits: int, max_fetch_computer: int, start_date_arg: str = "",
end_date_arg: str = "") -> EventResult:
def fetch_events(client: Client, max_fetch_alerts: int, max_fetch_audits: int, max_fetch_computer: int, fetch_all_computers: bool,
start_date_arg: str = "", end_date_arg: str = "") -> EventResult:
"""
Fetches events from the Jamf Protect API within a specified date range.

Expand Down Expand Up @@ -639,7 +656,9 @@ def fetch_events(client: Client, max_fetch_alerts: int, max_fetch_audits: int, m
audit_events, audit_next_run = get_events_audit_type(client, start_date_arg, end_date_arg, max_fetch_audits, last_run)
if no_next_pages or computer_next_page:
# The only case we don't trigger the computer event type cycle is when have only the alert and audit next page token.
computer_events, computer_next_run = get_events_computer_type(client, start_date_arg, max_fetch_computer, last_run)
computer_events, computer_next_run = get_events_computer_type(
client, start_date_arg, max_fetch_computer, last_run, fetch_all_computers
)
next_run: dict[str, Any] = {"alert": alert_next_run, "audit": audit_next_run, 'computer': computer_next_run}
if "next_page" in (alert_next_run | audit_next_run | computer_next_run):
# Will instantly re-trigger the fetch command.
Expand Down Expand Up @@ -708,7 +727,8 @@ def get_events_command(
max_fetch_audits=limit,
max_fetch_computer=limit,
start_date_arg=start_date,
end_date_arg=end_date
end_date_arg=end_date,
fetch_all_computers=False
)
events_with_time = {event_type: add_time_field(events[:limit])
for event_type, events in event_result.as_dict().items() if events}
Expand Down Expand Up @@ -762,6 +782,7 @@ def main() -> None: # pragma: no cover
max_fetch_audits = arg_to_number(params.get('max_fetch_audits')) or DEFAULT_MAX_FETCH_AUDIT
max_fetch_alerts = arg_to_number(params.get('max_fetch_alerts')) or DEFAULT_MAX_FETCH_ALERT
max_fetch_computer = arg_to_number(params.get('max_fetch_computer')) or DEFAULT_MAX_FETCH_COMPUTER
fetch_all_computers = argToBoolean(params.get('fetch_all_computers')) or False

demisto.debug(f'Command being called is {command}')

Expand All @@ -785,7 +806,8 @@ def main() -> None: # pragma: no cover
alert_events, audit_events, computer_events, new_last_run = fetch_events(client=client,
max_fetch_alerts=max_fetch_alerts,
max_fetch_audits=max_fetch_audits,
max_fetch_computer=max_fetch_computer
max_fetch_computer=max_fetch_computer,
fetch_all_computers=fetch_all_computers
)
events = alert_events + audit_events + computer_events
if events:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ configuration:
type: 0
advanced: true
section: Collect
- display: Fetch all computers
yedidyacohenpalo marked this conversation as resolved.
Show resolved Hide resolved
name: fetch_all_computers
defaultvalue: "false"
type: 8
required: false
advanced: true
section: Collect
additionalinfo: When set to true, retrieves all available computers during the initial fetch.
description: Use this integration to fetch audit logs, alerts and computer events from Jamf Protect as events in Cortex XSIAM.
display: Jamf Protect Event Collector
name: Jamf Protect Event Collector
Expand Down Expand Up @@ -84,7 +92,7 @@ script:
type: python
subtype: python3
isfetchevents: true
dockerimage: demisto/python3:3.11.9.105369
dockerimage: demisto/python3:3.11.10.116439
marketplaces:
- marketplacev2
fromversion: 6.9.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,49 +124,57 @@ def test_calculate_fetch_dates_without_arguments(client):
@pytest.mark.parametrize("with_alert_next_page", [True, False])
@pytest.mark.parametrize("with_audit_next_page", [True, False])
@pytest.mark.parametrize("with_computer_next_page", [True, False])
@pytest.mark.parametrize("fetch_all_computers", [True, False])
def test_nextTrigger(
with_alert_next_page: bool,
with_audit_next_page: bool,
with_computer_next_page: bool,
fetch_all_computers: bool,
mocker: MockerFixture
):
"""
Given: A mock JamfProtect client.
When: Running fetch_events with different next pages for alerts, audits and computers.
When: Running fetch_events with different next pages for alerts, audits, and computers,
and different values of fetch_all_computers.
Then: Ensure the nextTrigger is set to 0 when there are no next pages, and the next page is set when there are next pages.
"""
from JamfProtectEventCollector import fetch_events, Client
mocked_alerts = util_load_json('test_data/raw_alerts.json')
mocked_audits = util_load_json('test_data/raw_audits.json')
mocked_computers = util_load_json('test_data/raw_computers.json')

if with_alert_next_page:
mocked_alerts["data"]["listAlerts"]["pageInfo"]["next"] = "example_next_page"
if with_audit_next_page:
mocked_audits["data"]["listAuditLogsByDate"]["pageInfo"]["next"] = "example_next_page"
if with_computer_next_page:
mocked_computers["data"]["listComputers"]["pageInfo"]["next"] = "example_next_page"

mocker.patch.object(Client, '_http_request',
side_effect=[mocked_alerts, mocked_audits, mocked_computers])
mocker.patch.object(Client, '_login', return_value="ExampleToken")
client = Client(base_url=MOCK_BASEURL, verify=False, proxy=False, client_id=MOCK_CLIENT_ID,
client_password=MOCK_CLIENT_PASSWORD)

yedidyacohenpalo marked this conversation as resolved.
Show resolved Hide resolved
_, _, _, next_run = fetch_events(client, 1, 1, 1)
_, _, _, next_run = fetch_events(client, 1, 1, 1, fetch_all_computers)

if with_alert_next_page:
assert next_run.get("nextTrigger") == "0"
assert next_run.get("alert").get("next_page") == "example_next_page"
assert next_run.get("alert", {}).get("next_page") == "example_next_page"
if not with_alert_next_page:
assert not next_run.get("alert").get("next_page")
assert not next_run.get("alert", {}).get("next_page")

if with_audit_next_page:
assert next_run.get("nextTrigger") == "0"
assert next_run.get("audit").get("next_page") == "example_next_page"
assert next_run.get("audit", {}).get("next_page") == "example_next_page"
if not with_audit_next_page:
assert not next_run.get("audit").get("next_page")
assert not next_run.get("audit", {}).get("next_page")

if with_computer_next_page:
assert next_run.get("nextTrigger") == "0"
assert next_run.get("computer").get("next_page") == "example_next_page"
if not with_computer_next_page:
assert not next_run.get("computer").get("next_page")
assert next_run.get("computer", {}).get("next_page") == "example_next_page"
else:
assert not next_run.get("computer", {}).get("next_page")


def test_next_trigger(mocker):
Expand All @@ -182,4 +190,39 @@ def test_next_trigger(mocker):
client_password=MOCK_CLIENT_PASSWORD)
mocker.patch('JamfProtectEventCollector.get_events_alert_type', return_value=([], {}))
mocker.patch('JamfProtectEventCollector.get_events_audit_type', return_value=([], {}))
fetch_events(client, 1, 1, 1)
fetch_events(client, 1, 1, 1, False)


@freeze_time(MOCK_TIME_UTC_NOW)
@pytest.mark.parametrize("fetch_all_computers", [True, False])
def test_get_events_computer_type(mocker: MockerFixture, client, fetch_all_computers):
"""
Test get_events_computer_type function with fetch_all_computers True and False.

Ensures the _http_request is called with the correct parameters.
"""
from JamfProtectEventCollector import get_events_computer_type

events, new_last_run = get_events_computer_type(
client=client,
start_date='',
max_fetch=200,
last_run={},
fetch_all_computers=fetch_all_computers,
)

assert client._http_request.call_count > 0 # Use the existing client fixture for assertion
assert len(events) > 0
assert "last_fetch" in new_last_run

# Extract the actual query sent in the HTTP request
called_args = client._http_request.call_args.kwargs
actual_query = called_args["json_data"]["query"]
actual_variables = called_args["json_data"]["variables"]

# Assertions for query content
if fetch_all_computers:
assert "$created: AWSDateTime" not in actual_query
else:
assert "$created: AWSDateTime" in actual_query
assert "created" in actual_variables
Loading
Loading