# Copyright (c) HashiCorp, Inc.# SPDX-License-Identifier: MPL-2.0"""Slack Audit API client.The official Slack SDK does not currently support the Audit API, this client has beencreated in the interim."""importloggingimporttimefromtypingimportDict,Optionalimportrequestsfromgrove.exceptionsimportRateLimitException,RequestFailedExceptionfromgrove.typesimportAuditLogEntries,HTTPResponseAPI_BASE_URI="https://api.slack.com/audit/v1"
[docs]classClient:def__init__(self,token:Optional[str]=None,retry:Optional[bool]=True,):"""Setup a new Slack audit API client. :param token: Slack API Bearer token. :param retry: Automatically retry if recoverable errors are encountered, such as rate-limiting. """self.retry=retryself.logger=logging.getLogger(__name__)self.headers={"Accept":"application/json","Authorization":f"Bearer {token}",}def_get(self,url:str,params:Optional[Dict[str,Optional[str]]]=None,)->HTTPResponse:"""A GET wrapper to handle retries for the caller. :param url: URL to perform the HTTP GET against. :param params: HTTP parameters to add to the request. :raises RateLimitException: A rate limit was encountered. :raises RequestFailedException: An HTTP request failed. :return: HTTP Response object containing the headers and body of a response. """whileTrue:try:response=requests.get(url,headers=self.headers,params=params)response.raise_for_status()breakexceptrequests.exceptions.RequestExceptionaserr:# Retry on rate-limit, but only if requested.ifgetattr(err.response,"status_code",None)==429:self.logger.warning("Rate-limit was exceeded during request")ifself.retry:time.sleep(int(err.response.headers.get("Retry-After","1")))continueelse:raiseRateLimitException(err)raiseRequestFailedException(err)returnHTTPResponse(headers=response.headers,body=response.json())
[docs]defget_logs(self,latest:Optional[str]=None,oldest:Optional[str]=None,action:Optional[str]=None,cursor:Optional[str]=None,)->AuditLogEntries:"""Fetches a list of audit logs which match the provided filters. :param latest: Unix timestamp of the most recent event to include (inclusive). :param oldest: Unix timestamp of the least recent event to include (inclusive). :param action: Name of the action to request events for. :param cursor: Cursor to use when fetching events (pagination). :return: AuditLogEntries object containing a pagination cursor, and log entries. """# See psf/requests issue #2651 for why we can happily pass in None values and# not have the request key added to the URI.result=self._get(f"{API_BASE_URI}/logs",params={"latest":latest,"oldest":oldest,"action":action,"cursor":cursor,"limit":"1000",},)# Slack appears to return an empty string for a cursor if there isn't one, so# swap this for None in this case to avoid having to rely on "falsy" conditions.cursor=result.body.get("response_metadata",{}).get("next_cursor",None)ifcursor=="":cursor=None# Return the cursor and the results to allow the caller to page as required.returnAuditLogEntries(cursor=cursor,entries=result.body.get("entries",[]))