forked from googleads/googleads-python-lib
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoauth2.py
More file actions
327 lines (252 loc) · 10.8 KB
/
Copy pathoauth2.py
File metadata and controls
327 lines (252 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# Copyright 2013 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""OAuth2 integration for the googleads library.
This module provides a basic interface which the googleads library uses to
authorize API requests and some simple implementations built on
oauth2client.
If our OAuth2 workflows doesn't meet your requirements, you can implement this
interface in your own way. For example, you could pull credentials from a shared
server and/or centralize refreshing credentials to prevent every Python process
from independently refreshing the credentials.
"""
import googleads.errors
import requests
import google.auth.transport.requests
import google.oauth2.credentials
import google.oauth2.service_account
# The scopes used for authorizing with the APIs supported by this library.
SCOPES = {'adwords': 'https://www.googleapis.com/auth/adwords',
'ad_manager': 'https://www.googleapis.com/auth/dfp'}
def GetAPIScope(api_name):
"""Retrieves the scope for the given API name.
Args:
api_name: A string identifying the name of the API we want to retrieve a
scope for.
Returns:
A string that is the scope for the given API name.
Raises:
GoogleAdsValueError: If the given api_name is invalid; accepted values are
"adwords" and "ad_manager".
"""
try:
return SCOPES[api_name]
except KeyError:
raise googleads.errors.GoogleAdsValueError(
'Invalid API name "%s" provided. Acceptable values are: %s' %
(api_name, SCOPES.keys()))
class GoogleOAuth2Client(object):
"""An OAuth2 client for use with Google APIs."""
# The web address for generating OAuth2 credentials at Google.
_GOOGLE_OAUTH2_ENDPOINT = 'https://accounts.google.com/o/oauth2/token'
# The placeholder URL is used when adding the access token to our request. A
# well-formed URL is required, but since we're using HTTP header placement for
# the token, this URL is completely unused.
_TOKEN_URL = 'https://www.google.com'
_USER_AGENT = 'Google Ads Python Client Library'
def CreateHttpHeader(self):
"""Creates an OAuth2 HTTP header.
The OAuth2 credentials will be refreshed as necessary.
Returns:
A dictionary containing one entry: the OAuth2 Bearer header under the
'Authorization' key.
"""
raise NotImplementedError('You must subclass GoogleOAuth2Client.')
class GoogleRefreshableOAuth2Client(GoogleOAuth2Client):
"""A refreshable OAuth2 client for use with Google APIs.
This interface assumes all responsibility for refreshing credentials when
necessary.
"""
def Refresh(self):
"""Refreshes the access token used by the client."""
raise NotImplementedError(
'You must subclass GoogleRefreshableOAuth2Client.')
class GoogleAccessTokenClient(GoogleOAuth2Client):
"""A simple client for using OAuth2 for Google APIs with an access token.
This class is not capable of supporting any flows other than taking an
existing, active access token. It does not matter which of Google's OAuth2
flows you used to generate the access token (installed application, web flow,
etc.).
When the provided access token expires, a GoogleAdsError will be raised.
"""
def __init__(self, access_token, token_expiry):
"""Initializes a GoogleAccessTokenClient.
Args:
access_token: A string containing your access token.
token_expiry: A datetime instance indicating when the given access token
expires.
"""
self.creds = google.oauth2.credentials.Credentials(
token=access_token)
self.creds.expiry = token_expiry
def CreateHttpHeader(self):
"""Creates an OAuth2 HTTP header.
The OAuth2 credentials will be refreshed as necessary. In the event that
the credentials fail to refresh, a message is logged but no exception is
raised.
Returns:
A dictionary containing one entry: the OAuth2 Bearer header under the
'Authorization' key.
Raises:
GoogleAdsError: If the access token has expired.
"""
oauth2_header = {}
if self.creds.expired:
raise googleads.errors.GoogleAdsError('Access token has expired.')
self.creds.apply(oauth2_header)
return oauth2_header
class GoogleRefreshTokenClient(GoogleRefreshableOAuth2Client):
"""A simple client for using OAuth2 for Google APIs with a refresh token.
This class is not capable of supporting any flows other than taking an
existing, active refresh token and generating credentials from it. It does not
matter which of Google's OAuth2 flows you used to generate the refresh
token (installed application, web flow, etc.).
Attributes:
proxy_info: A ProxyInfo instance used for refresh requests.
"""
def __init__(self, client_id, client_secret, refresh_token, **kwargs):
"""Initializes a GoogleRefreshTokenClient.
Args:
client_id: A string containing your client ID.
client_secret: A string containing your client secret.
refresh_token: A string containing your refresh token.
**kwargs: Keyword arguments.
Keyword Arguments:
access_token: A string containing your access token.
token_expiry: A datetime instance indicating when the given access token
expires.
proxy_config: A googleads.common.ProxyConfig instance or None if a proxy
isn't being used.
"""
self.creds = google.oauth2.credentials.Credentials(
kwargs.get('access_token'), refresh_token=refresh_token,
client_id=client_id, client_secret=client_secret,
token_uri=self._GOOGLE_OAUTH2_ENDPOINT)
self.creds.expiry = kwargs.get('token_expiry')
self.proxy_config = kwargs.get('proxy_config',
googleads.common.ProxyConfig())
def CreateHttpHeader(self):
"""Creates an OAuth2 HTTP header.
The OAuth2 credentials will be refreshed as necessary. In the event that
the credentials fail to refresh, a message is logged but no exception is
raised.
Returns:
A dictionary containing one entry: the OAuth2 Bearer header under the
'Authorization' key.
Raises:
google.auth.exceptions.RefreshError: If the refresh fails.
"""
oauth2_header = {}
if self.creds.expiry is None or self.creds.expired:
self.Refresh()
self.creds.apply(oauth2_header)
return oauth2_header
def Refresh(self):
"""Uses the Refresh Token to retrieve and set a new Access Token.
Raises:
google.auth.exceptions.RefreshError: If the refresh fails.
"""
with requests.Session() as session:
session.proxies = self.proxy_config.proxies
session.verify = not self.proxy_config.disable_certificate_validation
session.cert = self.proxy_config.cafile
self.creds.refresh(
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.
This class is not capable of supporting any flows other than generating
credentials from a service account email and key file. This is incompatible
with App Engine.
Attributes:
proxy_info: A ProxyInfo instance used for refresh requests.
"""
_USER_AGENT = 'Google Ads Python Client Library'
_FILE_NOT_FOUND_TEMPLATE = 'The specified key file (%s) does not exist.'
def __init__(self, key_file, scope, sub=None, proxy_config=None):
"""Initializes a GoogleServiceAccountClient.
Args:
key_file: A string containing the path to your Service Account
JSON key file.
scope: The scope of the API you're authorizing for.
[optional]
sub: A string containing the email address of a user account you want to
impersonate.
proxy_config: A googleads.common.ProxyConfig instance.
Raises:
GoogleAdsError: If an unsupported version of oauth2client is installed.
GoogleAdsValueError: If the given key file does not exist.
"""
try:
self.creds = (
google.oauth2.service_account.Credentials.from_service_account_file(
key_file, scopes=[scope], subject=sub))
except IOError:
raise googleads.errors.GoogleAdsValueError(
self._FILE_NOT_FOUND_TEMPLATE % key_file)
self.proxy_config = (proxy_config if proxy_config else
googleads.common.ProxyConfig())
self.Refresh()
def CreateHttpHeader(self):
"""Creates an OAuth2 HTTP header.
The OAuth2 credentials will be refreshed as necessary. In the event that
the credentials fail to refresh, a message is logged but no exception is
raised.
Returns:
A dictionary containing one entry: the OAuth2 Bearer header under the
'Authorization' key.
Raises:
google.auth.exceptions.RefreshError: If the refresh fails.
"""
oauth2_header = {}
if self.creds.expiry is None or self.creds.expired:
self.Refresh()
self.creds.apply(oauth2_header)
return oauth2_header
def Refresh(self):
"""Retrieve and set a new Access Token.
Raises:
google.auth.exceptions.RefreshError: If the refresh fails.
"""
with requests.Session() as session:
session.proxies = self.proxy_config.proxies
session.verify = not self.proxy_config.disable_certificate_validation
session.cert = self.proxy_config.cafile
self.creds.refresh(
google.auth.transport.requests.Request(session=session))