Skip to content

Commit

Permalink
OktaEventCollector events date parsing (#37412)
Browse files Browse the repository at this point in the history
* OktaEventCollector events date parsing

* improve test coverage.

* change time in test

* Adding more test coverage

* more coverage

* use CI/CD time

* remove unused variable
  • Loading branch information
thefrieddan1 authored Nov 25, 2024
1 parent eabd7e1 commit fa1d6f7
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 23 deletions.
23 changes: 17 additions & 6 deletions Packs/Okta/Integrations/OktaEventCollector/OktaEventCollector.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from http import HTTPStatus
from typing import cast

from dateutil.parser import parse
from CommonServerPython import *

VENDOR = "okta"
Expand Down Expand Up @@ -131,7 +131,14 @@ def get_last_run(events: List[dict], last_run_after, next_link) -> dict:
if event.get('published') != last_time:
break
ids.append(event.get('uuid'))
last_time = datetime.strptime(str(last_time).lower().replace('z', ''), '%Y-%m-%dt%H:%M:%S.%f')
try:
last_time = datetime.strptime(str(last_time).lower().replace('z', ''), '%Y-%m-%dt%H:%M:%S.%f')
except ValueError:
last_time = parse(str(last_time).lower().replace('z', ''))
except Exception as e: # General exception
demisto.error(f'Unexpected error parsing published date from event: {e}')
return {}

return {'after': last_time.isoformat(), 'ids': ids, 'next_link': next_link}


Expand Down Expand Up @@ -160,7 +167,7 @@ def fetch_events(client: Client,
return events, next_link


def main(): # pragma: no cover
def main():
try:
start_time_epoch = int(time.time())
demisto_params = demisto.params()
Expand All @@ -179,7 +186,7 @@ def main(): # pragma: no cover
get_events_command(client, events_limit, since=after.isoformat())
demisto.results('ok')

if command == 'okta-get-events':
elif command == 'okta-get-events':
after = cast(datetime, dateparser.parse(demisto_args.get('from_date').strip()))
events, _, _ = get_events_command(client, events_limit, since=after.isoformat())
command_results = CommandResults(
Expand All @@ -205,10 +212,14 @@ def main(): # pragma: no cover
last_run_after=last_run_after, last_object_ids=last_object_ids, next_link=next_link)
demisto.debug(f'sending_events_to_xsiam: {len(events)}')
send_events_to_xsiam(events[:events_limit], vendor=VENDOR, product=PRODUCT)
demisto.setLastRun(get_last_run(events, last_run_after, next_link))
last_run = get_last_run(events, last_run_after, next_link)
if last_run:
demisto.setLastRun(get_last_run(events, last_run_after, next_link))
else:
return_error('Unrecognized command: ' + demisto.command())

except Exception as e:
return_error(f'Failed to execute {demisto.command()} command. Error: {str(e)}')
return_error(f'Failed to execute {demisto.command()} command. Error: {e}')


if __name__ in ('__main__', '__builtin__', 'builtins'):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ script:
required: false
description: Manual command to fetch events and display them.
name: okta-get-events
dockerimage: demisto/fastapi:0.115.4.115067
dockerimage: demisto/fastapi:0.115.5.117397
isfetchevents: true
subtype: python3
marketplaces:
Expand Down
111 changes: 96 additions & 15 deletions Packs/Okta/Integrations/OktaEventCollector/OktaEventCollector_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from unittest.mock import MagicMock

import dateutil.parser._parser
import pytest
from freezegun import freeze_time
from OktaEventCollector import Client, DemistoException, fetch_events, get_events_command, get_last_run, main, remove_duplicates

import requests_mock
import demistomock as demisto


Expand Down Expand Up @@ -71,11 +71,36 @@ def test_remove_duplicates(events, ids, result):
'1d0844b6-3148-11ec-9027-a5b57ec5fbbb'], 'next_link': ''}),
([],
'2022-04-17T12:31:36.667',
{'after': '2022-04-17T12:31:36.667000', 'ids': [], 'next_link': ''})])
{'after': '2022-04-17T12:31:36.667000', 'ids': [], 'next_link': ''})
])
def test_get_last_run(events, last_run_after, result):
assert get_last_run(events, last_run_after, next_link='') == result


def test_get_last_run_with_different_format():
events = [{'published': '2022-04-17T12:31:36',
'uuid': '1d0844b6-3148-11ec-9027-a5b57ec5faaa'},
{'published': '2022-04-17T12:32:36',
'uuid': '1d0844b6-3148-11ec-9027-a5b57ec5fbbb'},
{'published': '2022-04-17T12:33:36',
'uuid': '1d0844b6-3148-11ec-9027-a5b57ec5fccc'}]
last_run_after = '2022-04-17T11:30:00'
expected_result = {'after': '2022-04-17T12:33:36', 'ids': ['1d0844b6-3148-11ec-9027-a5b57ec5fccc'], 'next_link': ''}
assert get_last_run(events, last_run_after, next_link='') == expected_result


def test_get_last_run_invalid_date_format():
events = [{'published': '2022-04-17T12:31:36',
'uuid': '1d0844b6-3148-11ec-9027-a5b57ec5faaa'},
{'published': '2022-04-17T12:32:36',
'uuid': '1d0844b6-3148-11ec-9027-a5b57ec5fbbb'},
{'published': 'xxxyyyzzz',
'uuid': '1d0844b6-3148-11ec-9027-a5b57ec5fccc'}]
last_run_after = '2022-04-17T11:30:00'
with pytest.raises(dateutil.parser._parser.ParserError):
get_last_run(events, last_run_after, next_link='')


def test_get_events_success(dummy_client, mocker):
mock_remove_duplicates = MagicMock()
mock_remove_duplicates.return_value = [{'id': 1,
Expand Down Expand Up @@ -143,7 +168,7 @@ def test_fetch_event(dummy_client, mocker):


@freeze_time('2022-04-17T12:32:36.667Z')
def test_429_too_many_requests(mocker, requests_mock):
def test_429_too_many_requests(mocker):

mock_events = [
{
Expand All @@ -163,16 +188,6 @@ def test_429_too_many_requests(mocker, requests_mock):
'published': '2022-04-17T14:00:03.000Z'
}
]
requests_mock.get(
'https://testurl.com/api/v1/logs?since=2022-04-17T12%3A32%3A36.667000%2B00%3A00&sortOrder=ASCENDING&limit=5',
json=mock_events)
requests_mock.get('https://testurl.com/api/v1/logs?since=2022-04-17T14%3A00%3A03.000Z&sortOrder=ASCENDING&limit=5',
status_code=429,
reason='Too many requests',
headers={
'x-rate-limit-remaining': '0',
'x-rate-limit-reset': '1698343702',
})

mocker.patch.object(demisto, 'command', return_value='fetch-events')
mocker.patch.object(demisto, 'getLastRun', return_value={})
Expand All @@ -188,6 +203,72 @@ def test_429_too_many_requests(mocker, requests_mock):
})
send_events_to_xsiam_mock = mocker.patch('OktaEventCollector.send_events_to_xsiam', return_value={})

main()
with requests_mock.Mocker() as m:
m.get(
'https://testurl.com/api/v1/logs?since=2022-04-17T12%3A32%3A36.667000%2B00%3A00&sortOrder=ASCENDING&limit=5',
json=mock_events)
m.get('https://testurl.com/api/v1/logs?since=2022-04-17T14%3A00%3A03.000Z&sortOrder=ASCENDING&limit=5',
status_code=429,
reason='Too many requests',
headers={
'x-rate-limit-remaining': '0',
'x-rate-limit-reset': '1698343702',
})

main()

send_events_to_xsiam_mock.assert_called_once_with(mock_events, vendor='okta', product='okta')


@freeze_time('2022-04-17T12:32:36.667Z')
@pytest.mark.parametrize("address, command", [
('https://testurl.com/api/v1/logs?sortOrder=ASCENDING&since=2022-04-16T12%3A32%3A36.667000&limit=5', 'okta-get-events'),
('https://testurl.com/api/v1/logs?sortOrder=ASCENDING&since=2022-04-17T11%3A32%3A36.667000&limit=5', 'test-module')
])
def test_okta_get_events(mocker, address, command):

mock_events = [
{
'uuid': 1,
'published': '2022-04-17T14:00:00.000Z'
},
{
'uuid': 2,
'published': '2022-04-17T14:00:01.000Z'
},
{
'uuid': 3,
'published': '2022-04-17T14:00:02.000Z'
},
{
'uuid': 4,
'published': '2022-04-17T14:00:03.000Z'
}
]
mocker.patch.object(demisto, 'command', return_value=command)
mocker.patch.object(demisto, 'getLastRun', return_value={})
mocker.patch.object(demisto, 'args', return_value={
'from_date': '1 day',
'should_push_events': True,
})
mocker.patch.object(demisto, 'params', return_value={
'url': 'https://testurl.com',
'api_key': {
'password': 'TESTAPIKEY'
},
'limit': 5,
'after': '2022-04-17T12:32:36.667Z',
'proxy': False,
'verify': False
})
send_events_to_xsiam_mock = mocker.patch('OktaEventCollector.send_events_to_xsiam', return_value={})
with requests_mock.Mocker() as m:
m.get(
address,
json=mock_events)
main()

if command == 'test-module':
send_events_to_xsiam_mock.assert_not_called()
else:
send_events_to_xsiam_mock.assert_called_once_with(mock_events, vendor='okta', product='okta')
7 changes: 7 additions & 0 deletions Packs/Okta/ReleaseNotes/3_3_7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

#### Integrations

##### Okta Event Collector

- Improved implementation of date parsing for events.
- Updated the Docker image to: *demisto/fastapi:0.115.5.117397*.
2 changes: 1 addition & 1 deletion Packs/Okta/pack_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Okta",
"description": "Integration with Okta's cloud-based identity management service.",
"support": "xsoar",
"currentVersion": "3.3.6",
"currentVersion": "3.3.7",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
Expand Down

0 comments on commit fa1d6f7

Please sign in to comment.