# Copyright (c) HashiCorp, Inc.# SPDX-License-Identifier: MPL-2.0"""Google GSuite Alerts connector for Grove."""importjsonfromdatetimeimportdatetime,timedeltaimportgoogle_auth_httplib2importhttplib2fromgoogle.auth.exceptionsimportGoogleAuthErrorfromgoogle.oauth2importservice_accountfromgoogleapiclient.discoveryimportbuildfromgoogleapiclient.errorsimportErrorfromgrove.connectorsimportBaseConnectorfromgrove.constantsimportCHRONOLOGICALfromgrove.exceptionsimport(ConfigurationException,NotFoundException,RequestFailedException,)# This connector is only interested in the GSuite Alerts API.SCOPES=["https://www.googleapis.com/auth/apps.alerts"]
[docs]defcollect(self):"""Collects all alerts from the Google GSuite Alerts API. As the Google APIs use OAuth 2.0 2LO ('two-legged OAuth') which contains a number of fields inside of a JSON 'service account file' the key and identity are treated a little differently in this connector. Rather than the key being a single authentication token, the key should contain the entire 'service account file' in JSON format - as generated by the Google API console. The identity must be the name of a service account which has been granted domain wide delegation. Please see the following guides for more information: https://developers.google.com/admin-sdk/alertcenter/guides/prerequisites https://developers.google.com/admin-sdk/directory/v1/guides/delegation :raises RequestFailedException: An HTTP request failed. """cursor=str()http=google_auth_httplib2.AuthorizedHttp(self.get_credentials(),http=self.get_http_transport(),)# If no pointer is stored then a previous run hasn't been performed, so set the# pointer to a week ago. In the case of the GSuite audit API the pointer is the# value of the "createTime" field from the latest record retrieved from the# API.try:_=self.pointerexceptNotFoundException:self.pointer=(datetime.utcnow()-timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ")# Page over all alerts since the last pointer.more_requests=Truepage_size=1000withbuild("alertcenter","v1beta1",http=http)asservice:whilemore_requests:# The cursor may work with the filter, but let's not combine them, just# to try and avoid any strange and difficult to debug behaviour.self.logger.debug("Requesting GSuite alert list.",extra={"operation":self.operation})ifcursor:self.logger.debug("Using pageToken as cursor to request next page of results",extra={"cursor":cursor},)request=service.alerts().list(orderBy="createTime asc",pageSize=page_size,pageToken=cursor,)else:self.logger.debug("Using createTime from pointer",extra={"pointer":self.pointer},)request=service.alerts().list(orderBy="createTime asc",pageSize=page_size,filter=f'createTime > "{self.pointer}"',)# Page over results and save.try:results=request.execute()alerts=results.get("alerts",[])self.logger.debug("Got alerts from the GSuite API",extra={"count":len(alerts)})self.save(alerts)except(GoogleAuthError,Error)aserr:raiseRequestFailedException(f"Request to GSuite API failed: {err}")# Determine whether we're still paging.if"nextPageToken"notinresults:self.logger.debug("No nextPageToken, finishing collection.")more_requests=Falsebreakself.logger.debug("nextPageToken is present in response, paging.")cursor=results["nextPageToken"]more_requests=True
[docs]defget_http_transport(self):"""Creates an HTTP object for use by the Google API Client. :return: An httplib2.Http object for use with the Google API client. """returnhttplib2.Http()
[docs]defget_credentials(self):"""Generates and returns a credentials instance from the connector's configured service account info. This is used for required to perform operations using the Google API client. :return: A credentials instance built from configured service account info. :raises ConfigurationException: There is an issue with the configuration for this connector. """try:service_account_info=json.loads(self.key)exceptjson.JSONDecodeErroraserr:raiseConfigurationException(f"Unable to load service account JSON for {self.identity}: {err}")# Construct the credentials, including scopes and delegation.try:credentials=service_account.Credentials.from_service_account_info(service_account_info,scopes=SCOPES,subject=self.identity,)exceptValueErroraserr:raiseConfigurationException("Unable to generate credentials from service account info for "f"{self.identity}: {err}")returncredentials