ZoomEventCollector
This is the Zoom event collector integration for Cortex XSIAM.
Analytics & SIEM · Zoom
Details
| ID | ZoomEventCollector |
|---|---|
| Provider | Zoom |
| Category | Analytics & SIEM |
| From Version | 6.8.0 |
| Docker Image | demisto/auth-utils:1.0.0.10133006 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
This is the Zoom event collector integration for Cortex XSIAM.
This integration was integrated and tested with version 2.0.0 of Zoom
This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.
Configure Zoom Event Collector in Cortex
| Parameter | Required |
|---|---|
| Server URL (e.g., ‘https://api.zoom.us/v2/’) | True |
| Account ID (OAuth) | True |
| Client ID (OAuth) | True |
| Client Secret (OAuth) | True |
| First fetch time (<number> <time unit>, e.g., 12 hours, 7 days) - within the last six months | False |
| Trust any certificate (not secure) | False |
| Use system proxy settings | False |
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.
zoom-get-events
Gets events from Zoom.
Base Command
zoom-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| should_push_events | If true, the command will create events, otherwise it will only display them. Possible values are: true, false. Default is false. | Required |
| limit | Maximum results to return. The maximum is 300. | Optional |
Context Output
There is no context output for this command.
Command Example
!zoom-get-events should_push_events=true limit=1
Human Readable Output
operationlogs Events
| action | category_type | operation_detail | operator | time |
|---|---|---|---|---|
| Delete | User | Delete User example@example.com | example@example.com | 2023-01-16T09:51:59Z |
activities Events
| client_type | ip_address | time | type | version | |
|---|---|---|---|---|---|
| Browser | example@example.com | 8.8.8.8 | 2023-01-19T14:44:23Z | Sign in | - |
Rate Limits
To prevent abuse and ensure service stability, all API requests are rate limited. Rate limits specify the maximum number of API calls that can be made in a minute period. The exact number of calls that your application can make per minute varies based on company plan.
For more information, please refer to the Zoom API documentation on Rate limits by account type.
Configuration parameters
url— Server URL (e.g., 'https://api.zoom.us/v2/') (required)account_id— Account ID (OAuth) (required)credentials— Client ID (OAuth) (required)first_fetch— First fetch time (<number> <time unit>, e.g., 12 hours, 7 days) - within the last six monthsinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
zoom-get-eventsGets events from Zoom.
from datetime import UTC, datetime, timedelta from typing import Any import demistomock as demisto import urllib3 from CommonServerPython import * from dateutil import relativedelta from ZoomApiModule import * # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ REQUEST_DATE_FORMAT = "%Y-%m-%d" RESPONSE_TIME_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" VENDOR = "Zoom" PRODUCT = "Zoom" EXTRA_PARAMS = """Too many fields were filled. You should fill the Account ID, Client ID, and Client Secret fields ( OAuth)""" INVALID_FIRST_FETCH_TIME = "The First fetch time should fall within the last six months." LOG_TYPES = {"operationlogs": "operation_logs", "activities": "activity_logs"} EVENT_TYPE = {"operationlogs": "operation_log", "activities": "activity_log"} """ CLIENT CLASS """ class Client(Zoom_Client): """A client class that implements logic to authenticate with Zoom application.""" def search_events( self, log_type: str, first_fetch_time: datetime, last_time: str = "", limit: int = None ) -> tuple[str, list[dict[str, Any]]]: """ Searches for Zoom logs using the '/<url_suffix>' API endpoint. Args: log_type: str, The API endpoint to request. last_time: datetime, The datetime of the last event fetched. first_fetch_time: datetime, The first fetch time as configured in the integration params. limit: int, the limit of the results to return. (is received only in zoom-get-events command) Returns: Tuple: str: The time of the latest event fetched. List: A list containing the events. """ results: list[dict] = [] next_page_token = "" next_last_time = last_time demisto.debug(f"Last run before the fetch run: {last_time} for {log_type}") start_date = first_fetch_time if not last_time else dateparser.parse(last_time).replace(tzinfo=UTC) # type: ignore[union-attr] end_date = datetime.now(UTC) demisto.debug(f"Starting to get logs from: {start_date} to: {end_date} for {log_type}") while start_date <= end_date: first_page = True params = { "page_size": limit if limit else MAX_RECORDS_PER_PAGE, "from": start_date.strftime(REQUEST_DATE_FORMAT), "to": get_next_month(start_date).strftime(REQUEST_DATE_FORMAT) if start_date.month != end_date.month else end_date.strftime(REQUEST_DATE_FORMAT), } if next_page_token: params["next_page_token"] = next_page_token first_page = False demisto.debug(f"Sending HTTP request to /report/{log_type} with params: {params}") response = self.error_handled_http_request( method="GET", url_suffix=f"report/{log_type}", headers={"authorization": f"Bearer {self.access_token}"}, params=params, ) logs = response.get(LOG_TYPES.get(log_type)) for i, log in enumerate(logs): log_time = log.get("time") if not i and first_page: next_last_time = log_time # save the latest time if last_time and last_time == log_time: # no more results limit = True break results.append(log) if limit: break if not (next_page_token := response.get("next_page_token")): start_date = get_next_month(start_date) + timedelta(days=1) demisto.debug(f"Last run after the fetch run: {next_last_time} for {log_type}") return next_last_time, results def test_module(client: Client) -> str: """ Tests API connectivity and authentication' When 'ok' is returned it indicates the integration works like it is supposed to and connection to the service is successful. Raises exceptions if something goes wrong. Args: client (Client): Zoom client to use. Returns: str: 'ok' if test passed, anything else will raise an exception and will fail the test. """ try: client.search_events(log_type=next(iter(LOG_TYPES)), limit=1, first_fetch_time=datetime.now(UTC)) except DemistoException as e: error_message = e.message if "Invalid access token" in error_message: error_message = INVALID_CREDENTIALS elif "The Token's Signature resulted invalid" in error_message: error_message = INVALID_API_SECRET elif "Invalid client_id or client_secret" in error_message: error_message = INVALID_ID_OR_SECRET else: error_message = f"Problem reaching Zoom API, check your credentials. Error message: {error_message}" return error_message return "ok" def get_events( client: Client, first_fetch_time: datetime, limit: int = MAX_RECORDS_PER_PAGE ) -> tuple[list[dict[str, Any]], CommandResults]: """ Gets all the events from the Zoom API for each log type. Args: client (Client): Zoom client to use. limit: int, the limit of the results to return per log_type. first_fetch_time(datetime): If last_run is None (first time we are fetching), it contains the timestamp in milliseconds on when to start fetching events. Returns: list: A list containing the events CommandResults: A CommandResults object that contains the events in a table format. """ events: list[dict] = [] if limit > MAX_RECORDS_PER_PAGE: raise DemistoException( f"The requested limit ({limit}) exceeds the maximum number of records per page ({MAX_RECORDS_PER_PAGE})." f" Please reduce the limit and try again." ) hr = "" for log_type in LOG_TYPES: _, log_events = client.search_events(log_type=log_type, limit=limit, first_fetch_time=first_fetch_time) if log_events: hr += tableToMarkdown(name=f"{log_type} Events", t=log_events) events.extend(log_events) else: hr += f"No events found for {log_type}.\n" return events, CommandResults(readable_output=hr) def fetch_events( client: Client, last_run: dict[str, str], first_fetch_time: datetime ) -> tuple[dict[str, str], list[dict[str, Any]]]: """ This function retrieves new alerts every interval (default is 1 minute). It will use last_run to save the timestamp of the last event it processed. If last_run is not provided, it should use the integration parameter first_fetch_time to determine when to start fetching the first time. Args: client (Client): Zoom client to use. last_run (dict): A dict with a key containing the latest event time we got from last fetch. first_fetch_time(datetime): If last_run is None (first time we are fetching), it contains the timestamp in milliseconds on when to start fetching events. Returns: dict: Next run dictionary containing the timestamp that will be used in ``last_run`` on the next fetch. list: List of events that will be created in XSIAM. """ next_run: dict[str, str] = {} events = [] for log_type in LOG_TYPES: next_run_time, log_events = client.search_events( log_type=log_type, last_time=last_run.get(log_type, ""), first_fetch_time=first_fetch_time, ) next_run[log_type] = next_run_time demisto.debug(f"Received {len(log_events)} events for log type {log_type}") log_events = [event | {"event_type": EVENT_TYPE.get(log_type)} for event in log_events] events.extend(log_events) demisto.debug(f"Returning {len(events)} events in total") return next_run, events def get_next_month(date_obj: datetime) -> datetime: """ Given a datetime object, returns the datetime object of the next month on the same day. Args: date_obj (datetime): A datetime object representing the input date. Returns: datetime: A datetime object representing the date of the next month on the same day. Examples: get_next_month(datetime(2021, 12, 3)) -> 2022-01-03 00:00:00 """ return date_obj + relativedelta.relativedelta(months=1) def call_send_events_to_xsiam(events): """Enhances and sends events to XSIAM""" for event in events: event["_time"] = event.get("time") send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) """ MAIN FUNCTION """ def main() -> None: """ main function, parses params and runs command functions """ params = demisto.params() args = demisto.args() base_url = params.get("url") account_id = params.get("account_id") client_id = params.get("credentials", {}).get("identifier") client_secret = params.get("credentials", {}).get("password") verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) command = demisto.command() demisto.debug(f"Command being called is {command}") try: # How much time before the first fetch to retrieve events first_fetch_time = params.get("first_fetch", "3 days") first_fetch_datetime: datetime = arg_to_datetime(arg=first_fetch_time, arg_name="First fetch time", required=True) # type: ignore[assignment] if first_fetch_time == "6 months": first_fetch_datetime += timedelta(days=1) if first_fetch_datetime <= dateparser.parse("6 months", settings={"TIMEZONE": "UTC"}): # type: ignore[operator] raise DemistoException( "The First fetch time should fall within the last six months. " "Please provide a valid date within the last six months." ) demisto.info(f"First fetch timestamp: {first_fetch_datetime}") client = Client( base_url=base_url, verify=verify_certificate, proxy=proxy, account_id=account_id, client_id=client_id, client_secret=client_secret, ) if command == "test-module": # This is the call made when pressing the integration Test button. result = test_module(client) return_results(result) elif command == "zoom-get-events": events, results = get_events( client=client, limit=arg_to_number(args.get("limit")) or MAX_RECORDS_PER_PAGE, first_fetch_time=first_fetch_datetime.replace(tzinfo=UTC), ) return_results(results) if argToBoolean(args.pop("should_push_events")): call_send_events_to_xsiam(events) elif command == "fetch-events": last_run = demisto.getLastRun() next_run, events = fetch_events( client=client, last_run=last_run, first_fetch_time=first_fetch_datetime.replace(tzinfo=UTC), ) call_send_events_to_xsiam(events) # saves next_run for the time fetch-events is invoked demisto.debug(f"Set last run to {next_run}") demisto.setLastRun(next_run) # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()