Skip to content

Commit f3065bd

Browse files
author
David Wihl
committed
Patch to allow use of Application Default Credentials
1 parent 7c41584 commit f3065bd

File tree

4 files changed

+90
-6
lines changed

4 files changed

+90
-6
lines changed

ChangeLog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
24.1.0 -- 07/13/2020
2+
* Added 'GoogleCredentialsClient' class to re-use an OAuth2 credentials object
3+
14
24.0.0 -- 05/12/2020
25
* Added support for Ad Manager v202005.
36
* Removed support for Ad Manager v201905.

googleads/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
'compatibility with this library, upgrade to Python 3.6 or higher.')
5656

5757

58-
VERSION = '24.0.0'
58+
VERSION = '24.1.0'
5959
_COMMON_LIB_SIG = 'googleads/%s' % VERSION
6060
_LOGGING_KEY = 'logging'
6161
_HTTP_PROXY_YAML_KEY = 'http'

googleads/oauth2.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
from independently refreshing the credentials.
2424
"""
2525

26+
2627
import googleads.errors
2728
import requests
29+
2830
import google.auth.transport.requests
2931
import google.oauth2.credentials
3032
import google.oauth2.service_account
@@ -211,6 +213,42 @@ def Refresh(self):
211213
google.auth.transport.requests.Request(session=session))
212214

213215

216+
class GoogleCredentialsClient(GoogleRefreshableOAuth2Client):
217+
"""A simple client for using OAuth2 for Google APIs with a credentials object.
218+
219+
This class is not capable of supporting any flows other than taking an
220+
existing credentials (google.auth.credentials) to generate the refresh
221+
and access tokens.
222+
"""
223+
224+
def __init__(self, credentials):
225+
"""Initializes an OAuth2 client using a credentials object.
226+
227+
Args:
228+
credentials: A credentials object implementing google.auth.credentials.
229+
"""
230+
self.creds = credentials
231+
232+
def CreateHttpHeader(self):
233+
"""Creates an OAuth2 HTTP header.
234+
235+
Returns:
236+
A dictionary containing one entry: the OAuth2 Bearer header under the
237+
'Authorization' key.
238+
"""
239+
if self.creds.expiry is None or self.creds.expired:
240+
self.Refresh()
241+
242+
oauth2_header = {}
243+
self.creds.apply(oauth2_header)
244+
return oauth2_header
245+
246+
def Refresh(self):
247+
"""Uses the credentials object to retrieve and set a new Access Token."""
248+
transport = google.auth.transport.requests.Request()
249+
self.creds.refresh(transport)
250+
251+
214252
class GoogleServiceAccountClient(GoogleRefreshableOAuth2Client):
215253
"""A simple client for using OAuth2 for Google APIs with a service account.
216254

tests/oauth2_test.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def setUp(self):
3939
self.scope_ad_manager = 'https://www.googleapis.com/auth/dfp'
4040

4141
def testGetAPIScope_adwords(self):
42-
self.assertEquals(googleads.oauth2.GetAPIScope(self.api_name_adwords),
43-
self.scope_adwords)
42+
self.assertEqual(googleads.oauth2.GetAPIScope(self.api_name_adwords),
43+
self.scope_adwords)
4444

4545
def testGetAPIScope_badKey(self):
4646
self.assertRaises(googleads.errors.GoogleAdsValueError,
@@ -153,7 +153,7 @@ def testCreateHttpHeader_refresh(self):
153153
header = self.refresh_client.CreateHttpHeader()
154154
self.assertEqual(mock_session_instance.proxies, {})
155155
self.assertEqual(mock_session_instance.verify, True)
156-
self.assertEqual(mock_session_instance.cert, None)
156+
self.assertIsNone(mock_session_instance.cert)
157157
self.mock_req.assert_called_once_with(session=mock_session_instance)
158158
self.assertEqual(expected_header, header)
159159
self.mock_credentials_instance.refresh.assert_called_once_with(
@@ -195,6 +195,49 @@ def testCreateHttpHeader_refreshFails(self):
195195
self.assertFalse(self.mock_credentials_instance.apply.called)
196196

197197

198+
class GoogleCredentialsClientTest(unittest.TestCase):
199+
"""Tests for the googleads.oauth2.GoogleCredentialsClient class."""
200+
201+
def setUp(self):
202+
self.access_token = 'a'
203+
204+
# Mock out google.auth.transport.Request for testing.
205+
self.mock_req = mock.Mock(spec=Request)
206+
self.mock_req.return_value = mock.Mock()
207+
self.mock_req_instance = self.mock_req.return_value
208+
209+
# Mock out google.oauth2.credentials.Credentials for testing
210+
self.mock_credentials = mock.Mock(spec=Credentials)
211+
self.mock_credentials.expiry = None
212+
213+
def apply(headers, token=None):
214+
headers['authorization'] = ('Bearer %s' % self.access_token)
215+
216+
self.mock_credentials.apply = mock.Mock(side_effect=apply)
217+
218+
self.client = googleads.oauth2.GoogleCredentialsClient(
219+
self.mock_credentials)
220+
221+
def testRefresh(self):
222+
with mock.patch('google.auth.transport.requests.Request', self.mock_req):
223+
self.client.Refresh()
224+
self.mock_req.assert_called_once()
225+
226+
def testCreateHttpHeader_credentialsExpired(self):
227+
self.mock_credentials.expiry = object()
228+
self.mock_credentials.expired = True
229+
with mock.patch('google.auth.transport.requests.Request', self.mock_req):
230+
self.client.CreateHttpHeader()
231+
self.mock_req.assert_called_once()
232+
233+
def testCreateHttpHeader_applyCredentialsToken(self):
234+
expected_header = {u'authorization': 'Bearer %s' % self.access_token}
235+
236+
with mock.patch('google.auth.transport.requests.Request', self.mock_req):
237+
header = self.client.CreateHttpHeader()
238+
self.assertEqual(header, expected_header)
239+
240+
198241
class GoogleServiceAccountTest(unittest.TestCase):
199242
"""Tests for the googleads.oauth2.GoogleServiceAccountClient class."""
200243

@@ -240,7 +283,7 @@ def apply(headers, token=None):
240283
def refresh(request):
241284
self.mock_credentials_instance.token = (
242285
self.access_token_unrefreshed if
243-
self.mock_credentials_instance.token is 'x'
286+
self.mock_credentials_instance.token == 'x'
244287
else self.access_token_refreshed)
245288
self.mock_credentials_instance.token_expiry = datetime.datetime.utcnow()
246289

@@ -283,7 +326,7 @@ def testCreateHttpHeader_refresh(self):
283326
header = self.sa_client.CreateHttpHeader()
284327
self.assertEqual(mock_session_instance.proxies, {})
285328
self.assertEqual(mock_session_instance.verify, True)
286-
self.assertEqual(mock_session_instance.cert, None)
329+
self.assertIsNone(mock_session_instance.cert)
287330
self.mock_req.assert_called_once_with(session=mock_session_instance)
288331
self.assertEqual(expected_header, header)
289332
self.mock_credentials_instance.refresh.assert_called_once_with(

0 commit comments

Comments
 (0)