From eee9def2632f1564afcd335721f205239ac83d28 Mon Sep 17 00:00:00 2001 From: Arabindaksha-Mishra Date: Wed, 15 Apr 2026 06:00:21 +0000 Subject: [PATCH 1/9] Initial Commit --- .../connectors/PagerDutyConnector.py | 33 +- .../connectors/PagerDutyConnector.yaml | 28 ++ .../pager_duty/core/PagerDutyManager.py | 340 ++++++++++++++---- .../pager_duty/jobs/SyncIncidents.py | 256 +++++++++++++ .../pager_duty/jobs/SyncIncidents.yaml | 19 + .../community/pager_duty/pyproject.toml | 8 +- .../community/pager_duty/release_notes.yaml | 6 + 7 files changed, 608 insertions(+), 82 deletions(-) create mode 100644 content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py create mode 100644 content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml diff --git a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py index a243e9576..ecdc73b14 100644 --- a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py +++ b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py @@ -1,7 +1,9 @@ from __future__ import annotations +import datetime import sys import uuid +from typing import Any from soar_sdk.SiemplifyConnectors import SiemplifyConnectorExecution from soar_sdk.SiemplifyConnectorsDataModel import AlertInfo @@ -10,6 +12,7 @@ dict_to_flat, output_handler, ) +from TIPCommon.types import SingleJson from ..core.PagerDutyManager import PagerDutyManager @@ -19,8 +22,8 @@ @output_handler -def main(is_test_run): - processed_alerts = [] # The main output of each connector run +def main(is_test_run: bool) -> None: + processed_alerts: list[AlertInfo] = [] # The main output of each connector run siemplify = SiemplifyConnectorExecution() # Siemplify main SDK wrapper siemplify.script_name = CONNECTOR_NAME @@ -33,12 +36,32 @@ def main(is_test_run): api_key = siemplify.extract_connector_param(param_name="apiKey") acknowledge_enabled = siemplify.extract_connector_param(param_name="acknowledge") + max_hours_backwards = siemplify.extract_connector_param( + param_name="max_hours_backwards", + input_type=int, default_value=24, + ) + services = siemplify.extract_connector_param(param_name="Services") + proxy_address = siemplify.extract_connector_param(param_name="Proxy Server Address") + proxy_username = siemplify.extract_connector_param(param_name="Proxy Username") + proxy_password = siemplify.extract_connector_param(param_name="Proxy Password") siemplify.LOGGER.info("------------------- Main - Started -------------------") - manager = PagerDutyManager(api_key) + manager = PagerDutyManager( + api_key=api_key, + proxy_address=proxy_address, + proxy_username=proxy_username, + proxy_password=proxy_password, + ) try: - incidents_list = manager.list_incidents() + time_diff: datetime.timedelta = datetime.timedelta(hours=max_hours_backwards) + since: str = (datetime.datetime.utcnow() - time_diff).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ) + params: dict[str, Any] = {"since": since} + if services: + params["service_ids[]"] = [s.strip() for s in services.split(",") if s.strip()] + incidents_list: list[SingleJson] = manager.list_filtered_incidents(params=params) if incidents_list is None: siemplify.LOGGER.info( "No events were retrieved for the specified timeframe from PagerDuty", @@ -57,7 +80,7 @@ def main(is_test_run): siemplify.LOGGER.info(f"Added incident {alert_id} to package results") # `acknowledge_enabled` is a str, hence the str comparison below if acknowledge_enabled: - incident_got = manager.acknowledge_incident(alert_id) + incident_got = manager.acknowledge_incident(incident["id"]) siemplify.LOGGER.info( f"Incident {incident_got} acknowledged in PagerDuty", ) diff --git a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml index 28ac1bf4a..c62422a0f 100644 --- a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml +++ b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml @@ -16,6 +16,13 @@ parameters: is_mandatory: true is_advanced: false mode: script + - name: max_hours_backwards + default_value: '24' + type: integer + description: Max hours backwards to fetch incidents on first run. + is_mandatory: false + is_advanced: false + mode: script - name: DeviceProductField default_value: device_product type: string @@ -38,6 +45,27 @@ parameters: is_mandatory: true is_advanced: false mode: regular + - name: Proxy Server Address + default_value: '' + type: string + description: Address of the proxy server, if applicable. + is_mandatory: false + is_advanced: true + mode: script + - name: Proxy Username + default_value: 'null' + type: string + description: Username for proxy authentication. + is_mandatory: false + is_advanced: true + mode: script + - name: Proxy Password + default_value: 'null' + type: string + description: Password for proxy authentication. + is_mandatory: false + is_advanced: true + mode: script description: The connector pulls events from the Pagerduty API https://developer.pagerduty.com/api-reference/9d0b4b12e36f9-list-incidents. integration: PagerDuty rules: [ ] diff --git a/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py b/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py index 856a52a4c..356a304f2 100644 --- a/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py +++ b/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py @@ -1,48 +1,72 @@ from __future__ import annotations +from typing import Any from urllib.parse import quote_plus import requests +from TIPCommon.types import SingleJson class PagerDutyManager: - BASE_URL = "https://api.pagerduty.com" - INCIDENTS_URI = "/incidents" + BASE_URL: str = "https://api.pagerduty.com" + INCIDENTS_URI: str = "/incidents" - def __init__(self, api_key: str, verify_ssl: bool = False): - """Initializes PagerDutyManager with params as set in connector config""" - self.api_key = api_key - self.verify_ssl = verify_ssl + def __init__( + self, + api_key: str, + verify_ssl: bool = False, + proxy_address: str | None = None, + proxy_username: str | None = None, + proxy_password: str | None = None, + ) -> None: + """Initializes PagerDutyManager with params as set in connector config. - self.requests_session = requests.Session() - self.requests_session.verify = self.verify_ssl + Args: + api_key (str): PagerDuty API key. + verify_ssl (bool): Whether to verify SSL certificates. + proxy_address (str): Address of the proxy server. + proxy_username (str): Username for proxy authentication. + proxy_password (str): Password for proxy authentication. + """ + self.api_key: str = api_key + self.verify_ssl: bool = verify_ssl + + self.requests_session: requests.Session = requests.Session() + self.requests_session.verify: bool = self.verify_ssl - def test_connectivity(self): + def test_connectivity(self) -> None: """Tests connectivity and authentication to the PagerDuty API.""" - url = self.BASE_URL + "/abilities" - headers = { + url: str = self.BASE_URL + "/abilities" + headers: dict[str, str] = { "Accept": "application/vnd.pagerduty+json;version=2", "Authorization": f"Token token={self.api_key}", } - response = self.requests_session.get( + response: requests.Response = self.requests_session.get( url, headers=headers, timeout=10, ) response.raise_for_status() - def acknowledge_incident(self, incident_id): - """Acknowledges an incident in PagerDuty + def acknowledge_incident(self, incident_id: str) -> requests.Response: + """Acknowledges an incident in PagerDuty. + API Reference: https://developer.pagerduty.com/api-reference/8a0e1aa2ec666-update-an-incident + + Args: + incident_id (str): The ID of the incident to acknowledge. + + Returns: + requests.Response: The API response. """ - url = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" - data = {"statuses[]": "acknowledged"} - headers = { + url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" + data: dict[str, str] = {"statuses[]": "acknowledged"} + headers: dict[str, str] = { "Content-Type": "application/json", "Accept": "application/vnd.pagerduty+json;version=2", "Authorization": f"Token token={self.api_key}", } - response = requests.request( + response: requests.Response = requests.request( "GET", url, headers=headers, @@ -51,48 +75,159 @@ def acknowledge_incident(self, incident_id): ) return response - def list_oncalls(self): - url = f"{self.BASE_URL}/oncalls" + def resolve_incident(self, incident_id: str) -> SingleJson: + """Resolves an incident in PagerDuty. + + Args: + incident_id (str): The ID of the incident to resolve. + + Returns: + SingleJson: The API response. + """ + url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" + payload: SingleJson = { + "incident": { + "type": "incident", + "status": "resolved" + } + } + headers: dict[str, str] = self._get_auth_headers() + headers["Content-Type"] = "application/json" + response = self.requests_session.put( + url, + json=payload, + headers=headers, + timeout=10, + ) + response.raise_for_status() + return response.json() + + def add_incident_note(self, incident_id: str, content: str) -> SingleJson: + """Adds a note to an incident in PagerDuty. + + Args: + incident_id (str): The ID of the incident to add the note to. + content (str): The content of the note. + + Returns: + SingleJson: The API response. + """ + url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}/notes" + payload: SingleJson = { + "note": { + "content": content + } + } + headers: dict[str, str] = self._get_auth_headers() + headers["Content-Type"] = "application/json" + response = self.requests_session.post( + url, + json=payload, + headers=headers, + timeout=10, + ) + response.raise_for_status() + return response.json() + + def get_incident(self, incident_id: str) -> SingleJson: + """Gets an incident from PagerDuty by ID. + + Args: + incident_id (str): The ID of the incident to retrieve. + + Returns: + SingleJson: The incident details. + """ + url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" + response = self.requests_session.get( + url, + headers=self._get_auth_headers(), + timeout=10, + ) + response.raise_for_status() + return response.json().get("incident") + + def list_oncalls(self) -> list[SingleJson]: + """Lists on-calls. + + Returns: + list[SingleJson]: List of on-calls. + """ + url: str = f"{self.BASE_URL}/oncalls" response = self.requests_session.get( url=url, headers=self._get_auth_headers(), timeout=10 ) response.raise_for_status() return response.json().get("oncalls", []) - def get_all_incidents(self): + def get_all_incidents(self) -> list[SingleJson]: + """Gets all incidents. + + Returns: + list[SingleJson]: List of incidents. + """ self.requests_session.headers.update( {"Authorization": f"Token token={self.api_key}"}, ) - url = self.BASE_URL + "/incidents" - response = self.requests_session.get(url=url, timeout=10) + url: str = self.BASE_URL + "/incidents" + response: requests.Response = self.requests_session.get(url=url, timeout=10) response.raise_for_status() - incident_data = response.json() + incident_data: SingleJson = response.json() return incident_data.get("incidents") - def list_incidents(self): - url = f"{self.BASE_URL}/incidents" - response = self.requests_session.get( + def list_incidents(self) -> list[SingleJson] | str: + """Lists incidents. + + Returns: + list[SingleJson] | str: List of incidents or "No Incidents Found". + """ + url: str = f"{self.BASE_URL}/incidents" + response: requests.Response = self.requests_session.get( url=url, headers=self._get_auth_headers(), timeout=10 ) response.raise_for_status() - incidents = response.json().get("incidents") + incidents: list[SingleJson] = response.json().get("incidents") if incidents: return incidents return "No Incidents Found" - def list_users(self): - url = f"{self.BASE_URL}/users" - response = self.requests_session.get( + def list_users(self) -> list[SingleJson]: + """Lists users. + + Returns: + list[SingleJson]: List of users. + """ + url: str = f"{self.BASE_URL}/users" + response: requests.Response = self.requests_session.get( url=url, headers=self._get_auth_headers(), timeout=10 ) response.raise_for_status() return response.json().get("users", []) - def create_incident(self, email_from, title, service, urgency, body): + def create_incident( + self, + email_from: str, + title: str, + service: str, + urgency: str, + body: str, + ) -> SingleJson: + """Creates an incident. + + Args: + email_from (str): Email address of the user creating the incident. + title (str): Title of the incident. + service (str): Service ID. + urgency (str): Urgency level. + body (str): Incident details. + + Returns: + SingleJson: The created incident or message. + """ self.requests_session.headers.update( {"Authorization": f"Token token={self.api_key}", "From": f"{email_from}"}, ) - payload = { + payload: SingleJson = { "incident": { "type": "incident", "title": f"{title}", @@ -101,24 +236,37 @@ def create_incident(self, email_from, title, service, urgency, body): "body": {"type": "incident_body", "details": f"{body}"}, }, } - url = self.BASE_URL + "/incidents" + url: str = self.BASE_URL + "/incidents" response = self.requests_session.post(url=url, json=payload, timeout=10) + if response.status_code == 400: + raise Exception(f"400 Bad Request: {response.text}") response.raise_for_status() if response.json().get("incident_number") != 0: return response.json() return {"message": "No Incident Found"} - def get_incident_ID(self, incidentID, email_from): + def get_incident_ID(self, incidentID: str, email_from: str) -> SingleJson: + """Gets incident by ID. + + Args: + incidentID (str): Incident ID or key. + email_from (str): Email address. + + Returns: + SingleJson: Incident data. + """ self.requests_session.headers.update( {"Authorization": f"Token token={self.api_key}", "From": f"{email_from}"}, ) - parameters = {"user_ids[]": incidentID} - url = self.BASE_URL + self.INCIDENTS_URI - response = self.requests_session.get(url=url, json=parameters, timeout=10) + parameters: dict[str, str] = {"user_ids[]": incidentID} + url: str = self.BASE_URL + self.INCIDENTS_URI + response: requests.Response = self.requests_session.get( + url=url, json=parameters, timeout=10 + ) response.raise_for_status() - incident_data = {} - info_got = response.json().get("incidents") + incident_data: SingleJson = {} + info_got: list[SingleJson] = response.json().get("incidents") for incident in info_got: if incident.get("incident_key") == incidentID: @@ -126,27 +274,43 @@ def get_incident_ID(self, incidentID, email_from): return incident_data - def get_user_by_email(self, email): - url = f"{self.BASE_URL}/users" - params = {"query": email} - response = self.requests_session.get( + def get_user_by_email(self, email: str) -> SingleJson | str: + """Gets user by email. + + Args: + email (str): User email. + + Returns: + SingleJson | str: User dict or "No User Found". + """ + url: str = f"{self.BASE_URL}/users" + params: dict[str, str] = {"query": email} + response: requests.Response = self.requests_session.get( url=url, headers=self._get_auth_headers(), params=params, timeout=10 ) response.raise_for_status() - users = response.json().get("users", []) + users: list[SingleJson] = response.json().get("users", []) # The API returns a list, find the exact email match for user in users: if user.get("email") == email: return user return "No User Found" - def get_user_by_ID(self, userID): - headers = { + def get_user_by_ID(self, userID: str) -> SingleJson | str: + """Gets user by ID. + + Args: + userID (str): User ID. + + Returns: + SingleJson | str: User dict or "No User Found". + """ + headers: dict[str, str] = { "Content-Type": "application/json", "Accept": "application/vnd.pagerduty+json;version=2", "Authorization": f"Token token={self.api_key}", } - url = self.BASE_URL + "/users/" + userID + url: str = self.BASE_URL + "/users/" + userID response = self.requests_session.request( "GET", url, headers=headers, timeout=10 ) @@ -155,15 +319,20 @@ def get_user_by_ID(self, userID): return response.json()["user"] return "No User Found" - def list_filtered_incidents(self, params: dict): - base_url = self.BASE_URL + self.INCIDENTS_URI - headers = { - "Content-Type": "application/json", - "Accept": "application/vnd.pagerduty+json;version=2", - "Authorization": f"Token token={self.api_key}", - } + def list_filtered_incidents(self, params: dict[str, Any]) -> list[SingleJson]: + """Lists filtered incidents. + + Args: + params (dict[str, Any]): Filter parameters. - query_parts = [] + Returns: + list[SingleJson]: List of incidents. + """ + base_url: str = self.BASE_URL + self.INCIDENTS_URI + headers: dict[str, str] = self._get_auth_headers() + headers["Content-Type"] = "application/json" + + query_parts: list[str] = [] for key, value in params.items(): encoded_key = quote_plus(key) if isinstance(value, list): @@ -176,11 +345,11 @@ def list_filtered_incidents(self, params: dict): query_string = "&".join(query_parts) - full_url = base_url + full_url: str = base_url if query_string: full_url += f"?{query_string}" - response = self.requests_session.get( + response: requests.Response = self.requests_session.get( full_url, headers=headers, timeout=10, @@ -189,27 +358,48 @@ def list_filtered_incidents(self, params: dict): response.raise_for_status() return response.json().get("incidents") - def snooze_incident(self, email_from, incident_id): + def snooze_incident(self, email_from: str, incident_id: str) -> SingleJson: + """Snoozes an incident. + + Args: + email_from (str): Email address. + incident_id (str): Incident ID. + + Returns: + SingleJson: API response. + """ self.requests_session.headers.update( {"Authorization": f"Token token={self.api_key}", "From": f"{email_from}"}, ) - payload = {"duration": 3600} - url = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" + "/snooze" - response = self.requests_session.post(url=url, json=payload, timeout=10) + payload: dict[str, int] = {"duration": 3600} + url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" + "/snooze" + response: requests.Response = self.requests_session.post(url=url, json=payload, timeout=10) response.raise_for_status() return response.json() - def run_response_play(self, email, response_plays_id, user_id): - payload = {"incident": {"id": f"{user_id}", "type": "incident_reference"}} + def run_response_play( + self, + email: str, + response_plays_id: str, + user_id: str + ) -> SingleJson: + """Runs a response play. - headers = { - "Content-Type": "application/json", - "Accept": "application/vnd.pagerduty+json;version=2", - "From": f"{email}", - "Authorization": f"Token token={self.api_key}", - } + Args: + email (str): Email address. + response_plays_id (str): Response play ID. + user_id (str): User ID (used as incident ID in payload). - full_url = self.BASE_URL + "/response_plays/" + response_plays_id + "/run" + Returns: + SingleJson: API response message. + """ + payload: SingleJson = {"incident": {"id": f"{user_id}", "type": "incident_reference"}} + + headers: dict[str, str] = self._get_auth_headers() + headers["Content-Type"] = "application/json" + headers["From"] = f"{email}" + + full_url: str = self.BASE_URL + "/response_plays/" + response_plays_id + "/run" response = self.requests_session.request( "POST", full_url, @@ -220,8 +410,12 @@ def run_response_play(self, email, response_plays_id, user_id): response.raise_for_status() return {"message": response} - def _get_auth_headers(self): - """Returns a dictionary with standard authentication headers.""" + def _get_auth_headers(self) -> dict[str, str]: + """Returns a dictionary with standard authentication headers. + + Returns: + dict[str, str]: Auth headers. + """ return { "Accept": "application/vnd.pagerduty+json;version=2", "Authorization": f"Token token={self.api_key}", diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py new file mode 100644 index 000000000..5d0a7861b --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py @@ -0,0 +1,256 @@ +from __future__ import annotations + +import uuid +from typing import Any + +from TIPCommon.base.job.base_sync_job import BaseSyncJob +from TIPCommon.base.job.job_case import JobCase, JobStatusResult, SyncMetadata +from TIPCommon.data_models import AlertCard +from TIPCommon.types import SingleJson, SyncItem + +from ..core.PagerDutyManager import PagerDutyManager + + +def is_uuid(value: str) -> bool: + """Checks if a string value is a valid UUID.""" + if not isinstance(value, str): + return False + try: + uuid.UUID(value) + return True + except ValueError: + return False + + +class SyncIncidents(BaseSyncJob[PagerDutyManager]): + def __init__(self) -> None: + """Initializes the SyncIncidents job.""" + super().__init__( + job_name="PagerDuty Sync Incidents", + context_identifier="pagerduty_sync", + tags_identifiers=["Pagerduty Alert"], + ) + + def _init_api_clients(self) -> PagerDutyManager: + """Initializes the PagerDuty API client. + + Returns: + PagerDutyManager: The initialized API client. + """ + api_key: str = self.params.api_key + return PagerDutyManager(api_key=api_key) + + def _extract_product_id_from_alert(self, alert: AlertCard) -> str | None: + """Extracts PagerDuty Incident ID from a single alert. + + Args: + alert (AlertCard): The alert to extract the ID from. + + Returns: + str | None: The extracted PagerDuty Incident ID, or None. + """ + if alert.ticket_id is not None and not is_uuid(alert.ticket_id): + return alert.ticket_id + + if hasattr(alert, "alert_group_identifier") and alert.alert_group_identifier: + val: str | None = self.soar_job.get_context_property( + 2, # ENTITY_TYPE + alert.alert_group_identifier, + "Alert_ID", # CONTEXT_ALERT_ID_FIELD + ) + if val: + return val + + # Fallback: Extract from alert identifier if it ends with _ + if hasattr(alert, "identifier") and alert.identifier: + parts: list[str] = alert.identifier.split("_") + if len(parts) > 1: + potential_id: str = parts[-1] + if not is_uuid(potential_id) and potential_id.isalnum(): + self.logger.info( + f"Extracted incident ID {potential_id} from alert identifier " + f"{alert.identifier}" + ) + return potential_id + + return None + + def _extract_product_ids_from_case(self, job_case: JobCase) -> SyncItem: + """Extracts PagerDuty Incident IDs from the case alerts. + + Args: + job_case (JobCase): The case containing alerts. + + Returns: + SyncItem: A list of extracted incident IDs. + """ + incident_ids: list[str] = [] + for alert in job_case.case_detail.alerts: + incident_id: str | None = self._extract_product_id_from_alert(alert) + if incident_id: + incident_ids.append(incident_id) + + return incident_ids + + def map_product_data_to_case(self, job_case: JobCase) -> None: + """Maps product data to the case. + + Args: + job_case (JobCase): The case to map data to. + """ + for alert in job_case.case_detail.alerts: + incident_id: str | None = self._extract_product_id_from_alert(alert) + if incident_id: + try: + incident: SingleJson = self.api_client.get_incident(incident_id) + + job_case.alert_metadata[alert.identifier] = SyncMetadata( + status=incident.get("status"), + incident_number=incident_id, + closure_reason=None, + ) + except Exception as e: + self.logger.error( + f"Failed to fetch PagerDuty incident {incident_id}: {e}" + ) + + def sync_status(self, job_case: JobCase) -> None: + """Syncs status between PagerDuty and SecOps. + + Args: + job_case (JobCase): The case to sync status for. + """ + res: JobStatusResult = job_case.get_status_to_sync("resolved") + + synced_to_remove: list[tuple[str, str]] = [] + + # 1. Synchronise PagerDuty with Google SecOps Alerts statuses (SOAR to PagerDuty) + for req in res.incidents_to_close_in_product: + incident_id: str = req["meta"].incident_number + alert: AlertCard = req["alert"] + + reason: str = "Other" + if hasattr(alert, "closure_details") and alert.closure_details: + reason = alert.closure_details.get("reason", "Other") + + classification: str = "Other" + if reason == "Malicious": + classification = "True Positive" + elif reason == "Not Malicious": + classification = "False Positive" + + try: + self.api_client.add_incident_note(incident_id, f"Classification: {classification}") + self.api_client.resolve_incident(incident_id) + self.logger.info(f"Resolved PagerDuty incident {incident_id} with classification {classification}") + + if alert.status.lower() == "close": + synced_to_remove.append((str(job_case.case_detail.id_), incident_id)) + + except Exception as e: + self.logger.error( + f"Failed to resolve PagerDuty incident {incident_id}: {e}" + ) + + # 2. Synchronise Google SecOps Alerts with Pagerduty Incidents (PagerDuty to SOAR) + for alert, meta in res.alerts_to_close_in_soar: + reason_soar = "Resolved in PagerDuty" + + try: + open_alerts = [a for a in job_case.case_detail.alerts if a.status.lower() == "open"] + if len(open_alerts) == 1 and open_alerts[0].identifier == alert.identifier: + # It's the last open alert! Close the case. + self.soar_job.close_case( + root_cause="Other", + case_id=job_case.case_detail.id_, + reason=reason_soar, + comment=f"Closed case because last alert {alert.identifier} was resolved in PagerDuty.", + alert_identifier=None, + ) + self.logger.info(f"Closed case {job_case.case_detail.id_} as it was the last open alert.") + else: + self.sync_product_status_to_case( + case_id=job_case.case_detail.id_, + alert_id=alert.identifier, + reason=reason_soar, + root_cause="Other", + comment=f"PagerDuty incident {meta.incident_number} was resolved.", + ) + + if meta.status == "resolved": + synced_to_remove.append((str(job_case.case_detail.id_), meta.incident_number)) + except Exception as e: + self.logger.error( + f"Failed to close alert {alert.identifier} or case in SecOps: {e}" + ) + + # Remove synced entries if closed on either side + self._remove_synced_entries(synced_to_remove) + + def sync_comments(self, job_case: JobCase) -> None: + """Syncs comments. + + Args: + job_case (JobCase): The case to sync comments for. + """ + pass + + def sync_tags(self, job_case: JobCase) -> None: + """Syncs tags. + + Args: + job_case (JobCase): The case to sync tags for. + """ + pass + + def sync_severity(self, job_case: JobCase) -> None: + """Syncs severity. + + Args: + job_case (JobCase): The case to sync severity for. + """ + pass + + def sync_assignee(self, job_case: JobCase) -> None: + """Syncs assignee. + + Args: + job_case (JobCase): The case to sync assignee for. + """ + pass + + def is_alert_and_product_closed(self, job_case: JobCase, product: Any) -> bool: + """Checks if alert and product are closed. + + Args: + job_case (JobCase): The case. + product (Any): The product data. + + Returns: + bool: True if closed, False otherwise. + """ + # Assume product is SyncMetadata or similar + if isinstance(product, SyncMetadata): + return product.status == "resolved" + return False + + def remove_synced_data_from_db( + self, job_case: JobCase, product_details: Any + ) -> None: + """Removes synced data from DB. + + Args: + job_case (JobCase): The case. + product_details (Any): The product details. + """ + # Handled in sync_status directly or via _remove_synced_entries + pass + + +def main() -> None: + SyncIncidents().start() + + +if __name__ == "__main__": + main() + diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml new file mode 100644 index 000000000..e23e769e5 --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml @@ -0,0 +1,19 @@ +name: Sync Incidents +parameters: + - name: api_key + type: password + description: PagerDuty API Key + is_mandatory: true + - name: max_hours_backwards + type: integer + default_value: 24 + description: Max hours backwards to sync incidents on first run. + is_mandatory: false + - name: environment_name + type: string + default_value: Default + description: Environment name to sync cases from. + is_mandatory: false +description: Sync status between PagerDuty and SecOps. +integration: PagerDuty +creator: Admin diff --git a/content/response_integrations/third_party/community/pager_duty/pyproject.toml b/content/response_integrations/third_party/community/pager_duty/pyproject.toml index 9fd45d849..3271c162f 100644 --- a/content/response_integrations/third_party/community/pager_duty/pyproject.toml +++ b/content/response_integrations/third_party/community/pager_duty/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "PagerDuty" -version = "21.0" +version = "22.0" description = "PagerDuty helps developers, ITOps, DevOps and teams across the business provide a perfect digital experience to their customers, every time." requires-python = ">=3.11,<3.12" dependencies = [ "pdpyras==5.4.0", - "requests==2.32.3", + "requests==2.32.5", "tipcommon", "environmentcommon", ] @@ -20,8 +20,8 @@ dev = [ [tool.uv.sources] soar-sdk = { git = "https://github.com/chronicle/soar-sdk.git" } -integration-testing = { path = "../../../../../packages/integration_testing_whls/integration_testing-2.2.7-py3-none-any.whl" } -tipcommon = { path = "../../../../../packages/tipcommon/whls/TIPCommon-2.2.7-py2.py3-none-any.whl" } +integration-testing = { path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.5-py3-none-any.whl" } +tipcommon = { path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.5-py3-none-any.whl" } environmentcommon = { path = "../../../../../packages/envcommon/whls/EnvironmentCommon-1.0.2-py2.py3-none-any.whl" } [[tool.uv.index]] diff --git a/content/response_integrations/third_party/community/pager_duty/release_notes.yaml b/content/response_integrations/third_party/community/pager_duty/release_notes.yaml index 36360660c..84d32cc3e 100644 --- a/content/response_integrations/third_party/community/pager_duty/release_notes.yaml +++ b/content/response_integrations/third_party/community/pager_duty/release_notes.yaml @@ -82,3 +82,9 @@ item_type: Integration publish_time: '2026-03-18' ticket_number: '' +- description: SyncIncidents - New Job added. + integration_version: 22.0 + item_name: SyncIncidents + item_type: Job + publish_time: '2026-04-14' + ticket_number: '475592230' From a41ae2b7a5e2963f6442f193aaf902e91ceb81a4 Mon Sep 17 00:00:00 2001 From: Arabindaksha-Mishra Date: Wed, 15 Apr 2026 20:00:34 +0000 Subject: [PATCH 2/9] Code Formatting --- .../connectors/PagerDutyConnector.py | 67 ++++--- .../pager_duty/core/PagerDutyManager.py | 55 ++++-- .../community/pager_duty/core/constants.py | 9 + .../pager_duty/jobs/SyncIncidents.py | 120 +++++++----- .../pager_duty/jobs/SyncIncidents.yaml | 15 +- .../third_party/community/pager_duty/uv.lock | 177 +++++++++--------- 6 files changed, 268 insertions(+), 175 deletions(-) diff --git a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py index ecdc73b14..3c805fa57 100644 --- a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py +++ b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py @@ -2,7 +2,6 @@ import datetime import sys -import uuid from typing import Any from soar_sdk.SiemplifyConnectors import SiemplifyConnectorExecution @@ -14,6 +13,7 @@ ) from TIPCommon.types import SingleJson +from ..core.constants import SEVERITY_HIGH, SEVERITY_LOW from ..core.PagerDutyManager import PagerDutyManager CONNECTOR_NAME = "PagerDuty" @@ -23,8 +23,8 @@ @output_handler def main(is_test_run: bool) -> None: - processed_alerts: list[AlertInfo] = [] # The main output of each connector run - siemplify = SiemplifyConnectorExecution() # Siemplify main SDK wrapper + processed_alerts: list[AlertInfo] = [] + siemplify = SiemplifyConnectorExecution() siemplify.script_name = CONNECTOR_NAME if is_test_run: @@ -34,16 +34,21 @@ def main(is_test_run: bool) -> None: siemplify.LOGGER.info("----------------- Main - Param Init -----------------") - api_key = siemplify.extract_connector_param(param_name="apiKey") - acknowledge_enabled = siemplify.extract_connector_param(param_name="acknowledge") - max_hours_backwards = siemplify.extract_connector_param( + api_key: str = siemplify.extract_connector_param(param_name="apiKey") + acknowledge_enabled: str = siemplify.extract_connector_param( + param_name="acknowledge" + ) + max_hours_backwards: int = siemplify.extract_connector_param( param_name="max_hours_backwards", - input_type=int, default_value=24, + input_type=int, + default_value=24, + ) + services: str = siemplify.extract_connector_param(param_name="Services") + proxy_address: str = siemplify.extract_connector_param( + param_name="Proxy Server Address" ) - services = siemplify.extract_connector_param(param_name="Services") - proxy_address = siemplify.extract_connector_param(param_name="Proxy Server Address") - proxy_username = siemplify.extract_connector_param(param_name="Proxy Username") - proxy_password = siemplify.extract_connector_param(param_name="Proxy Password") + proxy_username: str = siemplify.extract_connector_param(param_name="Proxy Username") + proxy_password: str = siemplify.extract_connector_param(param_name="Proxy Password") siemplify.LOGGER.info("------------------- Main - Started -------------------") manager = PagerDutyManager( @@ -55,13 +60,17 @@ def main(is_test_run: bool) -> None: try: time_diff: datetime.timedelta = datetime.timedelta(hours=max_hours_backwards) - since: str = (datetime.datetime.utcnow() - time_diff).strftime( - "%Y-%m-%dT%H:%M:%SZ" - ) + since: str = ( + datetime.datetime.now(datetime.timezone.utc) - time_diff + ).strftime("%Y-%m-%dT%H:%M:%SZ") params: dict[str, Any] = {"since": since} if services: - params["service_ids[]"] = [s.strip() for s in services.split(",") if s.strip()] - incidents_list: list[SingleJson] = manager.list_filtered_incidents(params=params) + params["service_ids[]"] = [ + s.strip() for s in services.split(",") if s.strip() + ] + incidents_list: list[SingleJson] = manager.list_filtered_incidents( + params=params + ) if incidents_list is None: siemplify.LOGGER.info( "No events were retrieved for the specified timeframe from PagerDuty", @@ -69,16 +78,16 @@ def main(is_test_run: bool) -> None: return siemplify.LOGGER.info(f"Retrieved {len(incidents_list)} events from PagerDuty") for incident in incidents_list: - alert_id = incident["incident_key"] - # Map the severity in PagerDuty to the severity levels in Siemplify - severity = get_siemplify_mapped_severity(incident["urgency"]) + alert_id: str = incident["incident_key"] + + severity: str | None = get_siemplify_mapped_severity(incident["urgency"]) - siemplify_alert = build_alert_info(siemplify, incident, severity) + siemplify_alert: AlertInfo = build_alert_info(siemplify, incident, severity) if siemplify_alert: processed_alerts.append(siemplify_alert) siemplify.LOGGER.info(f"Added incident {alert_id} to package results") - # `acknowledge_enabled` is a str, hence the str comparison below + if acknowledge_enabled: incident_got = manager.acknowledge_incident(incident["id"]) siemplify.LOGGER.info( @@ -94,17 +103,19 @@ def main(is_test_run: bool) -> None: siemplify.return_package(processed_alerts) -def get_siemplify_mapped_severity(severity): - severity_map = {"high": "100", "low": "-1"} - return severity_map.get(severity) +def get_siemplify_mapped_severity(severity: str) -> str | None: + severity_map: dict[str, str] = {"high": SEVERITY_HIGH, "low": SEVERITY_LOW} + return severity_map.get(severity.lower()) if severity else None -def build_alert_info(siemplify, incident, severity): +def build_alert_info( + siemplify: SiemplifyConnectorExecution, incident: SingleJson, severity: str | None +) -> AlertInfo: """Returns an alert, which is an aggregation of basic events.""" - alert_info = AlertInfo() + alert_info: AlertInfo = AlertInfo() alert_info.display_id = incident["id"] - alert_info.ticket_id = str(uuid.uuid4()) - alert_info.name = "PagerDuty Incident: " + incident["title"] + alert_info.ticket_id = incident["id"] + alert_info.name = f"PagerDuty Incident: {incident['title']}" alert_info.rule_generator = incident["first_trigger_log_entry"]["summary"] alert_info.start_time = convert_string_to_unix_time(incident["created_at"]) alert_info.end_time = alert_info.start_time diff --git a/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py b/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py index 356a304f2..8db6d9cc9 100644 --- a/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py +++ b/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py @@ -14,26 +14,48 @@ class PagerDutyManager: def __init__( self, api_key: str, - verify_ssl: bool = False, + verify_ssl: bool = True, proxy_address: str | None = None, proxy_username: str | None = None, proxy_password: str | None = None, + from_email: str | None = None, ) -> None: """Initializes PagerDutyManager with params as set in connector config. Args: - api_key (str): PagerDuty API key. - verify_ssl (bool): Whether to verify SSL certificates. - proxy_address (str): Address of the proxy server. - proxy_username (str): Username for proxy authentication. - proxy_password (str): Password for proxy authentication. + api_key: PagerDuty API key. + verify_ssl: Whether to verify SSL certificates. + proxy_address: Address of the proxy server. + proxy_username: Username for proxy authentication. + proxy_password: Password for proxy authentication. + from_email: The email address of the user performing the action. """ self.api_key: str = api_key self.verify_ssl: bool = verify_ssl + self.proxy_address: str | None = proxy_address + self.proxy_username: str | None = proxy_username + self.proxy_password: str | None = proxy_password + self.from_email: str | None = from_email self.requests_session: requests.Session = requests.Session() self.requests_session.verify: bool = self.verify_ssl + if self.proxy_address: + if "://" not in self.proxy_address: + self.proxy_address = "http://" + self.proxy_address + from urllib.parse import urlparse + server_url = urlparse(self.proxy_address) + scheme = server_url.scheme + hostname = server_url.hostname + port = server_url.port + credentials = "" + if self.proxy_username and self.proxy_password: + credentials = f"{self.proxy_username}:{self.proxy_password}@" + proxy_str = f"{scheme}://{credentials}{hostname}" + if port: + proxy_str += f":{port}" + self.requests_session.proxies = {"http": proxy_str, "https": proxy_str} + def test_connectivity(self) -> None: """Tests connectivity and authentication to the PagerDuty API.""" url: str = self.BASE_URL + "/abilities" @@ -79,7 +101,7 @@ def resolve_incident(self, incident_id: str) -> SingleJson: """Resolves an incident in PagerDuty. Args: - incident_id (str): The ID of the incident to resolve. + incident_id: The ID of the incident to resolve. Returns: SingleJson: The API response. @@ -106,8 +128,8 @@ def add_incident_note(self, incident_id: str, content: str) -> SingleJson: """Adds a note to an incident in PagerDuty. Args: - incident_id (str): The ID of the incident to add the note to. - content (str): The content of the note. + incident_id: The ID of the incident to add the note to. + content: The content of the note. Returns: SingleJson: The API response. @@ -290,7 +312,7 @@ def get_user_by_email(self, email: str) -> SingleJson | str: ) response.raise_for_status() users: list[SingleJson] = response.json().get("users", []) - # The API returns a list, find the exact email match + for user in users: if user.get("email") == email: return user @@ -373,7 +395,9 @@ def snooze_incident(self, email_from: str, incident_id: str) -> SingleJson: ) payload: dict[str, int] = {"duration": 3600} url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" + "/snooze" - response: requests.Response = self.requests_session.post(url=url, json=payload, timeout=10) + response: requests.Response = self.requests_session.post( + url=url, json=payload, timeout=10 + ) response.raise_for_status() return response.json() @@ -393,7 +417,9 @@ def run_response_play( Returns: SingleJson: API response message. """ - payload: SingleJson = {"incident": {"id": f"{user_id}", "type": "incident_reference"}} + payload: SingleJson = { + "incident": {"id": f"{user_id}", "type": "incident_reference"} + } headers: dict[str, str] = self._get_auth_headers() headers["Content-Type"] = "application/json" @@ -416,7 +442,10 @@ def _get_auth_headers(self) -> dict[str, str]: Returns: dict[str, str]: Auth headers. """ - return { + headers: dict[str, str] = { "Accept": "application/vnd.pagerduty+json;version=2", "Authorization": f"Token token={self.api_key}", } + if self.from_email: + headers["From"] = self.from_email + return headers diff --git a/content/response_integrations/third_party/community/pager_duty/core/constants.py b/content/response_integrations/third_party/community/pager_duty/core/constants.py index 6b0930281..c003b2a0e 100644 --- a/content/response_integrations/third_party/community/pager_duty/core/constants.py +++ b/content/response_integrations/third_party/community/pager_duty/core/constants.py @@ -11,3 +11,12 @@ SCRIPT_NAME_RUN_RESPONSE = "_Run_Response_Play" SCRIPT_NAME_PING = "_Ping" SCRIPT_NAME_SNOOZE = "_SnoozeIncident" + +REASON_MALICIOUS: str = "Malicious" +REASON_NOT_MALICIOUS: str = "Not Malicious" +CLASSIFICATION_TRUE_POSITIVE: str = "True Positive" +CLASSIFICATION_FALSE_POSITIVE: str = "False Positive" +CLASSIFICATION_OTHER: str = "Other" +REASON_RESOLVED_IN_PAGERDUTY: str = "Resolved in PagerDuty" +SEVERITY_HIGH: str = "80" +SEVERITY_LOW: str = "40" diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py index 5d0a7861b..e765edfd3 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re import uuid from typing import Any @@ -8,6 +9,14 @@ from TIPCommon.data_models import AlertCard from TIPCommon.types import SingleJson, SyncItem +from ..core.constants import ( + CLASSIFICATION_FALSE_POSITIVE, + CLASSIFICATION_OTHER, + CLASSIFICATION_TRUE_POSITIVE, + REASON_MALICIOUS, + REASON_NOT_MALICIOUS, + REASON_RESOLVED_IN_PAGERDUTY, +) from ..core.PagerDutyManager import PagerDutyManager @@ -22,6 +31,9 @@ def is_uuid(value: str) -> bool: return False +ENTITY_TYPE_ALERT = 2 + + class SyncIncidents(BaseSyncJob[PagerDutyManager]): def __init__(self) -> None: """Initializes the SyncIncidents job.""" @@ -37,8 +49,13 @@ def _init_api_clients(self) -> PagerDutyManager: Returns: PagerDutyManager: The initialized API client. """ - api_key: str = self.params.api_key - return PagerDutyManager(api_key=api_key) + verify_ssl: bool = getattr(self.params, "verify_ssl", True) + from_email: str | None = getattr(self.params, "from_email", None) + return PagerDutyManager( + api_key=self.params.api_key, + verify_ssl=verify_ssl, + from_email=from_email, + ) def _extract_product_id_from_alert(self, alert: AlertCard) -> str | None: """Extracts PagerDuty Incident ID from a single alert. @@ -54,19 +71,18 @@ def _extract_product_id_from_alert(self, alert: AlertCard) -> str | None: if hasattr(alert, "alert_group_identifier") and alert.alert_group_identifier: val: str | None = self.soar_job.get_context_property( - 2, # ENTITY_TYPE + ENTITY_TYPE_ALERT, alert.alert_group_identifier, - "Alert_ID", # CONTEXT_ALERT_ID_FIELD + "Alert_ID", ) if val: return val - - # Fallback: Extract from alert identifier if it ends with _ + if hasattr(alert, "identifier") and alert.identifier: parts: list[str] = alert.identifier.split("_") if len(parts) > 1: potential_id: str = parts[-1] - if not is_uuid(potential_id) and potential_id.isalnum(): + if re.match(r"^[A-Z0-9]{14}$", potential_id): self.logger.info( f"Extracted incident ID {potential_id} from alert identifier " f"{alert.identifier}" @@ -89,7 +105,7 @@ def _extract_product_ids_from_case(self, job_case: JobCase) -> SyncItem: incident_id: str | None = self._extract_product_id_from_alert(alert) if incident_id: incident_ids.append(incident_id) - + return incident_ids def map_product_data_to_case(self, job_case: JobCase) -> None: @@ -103,7 +119,7 @@ def map_product_data_to_case(self, job_case: JobCase) -> None: if incident_id: try: incident: SingleJson = self.api_client.get_incident(incident_id) - + job_case.alert_metadata[alert.identifier] = SyncMetadata( status=incident.get("status"), incident_number=incident_id, @@ -121,53 +137,70 @@ def sync_status(self, job_case: JobCase) -> None: job_case (JobCase): The case to sync status for. """ res: JobStatusResult = job_case.get_status_to_sync("resolved") - + synced_to_remove: list[tuple[str, str]] = [] - - # 1. Synchronise PagerDuty with Google SecOps Alerts statuses (SOAR to PagerDuty) + for req in res.incidents_to_close_in_product: incident_id: str = req["meta"].incident_number alert: AlertCard = req["alert"] - - reason: str = "Other" + + reason: str = CLASSIFICATION_OTHER if hasattr(alert, "closure_details") and alert.closure_details: - reason = alert.closure_details.get("reason", "Other") - - classification: str = "Other" - if reason == "Malicious": - classification = "True Positive" - elif reason == "Not Malicious": - classification = "False Positive" - + reason = alert.closure_details.get("reason", CLASSIFICATION_OTHER) + + classification: str = CLASSIFICATION_OTHER + if reason == REASON_MALICIOUS: + classification = CLASSIFICATION_TRUE_POSITIVE + elif reason == REASON_NOT_MALICIOUS: + classification = CLASSIFICATION_FALSE_POSITIVE + try: - self.api_client.add_incident_note(incident_id, f"Classification: {classification}") + note_content = ( + f"Classification: {classification}\n" + f"Incident closed from Google SecOps" + ) + self.api_client.add_incident_note(incident_id, note_content) self.api_client.resolve_incident(incident_id) - self.logger.info(f"Resolved PagerDuty incident {incident_id} with classification {classification}") - + self.logger.info( + f"Resolved PagerDuty incident {incident_id} " + f"with classification {classification}" + ) + if alert.status.lower() == "close": - synced_to_remove.append((str(job_case.case_detail.id_), incident_id)) - + synced_to_remove.append( + (str(job_case.case_detail.id_), incident_id) + ) + except Exception as e: self.logger.error( f"Failed to resolve PagerDuty incident {incident_id}: {e}" ) - - # 2. Synchronise Google SecOps Alerts with Pagerduty Incidents (PagerDuty to SOAR) + for alert, meta in res.alerts_to_close_in_soar: - reason_soar = "Resolved in PagerDuty" - + reason_soar = REASON_RESOLVED_IN_PAGERDUTY + try: - open_alerts = [a for a in job_case.case_detail.alerts if a.status.lower() == "open"] - if len(open_alerts) == 1 and open_alerts[0].identifier == alert.identifier: - # It's the last open alert! Close the case. + open_alerts = [ + a for a in job_case.case_detail.alerts if a.status.lower() == "open" + ] + if ( + len(open_alerts) == 1 + and open_alerts[0].identifier == alert.identifier + ): self.soar_job.close_case( - root_cause="Other", + root_cause=CLASSIFICATION_OTHER, case_id=job_case.case_detail.id_, reason=reason_soar, - comment=f"Closed case because last alert {alert.identifier} was resolved in PagerDuty.", + comment=( + f"Closed case because last alert {alert.identifier} was " + "resolved in PagerDuty." + ), alert_identifier=None, ) - self.logger.info(f"Closed case {job_case.case_detail.id_} as it was the last open alert.") + self.logger.info( + f"Closed case {job_case.case_detail.id_} as it was the last " + "open alert." + ) else: self.sync_product_status_to_case( case_id=job_case.case_detail.id_, @@ -176,16 +209,18 @@ def sync_status(self, job_case: JobCase) -> None: root_cause="Other", comment=f"PagerDuty incident {meta.incident_number} was resolved.", ) - + if meta.status == "resolved": - synced_to_remove.append((str(job_case.case_detail.id_), meta.incident_number)) + synced_to_remove.append( + (str(job_case.case_detail.id_), meta.incident_number) + ) except Exception as e: self.logger.error( f"Failed to close alert {alert.identifier} or case in SecOps: {e}" ) - # Remove synced entries if closed on either side self._remove_synced_entries(synced_to_remove) + self.logger.info(f"Case {job_case.case_detail.id_} successfully synced.") def sync_comments(self, job_case: JobCase) -> None: """Syncs comments. @@ -229,7 +264,7 @@ def is_alert_and_product_closed(self, job_case: JobCase, product: Any) -> bool: Returns: bool: True if closed, False otherwise. """ - # Assume product is SyncMetadata or similar + if isinstance(product, SyncMetadata): return product.status == "resolved" return False @@ -243,7 +278,7 @@ def remove_synced_data_from_db( job_case (JobCase): The case. product_details (Any): The product details. """ - # Handled in sync_status directly or via _remove_synced_entries + pass @@ -253,4 +288,3 @@ def main() -> None: if __name__ == "__main__": main() - diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml index e23e769e5..8a6287a35 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml @@ -11,9 +11,20 @@ parameters: is_mandatory: false - name: environment_name type: string - default_value: Default + default_value: Default Environment description: Environment name to sync cases from. is_mandatory: false -description: Sync status between PagerDuty and SecOps. + - name: verify_ssl + type: boolean + default_value: true + description: Verify SSL + is_mandatory: false + - name: from_email + type: string + description: PagerDuty From Email (Required for notes and status changes) + is_mandatory: false + +description: | + This job synchronizes Google SecOps Alerts and PagerDuty Incidents . It ensures that status is synchronized bi-directionally between both systems. For the job to identify the correct information, the Google SecOps case must have the "Pagerduty Alert" tag. If the alert didn’t originate from "Pagerduty Connector", you will need to add an "Alert_ID" context value to the alert for the job to be able to find the correct information. integration: PagerDuty creator: Admin diff --git a/content/response_integrations/third_party/community/pager_duty/uv.lock b/content/response_integrations/third_party/community/pager_duty/uv.lock index 91d894a60..c1bbb3bdf 100644 --- a/content/response_integrations/third_party/community/pager_duty/uv.lock +++ b/content/response_integrations/third_party/community/pager_duty/uv.lock @@ -93,15 +93,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] -[[package]] -name = "cachetools" -version = "5.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, -] - [[package]] name = "certifi" version = "2025.6.15" @@ -113,25 +104,26 @@ wheels = [ [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, ] [[package]] @@ -176,43 +168,47 @@ wheels = [ [[package]] name = "cryptography" -version = "45.0.4" +version = "46.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890, upload-time = "2025-06-10T00:03:51.297Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/1c/92637793de053832523b410dbe016d3f5c11b41d0cf6eef8787aabb51d41/cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", size = 7055712, upload-time = "2025-06-10T00:02:38.826Z" }, - { url = "https://files.pythonhosted.org/packages/ba/14/93b69f2af9ba832ad6618a03f8a034a5851dc9a3314336a3d71c252467e1/cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", size = 4205335, upload-time = "2025-06-10T00:02:41.64Z" }, - { url = "https://files.pythonhosted.org/packages/67/30/fae1000228634bf0b647fca80403db5ca9e3933b91dd060570689f0bd0f7/cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", size = 4431487, upload-time = "2025-06-10T00:02:43.696Z" }, - { url = "https://files.pythonhosted.org/packages/6d/5a/7dffcf8cdf0cb3c2430de7404b327e3db64735747d641fc492539978caeb/cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e", size = 4208922, upload-time = "2025-06-10T00:02:45.334Z" }, - { url = "https://files.pythonhosted.org/packages/c6/f3/528729726eb6c3060fa3637253430547fbaaea95ab0535ea41baa4a6fbd8/cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", size = 3900433, upload-time = "2025-06-10T00:02:47.359Z" }, - { url = "https://files.pythonhosted.org/packages/d9/4a/67ba2e40f619e04d83c32f7e1d484c1538c0800a17c56a22ff07d092ccc1/cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", size = 4464163, upload-time = "2025-06-10T00:02:49.412Z" }, - { url = "https://files.pythonhosted.org/packages/7e/9a/b4d5aa83661483ac372464809c4b49b5022dbfe36b12fe9e323ca8512420/cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", size = 4208687, upload-time = "2025-06-10T00:02:50.976Z" }, - { url = "https://files.pythonhosted.org/packages/db/b7/a84bdcd19d9c02ec5807f2ec2d1456fd8451592c5ee353816c09250e3561/cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", size = 4463623, upload-time = "2025-06-10T00:02:52.542Z" }, - { url = "https://files.pythonhosted.org/packages/d8/84/69707d502d4d905021cac3fb59a316344e9f078b1da7fb43ecde5e10840a/cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", size = 4332447, upload-time = "2025-06-10T00:02:54.63Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ee/d4f2ab688e057e90ded24384e34838086a9b09963389a5ba6854b5876598/cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", size = 4572830, upload-time = "2025-06-10T00:02:56.689Z" }, - { url = "https://files.pythonhosted.org/packages/70/d4/994773a261d7ff98034f72c0e8251fe2755eac45e2265db4c866c1c6829c/cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", size = 2932769, upload-time = "2025-06-10T00:02:58.467Z" }, - { url = "https://files.pythonhosted.org/packages/5a/42/c80bd0b67e9b769b364963b5252b17778a397cefdd36fa9aa4a5f34c599a/cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", size = 3410441, upload-time = "2025-06-10T00:03:00.14Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0b/2488c89f3a30bc821c9d96eeacfcab6ff3accc08a9601ba03339c0fd05e5/cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", size = 7031836, upload-time = "2025-06-10T00:03:01.726Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/8c584ed426093aac257462ae62d26ad61ef1cbf5b58d8b67e6e13c39960e/cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", size = 4195746, upload-time = "2025-06-10T00:03:03.94Z" }, - { url = "https://files.pythonhosted.org/packages/5c/7d/4b0ca4d7af95a704eef2f8f80a8199ed236aaf185d55385ae1d1610c03c2/cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", size = 4424456, upload-time = "2025-06-10T00:03:05.589Z" }, - { url = "https://files.pythonhosted.org/packages/1d/45/5fabacbc6e76ff056f84d9f60eeac18819badf0cefc1b6612ee03d4ab678/cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", size = 4198495, upload-time = "2025-06-10T00:03:09.172Z" }, - { url = "https://files.pythonhosted.org/packages/55/b7/ffc9945b290eb0a5d4dab9b7636706e3b5b92f14ee5d9d4449409d010d54/cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", size = 3885540, upload-time = "2025-06-10T00:03:10.835Z" }, - { url = "https://files.pythonhosted.org/packages/7f/e3/57b010282346980475e77d414080acdcb3dab9a0be63071efc2041a2c6bd/cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", size = 4452052, upload-time = "2025-06-10T00:03:12.448Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/ddc4ac2558bf2ef517a358df26f45bc774a99bf4653e7ee34b5e749c03e3/cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", size = 4198024, upload-time = "2025-06-10T00:03:13.976Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c0/85fa358ddb063ec588aed4a6ea1df57dc3e3bc1712d87c8fa162d02a65fc/cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", size = 4451442, upload-time = "2025-06-10T00:03:16.248Z" }, - { url = "https://files.pythonhosted.org/packages/33/67/362d6ec1492596e73da24e669a7fbbaeb1c428d6bf49a29f7a12acffd5dc/cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", size = 4325038, upload-time = "2025-06-10T00:03:18.4Z" }, - { url = "https://files.pythonhosted.org/packages/53/75/82a14bf047a96a1b13ebb47fb9811c4f73096cfa2e2b17c86879687f9027/cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", size = 4560964, upload-time = "2025-06-10T00:03:20.06Z" }, - { url = "https://files.pythonhosted.org/packages/cd/37/1a3cba4c5a468ebf9b95523a5ef5651244693dc712001e276682c278fc00/cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", size = 2924557, upload-time = "2025-06-10T00:03:22.563Z" }, - { url = "https://files.pythonhosted.org/packages/2a/4b/3256759723b7e66380397d958ca07c59cfc3fb5c794fb5516758afd05d41/cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", size = 3395508, upload-time = "2025-06-10T00:03:24.586Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ba/cf442ae99ef363855ed84b39e0fb3c106ac66b7a7703f3c9c9cfe05412cb/cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c", size = 3590512, upload-time = "2025-06-10T00:03:36.982Z" }, - { url = "https://files.pythonhosted.org/packages/28/9a/a7d5bb87d149eb99a5abdc69a41e4e47b8001d767e5f403f78bfaafc7aa7/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4", size = 4146899, upload-time = "2025-06-10T00:03:38.659Z" }, - { url = "https://files.pythonhosted.org/packages/17/11/9361c2c71c42cc5c465cf294c8030e72fb0c87752bacbd7a3675245e3db3/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349", size = 4388900, upload-time = "2025-06-10T00:03:40.233Z" }, - { url = "https://files.pythonhosted.org/packages/c0/76/f95b83359012ee0e670da3e41c164a0c256aeedd81886f878911581d852f/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8", size = 4146422, upload-time = "2025-06-10T00:03:41.827Z" }, - { url = "https://files.pythonhosted.org/packages/09/ad/5429fcc4def93e577a5407988f89cf15305e64920203d4ac14601a9dc876/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862", size = 4388475, upload-time = "2025-06-10T00:03:43.493Z" }, - { url = "https://files.pythonhosted.org/packages/99/49/0ab9774f64555a1b50102757811508f5ace451cf5dc0a2d074a4b9deca6a/cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d", size = 3337594, upload-time = "2025-06-10T00:03:45.523Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, + { url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" }, ] [[package]] @@ -267,7 +263,7 @@ wheels = [ [[package]] name = "google-api-python-client" -version = "2.174.0" +version = "2.188.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -276,36 +272,35 @@ dependencies = [ { name = "httplib2" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/fd/860fef0cf3edbad828e2ab4d2ddee5dfe8e595b6da748ac6c77e95bc7bef/google_api_python_client-2.174.0.tar.gz", hash = "sha256:9eb7616a820b38a9c12c5486f9b9055385c7feb18b20cbafc5c5a688b14f3515", size = 13127872, upload-time = "2025-06-25T19:27:12.977Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/d7/14613c7efbab5b428b400961f5dbac46ad9e019c44e1f3fd14d67c33111c/google_api_python_client-2.188.0.tar.gz", hash = "sha256:5c469db6614f071009e3e5bb8b6aeeccae3beb3647fa9c6cd97f0d551edde0b6", size = 14302906, upload-time = "2026-01-13T22:15:13.747Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/2d/4250b81e8f5309b58650660f403584db6f64067acac74475893a8f33348d/google_api_python_client-2.174.0-py3-none-any.whl", hash = "sha256:f695205ceec97bfaa1590a14282559c4109326c473b07352233a3584cdbf4b89", size = 13650466, upload-time = "2025-06-25T19:27:10.426Z" }, + { url = "https://files.pythonhosted.org/packages/33/67/a99a7d79d7a37a67cb8008f1d7dcedc46d29c6df5063aeb446112afd4aa4/google_api_python_client-2.188.0-py3-none-any.whl", hash = "sha256:3cad1b68f9d48b82b93d77927e8370a6f43f33d97848242601f14a93a1c70ef5", size = 14870005, upload-time = "2026-01-13T22:15:11.345Z" }, ] [[package]] name = "google-auth" -version = "2.40.3" +version = "2.47.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cachetools" }, { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/3c/ec64b9a275ca22fa1cd3b6e77fefcf837b0732c890aa32d2bd21313d9b33/google_auth-2.47.0.tar.gz", hash = "sha256:833229070a9dfee1a353ae9877dcd2dec069a8281a4e72e72f77d4a70ff945da", size = 323719, upload-time = "2026-01-06T21:55:31.045Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, + { url = "https://files.pythonhosted.org/packages/db/18/79e9008530b79527e0d5f79e7eef08d3b179b7f851cfd3a2f27822fbdfa9/google_auth-2.47.0-py3-none-any.whl", hash = "sha256:c516d68336bfde7cf0da26aab674a36fedcf04b37ac4edd59c597178760c3498", size = 234867, upload-time = "2026-01-06T21:55:28.6Z" }, ] [[package]] name = "google-auth-httplib2" -version = "0.2.0" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, { name = "httplib2" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842, upload-time = "2023-12-12T17:40:30.722Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/ad/c1f2b1175096a8d04cf202ad5ea6065f108d26be6fc7215876bde4a7981d/google_auth_httplib2-0.3.0.tar.gz", hash = "sha256:177898a0175252480d5ed916aeea183c2df87c1f9c26705d74ae6b951c268b0b", size = 11134, upload-time = "2025-12-15T22:13:51.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253, upload-time = "2023-12-12T17:40:13.055Z" }, + { url = "https://files.pythonhosted.org/packages/99/d5/3c97526c8796d3caf5f4b3bed2b05e8a7102326f00a334e7a438237f3b22/google_auth_httplib2-0.3.0-py3-none-any.whl", hash = "sha256:426167e5df066e3f5a0fc7ea18768c08e7296046594ce4c8c409c2457dd1f776", size = 9529, upload-time = "2025-12-15T22:13:51.048Z" }, ] [[package]] @@ -389,8 +384,8 @@ wheels = [ [[package]] name = "integration-testing" -version = "2.2.7" -source = { path = "../../../../../packages/integration_testing_whls/integration_testing-2.2.7-py3-none-any.whl" } +version = "2.3.5" +source = { path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.5-py3-none-any.whl" } dependencies = [ { name = "aiohttp" }, { name = "environmentcommon" }, @@ -401,7 +396,7 @@ dependencies = [ { name = "yarl" }, ] wheels = [ - { filename = "integration_testing-2.2.7-py3-none-any.whl", hash = "sha256:50d8f71e80162f4b312c1048d775fc4247cae15933b2d6954b5b941ed5555b53" }, + { filename = "integration_testing-2.3.5-py3-none-any.whl", hash = "sha256:ad74ec84cdd3245e09ef7edd89e132cf16064ad45ae76bb5ae0ba307a08ed05b" }, ] [package.metadata] @@ -453,7 +448,7 @@ wheels = [ [[package]] name = "pagerduty" -version = "21.0" +version = "22.0" source = { virtual = "." } dependencies = [ { name = "environmentcommon" }, @@ -474,13 +469,13 @@ dev = [ requires-dist = [ { name = "environmentcommon", path = "../../../../../packages/envcommon/whls/EnvironmentCommon-1.0.2-py2.py3-none-any.whl" }, { name = "pdpyras", specifier = "==5.4.0" }, - { name = "requests", specifier = "==2.32.3" }, - { name = "tipcommon", path = "../../../../../packages/tipcommon/whls/TIPCommon-2.2.7-py2.py3-none-any.whl" }, + { name = "requests", specifier = "==2.32.5" }, + { name = "tipcommon", path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.5-py3-none-any.whl" }, ] [package.metadata.requires-dev] dev = [ - { name = "integration-testing", path = "../../../../../packages/integration_testing_whls/integration_testing-2.2.7-py3-none-any.whl" }, + { name = "integration-testing", path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.5-py3-none-any.whl" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-json-report", specifier = ">=1.5.0" }, { name = "soar-sdk", git = "https://github.com/chronicle/soar-sdk.git" }, @@ -619,15 +614,15 @@ wheels = [ [[package]] name = "pyopenssl" -version = "25.1.0" +version = "25.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/8c/cd89ad05804f8e3c17dea8f178c3f40eeab5694c30e0c9f5bcd49f576fc3/pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b", size = 179937, upload-time = "2025-05-17T16:28:31.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/28/2659c02301b9500751f8d42f9a6632e1508aa5120de5e43042b8b30f8d5d/pyopenssl-25.1.0-py3-none-any.whl", hash = "sha256:2b11f239acc47ac2e5aca04fd7fa829800aeee22a2eb30d744572a157bd8a1ab", size = 56771, upload-time = "2025-05-17T16:28:29.197Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, ] [[package]] @@ -720,7 +715,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.3" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -728,9 +723,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] @@ -794,28 +789,32 @@ dependencies = [ [[package]] name = "tipcommon" -version = "2.2.7" -source = { path = "../../../../../packages/tipcommon/whls/TIPCommon-2.2.7-py2.py3-none-any.whl" } +version = "2.3.5" +source = { path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.5-py3-none-any.whl" } dependencies = [ { name = "google-api-python-client" }, { name = "google-auth" }, { name = "google-auth-httplib2" }, { name = "httpx" }, { name = "pycryptodome" }, + { name = "pyopenssl" }, { name = "requests" }, + { name = "requests-toolbelt" }, ] wheels = [ - { filename = "tipcommon-2.2.7-py2.py3-none-any.whl", hash = "sha256:e814f0ffa0b177aac36b49aee479bbafdce756bed5eedf9955f4d3d0d5e7fa85" }, + { filename = "tipcommon-2.3.5-py3-none-any.whl", hash = "sha256:c30c13dcec152d271d198e54801bf42e76e87ecfc4b3ba57544bec021f143e9f" }, ] [package.metadata] requires-dist = [ - { name = "google-api-python-client" }, - { name = "google-auth" }, - { name = "google-auth-httplib2" }, - { name = "httpx" }, - { name = "pycryptodome" }, - { name = "requests" }, + { name = "google-api-python-client", specifier = "==2.188.0" }, + { name = "google-auth", specifier = "==2.47.0" }, + { name = "google-auth-httplib2", specifier = "==0.3.0" }, + { name = "httpx", specifier = "==0.28.1" }, + { name = "pycryptodome", specifier = "==3.23.0" }, + { name = "pyopenssl", specifier = "==25.3.0" }, + { name = "requests", specifier = "==2.32.5" }, + { name = "requests-toolbelt", specifier = "==1.0.0" }, ] [[package]] From c8e0862ba308626792e19a72b92551c38cfd507f Mon Sep 17 00:00:00 2001 From: Arabindaksha-Mishra Date: Thu, 16 Apr 2026 05:17:36 +0000 Subject: [PATCH 3/9] added unit tests --- .../connectors/PagerDutyConnector.yaml | 7 + .../pager_duty/jobs/SyncIncidents.py | 21 +- .../pager_duty/jobs/SyncIncidents.yaml | 1 + .../community/pager_duty/tests/conftest.py | 182 +++++++++++++++++- .../pager_duty/tests/core/product.py | 33 +++- .../pager_duty/tests/core/session.py | 34 ++++ .../tests/test_jobs/test_sync_incidents.py | 108 +++++++++++ 7 files changed, 373 insertions(+), 13 deletions(-) create mode 100644 content/response_integrations/third_party/community/pager_duty/tests/test_jobs/test_sync_incidents.py diff --git a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml index c62422a0f..32bfbadd8 100644 --- a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml +++ b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml @@ -23,6 +23,13 @@ parameters: is_mandatory: false is_advanced: false mode: script + - name: Services + default_value: '' + type: string + description: Comma-separated list of service IDs to filter incidents by. + is_mandatory: false + is_advanced: true + mode: script - name: DeviceProductField default_value: device_product type: string diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py index e765edfd3..4003139f8 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py @@ -47,7 +47,7 @@ def _init_api_clients(self) -> PagerDutyManager: """Initializes the PagerDuty API client. Returns: - PagerDutyManager: The initialized API client. + The initialized API client. """ verify_ssl: bool = getattr(self.params, "verify_ssl", True) from_email: str | None = getattr(self.params, "from_email", None) @@ -61,10 +61,10 @@ def _extract_product_id_from_alert(self, alert: AlertCard) -> str | None: """Extracts PagerDuty Incident ID from a single alert. Args: - alert (AlertCard): The alert to extract the ID from. + alert: The alert to extract the ID from. Returns: - str | None: The extracted PagerDuty Incident ID, or None. + The extracted PagerDuty Incident ID, or None. """ if alert.ticket_id is not None and not is_uuid(alert.ticket_id): return alert.ticket_id @@ -95,10 +95,10 @@ def _extract_product_ids_from_case(self, job_case: JobCase) -> SyncItem: """Extracts PagerDuty Incident IDs from the case alerts. Args: - job_case (JobCase): The case containing alerts. + job_case: The case containing alerts. Returns: - SyncItem: A list of extracted incident IDs. + A list of extracted incident IDs. """ incident_ids: list[str] = [] for alert in job_case.case_detail.alerts: @@ -112,7 +112,7 @@ def map_product_data_to_case(self, job_case: JobCase) -> None: """Maps product data to the case. Args: - job_case (JobCase): The case to map data to. + job_case: The case to map data to. """ for alert in job_case.case_detail.alerts: incident_id: str | None = self._extract_product_id_from_alert(alert) @@ -134,7 +134,7 @@ def sync_status(self, job_case: JobCase) -> None: """Syncs status between PagerDuty and SecOps. Args: - job_case (JobCase): The case to sync status for. + job_case: The case to sync status for. """ res: JobStatusResult = job_case.get_status_to_sync("resolved") @@ -176,12 +176,16 @@ def sync_status(self, job_case: JobCase) -> None: f"Failed to resolve PagerDuty incident {incident_id}: {e}" ) + closed_alert_ids: set[str] = set() for alert, meta in res.alerts_to_close_in_soar: reason_soar = REASON_RESOLVED_IN_PAGERDUTY try: open_alerts = [ - a for a in job_case.case_detail.alerts if a.status.lower() == "open" + a + for a in job_case.case_detail.alerts + if a.status.lower() == "open" + and a.identifier not in closed_alert_ids ] if ( len(open_alerts) == 1 @@ -209,6 +213,7 @@ def sync_status(self, job_case: JobCase) -> None: root_cause="Other", comment=f"PagerDuty incident {meta.incident_number} was resolved.", ) + closed_alert_ids.add(alert.identifier) if meta.status == "resolved": synced_to_remove.append( diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml index 8a6287a35..09383b2ab 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml @@ -24,6 +24,7 @@ parameters: description: PagerDuty From Email (Required for notes and status changes) is_mandatory: false + description: | This job synchronizes Google SecOps Alerts and PagerDuty Incidents . It ensures that status is synchronized bi-directionally between both systems. For the job to identify the correct information, the Google SecOps case must have the "Pagerduty Alert" tag. If the alert didn’t originate from "Pagerduty Connector", you will need to add an "Alert_ID" context value to the alert for the job to be able to find the correct information. integration: PagerDuty diff --git a/content/response_integrations/third_party/community/pager_duty/tests/conftest.py b/content/response_integrations/third_party/community/pager_duty/tests/conftest.py index 3c97ff3f5..0bf6b4c87 100644 --- a/content/response_integrations/third_party/community/pager_duty/tests/conftest.py +++ b/content/response_integrations/third_party/community/pager_duty/tests/conftest.py @@ -1,5 +1,9 @@ from __future__ import annotations +import io +import sys +from unittest.mock import MagicMock + import pytest from integration_testing.common import use_live_api @@ -11,6 +15,7 @@ @pytest.fixture def pagerduty() -> PagerDuty: + """Fixture providing a PagerDuty mock container.""" return PagerDuty() @@ -19,10 +24,185 @@ def script_session( monkeypatch: pytest.MonkeyPatch, pagerduty: PagerDuty, ) -> PagerDutySession: - """Mock PagerDuty scripts' session and get back an object to view request history""" + """Fixture to mock PagerDuty scripts' session and view request history.""" session: PagerDutySession = PagerDutySession(pagerduty) if not use_live_api(): monkeypatch.setattr("requests.Session", lambda: session) return session + + +@pytest.fixture +def mock_job_env(): + """Fixture to mock sys.stdin for job execution.""" + class MockStdin: + def __init__(self) -> None: + self.buffer: io.BytesIO = io.BytesIO(b'{"parameters": {}}') + + original_stdin = sys.stdin + sys.stdin = MockStdin() + yield + sys.stdin = original_stdin + + +@pytest.fixture +def job(mock_job_env): + """Fixture providing a SyncIncidents job instance with mocked properties.""" + from pager_duty.jobs.SyncIncidents import SyncIncidents + + original_params = SyncIncidents.params + original_api_client = SyncIncidents.api_client + original_logger = SyncIncidents.logger + original_soar_job = SyncIncidents.soar_job + original_sync_product_status_to_case = SyncIncidents.sync_product_status_to_case + + SyncIncidents.params = property(lambda self: MagicMock(api_key="test_key")) + + mock_logger = MagicMock() + SyncIncidents.logger = property(lambda self: mock_logger) + + mock_soar_job = MagicMock() + SyncIncidents.soar_job = property(lambda self: mock_soar_job) + + mock_sync_method = MagicMock() + SyncIncidents.sync_product_status_to_case = mock_sync_method + + job_instance = SyncIncidents() + client = job_instance._init_api_clients() + SyncIncidents.api_client = property(lambda self: client) + + job_instance._remove_synced_entries = MagicMock() + + yield job_instance + + SyncIncidents.params = original_params + SyncIncidents.api_client = original_api_client + SyncIncidents.logger = original_logger + SyncIncidents.soar_job = original_soar_job + SyncIncidents.sync_product_status_to_case = original_sync_product_status_to_case + + +@pytest.fixture +def job_failing_api(mock_job_env): + """Fixture providing a SyncIncidents job instance with a failing API client.""" + from pager_duty.jobs.SyncIncidents import SyncIncidents + + original_params = SyncIncidents.params + original_api_client = SyncIncidents.api_client + original_logger = SyncIncidents.logger + original_soar_job = SyncIncidents.soar_job + original_sync_product_status_to_case = SyncIncidents.sync_product_status_to_case + + SyncIncidents.params = property(lambda self: MagicMock(api_key="test_key")) + + mock_logger = MagicMock() + SyncIncidents.logger = property(lambda self: mock_logger) + + mock_soar_job = MagicMock() + SyncIncidents.soar_job = property(lambda self: mock_soar_job) + + mock_sync_method = MagicMock() + SyncIncidents.sync_product_status_to_case = mock_sync_method + + job_instance = SyncIncidents() + client = job_instance._init_api_clients() + + client.resolve_incident = MagicMock(side_effect=Exception("API Failure")) + + SyncIncidents.api_client = property(lambda self: client) + + job_instance._remove_synced_entries = MagicMock() + + yield job_instance + + SyncIncidents.params = original_params + SyncIncidents.api_client = original_api_client + SyncIncidents.logger = original_logger + SyncIncidents.soar_job = original_soar_job + SyncIncidents.sync_product_status_to_case = original_sync_product_status_to_case + + +@pytest.fixture +def job_case_map(): + """Fixture providing a JobCase mock for mapping tests.""" + from TIPCommon.base.job.job_case import JobCase + job_case = MagicMock(spec=JobCase) + alert = MagicMock() + alert.identifier = "alert_1" + alert.ticket_id = "P123" + job_case.case_detail.alerts = [alert] + job_case.alert_metadata = {} + return job_case + + +@pytest.fixture +def job_case_sync(): + """Fixture providing a JobCase mock for syncing status (SOAR to PagerDuty).""" + from TIPCommon.base.job.job_case import JobCase, SyncMetadata + job_case = MagicMock(spec=JobCase) + res = MagicMock() + + alert = MagicMock() + alert.status = "close" + alert.closure_details = {"reason": "Malicious"} + + meta = SyncMetadata(status="triggered", incident_number="P123", closure_reason=None) + + res.incidents_to_close_in_product = [{"meta": meta, "alert": alert}] + res.alerts_to_close_in_soar = [] + + job_case.get_status_to_sync.return_value = res + job_case.case_detail.id_ = 1 + + return job_case + + +@pytest.fixture +def job_case_sync_close_case(): + """Fixture providing a JobCase mock for syncing status (PagerDuty to SOAR, close case).""" + from TIPCommon.base.job.job_case import JobCase, SyncMetadata + job_case = MagicMock(spec=JobCase) + res = MagicMock() + + alert = MagicMock() + alert.identifier = "alert_1" + alert.status = "open" + + meta = SyncMetadata(status="resolved", incident_number="P123", closure_reason=None) + + res.incidents_to_close_in_product = [] + res.alerts_to_close_in_soar = [(alert, meta)] + + job_case.get_status_to_sync.return_value = res + job_case.case_detail.id_ = 1 + job_case.case_detail.alerts = [alert] + + return job_case + + +@pytest.fixture +def job_case_sync_close_alert(): + """Fixture providing a JobCase mock for syncing status (PagerDuty to SOAR, close alert only).""" + from TIPCommon.base.job.job_case import JobCase, SyncMetadata + job_case = MagicMock(spec=JobCase) + res = MagicMock() + + alert1 = MagicMock() + alert1.identifier = "alert_1" + alert1.status = "open" + + alert2 = MagicMock() + alert2.identifier = "alert_2" + alert2.status = "open" + + meta = SyncMetadata(status="resolved", incident_number="P123", closure_reason=None) + + res.incidents_to_close_in_product = [] + res.alerts_to_close_in_soar = [(alert1, meta)] + + job_case.get_status_to_sync.return_value = res + job_case.case_detail.id_ = 1 + job_case.case_detail.alerts = [alert1, alert2] + + return job_case diff --git a/content/response_integrations/third_party/community/pager_duty/tests/core/product.py b/content/response_integrations/third_party/community/pager_duty/tests/core/product.py index e15d1ce5b..fbd4021a9 100644 --- a/content/response_integrations/third_party/community/pager_duty/tests/core/product.py +++ b/content/response_integrations/third_party/community/pager_duty/tests/core/product.py @@ -7,15 +7,13 @@ @dataclasses.dataclass(slots=True) class PagerDuty: + """Mock data container for PagerDuty integration tests.""" incidents: SingleJson = dataclasses.field(default_factory=dict) users: SingleJson = dataclasses.field(default_factory=dict) snoozed_incidents: dict[str, SingleJson] = dataclasses.field(default_factory=dict) def get_incidents(self, params: SingleJson) -> SingleJson: - """ - Returns incidents, filtering them based on provided parameters to - simulate the real API behavior. - """ + """Get incidents, optionally filtered by incident_key.""" if not self.incidents.get("incidents"): return {"incidents": []} @@ -30,6 +28,7 @@ def get_incidents(self, params: SingleJson) -> SingleJson: return self.incidents def snooze_incident(self, incident_id: str) -> SingleJson: + """Simulate snoozing an incident.""" self.snoozed_incidents[incident_id] = { "status": "snoozed", "snooze_until": "2025-10-06T14:05:03Z" } @@ -38,11 +37,37 @@ def snooze_incident(self, incident_id: str) -> SingleJson: "incident": {"id": incident_id, "status": "snoozed", "snooze_until": until} } + def get_incident(self, incident_id: str) -> SingleJson: + """Get a specific incident by ID.""" + if not self.incidents.get("incidents"): + return {} + for inc in self.incidents["incidents"]: + if inc.get("id") == incident_id: + return inc + return {} + + def resolve_incident(self, incident_id: str) -> SingleJson: + """Resolve a specific incident by ID.""" + if not self.incidents.get("incidents"): + return {} + for inc in self.incidents["incidents"]: + if inc.get("id") == incident_id: + inc["status"] = "resolved" + return {"incident": inc} + return {} + + def add_incident_note(self, incident_id: str, content: str) -> SingleJson: + """Simulate adding a note to an incident.""" + return {"note": {"content": content}} + def set_incidents(self, mock_incidents: SingleJson) -> None: + """Set the mock incidents data.""" self.incidents = mock_incidents def get_users(self) -> SingleJson: + """Get all mock users.""" return self.users def set_users(self, mock_users: SingleJson) -> None: + """Set the mock users data.""" self.users = mock_users diff --git a/content/response_integrations/third_party/community/pager_duty/tests/core/session.py b/content/response_integrations/third_party/community/pager_duty/tests/core/session.py index 183e7238a..37834be1c 100644 --- a/content/response_integrations/third_party/community/pager_duty/tests/core/session.py +++ b/content/response_integrations/third_party/community/pager_duty/tests/core/session.py @@ -13,17 +13,24 @@ class PagerDutySession(MockSession[MockRequest, MockResponse, PagerDuty]): + """Mock session for PagerDuty integration tests, handling routing.""" + def get_routed_functions(self) -> Iterable[RouteFunction[Response]]: + """Get the list of routed functions.""" return [ self.list_incidents, self.test_connectivity, self.get_incident_by_key, self.list_users, self.snooze_incident, + self.get_incident_by_id, + self.resolve_incident_by_id, + self.add_incident_note, ] @router.get(r"/incidents") def list_incidents(self, request: MockRequest) -> MockResponse: + """Mock for listing incidents.""" auth_header = request.headers.get("Authorization", "") if "invalid_key" in auth_header: return MockResponse( @@ -96,3 +103,30 @@ def snooze_incident(self, request: MockRequest) -> MockResponse: ) return MockResponse(content=self._product.snooze_incident(incident_id)) + + @router.get(r"/incidents/(?P[^/]+)") + def get_incident_by_id(self, request: MockRequest) -> MockResponse: + """Mock for getting a single incident by ID.""" + incident_id = request.url.path.split("/")[-1] + incident = self._product.get_incident(incident_id) + if incident: + return MockResponse(content={"incident": incident}) + return MockResponse(status_code=404, content={"error": {"message": "Incident not found"}}) + + @router.put(r"/incidents/(?P[^/]+)") + def resolve_incident_by_id(self, request: MockRequest) -> MockResponse: + """Mock for resolving an incident.""" + incident_id = request.url.path.split("/")[-1] + res = self._product.resolve_incident(incident_id) + if res: + return MockResponse(content=res) + return MockResponse(status_code=404, content={"error": {"message": "Incident not found"}}) + + @router.post(r"/incidents/(?P[^/]+)/notes") + def add_incident_note(self, request: MockRequest) -> MockResponse: + """Mock for adding a note to an incident.""" + incident_id = request.url.path.split("/")[-2] + payload = get_request_payload(request) + content = payload.get("note", {}).get("content", "") + res = self._product.add_incident_note(incident_id, content) + return MockResponse(content=res) diff --git a/content/response_integrations/third_party/community/pager_duty/tests/test_jobs/test_sync_incidents.py b/content/response_integrations/third_party/community/pager_duty/tests/test_jobs/test_sync_incidents.py new file mode 100644 index 000000000..682ea8121 --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/tests/test_jobs/test_sync_incidents.py @@ -0,0 +1,108 @@ +from __future__ import annotations + +from pager_duty.tests.core.product import PagerDuty +from pager_duty.tests.core.session import PagerDutySession + + +def test_map_product_data_to_case_success( + script_session: PagerDutySession, + pagerduty: PagerDuty, + job, + job_case_map, +) -> None: + """Tests mapping product data to case successfully.""" + pagerduty.set_incidents({ + "incidents": [ + {"id": "P123", "status": "resolved", "incident_key": "key1"} + ] + }) + + job.map_product_data_to_case(job_case_map) + + assert len(script_session.request_history) == 1 + assert script_session.request_history[0].request.url.path.endswith("/incidents/P123") + assert "alert_1" in job_case_map.alert_metadata + assert job_case_map.alert_metadata["alert_1"].status == "resolved" + + +def test_map_product_data_to_case_not_found_success( + script_session: PagerDutySession, + pagerduty: PagerDuty, + job, + job_case_map, +) -> None: + """Tests mapping product data to case when incident is not found.""" + job.map_product_data_to_case(job_case_map) + + assert len(script_session.request_history) == 1 + assert script_session.request_history[0].request.url.path.endswith("/incidents/P123") + assert "alert_1" not in job_case_map.alert_metadata + + +def test_sync_status_soar_to_pagerduty_success( + script_session: PagerDutySession, + pagerduty: PagerDuty, + job, + job_case_sync, +) -> None: + """Tests syncing status from SOAR to PagerDuty successfully.""" + pagerduty.set_incidents({ + "incidents": [ + {"id": "P123", "status": "triggered", "incident_key": "key1"} + ] + }) + + job.sync_status(job_case_sync) + + assert len(script_session.request_history) == 2 + assert script_session.request_history[0].request.url.path.endswith("/incidents/P123/notes") + assert script_session.request_history[1].request.url.path.endswith("/incidents/P123") + + incident = pagerduty.get_incident("P123") + assert incident["status"] == "resolved" + + +def test_sync_status_pagerduty_to_soar_close_case_success( + script_session: PagerDutySession, + pagerduty: PagerDuty, + job, + job_case_sync_close_case, +) -> None: + """Tests syncing status from PagerDuty to SOAR resulting in closing the case.""" + job.sync_status(job_case_sync_close_case) + + assert job.soar_job.close_case.called + assert not job.sync_product_status_to_case.called + + +def test_sync_status_pagerduty_to_soar_close_alert_success( + script_session: PagerDutySession, + pagerduty: PagerDuty, + job, + job_case_sync_close_alert, +) -> None: + """Tests syncing status from PagerDuty to SOAR resulting in closing only the alert.""" + job.sync_status(job_case_sync_close_alert) + + assert job.sync_product_status_to_case.called + assert not job.soar_job.close_case.called + + +def test_sync_status_soar_to_pagerduty_failure( + script_session: PagerDutySession, + pagerduty: PagerDuty, + job_failing_api, + job_case_sync, +) -> None: + """Tests syncing status from SOAR to PagerDuty with API failure handling.""" + pagerduty.set_incidents({ + "incidents": [ + {"id": "P123", "status": "triggered", "incident_key": "key1"} + ] + }) + + job_failing_api.sync_status(job_case_sync) + + assert len(script_session.request_history) == 1 + assert script_session.request_history[0].request.url.path.endswith("/incidents/P123/notes") + assert job_failing_api.logger.error.called From 92e6f9252a358493beafb1cf412f800df01778d6 Mon Sep 17 00:00:00 2001 From: Arabindaksha-Mishra Date: Thu, 16 Apr 2026 05:55:55 +0000 Subject: [PATCH 4/9] fixed the json error --- .../community/pager_duty/jobs/SyncIncidents.yaml | 10 +++++----- .../FilteredIncidentList_JsonResult_example.json | 10 ++++++++++ .../GetUserByEmail_JsonResult_example.json | 7 +++++++ .../GetUserById_JsonResult_example.json | 7 +++++++ .../ListAllOncall_JsonResult_example.json | 16 ++++++++++++++++ .../ListIncidents_JsonResult_example.json | 10 ++++++++++ .../resources/ListUsers_JsonResult_example.json | 9 +++++++++ .../RunResponsePlay_JsonResult_example.json | 4 ++++ .../SnoozeIncident_JsonResult_example.json | 4 ++++ 9 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 content/response_integrations/third_party/community/pager_duty/resources/FilteredIncidentList_JsonResult_example.json create mode 100644 content/response_integrations/third_party/community/pager_duty/resources/GetUserByEmail_JsonResult_example.json create mode 100644 content/response_integrations/third_party/community/pager_duty/resources/GetUserById_JsonResult_example.json create mode 100644 content/response_integrations/third_party/community/pager_duty/resources/ListAllOncall_JsonResult_example.json create mode 100644 content/response_integrations/third_party/community/pager_duty/resources/ListIncidents_JsonResult_example.json create mode 100644 content/response_integrations/third_party/community/pager_duty/resources/ListUsers_JsonResult_example.json create mode 100644 content/response_integrations/third_party/community/pager_duty/resources/RunResponsePlay_JsonResult_example.json create mode 100644 content/response_integrations/third_party/community/pager_duty/resources/SnoozeIncident_JsonResult_example.json diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml index 09383b2ab..cd3ce92b3 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml @@ -9,11 +9,6 @@ parameters: default_value: 24 description: Max hours backwards to sync incidents on first run. is_mandatory: false - - name: environment_name - type: string - default_value: Default Environment - description: Environment name to sync cases from. - is_mandatory: false - name: verify_ssl type: boolean default_value: true @@ -23,6 +18,11 @@ parameters: type: string description: PagerDuty From Email (Required for notes and status changes) is_mandatory: false + - name: environment_name + type: string + default_value: Default Environment + description: Environment name to sync cases from. + is_mandatory: false description: | diff --git a/content/response_integrations/third_party/community/pager_duty/resources/FilteredIncidentList_JsonResult_example.json b/content/response_integrations/third_party/community/pager_duty/resources/FilteredIncidentList_JsonResult_example.json new file mode 100644 index 000000000..9a74b2b18 --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/resources/FilteredIncidentList_JsonResult_example.json @@ -0,0 +1,10 @@ +[ + { + "id": "Q1C5WQ0U8TSCXE", + "incident_number": 1, + "title": "TEST TEST server under attack", + "status": "triggered", + "created_at": "2023-04-03T09:33:53Z", + "urgency": "high" + } +] diff --git a/content/response_integrations/third_party/community/pager_duty/resources/GetUserByEmail_JsonResult_example.json b/content/response_integrations/third_party/community/pager_duty/resources/GetUserByEmail_JsonResult_example.json new file mode 100644 index 000000000..f067de95e --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/resources/GetUserByEmail_JsonResult_example.json @@ -0,0 +1,7 @@ +{ + "id": "PCRYIVB", + "name": "Gal Pk", + "email": "gal@example.com", + "role": "admin", + "type": "user" +} diff --git a/content/response_integrations/third_party/community/pager_duty/resources/GetUserById_JsonResult_example.json b/content/response_integrations/third_party/community/pager_duty/resources/GetUserById_JsonResult_example.json new file mode 100644 index 000000000..f067de95e --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/resources/GetUserById_JsonResult_example.json @@ -0,0 +1,7 @@ +{ + "id": "PCRYIVB", + "name": "Gal Pk", + "email": "gal@example.com", + "role": "admin", + "type": "user" +} diff --git a/content/response_integrations/third_party/community/pager_duty/resources/ListAllOncall_JsonResult_example.json b/content/response_integrations/third_party/community/pager_duty/resources/ListAllOncall_JsonResult_example.json new file mode 100644 index 000000000..60e9cdf21 --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/resources/ListAllOncall_JsonResult_example.json @@ -0,0 +1,16 @@ +[ + { + "user": { + "id": "PCRYIVB", + "summary": "Gal Pk" + }, + "schedule": { + "id": "P12345", + "summary": "Daily Schedule" + }, + "escalation_policy": { + "id": "PMTZZA8", + "summary": "Community OnCall-ep" + } + } +] diff --git a/content/response_integrations/third_party/community/pager_duty/resources/ListIncidents_JsonResult_example.json b/content/response_integrations/third_party/community/pager_duty/resources/ListIncidents_JsonResult_example.json new file mode 100644 index 000000000..9a74b2b18 --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/resources/ListIncidents_JsonResult_example.json @@ -0,0 +1,10 @@ +[ + { + "id": "Q1C5WQ0U8TSCXE", + "incident_number": 1, + "title": "TEST TEST server under attack", + "status": "triggered", + "created_at": "2023-04-03T09:33:53Z", + "urgency": "high" + } +] diff --git a/content/response_integrations/third_party/community/pager_duty/resources/ListUsers_JsonResult_example.json b/content/response_integrations/third_party/community/pager_duty/resources/ListUsers_JsonResult_example.json new file mode 100644 index 000000000..d0d857421 --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/resources/ListUsers_JsonResult_example.json @@ -0,0 +1,9 @@ +[ + { + "id": "PCRYIVB", + "name": "Gal Pk", + "email": "gal@example.com", + "role": "admin", + "type": "user" + } +] diff --git a/content/response_integrations/third_party/community/pager_duty/resources/RunResponsePlay_JsonResult_example.json b/content/response_integrations/third_party/community/pager_duty/resources/RunResponsePlay_JsonResult_example.json new file mode 100644 index 000000000..29df4c88e --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/resources/RunResponsePlay_JsonResult_example.json @@ -0,0 +1,4 @@ +{ + "status": "success", + "message": "Response play triggered" +} diff --git a/content/response_integrations/third_party/community/pager_duty/resources/SnoozeIncident_JsonResult_example.json b/content/response_integrations/third_party/community/pager_duty/resources/SnoozeIncident_JsonResult_example.json new file mode 100644 index 000000000..9d8aa9676 --- /dev/null +++ b/content/response_integrations/third_party/community/pager_duty/resources/SnoozeIncident_JsonResult_example.json @@ -0,0 +1,4 @@ +{ + "status": "success", + "message": "Incident snoozed" +} From a38c2be4ad3fed571c057bba4a99ef1222d15a32 Mon Sep 17 00:00:00 2001 From: Arabindaksha-Mishra Date: Thu, 16 Apr 2026 06:02:52 +0000 Subject: [PATCH 5/9] params name --- .../community/pager_duty/connectors/PagerDutyConnector.py | 2 +- .../pager_duty/connectors/PagerDutyConnector.yaml | 2 +- .../community/pager_duty/jobs/SyncIncidents.yaml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py index 3c805fa57..628e5cb78 100644 --- a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py +++ b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py @@ -39,7 +39,7 @@ def main(is_test_run: bool) -> None: param_name="acknowledge" ) max_hours_backwards: int = siemplify.extract_connector_param( - param_name="max_hours_backwards", + param_name="Max Hours Backwards", input_type=int, default_value=24, ) diff --git a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml index 32bfbadd8..1c9b3e0c8 100644 --- a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml +++ b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.yaml @@ -16,7 +16,7 @@ parameters: is_mandatory: true is_advanced: false mode: script - - name: max_hours_backwards + - name: Max Hours Backwards default_value: '24' type: integer description: Max hours backwards to fetch incidents on first run. diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml index cd3ce92b3..029f0b6cd 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml @@ -4,21 +4,21 @@ parameters: type: password description: PagerDuty API Key is_mandatory: true - - name: max_hours_backwards + - name: Max Hours Backwards type: integer default_value: 24 description: Max hours backwards to sync incidents on first run. is_mandatory: false - - name: verify_ssl + - name: Verify SSL type: boolean default_value: true description: Verify SSL is_mandatory: false - - name: from_email + - name: From Email type: string description: PagerDuty From Email (Required for notes and status changes) is_mandatory: false - - name: environment_name + - name: Environment Name type: string default_value: Default Environment description: Environment name to sync cases from. From 22853c7c1f04d0f30335421575bebe1edb3d1c77 Mon Sep 17 00:00:00 2001 From: Arabindaksha-Mishra Date: Fri, 17 Apr 2026 06:31:11 +0000 Subject: [PATCH 6/9] added feature to sync incidents from playbook cases --- .../community/pager_duty/actions/Ping.py | 9 +- .../connectors/PagerDutyConnector.py | 29 +- .../pager_duty/core/PagerDutyManager.py | 49 ++- .../community/pager_duty/definition.yaml | 6 + .../pager_duty/jobs/SyncIncidents.py | 372 ++++++++++++------ .../pager_duty/jobs/SyncIncidents.yaml | 2 +- .../community/pager_duty/pyproject.toml | 4 +- .../community/pager_duty/tests/conftest.py | 5 +- .../third_party/community/pager_duty/uv.lock | 16 +- packages/integration_testing/pyproject.toml | 2 +- packages/integration_testing/uv.lock | 2 +- ...integration_testing-2.3.6-py3-none-any.whl | Bin 0 -> 34049 bytes packages/tipcommon/TIPCommon/pyproject.toml | 2 +- .../src/TIPCommon/base/job/base_sync_job.py | 15 +- packages/tipcommon/TIPCommon/uv.lock | 2 +- .../whls/TIPCommon-2.3.6-py3-none-any.whl | Bin 0 -> 185594 bytes 16 files changed, 336 insertions(+), 179 deletions(-) create mode 100644 packages/integration_testing_whls/integration_testing-2.3.6-py3-none-any.whl create mode 100644 packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl diff --git a/content/response_integrations/third_party/community/pager_duty/actions/Ping.py b/content/response_integrations/third_party/community/pager_duty/actions/Ping.py index 6cc4af3ca..6a72c6e7d 100644 --- a/content/response_integrations/third_party/community/pager_duty/actions/Ping.py +++ b/content/response_integrations/third_party/community/pager_duty/actions/Ping.py @@ -31,10 +31,17 @@ def _extract_action_parameters(self) -> None: param_name="api_key", is_mandatory=True, ) + self.verify_ssl = extract_configuration_param( + self.soar_action, + provider_name=INTEGRATION_NAME, + param_name="verify_ssl", + default_value=True, + input_type=bool, + ) def _init_api_clients(self): """Prepare API client""" - return PagerDutyManager(self.api_key) + return PagerDutyManager(self.api_key, verify_ssl=self.verify_ssl) def _perform_action(self, _=None) -> None: self.api_client.test_connectivity() diff --git a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py index 628e5cb78..f340c3902 100644 --- a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py +++ b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py @@ -53,11 +53,24 @@ def main(is_test_run: bool) -> None: siemplify.LOGGER.info("------------------- Main - Started -------------------") manager = PagerDutyManager( api_key=api_key, - proxy_address=proxy_address, - proxy_username=proxy_username, - proxy_password=proxy_password, ) + if proxy_address: + if "://" not in proxy_address: + proxy_address = "http://" + proxy_address + from urllib.parse import urlparse + server_url = urlparse(proxy_address) + scheme: str = server_url.scheme + hostname: str | None = server_url.hostname + port: int | None = server_url.port + credentials: str = "" + if proxy_username and proxy_password: + credentials = f"{proxy_username}:{proxy_password}@" + proxy_str: str = f"{scheme}://{credentials}{hostname}" + if port: + proxy_str += f":{port}" + manager.requests_session.proxies = {"http": proxy_str, "https": proxy_str} + try: time_diff: datetime.timedelta = datetime.timedelta(hours=max_hours_backwards) since: str = ( @@ -78,9 +91,11 @@ def main(is_test_run: bool) -> None: return siemplify.LOGGER.info(f"Retrieved {len(incidents_list)} events from PagerDuty") for incident in incidents_list: - alert_id: str = incident["incident_key"] + alert_id: str = incident.get("incident_key", "") - severity: str | None = get_siemplify_mapped_severity(incident["urgency"]) + severity: str | None = get_siemplify_mapped_severity( + incident.get("urgency", "low") + ) siemplify_alert: AlertInfo = build_alert_info(siemplify, incident, severity) @@ -116,7 +131,9 @@ def build_alert_info( alert_info.display_id = incident["id"] alert_info.ticket_id = incident["id"] alert_info.name = f"PagerDuty Incident: {incident['title']}" - alert_info.rule_generator = incident["first_trigger_log_entry"]["summary"] + alert_info.rule_generator = ( + incident.get("first_trigger_log_entry", {}).get("summary", "No Summary") + ) alert_info.start_time = convert_string_to_unix_time(incident["created_at"]) alert_info.end_time = alert_info.start_time alert_info.severity = severity diff --git a/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py b/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py index 8db6d9cc9..f12b185af 100644 --- a/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py +++ b/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py @@ -15,9 +15,6 @@ def __init__( self, api_key: str, verify_ssl: bool = True, - proxy_address: str | None = None, - proxy_username: str | None = None, - proxy_password: str | None = None, from_email: str | None = None, ) -> None: """Initializes PagerDutyManager with params as set in connector config. @@ -25,37 +22,15 @@ def __init__( Args: api_key: PagerDuty API key. verify_ssl: Whether to verify SSL certificates. - proxy_address: Address of the proxy server. - proxy_username: Username for proxy authentication. - proxy_password: Password for proxy authentication. from_email: The email address of the user performing the action. """ self.api_key: str = api_key self.verify_ssl: bool = verify_ssl - self.proxy_address: str | None = proxy_address - self.proxy_username: str | None = proxy_username - self.proxy_password: str | None = proxy_password self.from_email: str | None = from_email self.requests_session: requests.Session = requests.Session() self.requests_session.verify: bool = self.verify_ssl - if self.proxy_address: - if "://" not in self.proxy_address: - self.proxy_address = "http://" + self.proxy_address - from urllib.parse import urlparse - server_url = urlparse(self.proxy_address) - scheme = server_url.scheme - hostname = server_url.hostname - port = server_url.port - credentials = "" - if self.proxy_username and self.proxy_password: - credentials = f"{self.proxy_username}:{self.proxy_password}@" - proxy_str = f"{scheme}://{credentials}{hostname}" - if port: - proxy_str += f":{port}" - self.requests_session.proxies = {"http": proxy_str, "https": proxy_str} - def test_connectivity(self) -> None: """Tests connectivity and authentication to the PagerDuty API.""" url: str = self.BASE_URL + "/abilities" @@ -106,6 +81,9 @@ def resolve_incident(self, incident_id: str) -> SingleJson: Returns: SingleJson: The API response. """ + if not self.from_email: + raise ValueError("from_email is required for resolve_incident operation.") + url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" payload: SingleJson = { "incident": { @@ -134,6 +112,9 @@ def add_incident_note(self, incident_id: str, content: str) -> SingleJson: Returns: SingleJson: The API response. """ + if not self.from_email: + raise ValueError("from_email is required for add_incident_note operation.") + url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}/notes" payload: SingleJson = { "note": { @@ -151,6 +132,24 @@ def add_incident_note(self, incident_id: str, content: str) -> SingleJson: response.raise_for_status() return response.json() + def get_incident_notes(self, incident_id: str) -> list[SingleJson]: + """Gets notes for an incident from PagerDuty. + + Args: + incident_id: The ID of the incident. + + Returns: + list[SingleJson]: List of notes. + """ + url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}/notes" + response = self.requests_session.get( + url, + headers=self._get_auth_headers(), + timeout=10, + ) + response.raise_for_status() + return response.json().get("notes", []) + def get_incident(self, incident_id: str) -> SingleJson: """Gets an incident from PagerDuty by ID. diff --git a/content/response_integrations/third_party/community/pager_duty/definition.yaml b/content/response_integrations/third_party/community/pager_duty/definition.yaml index 45a5c4630..e8c048a12 100644 --- a/content/response_integrations/third_party/community/pager_duty/definition.yaml +++ b/content/response_integrations/third_party/community/pager_duty/definition.yaml @@ -13,6 +13,12 @@ parameters: description: '' is_mandatory: true integration_identifier: PagerDuty +- name: verify_ssl + default_value: 'true' + type: boolean + description: 'Whether to verify SSL certificates.' + is_mandatory: false + integration_identifier: PagerDuty categories: [] svg_logo_path: resources/logo.svg image_path: resources/image.png diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py index 4003139f8..b031701d9 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py @@ -7,8 +7,12 @@ from TIPCommon.base.job.base_sync_job import BaseSyncJob from TIPCommon.base.job.job_case import JobCase, JobStatusResult, SyncMetadata from TIPCommon.data_models import AlertCard +from TIPCommon.transformation import convert_comma_separated_to_list from TIPCommon.types import SingleJson, SyncItem +PAGERDUTY_COMMENT_PREFIX = "PagerDuty:" +SIEM_COMMENT_PREFIX = "SecOps:" + from ..core.constants import ( CLASSIFICATION_FALSE_POSITIVE, CLASSIFICATION_OTHER, @@ -31,7 +35,7 @@ def is_uuid(value: str) -> bool: return False -ENTITY_TYPE_ALERT = 2 +ENTITY_TYPE_TICKET = 2 class SyncIncidents(BaseSyncJob[PagerDutyManager]): @@ -40,7 +44,7 @@ def __init__(self) -> None: super().__init__( job_name="PagerDuty Sync Incidents", context_identifier="pagerduty_sync", - tags_identifiers=["Pagerduty Alert"], + tags_identifiers=["Pagerduty Ticket"], ) def _init_api_clients(self) -> PagerDutyManager: @@ -57,56 +61,65 @@ def _init_api_clients(self) -> PagerDutyManager: from_email=from_email, ) - def _extract_product_id_from_alert(self, alert: AlertCard) -> str | None: - """Extracts PagerDuty Incident ID from a single alert. + def _extract_product_id_from_ticket(self, ticket: AlertCard) -> str | None: + """Extracts PagerDuty Incident ID from a single ticket. Args: - alert: The alert to extract the ID from. + ticket: The ticket to extract the ID from. Returns: The extracted PagerDuty Incident ID, or None. """ - if alert.ticket_id is not None and not is_uuid(alert.ticket_id): - return alert.ticket_id + if ticket.ticket_id is not None and not is_uuid(ticket.ticket_id): + return ticket.ticket_id - if hasattr(alert, "alert_group_identifier") and alert.alert_group_identifier: + if hasattr(ticket, "alert_group_identifier") and ticket.alert_group_identifier: val: str | None = self.soar_job.get_context_property( - ENTITY_TYPE_ALERT, - alert.alert_group_identifier, + ENTITY_TYPE_TICKET, + ticket.alert_group_identifier, "Alert_ID", ) if val: return val - if hasattr(alert, "identifier") and alert.identifier: - parts: list[str] = alert.identifier.split("_") + if hasattr(ticket, "identifier") and ticket.identifier: + parts: list[str] = ticket.identifier.split("_") if len(parts) > 1: potential_id: str = parts[-1] if re.match(r"^[A-Z0-9]{14}$", potential_id): self.logger.info( - f"Extracted incident ID {potential_id} from alert identifier " - f"{alert.identifier}" + f"Extracted incident ID {potential_id} from ticket identifier " + f"{ticket.identifier}" ) return potential_id return None def _extract_product_ids_from_case(self, job_case: JobCase) -> SyncItem: - """Extracts PagerDuty Incident IDs from the case alerts. + """Extracts PagerDuty Incident IDs from the case context and tickets. Args: - job_case: The case containing alerts. + job_case: The case containing tickets. Returns: A list of extracted incident IDs. """ incident_ids: list[str] = [] - for alert in job_case.case_detail.alerts: - incident_id: str | None = self._extract_product_id_from_alert(alert) + + val: str | None = self.soar_job.get_context_property( + 1, + str(job_case.case_detail.id_), + "TICKET_ID", + ) + if val: + incident_ids.extend(convert_comma_separated_to_list(val)) + + for ticket in job_case.case_detail.alerts: + incident_id: str | None = self._extract_product_id_from_ticket(ticket) if incident_id: incident_ids.append(incident_id) - return incident_ids + return list(set(incident_ids)) def map_product_data_to_case(self, job_case: JobCase) -> None: """Maps product data to the case. @@ -114,13 +127,13 @@ def map_product_data_to_case(self, job_case: JobCase) -> None: Args: job_case: The case to map data to. """ - for alert in job_case.case_detail.alerts: - incident_id: str | None = self._extract_product_id_from_alert(alert) + for ticket in job_case.case_detail.alerts: + incident_id: str | None = self._extract_product_id_from_ticket(ticket) if incident_id: try: incident: SingleJson = self.api_client.get_incident(incident_id) - job_case.alert_metadata[alert.identifier] = SyncMetadata( + job_case.alert_metadata[ticket.identifier] = SyncMetadata( status=incident.get("status"), incident_number=incident_id, closure_reason=None, @@ -130,43 +143,48 @@ def map_product_data_to_case(self, job_case: JobCase) -> None: f"Failed to fetch PagerDuty incident {incident_id}: {e}" ) - def sync_status(self, job_case: JobCase) -> None: - """Syncs status between PagerDuty and SecOps. + def _get_classification(self, ticket: AlertCard) -> str: + """Determines classification based on ticket closure details.""" + reason: str = CLASSIFICATION_OTHER + if hasattr(ticket, "closure_details") and ticket.closure_details: + reason = ticket.closure_details.get("reason", CLASSIFICATION_OTHER) - Args: - job_case: The case to sync status for. - """ - res: JobStatusResult = job_case.get_status_to_sync("resolved") + if reason == REASON_MALICIOUS: + return CLASSIFICATION_TRUE_POSITIVE + elif reason == REASON_NOT_MALICIOUS: + return CLASSIFICATION_FALSE_POSITIVE + return CLASSIFICATION_OTHER - synced_to_remove: list[tuple[str, str]] = [] + def _resolve_pagerduty_incident( + self, incident_id: str, classification: str + ) -> None: + """Adds a note and resolves the incident in PagerDuty.""" + note_content = ( + f"Classification: {classification}\n" + f"Incident closed from Google SecOps" + ) + self.api_client.add_incident_note(incident_id, note_content) + self.api_client.resolve_incident(incident_id) + self.logger.info( + f"Resolved PagerDuty incident {incident_id} " + f"with classification {classification}" + ) + def _close_incidents_in_pagerduty( + self, job_case: JobCase, res: JobStatusResult + ) -> list[tuple[str, str]]: + """Closes incidents in PagerDuty based on job status result.""" + synced_to_remove: list[tuple[str, str]] = [] for req in res.incidents_to_close_in_product: incident_id: str = req["meta"].incident_number - alert: AlertCard = req["alert"] + ticket: AlertCard = req["alert"] - reason: str = CLASSIFICATION_OTHER - if hasattr(alert, "closure_details") and alert.closure_details: - reason = alert.closure_details.get("reason", CLASSIFICATION_OTHER) - - classification: str = CLASSIFICATION_OTHER - if reason == REASON_MALICIOUS: - classification = CLASSIFICATION_TRUE_POSITIVE - elif reason == REASON_NOT_MALICIOUS: - classification = CLASSIFICATION_FALSE_POSITIVE + classification = self._get_classification(ticket) try: - note_content = ( - f"Classification: {classification}\n" - f"Incident closed from Google SecOps" - ) - self.api_client.add_incident_note(incident_id, note_content) - self.api_client.resolve_incident(incident_id) - self.logger.info( - f"Resolved PagerDuty incident {incident_id} " - f"with classification {classification}" - ) + self._resolve_pagerduty_incident(incident_id, classification) - if alert.status.lower() == "close": + if ticket.status.lower() == "close": synced_to_remove.append( (str(job_case.case_detail.id_), incident_id) ) @@ -175,45 +193,68 @@ def sync_status(self, job_case: JobCase) -> None: self.logger.error( f"Failed to resolve PagerDuty incident {incident_id}: {e}" ) + return synced_to_remove + + def _get_open_tickets( + self, job_case: JobCase, closed_ticket_ids: set[str] + ) -> list[AlertCard]: + """Returns a list of open tickets in the case that are not yet closed.""" + return [ + t + for t in job_case.case_detail.alerts + if t.status.lower() == "open" + and t.identifier not in closed_ticket_ids + ] + + def _close_case_or_ticket( + self, + job_case: JobCase, + ticket: AlertCard, + meta: Any, + open_tickets: list[AlertCard], + ) -> bool: + """Closes either the full case or just the specific ticket in SecOps.""" + reason_soar = REASON_RESOLVED_IN_PAGERDUTY + if len(open_tickets) == 1 and open_tickets[0].identifier == ticket.identifier: + self.soar_job.close_case( + root_cause=CLASSIFICATION_OTHER, + case_id=job_case.case_detail.id_, + reason=reason_soar, + comment=( + f"Closed case because last ticket {ticket.identifier} was " + "resolved in PagerDuty." + ), + alert_identifier=None, + ) + self.logger.info( + f"Closed case {job_case.case_detail.id_} as it was the last " + "open ticket." + ) + return False + else: + self.sync_product_status_to_case( + case_id=job_case.case_detail.id_, + alert_id=ticket.identifier, + reason=reason_soar, + root_cause="Other", + comment=( + f"PagerDuty incident {meta.incident_number} was resolved." + ), + ) + return True - closed_alert_ids: set[str] = set() - for alert, meta in res.alerts_to_close_in_soar: - reason_soar = REASON_RESOLVED_IN_PAGERDUTY - + def _close_tickets_in_soar( + self, job_case: JobCase, res: JobStatusResult + ) -> list[tuple[str, str]]: + """Closes tickets or cases in SecOps based on job status result.""" + synced_to_remove: list[tuple[str, str]] = [] + closed_ticket_ids: set[str] = set() + for ticket, meta in res.alerts_to_close_in_soar: try: - open_alerts = [ - a - for a in job_case.case_detail.alerts - if a.status.lower() == "open" - and a.identifier not in closed_alert_ids - ] - if ( - len(open_alerts) == 1 - and open_alerts[0].identifier == alert.identifier - ): - self.soar_job.close_case( - root_cause=CLASSIFICATION_OTHER, - case_id=job_case.case_detail.id_, - reason=reason_soar, - comment=( - f"Closed case because last alert {alert.identifier} was " - "resolved in PagerDuty." - ), - alert_identifier=None, - ) - self.logger.info( - f"Closed case {job_case.case_detail.id_} as it was the last " - "open alert." - ) - else: - self.sync_product_status_to_case( - case_id=job_case.case_detail.id_, - alert_id=alert.identifier, - reason=reason_soar, - root_cause="Other", - comment=f"PagerDuty incident {meta.incident_number} was resolved.", - ) - closed_alert_ids.add(alert.identifier) + open_tickets = self._get_open_tickets(job_case, closed_ticket_ids) + + if self._close_case_or_ticket(job_case, ticket, meta, open_tickets): + closed_ticket_ids.add(ticket.identifier) if meta.status == "resolved": synced_to_remove.append( @@ -221,43 +262,61 @@ def sync_status(self, job_case: JobCase) -> None: ) except Exception as e: self.logger.error( - f"Failed to close alert {alert.identifier} or case in SecOps: {e}" + f"Failed to close ticket {ticket.identifier} or case in SecOps: {e}" ) + return synced_to_remove - self._remove_synced_entries(synced_to_remove) - self.logger.info(f"Case {job_case.case_detail.id_} successfully synced.") - - def sync_comments(self, job_case: JobCase) -> None: - """Syncs comments. + def sync_status(self, job_case: JobCase) -> None: + """Syncs status between PagerDuty and SecOps. Args: - job_case (JobCase): The case to sync comments for. + job_case: The case to sync status for. """ - pass + res: JobStatusResult = job_case.get_status_to_sync("resolved") - def sync_tags(self, job_case: JobCase) -> None: - """Syncs tags. + synced_to_remove: list[tuple[str, str]] = [] - Args: - job_case (JobCase): The case to sync tags for. - """ - pass + synced_to_remove.extend(self._close_incidents_in_pagerduty(job_case, res)) + synced_to_remove.extend(self._close_tickets_in_soar(job_case, res)) - def sync_severity(self, job_case: JobCase) -> None: - """Syncs severity. + self._remove_synced_entries(synced_to_remove) + self._sync_untracked_incidents_status(job_case) - Args: - job_case (JobCase): The case to sync severity for. - """ - pass + self.logger.info(f"Case {job_case.case_detail.id_} successfully synced.") - def sync_assignee(self, job_case: JobCase) -> None: - """Syncs assignee. + def _sync_untracked_incidents_status(self, job_case: JobCase) -> None: + """Fallback sync for cases not tracked by standard metadata.""" + incident_ids = self._extract_product_ids_from_case(job_case) + is_case_closed = job_case.case_detail.status.lower() == "closed" - Args: - job_case (JobCase): The case to sync assignee for. - """ - pass + for incident_id in incident_ids: + try: + incident = self.api_client.get_incident(incident_id) + product_status = incident.get("status") + + if product_status == "resolved" and not is_case_closed: + self.soar_job.close_case( + root_cause=CLASSIFICATION_OTHER, + case_id=job_case.case_detail.id_, + reason=REASON_RESOLVED_IN_PAGERDUTY, + comment=( + f"Closed case because PagerDuty incident " + f"{incident_id} was resolved." + ), + alert_identifier=None, + ) + self.logger.info( + f"Closed case {job_case.case_detail.id_} due to " + f"resolved PagerDuty incident {incident_id}" + ) + elif product_status != "resolved" and is_case_closed: + self._resolve_pagerduty_incident(incident_id, CLASSIFICATION_OTHER) + + except Exception as e: + self.logger.error( + f"Failed to sync status for PagerDuty incident " + f"{incident_id}: {e}" + ) def is_alert_and_product_closed(self, job_case: JobCase, product: Any) -> bool: """Checks if alert and product are closed. @@ -274,16 +333,87 @@ def is_alert_and_product_closed(self, job_case: JobCase, product: Any) -> bool: return product.status == "resolved" return False - def remove_synced_data_from_db( - self, job_case: JobCase, product_details: Any - ) -> None: - """Removes synced data from DB. + def sync_comments(self, job_case: JobCase) -> None: + """Syncs comments between PagerDuty and SecOps.""" + incident_ids = self._extract_product_ids_from_case(job_case) + if not incident_ids: + return - Args: - job_case (JobCase): The case. - product_details (Any): The product details. - """ + for incident_id in incident_ids: + try: + notes = self.api_client.get_incident_notes(incident_id) + except Exception as e: + self.logger.error( + f"Failed to get PagerDuty notes for {incident_id}: {e}" + ) + continue + + try: + case_comments = self.soar_job.fetch_case_comments( + job_case.case_detail.id_ + ) + except Exception as e: + self.logger.error( + f"Failed to fetch SOAR comments for case " + f"{job_case.case_detail.id_}: {e}" + ) + case_comments = [] + + for note in notes: + content = note.get("content", "") + if content.startswith(SIEM_COMMENT_PREFIX): + continue + + already_exists = False + for c in case_comments: + if c.get("comment", "").endswith(content): + already_exists = True + break + + if not already_exists: + self.soar_job.add_comment( + comment=f"{PAGERDUTY_COMMENT_PREFIX} {content}", + case_id=job_case.case_detail.id_, + alert_identifier=None, + ) + + for comment in case_comments: + content = comment.get("comment", "") + if content.startswith(PAGERDUTY_COMMENT_PREFIX): + continue + + already_exists = False + for note in notes: + if note.get("content", "").endswith(content): + already_exists = True + break + + if not already_exists: + try: + self.api_client.add_incident_note( + incident_id, + f"{SIEM_COMMENT_PREFIX} {content}" + ) + except Exception as e: + self.logger.error( + f"Failed to add note to PagerDuty incident " + f"{incident_id}: {e}" + ) + + def remove_synced_data_from_db(self, job_case: JobCase, product_details: Any) -> None: + """Removes synced data from db.""" + pass + + def sync_assignee(self, job_case: JobCase) -> None: + """Syncs assignee.""" + pass + + def sync_severity(self, job_case: JobCase) -> None: + """Syncs severity.""" + pass + def sync_tags(self, job_case: JobCase) -> None: + """Syncs tags.""" pass diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml index 029f0b6cd..51caeca21 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml @@ -26,6 +26,6 @@ parameters: description: | - This job synchronizes Google SecOps Alerts and PagerDuty Incidents . It ensures that status is synchronized bi-directionally between both systems. For the job to identify the correct information, the Google SecOps case must have the "Pagerduty Alert" tag. If the alert didn’t originate from "Pagerduty Connector", you will need to add an "Alert_ID" context value to the alert for the job to be able to find the correct information. + This job synchronizes Google SecOps Alerts and PagerDuty Incidents. It ensures that status and comments are synchronized bi-directionally between both systems. For the job to identify the correct information, the Google SecOps case must have the "Pagerduty Ticket" tag. If the ticket didn’t originate from "Pagerduty Connector", you will need to add a "TICKET_ID" context value to the case for the job to be able to find the correct information. integration: PagerDuty creator: Admin diff --git a/content/response_integrations/third_party/community/pager_duty/pyproject.toml b/content/response_integrations/third_party/community/pager_duty/pyproject.toml index 3271c162f..d47e6bc14 100644 --- a/content/response_integrations/third_party/community/pager_duty/pyproject.toml +++ b/content/response_integrations/third_party/community/pager_duty/pyproject.toml @@ -20,8 +20,8 @@ dev = [ [tool.uv.sources] soar-sdk = { git = "https://github.com/chronicle/soar-sdk.git" } -integration-testing = { path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.5-py3-none-any.whl" } -tipcommon = { path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.5-py3-none-any.whl" } +integration-testing = { path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.6-py3-none-any.whl" } +tipcommon = { path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl" } environmentcommon = { path = "../../../../../packages/envcommon/whls/EnvironmentCommon-1.0.2-py2.py3-none-any.whl" } [[tool.uv.index]] diff --git a/content/response_integrations/third_party/community/pager_duty/tests/conftest.py b/content/response_integrations/third_party/community/pager_duty/tests/conftest.py index 0bf6b4c87..168df6f64 100644 --- a/content/response_integrations/third_party/community/pager_duty/tests/conftest.py +++ b/content/response_integrations/third_party/community/pager_duty/tests/conftest.py @@ -7,10 +7,13 @@ import pytest from integration_testing.common import use_live_api + +pytest_plugins = ("integration_testing.conftest",) + from .core.product import PagerDuty from .core.session import PagerDutySession -pytest_plugins = ("integration_testing.conftest",) + @pytest.fixture diff --git a/content/response_integrations/third_party/community/pager_duty/uv.lock b/content/response_integrations/third_party/community/pager_duty/uv.lock index c1bbb3bdf..45bbf6096 100644 --- a/content/response_integrations/third_party/community/pager_duty/uv.lock +++ b/content/response_integrations/third_party/community/pager_duty/uv.lock @@ -384,8 +384,8 @@ wheels = [ [[package]] name = "integration-testing" -version = "2.3.5" -source = { path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.5-py3-none-any.whl" } +version = "2.3.6" +source = { path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.6-py3-none-any.whl" } dependencies = [ { name = "aiohttp" }, { name = "environmentcommon" }, @@ -396,7 +396,7 @@ dependencies = [ { name = "yarl" }, ] wheels = [ - { filename = "integration_testing-2.3.5-py3-none-any.whl", hash = "sha256:ad74ec84cdd3245e09ef7edd89e132cf16064ad45ae76bb5ae0ba307a08ed05b" }, + { filename = "integration_testing-2.3.6-py3-none-any.whl", hash = "sha256:341a902ca71a227fa242cf843ff5a17a029437438647172b56e7acbc8756ca03" }, ] [package.metadata] @@ -470,12 +470,12 @@ requires-dist = [ { name = "environmentcommon", path = "../../../../../packages/envcommon/whls/EnvironmentCommon-1.0.2-py2.py3-none-any.whl" }, { name = "pdpyras", specifier = "==5.4.0" }, { name = "requests", specifier = "==2.32.5" }, - { name = "tipcommon", path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.5-py3-none-any.whl" }, + { name = "tipcommon", path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl" }, ] [package.metadata.requires-dev] dev = [ - { name = "integration-testing", path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.5-py3-none-any.whl" }, + { name = "integration-testing", path = "../../../../../packages/integration_testing_whls/integration_testing-2.3.6-py3-none-any.whl" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-json-report", specifier = ">=1.5.0" }, { name = "soar-sdk", git = "https://github.com/chronicle/soar-sdk.git" }, @@ -789,8 +789,8 @@ dependencies = [ [[package]] name = "tipcommon" -version = "2.3.5" -source = { path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.5-py3-none-any.whl" } +version = "2.3.6" +source = { path = "../../../../../packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl" } dependencies = [ { name = "google-api-python-client" }, { name = "google-auth" }, @@ -802,7 +802,7 @@ dependencies = [ { name = "requests-toolbelt" }, ] wheels = [ - { filename = "tipcommon-2.3.5-py3-none-any.whl", hash = "sha256:c30c13dcec152d271d198e54801bf42e76e87ecfc4b3ba57544bec021f143e9f" }, + { filename = "tipcommon-2.3.6-py3-none-any.whl", hash = "sha256:069de7378d9be267462e093c16b53a1cfac6333f4be2a3aa86eae9f504809ad9" }, ] [package.metadata] diff --git a/packages/integration_testing/pyproject.toml b/packages/integration_testing/pyproject.toml index 19be97204..31c106a66 100644 --- a/packages/integration_testing/pyproject.toml +++ b/packages/integration_testing/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "integration-testing" -version = "2.3.5" +version = "2.3.6" description = "Blackbox testing infrastructure to test and run marketplace integration scripts locally using mocks." readme = "README.md" authors = [ diff --git a/packages/integration_testing/uv.lock b/packages/integration_testing/uv.lock index 4489834f7..3c47e852d 100644 --- a/packages/integration_testing/uv.lock +++ b/packages/integration_testing/uv.lock @@ -395,7 +395,7 @@ wheels = [ [[package]] name = "integration-testing" -version = "2.3.5" +version = "2.3.6" source = { editable = "." } dependencies = [ { name = "aiohttp" }, diff --git a/packages/integration_testing_whls/integration_testing-2.3.6-py3-none-any.whl b/packages/integration_testing_whls/integration_testing-2.3.6-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..7112977cb5c095bf0fa5571ee13fcb10c4848643 GIT binary patch literal 34049 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≠ik|!C$MVP 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 literal 0 HcmV?d00001 diff --git a/packages/tipcommon/TIPCommon/pyproject.toml b/packages/tipcommon/TIPCommon/pyproject.toml index 043e8baeb..a08fc1adb 100644 --- a/packages/tipcommon/TIPCommon/pyproject.toml +++ b/packages/tipcommon/TIPCommon/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "TIPCommon" -version = "2.3.5" +version = "2.3.6" description = "General Purpose CLI tool for Google SecOps Marketplace" readme = "README.md" authors = [ diff --git a/packages/tipcommon/TIPCommon/src/TIPCommon/base/job/base_sync_job.py b/packages/tipcommon/TIPCommon/src/TIPCommon/base/job/base_sync_job.py index aec033c0e..eb8731102 100644 --- a/packages/tipcommon/TIPCommon/src/TIPCommon/base/job/base_sync_job.py +++ b/packages/tipcommon/TIPCommon/src/TIPCommon/base/job/base_sync_job.py @@ -103,23 +103,21 @@ def sync_status(self, job_case: JobCase) -> None: """ raise NotImplementedError - @abstractmethod def sync_comments(self, job_case: JobCase) -> None: """Sync comments between source and target items. Args: job_case (JobCase): The JobCase object containing the details of the case to sync. """ - raise NotImplementedError + pass - @abstractmethod def sync_tags(self, job_case: JobCase) -> None: """Sync tags between source and target items. Args: job_case (JobCase): The JobCase object containing the details of the case to sync. """ - raise NotImplementedError + pass @abstractmethod def is_alert_and_product_closed(self, job_case: JobCase, product: Any) -> bool: @@ -134,7 +132,6 @@ def is_alert_and_product_closed(self, job_case: JobCase, product: Any) -> bool: """ raise NotImplementedError - @abstractmethod def remove_synced_data_from_db(self, job_case: JobCase, product_details: Any) -> None: """Removes synced data from db. @@ -142,25 +139,23 @@ def remove_synced_data_from_db(self, job_case: JobCase, product_details: Any) -> job_case (JobCase): The JobCase object containing the details of the case to sync. product_details (Any): The details of the product to sync. """ - raise NotImplementedError + pass - @abstractmethod def sync_severity(self, job_case: JobCase) -> None: """Sync severity between source and target items. Args: job_case (JobCase): The JobCase object containing the details of the case to sync. """ - raise NotImplementedError + pass - @abstractmethod def sync_assignee(self, job_case: JobCase) -> None: """Sync assignee between source and target items. Args: job_case (JobCase): The JobCase object containing the details of the case to sync. """ - raise NotImplementedError + pass @abstractmethod def map_product_data_to_case(self, job_case: JobCase) -> None: diff --git a/packages/tipcommon/TIPCommon/uv.lock b/packages/tipcommon/TIPCommon/uv.lock index e3cfce573..fb35338a1 100644 --- a/packages/tipcommon/TIPCommon/uv.lock +++ b/packages/tipcommon/TIPCommon/uv.lock @@ -413,7 +413,7 @@ wheels = [ [[package]] name = "tipcommon" -version = "2.3.5" +version = "2.3.6" source = { editable = "." } dependencies = [ { name = "google-api-python-client" }, diff --git a/packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl b/packages/tipcommon/whls/TIPCommon-2.3.6-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..cc4cd274edbeed4ddd7e06cd9e72a046d4bc6dae GIT binary patch literal 185594 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≶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 Date: Fri, 17 Apr 2026 06:35:06 +0000 Subject: [PATCH 7/9] linter errors --- .../connectors/PagerDutyConnector.py | 2 +- .../community/pager_duty/core/constants.py | 4 +++ .../pager_duty/jobs/SyncIncidents.py | 30 +++++++++---------- .../community/pager_duty/tests/conftest.py | 5 +--- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py index f340c3902..639930aeb 100644 --- a/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py +++ b/content/response_integrations/third_party/community/pager_duty/connectors/PagerDutyConnector.py @@ -130,7 +130,7 @@ def build_alert_info( alert_info: AlertInfo = AlertInfo() alert_info.display_id = incident["id"] alert_info.ticket_id = incident["id"] - alert_info.name = f"PagerDuty Incident: {incident['title']}" + alert_info.name = incident['id'] alert_info.rule_generator = ( incident.get("first_trigger_log_entry", {}).get("summary", "No Summary") ) diff --git a/content/response_integrations/third_party/community/pager_duty/core/constants.py b/content/response_integrations/third_party/community/pager_duty/core/constants.py index c003b2a0e..2134a7937 100644 --- a/content/response_integrations/third_party/community/pager_duty/core/constants.py +++ b/content/response_integrations/third_party/community/pager_duty/core/constants.py @@ -20,3 +20,7 @@ REASON_RESOLVED_IN_PAGERDUTY: str = "Resolved in PagerDuty" SEVERITY_HIGH: str = "80" SEVERITY_LOW: str = "40" + +PAGERDUTY_COMMENT_PREFIX = "PagerDuty:" +SIEM_COMMENT_PREFIX = "SecOps:" +CONTEXT_KEY = "TICKET_ID" diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py index b031701d9..6335a9086 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py @@ -10,16 +10,16 @@ from TIPCommon.transformation import convert_comma_separated_to_list from TIPCommon.types import SingleJson, SyncItem -PAGERDUTY_COMMENT_PREFIX = "PagerDuty:" -SIEM_COMMENT_PREFIX = "SecOps:" - from ..core.constants import ( CLASSIFICATION_FALSE_POSITIVE, CLASSIFICATION_OTHER, CLASSIFICATION_TRUE_POSITIVE, + CONTEXT_KEY, + PAGERDUTY_COMMENT_PREFIX, REASON_MALICIOUS, REASON_NOT_MALICIOUS, REASON_RESOLVED_IN_PAGERDUTY, + SIEM_COMMENT_PREFIX, ) from ..core.PagerDutyManager import PagerDutyManager @@ -53,7 +53,7 @@ def _init_api_clients(self) -> PagerDutyManager: Returns: The initialized API client. """ - verify_ssl: bool = getattr(self.params, "verify_ssl", True) + verify_ssl: bool = getattr(self.params, "Verify SSL", True) from_email: str | None = getattr(self.params, "from_email", None) return PagerDutyManager( api_key=self.params.api_key, @@ -77,7 +77,7 @@ def _extract_product_id_from_ticket(self, ticket: AlertCard) -> str | None: val: str | None = self.soar_job.get_context_property( ENTITY_TYPE_TICKET, ticket.alert_group_identifier, - "Alert_ID", + CONTEXT_KEY, ) if val: return val @@ -105,7 +105,7 @@ def _extract_product_ids_from_case(self, job_case: JobCase) -> SyncItem: A list of extracted incident IDs. """ incident_ids: list[str] = [] - + val: str | None = self.soar_job.get_context_property( 1, str(job_case.case_detail.id_), @@ -113,7 +113,7 @@ def _extract_product_ids_from_case(self, job_case: JobCase) -> SyncItem: ) if val: incident_ids.extend(convert_comma_separated_to_list(val)) - + for ticket in job_case.case_detail.alerts: incident_id: str | None = self._extract_product_id_from_ticket(ticket) if incident_id: @@ -287,15 +287,15 @@ def sync_status(self, job_case: JobCase) -> None: def _sync_untracked_incidents_status(self, job_case: JobCase) -> None: """Fallback sync for cases not tracked by standard metadata.""" incident_ids = self._extract_product_ids_from_case(job_case) - is_case_closed = job_case.case_detail.status.lower() == "closed" + is_case_closed = job_case.case_detail.status == "Closed" for incident_id in incident_ids: try: incident = self.api_client.get_incident(incident_id) product_status = incident.get("status") - + if product_status == "resolved" and not is_case_closed: - self.soar_job.close_case( + self.soar_job.close_alert( root_cause=CLASSIFICATION_OTHER, case_id=job_case.case_detail.id_, reason=REASON_RESOLVED_IN_PAGERDUTY, @@ -311,7 +311,7 @@ def _sync_untracked_incidents_status(self, job_case: JobCase) -> None: ) elif product_status != "resolved" and is_case_closed: self._resolve_pagerduty_incident(incident_id, CLASSIFICATION_OTHER) - + except Exception as e: self.logger.error( f"Failed to sync status for PagerDuty incident " @@ -363,13 +363,13 @@ def sync_comments(self, job_case: JobCase) -> None: content = note.get("content", "") if content.startswith(SIEM_COMMENT_PREFIX): continue - + already_exists = False for c in case_comments: if c.get("comment", "").endswith(content): already_exists = True break - + if not already_exists: self.soar_job.add_comment( comment=f"{PAGERDUTY_COMMENT_PREFIX} {content}", @@ -381,13 +381,13 @@ def sync_comments(self, job_case: JobCase) -> None: content = comment.get("comment", "") if content.startswith(PAGERDUTY_COMMENT_PREFIX): continue - + already_exists = False for note in notes: if note.get("content", "").endswith(content): already_exists = True break - + if not already_exists: try: self.api_client.add_incident_note( diff --git a/content/response_integrations/third_party/community/pager_duty/tests/conftest.py b/content/response_integrations/third_party/community/pager_duty/tests/conftest.py index 168df6f64..0bf6b4c87 100644 --- a/content/response_integrations/third_party/community/pager_duty/tests/conftest.py +++ b/content/response_integrations/third_party/community/pager_duty/tests/conftest.py @@ -7,13 +7,10 @@ import pytest from integration_testing.common import use_live_api - -pytest_plugins = ("integration_testing.conftest",) - from .core.product import PagerDuty from .core.session import PagerDutySession - +pytest_plugins = ("integration_testing.conftest",) @pytest.fixture From 345736b0dde68256305b7bd2a8812af59cb73c3b Mon Sep 17 00:00:00 2001 From: Arabindaksha-Mishra Date: Mon, 20 Apr 2026 05:05:14 +0000 Subject: [PATCH 8/9] refactored the code to match the coding standards --- .../community/pager_duty/actions/Ping.py | 6 +- .../community/pager_duty/core/constants.py | 9 +- .../community/pager_duty/definition.yaml | 2 +- .../pager_duty/jobs/SyncIncidents.py | 744 +++++++++++------- .../pager_duty/jobs/SyncIncidents.yaml | 18 +- .../community/pager_duty/tests/conftest.py | 81 +- .../tests/test_jobs/test_sync_incidents.py | 17 +- .../src/integration_testing/common.py | 2 +- 8 files changed, 494 insertions(+), 385 deletions(-) diff --git a/content/response_integrations/third_party/community/pager_duty/actions/Ping.py b/content/response_integrations/third_party/community/pager_duty/actions/Ping.py index 6a72c6e7d..c0e99abe4 100644 --- a/content/response_integrations/third_party/community/pager_duty/actions/Ping.py +++ b/content/response_integrations/third_party/community/pager_duty/actions/Ping.py @@ -12,9 +12,7 @@ from typing import NoReturn -SUCCESS_MESSAGE: str = ( - "Successfully connected to the PagerDuty API." -) +SUCCESS_MESSAGE: str = "Successfully connected to the PagerDuty API." ERROR_MESSAGE: str = "Failed to connect to the PagerDuty API." @@ -34,7 +32,7 @@ def _extract_action_parameters(self) -> None: self.verify_ssl = extract_configuration_param( self.soar_action, provider_name=INTEGRATION_NAME, - param_name="verify_ssl", + param_name="Verify SSL", default_value=True, input_type=bool, ) diff --git a/content/response_integrations/third_party/community/pager_duty/core/constants.py b/content/response_integrations/third_party/community/pager_duty/core/constants.py index 2134a7937..238e90cc2 100644 --- a/content/response_integrations/third_party/community/pager_duty/core/constants.py +++ b/content/response_integrations/third_party/community/pager_duty/core/constants.py @@ -21,6 +21,9 @@ SEVERITY_HIGH: str = "80" SEVERITY_LOW: str = "40" -PAGERDUTY_COMMENT_PREFIX = "PagerDuty:" -SIEM_COMMENT_PREFIX = "SecOps:" -CONTEXT_KEY = "TICKET_ID" +PAGERDUTY_COMMENT_PREFIX: str = "PagerDuty:" +SIEM_COMMENT_PREFIX: str = "SecOps:" +CONTEXT_KEY: str = "TICKET_ID" +ENTITY_TYPE_TICKET: int = 1 +ENTITY_TYPE_ALERT: int = 2 +ALERT_ID_CONTEXT_KEY: str = "Alert_ID" diff --git a/content/response_integrations/third_party/community/pager_duty/definition.yaml b/content/response_integrations/third_party/community/pager_duty/definition.yaml index e8c048a12..771b7f275 100644 --- a/content/response_integrations/third_party/community/pager_duty/definition.yaml +++ b/content/response_integrations/third_party/community/pager_duty/definition.yaml @@ -13,7 +13,7 @@ parameters: description: '' is_mandatory: true integration_identifier: PagerDuty -- name: verify_ssl +- name: Verify SSL default_value: 'true' type: boolean description: 'Whether to verify SSL certificates.' diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py index 6335a9086..093959a23 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py @@ -1,419 +1,555 @@ from __future__ import annotations -import re -import uuid +from datetime import datetime +from types import SimpleNamespace from typing import Any +from TIPCommon.base.action.data_models import ( + CloseCaseOrAlertInconclusiveRootCauses, + CloseCaseOrAlertMaliciousRootCauses, + CloseCaseOrAlertNotMaliciousRootCauses, +) from TIPCommon.base.job.base_sync_job import BaseSyncJob -from TIPCommon.base.job.job_case import JobCase, JobStatusResult, SyncMetadata +from TIPCommon.base.job.job_case import ( + JobCase, + JobStatusResult, + SyncMetadata, +) from TIPCommon.data_models import AlertCard from TIPCommon.transformation import convert_comma_separated_to_list -from TIPCommon.types import SingleJson, SyncItem from ..core.constants import ( - CLASSIFICATION_FALSE_POSITIVE, - CLASSIFICATION_OTHER, - CLASSIFICATION_TRUE_POSITIVE, + ALERT_ID_CONTEXT_KEY, CONTEXT_KEY, + ENTITY_TYPE_ALERT, + ENTITY_TYPE_TICKET, PAGERDUTY_COMMENT_PREFIX, - REASON_MALICIOUS, - REASON_NOT_MALICIOUS, - REASON_RESOLVED_IN_PAGERDUTY, SIEM_COMMENT_PREFIX, ) from ..core.PagerDutyManager import PagerDutyManager - -def is_uuid(value: str) -> bool: - """Checks if a string value is a valid UUID.""" - if not isinstance(value, str): - return False - try: - uuid.UUID(value) - return True - except ValueError: - return False - - -ENTITY_TYPE_TICKET = 2 +MS_IN_SECOND: int = 1000 class SyncIncidents(BaseSyncJob[PagerDutyManager]): def __init__(self) -> None: - """Initializes the SyncIncidents job.""" super().__init__( job_name="PagerDuty Sync Incidents", - context_identifier="pagerduty_sync", + context_identifier="Pagerduty Ticket", tags_identifiers=["Pagerduty Ticket"], ) def _init_api_clients(self) -> PagerDutyManager: - """Initializes the PagerDuty API client. + """Initializes the API client. Returns: The initialized API client. """ verify_ssl: bool = getattr(self.params, "Verify SSL", True) - from_email: str | None = getattr(self.params, "from_email", None) + from_email: str | None = getattr(self.params, "From Email", None) return PagerDutyManager( api_key=self.params.api_key, verify_ssl=verify_ssl, from_email=from_email, ) - def _extract_product_id_from_ticket(self, ticket: AlertCard) -> str | None: - """Extracts PagerDuty Incident ID from a single ticket. + def modified_synced_case_ids_by_product( + self, + alert_ids: list[str], + sorted_modified_ids: list[tuple[str, int]], + ) -> list[tuple[str, int]]: + """Fetches modified incidents from PagerDuty and maps them to SecOps case IDs. Args: - ticket: The ticket to extract the ID from. + alert_ids: list of alert IDs to check for modifications. + sorted_modified_ids: list of tuples containing case IDs and their + last modified timestamps. Returns: - The extracted PagerDuty Incident ID, or None. + A list of tuples containing SecOps case IDs and their last modified times. """ - if ticket.ticket_id is not None and not is_uuid(ticket.ticket_id): - return ticket.ticket_id + if not alert_ids: + return [] - if hasattr(ticket, "alert_group_identifier") and ticket.alert_group_identifier: - val: str | None = self.soar_job.get_context_property( - ENTITY_TYPE_TICKET, - ticket.alert_group_identifier, - CONTEXT_KEY, + product_ids_to_case_map: dict[str, str] = {} + for case_id, product_ids in self.processed_items.items(): + for product_id in product_ids: + product_ids_to_case_map[product_id] = case_id + + from_timestamp = getattr(self, "last_run_time", 0) + if not from_timestamp: + return [] + + self.logger.info( + "Checking synced PagerDuty incidents for updates and new notes." + ) + modified_cases = [] + + for incident_id, case_id in product_ids_to_case_map.items(): + is_modified, latest_timestamp = self._is_incident_modified( + incident_id, from_timestamp ) - if val: - return val - - if hasattr(ticket, "identifier") and ticket.identifier: - parts: list[str] = ticket.identifier.split("_") - if len(parts) > 1: - potential_id: str = parts[-1] - if re.match(r"^[A-Z0-9]{14}$", potential_id): - self.logger.info( - f"Extracted incident ID {potential_id} from ticket identifier " - f"{ticket.identifier}" - ) - return potential_id + if is_modified: + self.logger.info( + f"Found update or new note for incident {incident_id}, " + f"triggering sync for case {case_id}" + ) + modified_cases.append((case_id, latest_timestamp)) - return None + return modified_cases - def _extract_product_ids_from_case(self, job_case: JobCase) -> SyncItem: - """Extracts PagerDuty Incident IDs from the case context and tickets. + def _get_incident_latest_timestamp(self, incident_id: str) -> int: + """Gets the latest timestamp of the incident itself. - Args: - job_case: The case containing tickets. + Returns: + The timestamp in milliseconds, or 0 if failed/not found. + """ + try: + incident = self.api_client.get_incident(incident_id) + updated_at_str = incident.get("updated_at") + if updated_at_str: + dt = datetime.fromisoformat(updated_at_str.replace("Z", "+00:00")) + return int(dt.timestamp() * MS_IN_SECOND) + except Exception as e: + self.logger.debug( + f"Failed to get incident timestamp for {incident_id}: {e}" + ) + return 0 + + def _get_notes_latest_timestamp(self, incident_id: str) -> int: + """Gets the latest timestamp among all incident notes. + + Returns: + The timestamp in milliseconds, or 0 if failed/not found. + """ + latest_timestamp = 0 + try: + notes = self.api_client.get_incident_notes(incident_id) + for note in notes: + created_at_str = note.get("created_at") + if created_at_str: + try: + dt = datetime.fromisoformat( + created_at_str.replace("Z", "+00:00") + ) + created_at_ms = int(dt.timestamp() * MS_IN_SECOND) + latest_timestamp = max(latest_timestamp, created_at_ms) + except Exception as e: + self.logger.debug( + f"Failed to parse created_at for note in incident " + f"{incident_id}: {e}" + ) + continue + except Exception as e: + self.logger.debug(f"Failed to get notes timestamp for {incident_id}: {e}") + return latest_timestamp + + def _is_incident_modified( + self, + incident_id: str, + from_timestamp: int, + ) -> tuple[bool, int]: + """Checks if incident or notes were modified since from_timestamp.""" + incident_ts = self._get_incident_latest_timestamp(incident_id) + notes_ts = self._get_notes_latest_timestamp(incident_id) + + latest_timestamp = max(incident_ts, notes_ts) + is_modified = latest_timestamp > from_timestamp + + if latest_timestamp == 0: + latest_timestamp = from_timestamp + return is_modified, latest_timestamp + + def _extract_product_ids_from_case(self, job_case: JobCase) -> list[str]: + """Extracts product incident IDs from the SecOps case alerts and context. + Args: + job_case: The SecOps case containing the alerts. Returns: - A list of extracted incident IDs. + A list of unique product incident IDs extracted from the case. """ incident_ids: list[str] = [] - val: str | None = self.soar_job.get_context_property( - 1, + context_value: str | None = self.soar_job.get_context_property( + ENTITY_TYPE_TICKET, str(job_case.case_detail.id_), - "TICKET_ID", + CONTEXT_KEY, ) - if val: - incident_ids.extend(convert_comma_separated_to_list(val)) - for ticket in job_case.case_detail.alerts: - incident_id: str | None = self._extract_product_id_from_ticket(ticket) + if not context_value: + context_value = self.soar_job.get_context_property( + ENTITY_TYPE_TICKET, + str(job_case.case_detail.id_), + ALERT_ID_CONTEXT_KEY, + ) + if context_value: + ids = convert_comma_separated_to_list(context_value) + incident_ids.extend(ids) + first_alert = job_case.get_first_alert(open_only=False) + if first_alert: + for incident_id in ids: + job_case.product_ids_from_secops_alerts[incident_id] = first_alert + + for alert in job_case.case_detail.alerts: + incident_id: str | None = self._extract_product_id_from_ticket(alert) if incident_id: incident_ids.append(incident_id) + job_case.product_ids_from_secops_alerts[incident_id] = alert - return list(set(incident_ids)) - - def map_product_data_to_case(self, job_case: JobCase) -> None: - """Maps product data to the case. + return sorted(set(incident_ids)) + def _extract_product_id_from_ticket(self, ticket: AlertCard) -> str | None: + """Extracts PagerDuty Incident ID from a single ticket. Args: - job_case: The case to map data to. + ticket: The ticket to extract ID from. + Returns: + The incident ID if found, None otherwise. """ - for ticket in job_case.case_detail.alerts: - incident_id: str | None = self._extract_product_id_from_ticket(ticket) - if incident_id: - try: - incident: SingleJson = self.api_client.get_incident(incident_id) - - job_case.alert_metadata[ticket.identifier] = SyncMetadata( - status=incident.get("status"), - incident_number=incident_id, - closure_reason=None, - ) - except Exception as e: - self.logger.error( - f"Failed to fetch PagerDuty incident {incident_id}: {e}" - ) - - def _get_classification(self, ticket: AlertCard) -> str: - """Determines classification based on ticket closure details.""" - reason: str = CLASSIFICATION_OTHER - if hasattr(ticket, "closure_details") and ticket.closure_details: - reason = ticket.closure_details.get("reason", CLASSIFICATION_OTHER) - - if reason == REASON_MALICIOUS: - return CLASSIFICATION_TRUE_POSITIVE - elif reason == REASON_NOT_MALICIOUS: - return CLASSIFICATION_FALSE_POSITIVE - return CLASSIFICATION_OTHER + if ticket.ticket_id: + return ticket.ticket_id - def _resolve_pagerduty_incident( - self, incident_id: str, classification: str - ) -> None: - """Adds a note and resolves the incident in PagerDuty.""" - note_content = ( - f"Classification: {classification}\n" - f"Incident closed from Google SecOps" - ) - self.api_client.add_incident_note(incident_id, note_content) - self.api_client.resolve_incident(incident_id) - self.logger.info( - f"Resolved PagerDuty incident {incident_id} " - f"with classification {classification}" + context_value: str | None = self.soar_job.get_context_property( + ENTITY_TYPE_ALERT, ticket.alert_group_identifier, ALERT_ID_CONTEXT_KEY ) + if context_value: + return context_value - def _close_incidents_in_pagerduty( - self, job_case: JobCase, res: JobStatusResult - ) -> list[tuple[str, str]]: - """Closes incidents in PagerDuty based on job status result.""" - synced_to_remove: list[tuple[str, str]] = [] - for req in res.incidents_to_close_in_product: - incident_id: str = req["meta"].incident_number - ticket: AlertCard = req["alert"] + return self.soar_job.get_context_property( + ENTITY_TYPE_ALERT, ticket.alert_group_identifier, CONTEXT_KEY + ) - classification = self._get_classification(ticket) + def map_product_data_to_case(self, job_case: JobCase) -> None: + """Maps PagerDuty incident data into metadata. + Args: + job_case: The SecOps case to map data for. + """ + product_ids = self._extract_product_ids_from_case(job_case) + product_details = self._fetch_product_details(product_ids) + self._attach_comments_to_product_details(product_details) + self._attach_product_details_to_case(job_case, product_details) + self._populate_alert_metadata(job_case, product_details) + self.remove_synced_data_from_db(job_case, product_details) + + def _fetch_product_details( + self, + product_ids: list[str], + ) -> list[dict[str, Any]]: + """Fetches PagerDuty incident details for given IDs.""" + results = [] + for product_id in product_ids: try: - self._resolve_pagerduty_incident(incident_id, classification) - - if ticket.status.lower() == "close": - synced_to_remove.append( - (str(job_case.case_detail.id_), incident_id) - ) + incident = self.api_client.get_incident(product_id) + if incident: + results.append(incident) + except Exception as e: + self.logger.error(f"Failed to fetch incident {product_id}: {e}") + return results + def _attach_comments_to_product_details( + self, + product_details: list[dict[str, Any]], + ) -> None: + """Attaches PagerDuty notes as comments to product details.""" + for detail in product_details: + incident_id = detail.get("id") + try: + notes = self.api_client.get_incident_notes(incident_id) + detail["comments"] = [ + SimpleNamespace(message=note.get("content", "")) for note in notes + ] except Exception as e: self.logger.error( - f"Failed to resolve PagerDuty incident {incident_id}: {e}" + f"Failed to fetch notes for incident {incident_id}: {e}" ) - return synced_to_remove - - def _get_open_tickets( - self, job_case: JobCase, closed_ticket_ids: set[str] - ) -> list[AlertCard]: - """Returns a list of open tickets in the case that are not yet closed.""" - return [ - t - for t in job_case.case_detail.alerts - if t.status.lower() == "open" - and t.identifier not in closed_ticket_ids - ] - - def _close_case_or_ticket( + + def _attach_product_details_to_case( self, job_case: JobCase, - ticket: AlertCard, - meta: Any, - open_tickets: list[AlertCard], - ) -> bool: - """Closes either the full case or just the specific ticket in SecOps.""" - reason_soar = REASON_RESOLVED_IN_PAGERDUTY - if len(open_tickets) == 1 and open_tickets[0].identifier == ticket.identifier: - self.soar_job.close_case( - root_cause=CLASSIFICATION_OTHER, - case_id=job_case.case_detail.id_, - reason=reason_soar, - comment=( - f"Closed case because last ticket {ticket.identifier} was " - "resolved in PagerDuty." - ), - alert_identifier=None, - ) - self.logger.info( - f"Closed case {job_case.case_detail.id_} as it was the last " - "open ticket." - ) - return False - else: - self.sync_product_status_to_case( - case_id=job_case.case_detail.id_, - alert_id=ticket.identifier, - reason=reason_soar, - root_cause="Other", - comment=( - f"PagerDuty incident {meta.incident_number} was resolved." - ), + product_details: list[dict[str, Any]] + ) -> None: + """Adds product incident details to the SecOps case.""" + for product_detail in product_details: + job_case.add_product_incident( + SimpleNamespace(**product_detail), product_key="id" ) - return True - - def _close_tickets_in_soar( - self, job_case: JobCase, res: JobStatusResult - ) -> list[tuple[str, str]]: - """Closes tickets or cases in SecOps based on job status result.""" - synced_to_remove: list[tuple[str, str]] = [] - closed_ticket_ids: set[str] = set() - for ticket, meta in res.alerts_to_close_in_soar: - try: - open_tickets = self._get_open_tickets(job_case, closed_ticket_ids) - if self._close_case_or_ticket(job_case, ticket, meta, open_tickets): - closed_ticket_ids.add(ticket.identifier) + def _populate_alert_metadata( + self, job_case: JobCase, product_details: list[dict[str, Any]] + ) -> None: + """Populates sync metadata for each SecOps alert.""" + product_map = {pd.get("id"): pd for pd in product_details} + case_id = str(job_case.case_detail.id_) + mapped_ids = self.processed_items.get(case_id, []) + for alert in job_case.case_detail.alerts: + product_id = self._extract_product_id_from_ticket(alert) + matching_product = None + if product_id: + matching_product = product_map.get(product_id) + else: + for pid in mapped_ids: + if pid in product_map: + matching_product = product_map[pid] + break - if meta.status == "resolved": - synced_to_remove.append( - (str(job_case.case_detail.id_), meta.incident_number) - ) - except Exception as e: - self.logger.error( - f"Failed to close ticket {ticket.identifier} or case in SecOps: {e}" + if matching_product: + comments = matching_product.get("comments", []) + closure_reason = None + for comment in comments: + content = getattr(comment, "message", "") + if content.startswith("Resolved: "): + closure_reason = content.replace("Resolved: ", "") + break + job_case.alert_metadata[alert.identifier] = SyncMetadata( + status=matching_product.get("status"), + incident_number=matching_product.get("id"), + closure_reason=closure_reason, ) - return synced_to_remove - - def sync_status(self, job_case: JobCase) -> None: - """Syncs status between PagerDuty and SecOps. - - Args: - job_case: The case to sync status for. - """ - res: JobStatusResult = job_case.get_status_to_sync("resolved") - - synced_to_remove: list[tuple[str, str]] = [] - - synced_to_remove.extend(self._close_incidents_in_pagerduty(job_case, res)) - synced_to_remove.extend(self._close_tickets_in_soar(job_case, res)) - - self._remove_synced_entries(synced_to_remove) - self._sync_untracked_incidents_status(job_case) - - self.logger.info(f"Case {job_case.case_detail.id_} successfully synced.") - - def _sync_untracked_incidents_status(self, job_case: JobCase) -> None: - """Fallback sync for cases not tracked by standard metadata.""" - incident_ids = self._extract_product_ids_from_case(job_case) - is_case_closed = job_case.case_detail.status == "Closed" - for incident_id in incident_ids: - try: - incident = self.api_client.get_incident(incident_id) - product_status = incident.get("status") + def sync_status( + self, + job_case: JobCase, + ) -> None: + """Syncs closure status between SecOps case alerts and PagerDuty incidents.""" + res = job_case.get_status_to_sync(product_closed_status="resolved") + self.sync_product_status_to_case(res, job_case) + self._sync_case_status_to_product(res, job_case) - if product_status == "resolved" and not is_case_closed: + def sync_product_status_to_case( + self, + res: JobStatusResult, + job_case: JobCase, + ) -> None: + """Syncs closures from PagerDuty to SecOps case alerts.""" + for alert, meta in res.alerts_to_close_in_soar: + reason, root_cause = self._get_secops_closure_details(meta.closure_reason) + open_alerts = [ + a + for a in job_case.case_detail.alerts + if a.status.lower() not in ["close", "closed"] + ] + comment = ( + f"{PAGERDUTY_COMMENT_PREFIX} Incident {meta.incident_number} " + f"was resolved." + ) + if len(open_alerts) <= 1: + try: + self.soar_job.close_case( + root_cause=root_cause, + comment=comment, + reason=reason, + case_id=job_case.case_detail.id_, + alert_identifier=alert.identifier, + ) + self.logger.info( + f"Successfully closed case {job_case.case_detail.id_} " + f"because alert {alert.identifier} was the last open alert " + f"and PagerDuty incident {meta.incident_number} was resolved." + ) + except Exception as e: + self.logger.error( + f"Failed to close case {job_case.case_detail.id_}: {e}" + ) + else: + try: self.soar_job.close_alert( - root_cause=CLASSIFICATION_OTHER, + root_cause=root_cause, + comment=comment, + reason=reason, case_id=job_case.case_detail.id_, - reason=REASON_RESOLVED_IN_PAGERDUTY, - comment=( - f"Closed case because PagerDuty incident " - f"{incident_id} was resolved." - ), - alert_identifier=None, + alert_id=alert.identifier, ) self.logger.info( - f"Closed case {job_case.case_detail.id_} due to " - f"resolved PagerDuty incident {incident_id}" + f"Successfully closed alert {alert.identifier} in case " + f"{job_case.case_detail.id_} because PagerDuty incident " + f"{meta.incident_number} was resolved." ) - elif product_status != "resolved" and is_case_closed: - self._resolve_pagerduty_incident(incident_id, CLASSIFICATION_OTHER) - - except Exception as e: - self.logger.error( - f"Failed to sync status for PagerDuty incident " - f"{incident_id}: {e}" - ) + except Exception as e: + self.logger.error(f"Failed to close alert {alert.identifier}: {e}") - def is_alert_and_product_closed(self, job_case: JobCase, product: Any) -> bool: - """Checks if alert and product are closed. + self._remove_synced_entries( + synced_list=[(job_case.case_detail.id_, f"{meta.incident_number}")], + ) - Args: - job_case (JobCase): The case. - product (Any): The product data. + def _get_secops_closure_details( + self, + classification: str | None, + ) -> tuple[str, str]: + """Maps a PagerDuty classification to SecOps case closure status and root - Returns: - bool: True if closed, False otherwise. + cause. """ + if classification == "True Positive": + return "Malicious", CloseCaseOrAlertMaliciousRootCauses.OTHER.value + elif classification == "False Positive": + return "Not Malicious", CloseCaseOrAlertNotMaliciousRootCauses.OTHER.value + + return ( + "Inconclusive", + CloseCaseOrAlertInconclusiveRootCauses.NO_CLEAR_CONCLUSION.value, + ) - if isinstance(product, SyncMetadata): - return product.status == "resolved" - return False - - def sync_comments(self, job_case: JobCase) -> None: - """Syncs comments between PagerDuty and SecOps.""" - incident_ids = self._extract_product_ids_from_case(job_case) - if not incident_ids: + def _sync_case_status_to_product( + self, + res: JobStatusResult, + job_case: JobCase, + ) -> None: + """Syncs case closure status from SecOps to PagerDuty.""" + if not res.incidents_to_close_in_product: return - for incident_id in incident_ids: - try: - notes = self.api_client.get_incident_notes(incident_id) - except Exception as e: - self.logger.error( - f"Failed to get PagerDuty notes for {incident_id}: {e}" - ) + for req in res.incidents_to_close_in_product: + meta = req.get("meta") + if not meta or not meta.incident_number: continue try: - case_comments = self.soar_job.fetch_case_comments( - job_case.case_detail.id_ + details = self.soar_job.get_case_closure_details( + [job_case.case_detail.id_] + ) + reason = details[0].get("reason", "") if details else "" + classification = self._get_pagerduty_classification(reason) + self.api_client.add_incident_note( + meta.incident_number, f"Resolved: {classification}" + ) + self.api_client.resolve_incident(meta.incident_number) + self.logger.info( + f"Successfully resolved PagerDuty incident " + f"{meta.incident_number} with classification " + f"{classification}." + ) + self._remove_synced_entries( + synced_list=[(job_case.case_detail.id_, f"{meta.incident_number}")], ) except Exception as e: self.logger.error( - f"Failed to fetch SOAR comments for case " - f"{job_case.case_detail.id_}: {e}" + f"Failed to resolve PagerDuty incident {meta.incident_number}: {e}" ) - case_comments = [] - for note in notes: - content = note.get("content", "") - if content.startswith(SIEM_COMMENT_PREFIX): - continue - - already_exists = False - for c in case_comments: - if c.get("comment", "").endswith(content): - already_exists = True - break + def _get_pagerduty_classification(self, reason: str) -> str: + """Maps SecOps reason to PagerDuty classification.""" + if reason == "Malicious": + return "True Positive" + elif reason == "Not Malicious": + return "False Positive" + return "Other" - if not already_exists: - self.soar_job.add_comment( - comment=f"{PAGERDUTY_COMMENT_PREFIX} {content}", - case_id=job_case.case_detail.id_, - alert_identifier=None, + def sync_comments(self, job_case: JobCase) -> None: + """Syncs comments between SecOps and PagerDuty.""" + if job_case.case_detail.status == "Closed": + return + + comments_to_sync = self.get_comments_to_sync( + job_case, + product_comment_prefix=PAGERDUTY_COMMENT_PREFIX, + case_comment_prefix=SIEM_COMMENT_PREFIX, + product_comment_key="message", + product_incident_key="id", + ) + + self.sync_product_comments_to_case( + case_id=job_case.case_detail.id_, + comments=comments_to_sync.product_comments_sync_to_case, + ) + if comments_to_sync.product_comments_sync_to_case: + self.logger.info( + f"Successfully synced " + f"{len(comments_to_sync.product_comments_sync_to_case)} " + f"comments from PagerDuty to SecOps case {job_case.case_detail.id_}." + ) + + self.sync_case_comments_to_product( + job_case=job_case, + comments=comments_to_sync.case_comments_sync_to_product, + ) + + def sync_case_comments_to_product( + self, + job_case: JobCase, + comments: list[str], + ) -> None: + """Syncs comments from SecOps case to PagerDuty incidents.""" + incident_ids = self._extract_product_ids_from_case(job_case) + for incident_id in incident_ids: + for comment in comments: + try: + self.api_client.add_incident_note( + incident_id, f"{SIEM_COMMENT_PREFIX} {comment}" + ) + self.logger.info( + f"Successfully added comment to PagerDuty incident " + f"{incident_id}." + ) + except Exception as e: + self.logger.error( + f"Failed to add note to PagerDuty incident {incident_id}: {e}" ) - for comment in case_comments: - content = comment.get("comment", "") - if content.startswith(PAGERDUTY_COMMENT_PREFIX): - continue + def is_alert_and_product_closed( + self, + job_case: JobCase, + product: dict[str, Any], + ) -> bool: + """Checks if both the SecOps alert and the corresponding PagerDuty + incident are closed. + """ + product_id = product.get("id") + + alert = next( + ( + alert + for alert in job_case.case_detail.alerts + if self._extract_product_id_from_ticket(alert) == product_id + ), + None, + ) - already_exists = False - for note in notes: - if note.get("content", "").endswith(content): - already_exists = True - break + if not alert: + return False - if not already_exists: - try: - self.api_client.add_incident_note( - incident_id, - f"{SIEM_COMMENT_PREFIX} {content}" - ) - except Exception as e: - self.logger.error( - f"Failed to add note to PagerDuty incident " - f"{incident_id}: {e}" - ) + alert_status = getattr(alert, "status", "") + alert_closed = False + if isinstance(alert_status, str): + alert_closed = alert_status.lower() in ["close", "closed"] + else: + alert_closed = str(alert_status).lower() in ["close", "closed"] - def remove_synced_data_from_db(self, job_case: JobCase, product_details: Any) -> None: - """Removes synced data from db.""" - pass + product_closed = product.get("status") == "resolved" + + return alert_closed and product_closed + + def remove_synced_data_from_db( + self, + job_case: JobCase, + product_details: list[dict[str, Any]], + ) -> None: + """Removes entries from synchronization tracking when both alert and + product are closed. + """ + for alert in job_case.case_detail.alerts: + alert_id = self._extract_product_id_from_ticket(alert) + if not alert_id: + continue + + matching_product = None + for product in product_details: + if product.get("id") == alert_id: + matching_product = product + break + + if not matching_product: + continue + + if self.is_alert_and_product_closed(job_case, matching_product): + self._remove_synced_entries([(job_case.case_detail.id_, alert_id)]) def sync_assignee(self, job_case: JobCase) -> None: - """Syncs assignee.""" pass def sync_severity(self, job_case: JobCase) -> None: - """Syncs severity.""" pass def sync_tags(self, job_case: JobCase) -> None: - """Syncs tags.""" pass diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml index 51caeca21..345f35628 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml @@ -2,30 +2,30 @@ name: Sync Incidents parameters: - name: api_key type: password - description: PagerDuty API Key + description: The API key for the PagerDuty instance. is_mandatory: true - name: Max Hours Backwards type: integer default_value: 24 - description: Max hours backwards to sync incidents on first run. - is_mandatory: false + description: The number of hours prior to the current time to synchronize incidents during the first job iteration. + is_mandatory: true - name: Verify SSL type: boolean default_value: true - description: Verify SSL - is_mandatory: false + description: If selected, the integration validates the SSL certificate when connecting to the PagerDuty server. + is_mandatory: true - name: From Email type: string - description: PagerDuty From Email (Required for notes and status changes) + description: The email address associated with the PagerDuty account. This parameter is required to synchronize notes and status changes. is_mandatory: false - name: Environment Name type: string default_value: Default Environment - description: Environment name to sync cases from. - is_mandatory: false + description: The name of the environment from which to synchronize cases. + is_mandatory: true description: | - This job synchronizes Google SecOps Alerts and PagerDuty Incidents. It ensures that status and comments are synchronized bi-directionally between both systems. For the job to identify the correct information, the Google SecOps case must have the "Pagerduty Ticket" tag. If the ticket didn’t originate from "Pagerduty Connector", you will need to add a "TICKET_ID" context value to the case for the job to be able to find the correct information. + This job synchronizes Google SecOps Alerts and PagerDuty tickets. It ensures that status is synchronized bi-directionally between both systems. For the job to identify the correct information, the Google SecOps case must have the "Pagerduty Ticket" tag. If the Ticket didn’t originate from "Pagerduty", you will need to add an "Ticket_ID" context value to the Ticket for the job to be able to find the correct information. In addition to status, this job now synchronizes comments bi-directionally between both systems. To prevent loops, comments are prefixed with “SecOps:” or “PagerDuty:”. For playbook-created cases, the job looks for the “TICKET_ID” context value at the Case level. integration: PagerDuty creator: Admin diff --git a/content/response_integrations/third_party/community/pager_duty/tests/conftest.py b/content/response_integrations/third_party/community/pager_duty/tests/conftest.py index 0bf6b4c87..f30d802e3 100644 --- a/content/response_integrations/third_party/community/pager_duty/tests/conftest.py +++ b/content/response_integrations/third_party/community/pager_duty/tests/conftest.py @@ -6,6 +6,9 @@ import pytest from integration_testing.common import use_live_api +from TIPCommon.base.job.job_case import JobCase, SyncMetadata + +from pager_duty.jobs.SyncIncidents import SyncIncidents from .core.product import PagerDuty from .core.session import PagerDutySession @@ -36,10 +39,11 @@ def script_session( @pytest.fixture def mock_job_env(): """Fixture to mock sys.stdin for job execution.""" + class MockStdin: def __init__(self) -> None: self.buffer: io.BytesIO = io.BytesIO(b'{"parameters": {}}') - + original_stdin = sys.stdin sys.stdin = MockStdin() yield @@ -49,84 +53,62 @@ def __init__(self) -> None: @pytest.fixture def job(mock_job_env): """Fixture providing a SyncIncidents job instance with mocked properties.""" - from pager_duty.jobs.SyncIncidents import SyncIncidents - original_params = SyncIncidents.params original_api_client = SyncIncidents.api_client original_logger = SyncIncidents.logger original_soar_job = SyncIncidents.soar_job - original_sync_product_status_to_case = SyncIncidents.sync_product_status_to_case - + SyncIncidents.params = property(lambda self: MagicMock(api_key="test_key")) - mock_logger = MagicMock() + SyncIncidents.logger = property(lambda self: mock_logger) - mock_soar_job = MagicMock() + SyncIncidents.soar_job = property(lambda self: mock_soar_job) - - mock_sync_method = MagicMock() - SyncIncidents.sync_product_status_to_case = mock_sync_method - job_instance = SyncIncidents() + client = job_instance._init_api_clients() SyncIncidents.api_client = property(lambda self: client) - job_instance._remove_synced_entries = MagicMock() - + yield job_instance - SyncIncidents.params = original_params SyncIncidents.api_client = original_api_client SyncIncidents.logger = original_logger SyncIncidents.soar_job = original_soar_job - SyncIncidents.sync_product_status_to_case = original_sync_product_status_to_case @pytest.fixture def job_failing_api(mock_job_env): """Fixture providing a SyncIncidents job instance with a failing API client.""" - from pager_duty.jobs.SyncIncidents import SyncIncidents - original_params = SyncIncidents.params original_api_client = SyncIncidents.api_client original_logger = SyncIncidents.logger original_soar_job = SyncIncidents.soar_job - original_sync_product_status_to_case = SyncIncidents.sync_product_status_to_case - + SyncIncidents.params = property(lambda self: MagicMock(api_key="test_key")) - mock_logger = MagicMock() SyncIncidents.logger = property(lambda self: mock_logger) - mock_soar_job = MagicMock() SyncIncidents.soar_job = property(lambda self: mock_soar_job) - - mock_sync_method = MagicMock() - SyncIncidents.sync_product_status_to_case = mock_sync_method - + job_instance = SyncIncidents() client = job_instance._init_api_clients() - client.resolve_incident = MagicMock(side_effect=Exception("API Failure")) - SyncIncidents.api_client = property(lambda self: client) - job_instance._remove_synced_entries = MagicMock() - + yield job_instance - + SyncIncidents.params = original_params SyncIncidents.api_client = original_api_client SyncIncidents.logger = original_logger SyncIncidents.soar_job = original_soar_job - SyncIncidents.sync_product_status_to_case = original_sync_product_status_to_case @pytest.fixture def job_case_map(): """Fixture providing a JobCase mock for mapping tests.""" - from TIPCommon.base.job.job_case import JobCase job_case = MagicMock(spec=JobCase) alert = MagicMock() alert.identifier = "alert_1" @@ -139,70 +121,67 @@ def job_case_map(): @pytest.fixture def job_case_sync(): """Fixture providing a JobCase mock for syncing status (SOAR to PagerDuty).""" - from TIPCommon.base.job.job_case import JobCase, SyncMetadata job_case = MagicMock(spec=JobCase) res = MagicMock() - + alert = MagicMock() alert.status = "close" alert.closure_details = {"reason": "Malicious"} - + meta = SyncMetadata(status="triggered", incident_number="P123", closure_reason=None) - + res.incidents_to_close_in_product = [{"meta": meta, "alert": alert}] res.alerts_to_close_in_soar = [] - + job_case.get_status_to_sync.return_value = res job_case.case_detail.id_ = 1 - + return job_case @pytest.fixture def job_case_sync_close_case(): """Fixture providing a JobCase mock for syncing status (PagerDuty to SOAR, close case).""" - from TIPCommon.base.job.job_case import JobCase, SyncMetadata job_case = MagicMock(spec=JobCase) res = MagicMock() - + alert = MagicMock() alert.identifier = "alert_1" alert.status = "open" - + meta = SyncMetadata(status="resolved", incident_number="P123", closure_reason=None) - + res.incidents_to_close_in_product = [] res.alerts_to_close_in_soar = [(alert, meta)] - + job_case.get_status_to_sync.return_value = res job_case.case_detail.id_ = 1 job_case.case_detail.alerts = [alert] - + return job_case @pytest.fixture def job_case_sync_close_alert(): """Fixture providing a JobCase mock for syncing status (PagerDuty to SOAR, close alert only).""" - from TIPCommon.base.job.job_case import JobCase, SyncMetadata job_case = MagicMock(spec=JobCase) res = MagicMock() - + alert1 = MagicMock() alert1.identifier = "alert_1" alert1.status = "open" - + alert2 = MagicMock() alert2.identifier = "alert_2" alert2.status = "open" - + meta = SyncMetadata(status="resolved", incident_number="P123", closure_reason=None) - + res.incidents_to_close_in_product = [] res.alerts_to_close_in_soar = [(alert1, meta)] - + job_case.get_status_to_sync.return_value = res job_case.case_detail.id_ = 1 job_case.case_detail.alerts = [alert1, alert2] - + return job_case diff --git a/content/response_integrations/third_party/community/pager_duty/tests/test_jobs/test_sync_incidents.py b/content/response_integrations/third_party/community/pager_duty/tests/test_jobs/test_sync_incidents.py index 682ea8121..6f173f96c 100644 --- a/content/response_integrations/third_party/community/pager_duty/tests/test_jobs/test_sync_incidents.py +++ b/content/response_integrations/third_party/community/pager_duty/tests/test_jobs/test_sync_incidents.py @@ -12,9 +12,7 @@ def test_map_product_data_to_case_success( ) -> None: """Tests mapping product data to case successfully.""" pagerduty.set_incidents({ - "incidents": [ - {"id": "P123", "status": "resolved", "incident_key": "key1"} - ] + "incidents": [{"id": "P123", "status": "resolved", "incident_key": "key1"}] }) job.map_product_data_to_case(job_case_map) @@ -47,9 +45,7 @@ def test_sync_status_soar_to_pagerduty_success( ) -> None: """Tests syncing status from SOAR to PagerDuty successfully.""" pagerduty.set_incidents({ - "incidents": [ - {"id": "P123", "status": "triggered", "incident_key": "key1"} - ] + "incidents": [{"id": "P123", "status": "triggered", "incident_key": "key1"}] }) job.sync_status(job_case_sync) @@ -57,7 +53,7 @@ def test_sync_status_soar_to_pagerduty_success( assert len(script_session.request_history) == 2 assert script_session.request_history[0].request.url.path.endswith("/incidents/P123/notes") assert script_session.request_history[1].request.url.path.endswith("/incidents/P123") - + incident = pagerduty.get_incident("P123") assert incident["status"] == "resolved" @@ -72,7 +68,6 @@ def test_sync_status_pagerduty_to_soar_close_case_success( job.sync_status(job_case_sync_close_case) assert job.soar_job.close_case.called - assert not job.sync_product_status_to_case.called def test_sync_status_pagerduty_to_soar_close_alert_success( @@ -84,7 +79,7 @@ def test_sync_status_pagerduty_to_soar_close_alert_success( """Tests syncing status from PagerDuty to SOAR resulting in closing only the alert.""" job.sync_status(job_case_sync_close_alert) - assert job.sync_product_status_to_case.called + assert job.soar_job.close_alert.called assert not job.soar_job.close_case.called @@ -96,9 +91,7 @@ def test_sync_status_soar_to_pagerduty_failure( ) -> None: """Tests syncing status from SOAR to PagerDuty with API failure handling.""" pagerduty.set_incidents({ - "incidents": [ - {"id": "P123", "status": "triggered", "incident_key": "key1"} - ] + "incidents": [{"id": "P123", "status": "triggered", "incident_key": "key1"}] }) job_failing_api.sync_status(job_case_sync) diff --git a/packages/integration_testing/src/integration_testing/common.py b/packages/integration_testing/src/integration_testing/common.py index bf36855e3..a87e33ee4 100644 --- a/packages/integration_testing/src/integration_testing/common.py +++ b/packages/integration_testing/src/integration_testing/common.py @@ -23,8 +23,8 @@ from typing import TYPE_CHECKING import yaml -from OverflowManager import OverflowManager, OverflowManagerSettings from SiemplifyDataModel import DomainEntityInfo +from soar_sdk.OverflowManager import OverflowManager, OverflowManagerSettings from TIPCommon.base.action import CaseComment, EntityTypesEnum from TIPCommon.base.job import JobParameter from TIPCommon.consts import NUM_OF_MILLI_IN_SEC From 278860cb3978ec9215481f7a07a43f13ad0c89ea Mon Sep 17 00:00:00 2001 From: Arabindaksha-Mishra Date: Thu, 23 Apr 2026 11:13:33 +0000 Subject: [PATCH 9/9] Updated the context property and closing comment sync --- .../pager_duty/core/PagerDutyManager.py | 6 - .../pager_duty/jobs/SyncIncidents.py | 135 +++++------------- .../pager_duty/jobs/SyncIncidents.yaml | 4 +- 3 files changed, 36 insertions(+), 109 deletions(-) diff --git a/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py b/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py index f12b185af..0b33df457 100644 --- a/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py +++ b/content/response_integrations/third_party/community/pager_duty/core/PagerDutyManager.py @@ -81,9 +81,6 @@ def resolve_incident(self, incident_id: str) -> SingleJson: Returns: SingleJson: The API response. """ - if not self.from_email: - raise ValueError("from_email is required for resolve_incident operation.") - url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}" payload: SingleJson = { "incident": { @@ -112,9 +109,6 @@ def add_incident_note(self, incident_id: str, content: str) -> SingleJson: Returns: SingleJson: The API response. """ - if not self.from_email: - raise ValueError("from_email is required for add_incident_note operation.") - url: str = self.BASE_URL + self.INCIDENTS_URI + f"/{incident_id}/notes" payload: SingleJson = { "note": { diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py index 093959a23..fff9aa855 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.py @@ -4,11 +4,6 @@ from types import SimpleNamespace from typing import Any -from TIPCommon.base.action.data_models import ( - CloseCaseOrAlertInconclusiveRootCauses, - CloseCaseOrAlertMaliciousRootCauses, - CloseCaseOrAlertNotMaliciousRootCauses, -) from TIPCommon.base.job.base_sync_job import BaseSyncJob from TIPCommon.base.job.job_case import ( JobCase, @@ -19,7 +14,6 @@ from TIPCommon.transformation import convert_comma_separated_to_list from ..core.constants import ( - ALERT_ID_CONTEXT_KEY, CONTEXT_KEY, ENTITY_TYPE_ALERT, ENTITY_TYPE_TICKET, @@ -136,8 +130,8 @@ def _get_notes_latest_timestamp(self, incident_id: str) -> int: latest_timestamp = max(latest_timestamp, created_at_ms) except Exception as e: self.logger.debug( - f"Failed to parse created_at for note in incident " - f"{incident_id}: {e}" + f"Failed to parse created_at for note in " + f"incident {incident_id}: {e}" ) continue except Exception as e: @@ -145,11 +139,11 @@ def _get_notes_latest_timestamp(self, incident_id: str) -> int: return latest_timestamp def _is_incident_modified( - self, - incident_id: str, - from_timestamp: int, + self, incident_id: str, from_timestamp: int ) -> tuple[bool, int]: - """Checks if incident or notes were modified since from_timestamp.""" + """Checks if an incident or its notes have been modified since + from_timestamp. + """ incident_ts = self._get_incident_latest_timestamp(incident_id) notes_ts = self._get_notes_latest_timestamp(incident_id) @@ -175,13 +169,6 @@ def _extract_product_ids_from_case(self, job_case: JobCase) -> list[str]: str(job_case.case_detail.id_), CONTEXT_KEY, ) - - if not context_value: - context_value = self.soar_job.get_context_property( - ENTITY_TYPE_TICKET, - str(job_case.case_detail.id_), - ALERT_ID_CONTEXT_KEY, - ) if context_value: ids = convert_comma_separated_to_list(context_value) incident_ids.extend(ids) @@ -208,12 +195,6 @@ def _extract_product_id_from_ticket(self, ticket: AlertCard) -> str | None: if ticket.ticket_id: return ticket.ticket_id - context_value: str | None = self.soar_job.get_context_property( - ENTITY_TYPE_ALERT, ticket.alert_group_identifier, ALERT_ID_CONTEXT_KEY - ) - if context_value: - return context_value - return self.soar_job.get_context_property( ENTITY_TYPE_ALERT, ticket.alert_group_identifier, CONTEXT_KEY ) @@ -231,10 +212,7 @@ def map_product_data_to_case(self, job_case: JobCase) -> None: self._populate_alert_metadata(job_case, product_details) self.remove_synced_data_from_db(job_case, product_details) - def _fetch_product_details( - self, - product_ids: list[str], - ) -> list[dict[str, Any]]: + def _fetch_product_details(self, product_ids: list[str]) -> list[dict[str, Any]]: """Fetches PagerDuty incident details for given IDs.""" results = [] for product_id in product_ids: @@ -247,8 +225,7 @@ def _fetch_product_details( return results def _attach_comments_to_product_details( - self, - product_details: list[dict[str, Any]], + self, product_details: list[dict[str, Any]] ) -> None: """Attaches PagerDuty notes as comments to product details.""" for detail in product_details: @@ -264,9 +241,7 @@ def _attach_comments_to_product_details( ) def _attach_product_details_to_case( - self, - job_case: JobCase, - product_details: list[dict[str, Any]] + self, job_case: JobCase, product_details: list[dict[str, Any]] ) -> None: """Adds product incident details to the SecOps case.""" for product_detail in product_details: @@ -295,43 +270,34 @@ def _populate_alert_metadata( if matching_product: comments = matching_product.get("comments", []) closure_reason = None - for comment in comments: - content = getattr(comment, "message", "") - if content.startswith("Resolved: "): - closure_reason = content.replace("Resolved: ", "") - break + if comments: + closure_reason = getattr(comments[-1], "message", "") + job_case.alert_metadata[alert.identifier] = SyncMetadata( status=matching_product.get("status"), incident_number=matching_product.get("id"), closure_reason=closure_reason, ) - def sync_status( - self, - job_case: JobCase, - ) -> None: + def sync_status(self, job_case: JobCase) -> None: """Syncs closure status between SecOps case alerts and PagerDuty incidents.""" res = job_case.get_status_to_sync(product_closed_status="resolved") self.sync_product_status_to_case(res, job_case) self._sync_case_status_to_product(res, job_case) def sync_product_status_to_case( - self, - res: JobStatusResult, - job_case: JobCase, + self, res: JobStatusResult, job_case: JobCase ) -> None: """Syncs closures from PagerDuty to SecOps case alerts.""" for alert, meta in res.alerts_to_close_in_soar: - reason, root_cause = self._get_secops_closure_details(meta.closure_reason) + reason = "Maintenance" + root_cause = "Other" open_alerts = [ a for a in job_case.case_detail.alerts if a.status.lower() not in ["close", "closed"] ] - comment = ( - f"{PAGERDUTY_COMMENT_PREFIX} Incident {meta.incident_number} " - f"was resolved." - ) + comment = meta.closure_reason or "Ticket was closed" if len(open_alerts) <= 1: try: self.soar_job.close_case( @@ -371,28 +337,8 @@ def sync_product_status_to_case( synced_list=[(job_case.case_detail.id_, f"{meta.incident_number}")], ) - def _get_secops_closure_details( - self, - classification: str | None, - ) -> tuple[str, str]: - """Maps a PagerDuty classification to SecOps case closure status and root - - cause. - """ - if classification == "True Positive": - return "Malicious", CloseCaseOrAlertMaliciousRootCauses.OTHER.value - elif classification == "False Positive": - return "Not Malicious", CloseCaseOrAlertNotMaliciousRootCauses.OTHER.value - - return ( - "Inconclusive", - CloseCaseOrAlertInconclusiveRootCauses.NO_CLEAR_CONCLUSION.value, - ) - def _sync_case_status_to_product( - self, - res: JobStatusResult, - job_case: JobCase, + self, res: JobStatusResult, job_case: JobCase ) -> None: """Syncs case closure status from SecOps to PagerDuty.""" if not res.incidents_to_close_in_product: @@ -404,36 +350,27 @@ def _sync_case_status_to_product( continue try: - details = self.soar_job.get_case_closure_details( - [job_case.case_detail.id_] - ) - reason = details[0].get("reason", "") if details else "" - classification = self._get_pagerduty_classification(reason) - self.api_client.add_incident_note( - meta.incident_number, f"Resolved: {classification}" - ) + closing_comment = self.get_secops_closure_comment(job_case, req) + + if closing_comment: + self.api_client.add_incident_note( + meta.incident_number, closing_comment + ) self.api_client.resolve_incident(meta.incident_number) self.logger.info( - f"Successfully resolved PagerDuty incident " - f"{meta.incident_number} with classification " - f"{classification}." + f"Successfully resolved PagerDuty incident {meta.incident_number}." ) self._remove_synced_entries( - synced_list=[(job_case.case_detail.id_, f"{meta.incident_number}")], + synced_list=[ + (job_case.case_detail.id_, f"{meta.incident_number}") + ], ) except Exception as e: self.logger.error( - f"Failed to resolve PagerDuty incident {meta.incident_number}: {e}" + f"Failed to resolve PagerDuty incident " + f"{meta.incident_number}: {e}" ) - def _get_pagerduty_classification(self, reason: str) -> str: - """Maps SecOps reason to PagerDuty classification.""" - if reason == "Malicious": - return "True Positive" - elif reason == "Not Malicious": - return "False Positive" - return "Other" - def sync_comments(self, job_case: JobCase) -> None: """Syncs comments between SecOps and PagerDuty.""" if job_case.case_detail.status == "Closed": @@ -452,9 +389,9 @@ def sync_comments(self, job_case: JobCase) -> None: comments=comments_to_sync.product_comments_sync_to_case, ) if comments_to_sync.product_comments_sync_to_case: + count = len(comments_to_sync.product_comments_sync_to_case) self.logger.info( - f"Successfully synced " - f"{len(comments_to_sync.product_comments_sync_to_case)} " + f"Successfully synced {count} " f"comments from PagerDuty to SecOps case {job_case.case_detail.id_}." ) @@ -464,9 +401,7 @@ def sync_comments(self, job_case: JobCase) -> None: ) def sync_case_comments_to_product( - self, - job_case: JobCase, - comments: list[str], + self, job_case: JobCase, comments: list[str] ) -> None: """Syncs comments from SecOps case to PagerDuty incidents.""" incident_ids = self._extract_product_ids_from_case(job_case) @@ -519,9 +454,7 @@ def is_alert_and_product_closed( return alert_closed and product_closed def remove_synced_data_from_db( - self, - job_case: JobCase, - product_details: list[dict[str, Any]], + self, job_case: JobCase, product_details: list[dict[str, Any]] ) -> None: """Removes entries from synchronization tracking when both alert and product are closed. diff --git a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml index 345f35628..9f9d5226e 100644 --- a/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml +++ b/content/response_integrations/third_party/community/pager_duty/jobs/SyncIncidents.yaml @@ -17,7 +17,7 @@ parameters: - name: From Email type: string description: The email address associated with the PagerDuty account. This parameter is required to synchronize notes and status changes. - is_mandatory: false + is_mandatory: true - name: Environment Name type: string default_value: Default Environment @@ -26,6 +26,6 @@ parameters: description: | - This job synchronizes Google SecOps Alerts and PagerDuty tickets. It ensures that status is synchronized bi-directionally between both systems. For the job to identify the correct information, the Google SecOps case must have the "Pagerduty Ticket" tag. If the Ticket didn’t originate from "Pagerduty", you will need to add an "Ticket_ID" context value to the Ticket for the job to be able to find the correct information. In addition to status, this job now synchronizes comments bi-directionally between both systems. To prevent loops, comments are prefixed with “SecOps:” or “PagerDuty:”. For playbook-created cases, the job looks for the “TICKET_ID” context value at the Case level. + This job synchronizes Google SecOps alerts and PagerDuty Tickets. It ensures that comments and statuses sync bi-directionally between both systems. Note: This job requires the 'Pagerduty Ticket' tag on the SecOps case. If the ticket wasn’t created using the PagerDuty connector, you must add a 'TICKET_ID' context value (or 'TICKET_ID' at the Case level for playbook-generated cases). To prevent synchronization loops, all mirrored comments are prefixed with 'SecOps:' or 'PagerDuty:' integration: PagerDuty creator: Admin