Skip to content

Commit

Permalink
Patch to allow use of Application Default Credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
David Wihl committed Jul 15, 2020
1 parent 7c41584 commit f3065bd
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 6 deletions.
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
24.1.0 -- 07/13/2020
* Added 'GoogleCredentialsClient' class to re-use an OAuth2 credentials object

24.0.0 -- 05/12/2020
* Added support for Ad Manager v202005.
* Removed support for Ad Manager v201905.
Expand Down
2 changes: 1 addition & 1 deletion googleads/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
'compatibility with this library, upgrade to Python 3.6 or higher.')


VERSION = '24.0.0'
VERSION = '24.1.0'
_COMMON_LIB_SIG = 'googleads/%s' % VERSION
_LOGGING_KEY = 'logging'
_HTTP_PROXY_YAML_KEY = 'http'
Expand Down
38 changes: 38 additions & 0 deletions googleads/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
from independently refreshing the credentials.
"""


import googleads.errors
import requests

import google.auth.transport.requests
import google.oauth2.credentials
import google.oauth2.service_account
Expand Down Expand Up @@ -211,6 +213,42 @@ def Refresh(self):
google.auth.transport.requests.Request(session=session))


class GoogleCredentialsClient(GoogleRefreshableOAuth2Client):
"""A simple client for using OAuth2 for Google APIs with a credentials object.
This class is not capable of supporting any flows other than taking an
existing credentials (google.auth.credentials) to generate the refresh
and access tokens.
"""

def __init__(self, credentials):
"""Initializes an OAuth2 client using a credentials object.
Args:
credentials: A credentials object implementing google.auth.credentials.
"""
self.creds = credentials

def CreateHttpHeader(self):
"""Creates an OAuth2 HTTP header.
Returns:
A dictionary containing one entry: the OAuth2 Bearer header under the
'Authorization' key.
"""
if self.creds.expiry is None or self.creds.expired:
self.Refresh()

oauth2_header = {}
self.creds.apply(oauth2_header)
return oauth2_header

def Refresh(self):
"""Uses the credentials object to retrieve and set a new Access Token."""
transport = google.auth.transport.requests.Request()
self.creds.refresh(transport)


class GoogleServiceAccountClient(GoogleRefreshableOAuth2Client):
"""A simple client for using OAuth2 for Google APIs with a service account.
Expand Down
53 changes: 48 additions & 5 deletions tests/oauth2_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def setUp(self):
self.scope_ad_manager = 'https://www.googleapis.com/auth/dfp'

def testGetAPIScope_adwords(self):
self.assertEquals(googleads.oauth2.GetAPIScope(self.api_name_adwords),
self.scope_adwords)
self.assertEqual(googleads.oauth2.GetAPIScope(self.api_name_adwords),
self.scope_adwords)

def testGetAPIScope_badKey(self):
self.assertRaises(googleads.errors.GoogleAdsValueError,
Expand Down Expand Up @@ -153,7 +153,7 @@ def testCreateHttpHeader_refresh(self):
header = self.refresh_client.CreateHttpHeader()
self.assertEqual(mock_session_instance.proxies, {})
self.assertEqual(mock_session_instance.verify, True)
self.assertEqual(mock_session_instance.cert, None)
self.assertIsNone(mock_session_instance.cert)
self.mock_req.assert_called_once_with(session=mock_session_instance)
self.assertEqual(expected_header, header)
self.mock_credentials_instance.refresh.assert_called_once_with(
Expand Down Expand Up @@ -195,6 +195,49 @@ def testCreateHttpHeader_refreshFails(self):
self.assertFalse(self.mock_credentials_instance.apply.called)


class GoogleCredentialsClientTest(unittest.TestCase):
"""Tests for the googleads.oauth2.GoogleCredentialsClient class."""

def setUp(self):
self.access_token = 'a'

# Mock out google.auth.transport.Request for testing.
self.mock_req = mock.Mock(spec=Request)
self.mock_req.return_value = mock.Mock()
self.mock_req_instance = self.mock_req.return_value

# Mock out google.oauth2.credentials.Credentials for testing
self.mock_credentials = mock.Mock(spec=Credentials)
self.mock_credentials.expiry = None

def apply(headers, token=None):
headers['authorization'] = ('Bearer %s' % self.access_token)

self.mock_credentials.apply = mock.Mock(side_effect=apply)

self.client = googleads.oauth2.GoogleCredentialsClient(
self.mock_credentials)

def testRefresh(self):
with mock.patch('google.auth.transport.requests.Request', self.mock_req):
self.client.Refresh()
self.mock_req.assert_called_once()

def testCreateHttpHeader_credentialsExpired(self):
self.mock_credentials.expiry = object()
self.mock_credentials.expired = True
with mock.patch('google.auth.transport.requests.Request', self.mock_req):
self.client.CreateHttpHeader()
self.mock_req.assert_called_once()

def testCreateHttpHeader_applyCredentialsToken(self):
expected_header = {u'authorization': 'Bearer %s' % self.access_token}

with mock.patch('google.auth.transport.requests.Request', self.mock_req):
header = self.client.CreateHttpHeader()
self.assertEqual(header, expected_header)


class GoogleServiceAccountTest(unittest.TestCase):
"""Tests for the googleads.oauth2.GoogleServiceAccountClient class."""

Expand Down Expand Up @@ -240,7 +283,7 @@ def apply(headers, token=None):
def refresh(request):
self.mock_credentials_instance.token = (
self.access_token_unrefreshed if
self.mock_credentials_instance.token is 'x'
self.mock_credentials_instance.token == 'x'
else self.access_token_refreshed)
self.mock_credentials_instance.token_expiry = datetime.datetime.utcnow()

Expand Down Expand Up @@ -283,7 +326,7 @@ def testCreateHttpHeader_refresh(self):
header = self.sa_client.CreateHttpHeader()
self.assertEqual(mock_session_instance.proxies, {})
self.assertEqual(mock_session_instance.verify, True)
self.assertEqual(mock_session_instance.cert, None)
self.assertIsNone(mock_session_instance.cert)
self.mock_req.assert_called_once_with(session=mock_session_instance)
self.assertEqual(expected_header, header)
self.mock_credentials_instance.refresh.assert_called_once_with(
Expand Down

0 comments on commit f3065bd

Please sign in to comment.