Skip to content

Commit cb5d83f

Browse files
authored
XSUP-49241/OktaASA (demisto#39574)
* XSUP-49241/OktaASA * finish implemention py, add unit-test,edit response data, edit RN * fix tests * fix tests * fix tests * fix tests * code review changes and add tests * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * CR review, change RN, add unit-test and simplify add_time_and_related_object_data_to_events * fix CR review comments
1 parent 2d3f100 commit cb5d83f

6 files changed

Lines changed: 312 additions & 114 deletions

File tree

Packs/OktaASA/Integrations/OktaASA/OktaASA.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,23 @@ def get_token_request(self) -> dict:
4949
token_response.pop("team_name", None)
5050
return token_response
5151

52-
def get_audit_events_request(self, params: dict) -> list:
52+
def get_audit_events_request(self, params: dict) -> dict:
5353
"""Gets audit events request.
5454
5555
Args:
5656
self (OktaASAClient): Okta ASA Client.
5757
params (Dict): Request parameters.
5858
5959
Returns:
60-
list: A list of events.
60+
dict: The response dict form: {"list": [], "related_objects": {}}.
6161
"""
62-
events_response: list = self._http_request("GET", "/auditsV2", params=params).get("list", [])
62+
events_response: dict = self._http_request("GET", "/auditsV2", params=params)
6363

6464
return events_response
6565

6666
def execute_audit_events_request(
6767
self, offset: Optional[str], count: Optional[int], descending: Optional[bool], prev: Optional[bool]
68-
) -> list:
68+
) -> tuple[list,dict]:
6969
"""Gets audit events request.
7070
7171
Args:
@@ -76,13 +76,13 @@ def execute_audit_events_request(
7676
prev (bool): Controls the direction of paging
7777
7878
Returns:
79-
Dict: The response.
79+
tuple[list,dict]: The response "list" and the response "related_objects".
8080
"""
8181

8282
params = assign_params(offset=offset, count=count, descending=descending, prev=prev)
8383
self.generate_token_if_required()
84-
events_response = self.get_audit_events_request(params)
85-
return events_response
84+
response = self.get_audit_events_request(params)
85+
return response.get("list",[]),response.get("related_objects",{})
8686

8787
def generate_token_if_required(self, hard: bool = False) -> None:
8888
"""Checks if token refresh required and return the token.
@@ -112,14 +112,15 @@ def generate_token_if_required(self, hard: bool = False) -> None:
112112
self._headers = {"Authorization": f"Bearer {token}"}
113113

114114
def search_events(
115-
self, limit: Optional[int] = 10000, offset: str | None = None
115+
self, limit: Optional[int] = 10000, add_time_mapping: bool = False, offset: str | None = None
116116
) -> tuple[List[Dict], Optional[str], Optional[str]]:
117117
"""
118118
Searches for Okta ASA events using the '/auditsV2' API endpoint.
119119
All the parameters are passed directly to the API as HTTP POST parameters in the request
120120
121121
Args:
122122
limit (int): limit.
123+
add_time_mapping (bool): whether to add time mapping.
123124
offset (str): The UUID of an object used as an offset for pagination.
124125
125126
Returns:
@@ -133,9 +134,12 @@ def search_events(
133134
count = min(limit, 1000) if limit else 1000
134135
while limit and len(results) < limit:
135136
descending = bool(not offset)
136-
events = self.execute_audit_events_request(offset=offset, count=count, descending=descending, prev=None)
137+
events, related_objects = self.execute_audit_events_request(
138+
offset=offset, count=count, descending=descending, prev=None
139+
)
137140
if not events:
138141
break
142+
add_time_and_related_object_data_to_events(events, related_objects, add_time_mapping)
139143
event_offset = events[0] if descending else events[len(events) - 1]
140144
offset = event_offset.get("id")
141145
returned_timestamp = event_offset.get("timestamp")
@@ -165,18 +169,32 @@ def is_token_expired(expires_date: str) -> bool:
165169
return current_utc_time > expires_datetime_date
166170

167171

168-
def add_time_to_events(events: List[Dict]):
172+
def add_time_and_related_object_data_to_events(events: List[Dict], related_objects: Dict, add_time_mapping: bool):
169173
"""
170-
Adds the _time key to the events.
174+
Adds the "_time" key to the event and enhances the "server", "project" keys values of an event.
175+
Related object structure is "related_objects": {"id_of_keys_to_enhance": {"type": "some_type", "object": {}}}
171176
Args:
172177
events: List[Dict] - list of events to add the _time key to.
178+
related_objects: Dict - A dict of events related_objects to add the related objects to.
179+
add_time_mapping (bool): whether to add time mapping.
173180
Returns:
174-
list: The events with the _time key.
181+
list: The events with the _time key and related object information.
175182
"""
183+
keys_to_enhance = ["project", "server"]
176184
for event in events:
177-
create_time = arg_to_datetime(arg=event.get("timestamp"))
178-
event["_time"] = create_time.strftime(DATE_FORMAT) if create_time else None
179-
185+
if add_time_mapping:
186+
create_time = arg_to_datetime(arg=event.get("timestamp"))
187+
event["_time"] = create_time.strftime(DATE_FORMAT) if create_time else None
188+
189+
for key in keys_to_enhance:
190+
event_details = event.get("details", {})
191+
id_of_key_to_enhance = event_details.get(key)
192+
if not event_details or not id_of_key_to_enhance:
193+
continue
194+
# structure is {"type": "some_type", "object": {}}
195+
related_object_dict = related_objects.get(id_of_key_to_enhance,{})
196+
if key == related_object_dict.get("type") and (related_object_data:=related_object_dict.get("object")):
197+
event_details[key] = related_object_data if related_object_data else id_of_key_to_enhance
180198

181199
"""COMMAND FUNCTIONS"""
182200

@@ -206,21 +224,23 @@ def test_module(client: OktaASAClient) -> str:
206224
return "ok"
207225

208226

209-
def get_events_command(client: OktaASAClient, args: dict = {}) -> tuple[List[Dict], CommandResults]:
227+
def get_events_command(
228+
client: OktaASAClient, args: dict = {}, add_time_mapping: bool = False
229+
) -> tuple[List[Dict], CommandResults]:
210230
"""
211231
Gets audit events from Audits Events endpoint.
212232
213233
Args:
214234
self (OktaASAClient): Okta ASA Client.
215235
args (dict): A dictionary containing the command arguments.
216-
236+
add_time_mapping (bool): whether to add time mapping.
217237
Returns:
218238
List[Dict]: list of events.
219239
CommandResults: command results containing Audits Events.
220240
"""
221241

222242
limit = arg_to_number(args.get("limit")) or 50
223-
events, _, _ = client.search_events(limit=limit)
243+
events, _, _ = client.search_events(limit=limit, add_time_mapping=add_time_mapping)
224244
hr = tableToMarkdown(name="Audits Events", t=events)
225245
return events, CommandResults(readable_output=hr)
226246

@@ -230,13 +250,15 @@ def fetch_events_command(
230250
last_run: dict[str, str],
231251
team_name: str,
232252
max_audit_events_per_fetch: Optional[int],
253+
add_time_mapping: bool
233254
) -> tuple[dict[str, str], List[Dict]]:
234255
"""
235256
Args:
236257
client (OktaASAClient): OktaASAClient client to use.
237258
last_run (dict): A dict with a key containing the latest event created time we got from last fetch.
238259
max_audit_events_per_fetch (int): number of events per fetch.
239260
team_name (str): The name of the team.
261+
add_time_mapping (bool): whether to add time mapping.
240262
Returns:
241263
dict: Next run dictionary containing the timestamp that will be used in ``last_run`` on the next fetch.
242264
list: List of events that will be created in XSIAM.
@@ -246,6 +268,7 @@ def fetch_events_command(
246268
events, offset, timestamp = client.search_events(
247269
limit=max_audit_events_per_fetch,
248270
offset=last_run.get("offset") if last_run and last_run.get("team_name") == team_name else None,
271+
add_time_mapping=add_time_mapping
249272
)
250273
# Save the next_run as a dict with the last_fetch key to be stored
251274
next_run: dict = {"offset": offset, "timestamp": timestamp, "team_name": team_name} if offset else last_run
@@ -271,7 +294,7 @@ def main() -> None: # pragma: no cover
271294
team_name = params.get("team_name", "").lower()
272295
base_url = urljoin(params.get("url"), f"/v1/teams/{team_name}")
273296
verify_certificate = not params.get("insecure", False)
274-
max_audit_events_per_fetch = arg_to_number(params.get("max_audit_events_per_fetch", "10000"))
297+
max_audit_events_per_fetch = arg_to_number(params.get("max_audit_events_per_fetch", "5000"))
275298
proxy = params.get("proxy", False)
276299

277300
demisto.debug(f"{INTEGRATION_NAME}: Command being called is {command}")
@@ -286,19 +309,17 @@ def main() -> None: # pragma: no cover
286309

287310
elif command == "okta-asa-get-events":
288311
should_push_events = argToBoolean(args.pop("should_push_events"))
289-
events, results = get_events_command(client, demisto.args())
312+
events, results = get_events_command(client, demisto.args(), should_push_events)
290313
return_results(results)
291314
if should_push_events:
292-
add_time_to_events(events)
293315
send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT)
294316

295317
elif command == "fetch-events":
296318
last_run = demisto.getLastRun()
297319
next_run, events = fetch_events_command(
298-
client=client, last_run=last_run, max_audit_events_per_fetch=max_audit_events_per_fetch, team_name=team_name
320+
client=client, last_run=last_run, max_audit_events_per_fetch=max_audit_events_per_fetch, team_name=team_name,
321+
add_time_mapping = True
299322
)
300-
301-
add_time_to_events(events)
302323
send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT)
303324
demisto.setLastRun(next_run)
304325

0 commit comments

Comments
 (0)