Source
# pylint: disable=no-name-in-module # pylint: disable=no-self-argument from abc import ABC from collections.abc import Callable from enum import Enum from typing import Any import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 from pydantic import AnyUrl, BaseConfig, BaseModel, Field, validator from requests.auth import HTTPBasicAuth from CommonServerUserPython import * class Method(str, Enum): GET = "GET" POST = "POST" PUT = "PUT" HEAD = "HEAD" PATCH = "PATCH" DELETE = "DELETE" def load_json(v: Any) -> dict: if not isinstance(v, dict | str): raise ValueError("headers are not dict or a valid json") if isinstance(v, str): try: v = json.loads(v) if not isinstance(v, dict): raise ValueError("headers are not from dict type") except json.decoder.JSONDecodeError as exc: raise ValueError("headers are not valid Json object") from exc if isinstance(v, dict): return v return {} class IntegrationHTTPRequest(BaseModel): method: Method url: AnyUrl verify: bool = True headers: dict = {} # type: ignore[type-arg] auth: HTTPBasicAuth | None = None data: Any = None params: dict = {} # type: ignore[type-arg] class Config(BaseConfig): arbitrary_types_allowed = True _normalize_headers = validator("headers", pre=True, allow_reuse=True)( # type: ignore[type-var] load_json ) class Credentials(BaseModel): identifier: str | None password: str def set_authorization(request: IntegrationHTTPRequest, auth_credendtials): """Automatic authorization. Supports {Authorization: Bearer __token__} or Basic Auth. """ creds = Credentials.parse_obj(auth_credendtials) if creds.password and creds.identifier: request.auth = HTTPBasicAuth(creds.identifier, creds.password) auth = {"Authorization": f"Bearer {creds.password}"} if request.headers: request.headers |= auth # type: ignore[assignment, operator] else: request.headers = auth # type: ignore[assignment] class IntegrationOptions(BaseModel): """Add here any option you need to add to the logic""" proxy: bool | None = False limit: int | None = Field(None, ge=1) class IntegrationEventsClient(ABC): def __init__( self, request: IntegrationHTTPRequest, options: IntegrationOptions, session=requests.Session(), ): self.request = request self.options = options self.session = session self._set_proxy() self._skip_cert_verification() @abstractmethod def set_request_filter(self, after: Any): """TODO: set the next request's filter. Example: """ self.request.headers["after"] = after def __del__(self): try: self.session.close() except AttributeError as err: demisto.debug(f"ignore exceptions raised due to session not used by the client. {err=}") def call(self, request: IntegrationHTTPRequest) -> requests.Response: try: response = self.session.request(**request.dict()) response.raise_for_status() return response except Exception as exc: msg = f"something went wrong with the http call {exc}" demisto.debug(msg) raise DemistoException(msg) from exc def _skip_cert_verification(self, skip_cert_verification: Callable = skip_cert_verification): if not self.request.verify: skip_cert_verification() def _set_proxy(self): if self.options.proxy: ensure_proxy_has_http_prefix() else: skip_proxy() class IntegrationGetEvents(ABC): def __init__(self, client: IntegrationEventsClient, options: IntegrationOptions) -> None: self.client = client self.options = options def run(self): stored = [] for logs in self._iter_events(): stored.extend(logs) if self.options.limit: demisto.debug( f"{self.options.limit=} reached. \ slicing from {len(logs)=}. \ limit must be presented ONLY in commands and not in fetch-events." ) if len(stored) >= self.options.limit: return stored[: self.options.limit] return stored def call(self) -> requests.Response: return self.client.call(self.client.request) @staticmethod @abstractmethod def get_last_run(events: list) -> dict: """Logic to get the last run from the events Example: """ return {"after": events[-1]["created"]} @abstractmethod # noqa: B027 def _iter_events(self): """Create iterators with Yield"""
README
SiemAPIModule
Usage
To use this API Module, implement the next methods:
The IntegrationEventsClient Object:
class MyIntegrationEventsClient(IntegrationEventsClient):
def set_request_filter(self, after: Any):
"""Implement the next call run
Example:
>>> from datetime import datetime
>>> set_request_filter(datetime(year=2022, month=4, day=16))
"""
self.client.request.headers['after'] = after.isoformat()
The IntegrationGetEvents Object:
class MyIntegrationGetEvents(IntegrationGetEvents):
def get_last_run(events) -> dict:
"""Implement how to get the last run.
Example:
>>> get_last_run([{'created': '2022-4-16'}])
"""
return {'after': events[-1]['created']}
def _iter_events(self):
"""Create an iterator on the events.
If need extra authorisation, do that in the beggining of the command.
Example:
>>> for event in _iter_events():
...
"""
response = self.call(self.request)
while True:
events = response.json()
yield events
self.client.set_request_filter(events[-1]['created'])
self.call(self.request)