Permalink
May 30, 2014
Jul 11, 2014
May 30, 2014
Newer
100644
2171 lines (1789 sloc)
81.3 KB
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
# http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
55
EXPIRY_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
56
57
# Which certs to use to validate id_tokens received.
58
ID_TOKEN_VERIFICATION_CERTS = 'https://www.googleapis.com/oauth2/v1/certs'
59
# This symbol previously had a typo in the name; we keep the old name
60
# around for now, but will remove it in the future.
61
ID_TOKEN_VERIFICATON_CERTS = ID_TOKEN_VERIFICATION_CERTS
63
# Constant to use for the out of band OAuth 2.0 flow.
64
OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
65
66
# The value representing user credentials.
67
AUTHORIZED_USER = 'authorized_user'
68
69
# The value representing service account credentials.
70
SERVICE_ACCOUNT = 'service_account'
71
72
# The environment variable pointing the file with local
73
# Application Default Credentials.
74
GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS'
75
# The ~/.config subdirectory containing gcloud credentials. Intended
76
# to be swapped out in tests.
77
_CLOUDSDK_CONFIG_DIRECTORY = 'gcloud'
78
# The environment variable name which can replace ~/.config if set.
79
_CLOUDSDK_CONFIG_ENV_VAR = 'CLOUDSDK_CONFIG'
81
# The error message we show users when we can't find the Application
82
# Default Credentials.
83
ADC_HELP_MSG = (
84
'The Application Default Credentials are not available. They are '
85
'available if running in Google Compute Engine. Otherwise, the '
86
'environment variable ' +
87
GOOGLE_APPLICATION_CREDENTIALS +
89
'https://developers.google.com/accounts/docs/'
90
'application-default-credentials for more information.')
95
AccessTokenInfo = collections.namedtuple(
96
'AccessTokenInfo', ['access_token', 'expires_in'])
103
# Timeout in seconds to wait for the GCE metadata server when detecting the
104
# GCE environment.
105
try:
116
# Expose utcnow() at module level to allow for
117
# easier testing (by replacing with a stub).
118
_UTCNOW = datetime.datetime.utcnow
119
120
# NOTE: These names were previously defined in this module but have been
121
# moved into `oauth2client.transport`,
122
clean_headers = transport.clean_headers
123
MemoryCache = transport.MemoryCache
124
REFRESH_STATUS_CODES = transport.REFRESH_STATUS_CODES
125
144
class HttpAccessTokenRefreshError(AccessTokenRefreshError):
145
"""Error (with HTTP status) trying to refresh an expired access token."""
146
def __init__(self, *args, **kwargs):
147
super(HttpAccessTokenRefreshError, self).__init__(*args)
148
self.status = kwargs.get('status')
149
150
183
def _parse_expiry(expiry):
184
if expiry and isinstance(expiry, datetime.datetime):
185
return expiry.strftime(EXPIRY_FORMAT)
186
else:
187
return None
188
189
193
Subclasses must define an authorize() method that applies the credentials
194
to an HTTP transport.
196
Subclasses must also specify a classmethod named 'from_json' that takes a
197
JSON string as input and returns an instantiated Credentials object.
198
"""
202
def authorize(self, http):
203
"""Take an httplib2.Http instance (or equivalent) and authorizes it.
205
Authorizes it for the set of credentials, usually by replacing
206
http.request() with a method that adds in the appropriate headers and
207
then delegates to the original Http.request() method.
209
Args:
210
http: httplib2.Http, an http object to be used to make the refresh
211
request.
212
"""
218
Args:
219
http: httplib2.Http, an http object to be used to make the refresh
220
request.
221
"""
224
def revoke(self, http):
225
"""Revokes a refresh_token and makes the credentials void.
227
Args:
228
http: httplib2.Http, an http object to be used to make the revoke
229
request.
230
"""
251
Returns:
252
string, a JSON representation of this instance, suitable to pass to
253
from_json().
254
"""
255
curr_type = self.__class__
256
if to_serialize is None:
257
to_serialize = copy.copy(self.__dict__)
258
else:
259
# Assumes it is a str->str dictionary, so we don't deep copy.
260
to_serialize = copy.copy(to_serialize)
262
if member in to_serialize:
263
del to_serialize[member]
264
to_serialize['token_expiry'] = _parse_expiry(
265
to_serialize.get('token_expiry'))
266
# Add in information we will need later to reconstitute this instance.
267
to_serialize['_class'] = curr_type.__name__
268
to_serialize['_module'] = curr_type.__module__
269
for key, val in to_serialize.items():
275
276
def to_json(self):
277
"""Creating a JSON representation of an instance of Credentials.
279
Returns:
280
string, a JSON representation of this instance, suitable to pass to
281
from_json().
282
"""
294
Returns:
295
An instance of the subclass of Credentials that was serialized with
296
to_json().
297
"""
302
module_name = data['_module']
303
try:
304
module_obj = __import__(module_name)
305
except ImportError:
308
module_name = module_name.replace('.googleapiclient', '')
309
module_obj = __import__(module_name)
310
315
316
@classmethod
317
def from_json(cls, unused_data):
318
"""Instantiate a Credentials object from a JSON description of it.
339
Store and retrieve a single credential. This class supports locking
340
such that multiple processes and threads can operate on a single
341
store.
342
"""
343
def __init__(self, lock=None):
344
"""Create a Storage instance.
345
346
Args:
347
lock: An optional threading.Lock-like object. Must implement at
405
self.acquire_lock()
406
try:
407
return self.locked_get()
408
finally:
409
self.release_lock()
419
self.acquire_lock()
420
try:
421
self.locked_put(credentials)
422
finally:
423
self.release_lock()
428
Frees any resources associated with storing the credential.
429
The Storage lock must *not* be held when this is called.
434
self.acquire_lock()
435
try:
436
return self.locked_delete()
437
finally:
438
self.release_lock()
444
Credentials can be applied to an httplib2.Http object using the authorize()
445
method, which then adds the OAuth 2.0 access token to each request.
452
token_expiry, token_uri, user_agent, revoke_uri=None,
453
id_token=None, token_response=None, scopes=None,
457
This constructor is not usually called by the user, instead
458
OAuth2Credentials objects are instantiated by the OAuth2WebServerFlow.
459
460
Args:
461
access_token: string, access token.
462
client_id: string, client identifier.
463
client_secret: string, client secret.
464
refresh_token: string, refresh token.
465
token_expiry: datetime, when the access_token expires.
466
token_uri: string, URI of token endpoint.
467
user_agent: string, The HTTP User-Agent to provide for this
468
application.
469
revoke_uri: string, URI for revoke endpoint. Defaults to None; a
470
token can't be revoked if this is None.
471
id_token: object, The identity of the resource owner.
472
token_response: dict, the decoded response to the token request.
473
None if a token hasn't been requested yet. Stored
474
because some providers (e.g. wordpress.com) include
475
extra fields that clients may want.
476
scopes: list, authorized scopes for these credentials.
477
token_info_uri: string, the URI for the token info endpoint.
478
Defaults to None; scopes can not be refreshed if
479
this is None.
480
id_token_jwt: string, the encoded and signed identity JWT. The
481
decoded version of this is stored in id_token.
482
483
Notes:
484
store: callable, A callable that when passed a Credential
485
will store the credential back to where it came from.
486
This is needed to store the latest access_token if it
487
has expired and been refreshed.
488
"""
489
self.access_token = access_token
490
self.client_id = client_id
491
self.client_secret = client_secret
492
self.refresh_token = refresh_token
493
self.store = None
494
self.token_expiry = token_expiry
495
self.token_uri = token_uri
496
self.user_agent = user_agent
497
self.revoke_uri = revoke_uri
498
self.id_token = id_token
502
self.token_info_uri = token_info_uri
503
504
# True if the credentials have been revoked or expired and can't be
505
# refreshed.
506
self.invalid = False
507
508
def authorize(self, http):
509
"""Authorize an httplib2.Http instance with these credentials.
511
The modified http.request method will add authentication headers to
512
each request and will refresh access_tokens when a 401 is received on a
513
request. In addition the http.request method has a credentials
514
property, http.request.credentials, which is the Credentials object
515
that authorized it.
517
Args:
518
http: An instance of ``httplib2.Http`` or something that acts
519
like it.
529
You can't create a new OAuth subclass of httplib2.Authentication
530
because it never gets passed the absolute URI, which is needed for
531
signing. So instead we have to overload 'request' with a closure
532
that adds in the Authorization header and then calls the original
533
version of 'request()'.
534
"""
541
Args:
542
http: httplib2.Http, an http object to be used to make the refresh
543
request.
544
"""
547
def revoke(self, http):
548
"""Revokes a refresh_token and makes the credentials void.
550
Args:
551
http: httplib2.Http, an http object to be used to make the revoke
552
request.
553
"""
564
def has_scopes(self, scopes):
565
"""Verify that the credentials are authorized for the given scopes.
567
Returns True if the credentials authorized scopes contain all of the
568
scopes given.
570
Args:
571
scopes: list or string, the scopes to check.
572
573
Notes:
574
There are cases where the credentials are unaware of which scopes
575
are authorized. Notably, credentials obtained and stored before
576
this code was added will not have scopes, AccessTokenCredentials do
577
not have scopes. In both cases, you can use refresh_scopes() to
578
obtain the canonical set of scopes.
579
"""
588
Args:
589
http: httplib2.Http, an http object to be used to make the refresh
590
request.
591
592
Returns:
593
A set of strings containing the canonical list of scopes.
594
"""
619
data['access_token'],
620
data['client_id'],
621
data['client_secret'],
622
data['refresh_token'],
623
data['token_expiry'],
624
data['token_uri'],
625
data['user_agent'],
626
revoke_uri=data.get('revoke_uri', None),
627
id_token=data.get('id_token', None),
629
token_response=data.get('token_response', None),
630
scopes=data.get('scopes', None),
631
token_info_uri=data.get('token_info_uri', None))
635
@property
636
def access_token_expired(self):
637
"""True if the credential is expired or invalid.
648
if now >= self.token_expiry:
649
logger.info('access_token is expired. Now: %s, token_expiry: %s',
654
def get_access_token(self, http=None):
655
"""Return the access token and its expiration information.
657
If the token does not exist, get one.
658
If the token expired, refresh it.
659
"""
670
Args:
671
store: Storage, an implementation of Storage object.
672
This is needed to store the latest access_token if it
673
has expired and been refreshed. This implementation uses
674
locking to check for updates before updating the
675
access_token.
676
"""
679
def _expires_in(self):
680
"""Return the number of seconds until this token expires.
682
If token_expiry is in the past, this method will return 0, meaning the
683
token has already expired.
684
685
If token_expiry is None, this method will return None. Note that
686
returning 0 in such a case would not be fair: the token may still be
687
valid; we just don't know anything about it.
688
"""
691
if self.token_expiry > now:
692
time_delta = self.token_expiry - now
693
# TODO(orestica): return time_delta.total_seconds()
694
# once dropping support for Python 2.6
695
return time_delta.days * 86400 + time_delta.seconds
696
else:
697
return 0
698
699
def _updateFromCredential(self, other):
700
"""Update this Credential from another instance."""
701
self.__dict__.update(other.__getstate__())
702
703
def __getstate__(self):
704
"""Trim the state down to something that can be pickled."""
705
d = copy.copy(self.__dict__)
706
del d['store']
707
return d
708
709
def __setstate__(self, state):
710
"""Reconstitute the state of the object from being pickled."""
711
self.__dict__.update(state)
712
self.store = None
713
714
def _generate_refresh_request_body(self):
715
"""Generate the body that will be used in the refresh request."""
716
body = urllib.parse.urlencode({
717
'grant_type': 'refresh_token',
718
'client_id': self.client_id,
719
'client_secret': self.client_secret,
720
'refresh_token': self.refresh_token,
724
def _generate_refresh_request_headers(self):
725
"""Generate the headers that will be used in the refresh request."""
726
headers = {
738
This method first checks by reading the Storage object if available.
739
If a refresh is still needed, it holds the Storage lock until the
740
refresh is completed.
750
else:
751
self.store.acquire_lock()
752
try:
753
new_cred = self.store.locked_get()
756
new_cred.access_token != self.access_token and
757
not new_cred.access_token_expired):
758
logger.info('Updated access_token read from Storage')
759
self._updateFromCredential(new_cred)
760
else:
774
body = self._generate_refresh_request_body()
775
headers = self._generate_refresh_request_headers()
783
d = json.loads(content)
784
self.token_response = d
785
self.access_token = d['access_token']
786
self.refresh_token = d.get('refresh_token', self.refresh_token)
787
if 'expires_in' in d:
788
delta = datetime.timedelta(seconds=int(d['expires_in']))
789
self.token_expiry = delta + _UTCNOW()
798
# On temporary refresh errors, the user does not actually have to
799
# re-authorize, so we unflag here.
800
self.invalid = False
801
if self.store:
802
self.store.locked_put(self)
803
else:
804
# An {'error':...} response body means the token is expired or
805
# revoked, so we flag the credentials as such.
808
try:
809
d = json.loads(content)
810
if 'error' in d:
811
error_msg = d['error']
812
if 'error_description' in d:
813
error_msg += ': ' + d['error_description']
814
self.invalid = True
834
token: A string used as the token to be revoked. Can be either an
835
access_token or refresh_token.
836
837
Raises:
838
TokenRevokeError: If the revoke request does not return with a
839
200 OK.
840
"""
843
token_revoke_uri = _helpers.update_query_params(
844
self.revoke_uri, query_params)
846
if resp.status == http_client.METHOD_NOT_ALLOWED:
847
body = urllib.parse.urlencode(query_params)
848
resp, content = transport.request(http, token_revoke_uri,
849
method='POST', body=body)
856
if 'error' in d:
857
error_msg = d['error']
858
except (TypeError, ValueError):
859
pass
860
raise TokenRevokeError(error_msg)
878
token: A string used as the token to identify the credentials to
879
the provider.
880
881
Raises:
882
Error: When refresh fails, indicating the the access token is
883
invalid.
884
"""
885
logger.info('Refreshing scopes')
886
query_params = {'access_token': token, 'fields': 'scope'}
887
token_info_uri = _helpers.update_query_params(
888
self.token_info_uri, query_params)
896
try:
897
d = json.loads(content)
898
if 'error_description' in d:
899
error_msg = d['error_description']
900
except (TypeError, ValueError):
901
pass
902
raise Error(error_msg)
908
Credentials can be applied to an httplib2.Http object using the
909
authorize() method, which then signs each request from that object
910
with the OAuth 2.0 access token. This set of credentials is for the
911
use case where you have acquired an OAuth 2.0 access_token from
912
another place such as a JavaScript client or another web
913
application, and wish to use it from Python. Because only the
914
access_token is present it can not be refreshed and will in time
915
expire.
921
credentials = AccessTokenCredentials('<an access token>',
922
'my-user-agent/1.0')
923
http = httplib2.Http()
924
http = credentials.authorize(http)
926
Raises:
927
AccessTokenCredentialsExpired: raised when the access_token expires or
928
is revoked.
929
"""
931
def __init__(self, access_token, user_agent, revoke_uri=None):
932
"""Create an instance of OAuth2Credentials
934
This is one of the few types if Credentials that you should contrust,
935
Credentials objects are usually instantiated by a Flow.
937
Args:
938
access_token: string, access token.
939
user_agent: string, The HTTP User-Agent to provide for this
940
application.
941
revoke_uri: string, URI for revoke endpoint. Defaults to None; a
942
token can't be revoked if this is None.
943
"""
945
access_token,
946
None,
947
None,
948
None,
949
None,
950
None,
951
user_agent,
952
revoke_uri=revoke_uri)
962
def _refresh(self, http):
963
"""Refreshes the access token.
964
965
Args:
966
http: unused HTTP object.
967
968
Raises:
969
AccessTokenCredentialsError: always
970
"""
986
Returns:
987
Boolean indicating whether or not the current environment is Google
988
Compute Engine.
989
"""
990
# NOTE: The explicit ``timeout`` is a workaround. The underlying
991
# issue is that resolving an unknown host on some networks will take
992
# 20-30 seconds; making this timeout short fixes the issue, but
993
# could lead to false negatives in the event that we are on GCE, but
994
# the metadata resolution was particularly slow. The latter case is
995
# "unlikely".
998
response, _ = transport.request(
999
http, _GCE_METADATA_URI, headers=_GCE_HEADERS)
1000
return (
1001
response.status == http_client.OK and
1002
response.get(_METADATA_FLAVOR_HEADER) == _DESIRED_METADATA_FLAVOR)
1004
logger.info('Timeout attempting to reach GCE metadata service.')
1005
return False
1014
if SETTINGS.env_name is not None:
1015
return SETTINGS.env_name in ('GAE_PRODUCTION', 'GAE_LOCAL')
1018
import google.appengine # noqa: unused import
1019
except ImportError:
1020
pass
1021
else:
1023
if server_software.startswith('Google App Engine/'):
1024
SETTINGS.env_name = 'GAE_PRODUCTION'
1025
return True
1026
elif server_software.startswith('Development/'):
1027
SETTINGS.env_name = 'GAE_LOCAL'
1028
return True
1029
1030
return False
1039
if SETTINGS.env_name is not None:
1040
return SETTINGS.env_name == 'GCE_PRODUCTION'
1051
The Application Default Credentials are being constructed as a function of
1052
the environment where the code is being run.
1053
More details can be found on this page:
1054
https://developers.google.com/accounts/docs/application-default-credentials
1056
Here is an example of how to use the Application Default Credentials for a
1057
service that requires authentication::
1059
from googleapiclient.discovery import build
1060
from oauth2client.client import GoogleCredentials
1062
credentials = GoogleCredentials.get_application_default()
1063
service = build('compute', 'v1', credentials=credentials)
1065
PROJECT = 'bamboo-machine-422'
1066
ZONE = 'us-central1-a'
1067
request = service.instances().list(project=PROJECT, zone=ZONE)
1068
response = request.execute()
1083
This constructor is not usually called by the user, instead
1084
GoogleCredentials objects are instantiated by
1085
GoogleCredentials.from_stream() or
1086
GoogleCredentials.get_application_default().
1087
1088
Args:
1089
access_token: string, access token.
1090
client_id: string, client identifier.
1091
client_secret: string, client secret.
1092
refresh_token: string, refresh token.
1093
token_expiry: datetime, when the access_token expires.
1094
token_uri: string, URI of token endpoint.
1095
user_agent: string, The HTTP User-Agent to provide for this
1096
application.
1097
revoke_uri: string, URI for revoke endpoint. Defaults to
1102
access_token, client_id, client_secret, refresh_token,
1103
token_expiry, token_uri, user_agent, revoke_uri=revoke_uri)
1105
def create_scoped_required(self):
1106
"""Whether this Credentials object is scopeless.
1108
create_scoped(scopes) method needs to be called in order to create
1109
a Credentials object for API calls.
1110
"""
1113
def create_scoped(self, scopes):
1114
"""Create a Credentials object for the given scopes.
1124
from oauth2client import service_account
1125
data = json.loads(_helpers._from_bytes(json_data))
1128
# possible return type of GoogleCredentials.get_application_default()
1129
if (data['_module'] == 'oauth2client.service_account' and
1135
1136
token_expiry = _parse_expiry(data.get('token_expiry'))
1137
google_credentials = cls(
1138
data['access_token'],
1139
data['client_id'],
1140
data['client_secret'],
1141
data['refresh_token'],
1142
token_expiry,
1143
data['token_uri'],
1144
data['user_agent'],
1145
revoke_uri=data.get('revoke_uri', None))
1146
google_credentials.invalid = data['invalid']
1147
return google_credentials
1148
1153
'type': 'authorized_user',
1154
'client_id': self.client_id,
1155
'client_secret': self.client_secret,
1156
'refresh_token': self.refresh_token
1159
@staticmethod
1160
def _implicit_credentials_from_gae():
1161
"""Attempts to get implicit credentials in Google App Engine env.
1163
If the current environment is not detected as App Engine, returns None,
1164
indicating no Google App Engine credentials can be detected from the
1165
current environment.
1167
Returns:
1168
None, if not in GAE, else an appengine.AppAssertionCredentials
1169
object.
1170
"""
1176
@staticmethod
1177
def _implicit_credentials_from_gce():
1178
"""Attempts to get implicit credentials in Google Compute Engine env.
1180
If the current environment is not detected as Compute Engine, returns
1181
None, indicating no Google Compute Engine credentials can be detected
1182
from the current environment.
1184
Returns:
1185
None, if not in GCE, else a gce.AppAssertionCredentials object.
1186
"""
1192
@staticmethod
1193
def _implicit_credentials_from_files():
1194
"""Attempts to get implicit credentials from local credential files.
1196
First checks if the environment variable GOOGLE_APPLICATION_CREDENTIALS
1197
is set with a filename and then falls back to a configuration file (the
1198
"well known" file) associated with the 'gcloud' command line tool.
1199
1200
Returns:
1201
Credentials object associated with the
1202
GOOGLE_APPLICATION_CREDENTIALS file or the "well known" file if
1203
either exist. If neither file is define, returns None, indicating
1204
no credentials from a file can detected from the current
1205
environment.
1206
"""
1207
credentials_filename = _get_environment_variable_file()
1208
if not credentials_filename:
1209
credentials_filename = _get_well_known_file()
1210
if os.path.isfile(credentials_filename):
1211
extra_help = (' (produced automatically when running'
1213
else:
1214
credentials_filename = None
1215
else:
1216
extra_help = (' (pointed to by ' + GOOGLE_APPLICATION_CREDENTIALS +
1222
# If we can read the credentials from a file, we don't need to know
1223
# what environment we are in.
1237
Checks environment in order of precedence:
1238
- Environment variable GOOGLE_APPLICATION_CREDENTIALS pointing to
1239
a file with stored credentials information.
1240
- Stored "well known" file associated with `gcloud` command line tool.
1242
- Google Compute Engine production environment.
1243
1244
Raises:
1245
ApplicationDefaultCredentialsError: raised when the credentials
1246
fail to be retrieved.
1247
"""
1255
for checker in environ_checkers:
1256
credentials = checker()
1257
if credentials is not None:
1258
return credentials
1260
# If no credentials, fail.
1261
raise ApplicationDefaultCredentialsError(ADC_HELP_MSG)
1263
@staticmethod
1264
def get_application_default():
1265
"""Get the Application Default Credentials for the current environment.
1267
Raises:
1268
ApplicationDefaultCredentialsError: raised when the credentials
1269
fail to be retrieved.
1270
"""
1279
Args:
1280
credential_filename: the path to the file from where the
1281
credentials are to be read
1283
Raises:
1284
ApplicationDefaultCredentialsError: raised when the credentials
1285
fail to be retrieved.
1286
"""
1287
if credential_filename and os.path.isfile(credential_filename):
1288
try:
1289
return _get_application_default_credential_from_file(
1299
'The parameter passed to the from_stream() '
1300
'method should point to a file.')
1306
Args:
1307
filename: String. Absolute path to file.
1308
json_contents: JSON serializable object to be saved.
1309
"""
1310
temp_filename = tempfile.mktemp()
1311
file_desc = os.open(temp_filename, os.O_WRONLY | os.O_CREAT, 0o600)
1312
with os.fdopen(file_desc, 'w') as file_handle:
1313
json.dump(json_contents, file_handle, sort_keys=True,
1321
Args:
1322
credentials: the credentials to be saved to the well known file;
1323
it should be an instance of GoogleCredentials
1324
well_known_file: the name of the file where the credentials are to be
1325
saved; this parameter is supposed to be used for
1326
testing only
1327
"""
1328
# TODO(orestica): move this method to tools.py
1329
# once the argparse import gets fixed (it is not present in Python 2.6)
1334
config_dir = os.path.dirname(well_known_file)
1335
if not os.path.isdir(config_dir):
1339
credentials_data = credentials.serialization_data
1340
_save_private_file(well_known_file, credentials_data)
1347
if application_default_credential_filename:
1348
if os.path.isfile(application_default_credential_filename):
1349
return application_default_credential_filename
1350
else:
1351
raise ApplicationDefaultCredentialsError(
1352
'File ' + application_default_credential_filename +
1353
' (pointed by ' +
1354
GOOGLE_APPLICATION_CREDENTIALS +
1355
' environment variable) does not exist!')
1359
"""Get the well known file produced by command 'gcloud auth login'."""
1360
# TODO(orestica): Revisit this method once gcloud provides a better way
1361
# of pinpointing the exact location of the file.
1362
default_config_dir = os.getenv(_CLOUDSDK_CONFIG_ENV_VAR)
1363
if default_config_dir is None:
1364
if os.name == 'nt':
1365
try:
1366
default_config_dir = os.path.join(os.environ['APPDATA'],
1371
drive = os.environ.get('SystemDrive', 'C:')
1372
default_config_dir = os.path.join(drive, '\\',
1383
"""Build the Application Default Credentials from file."""
1384
# read the credentials from the file
1385
with open(filename) as file_obj:
1386
client_credentials = json.load(file_obj)
1388
credentials_type = client_credentials.get('type')
1389
if credentials_type == AUTHORIZED_USER:
1390
required_fields = set(['client_id', 'client_secret', 'refresh_token'])
1391
elif credentials_type == SERVICE_ACCOUNT:
1392
required_fields = set(['client_id', 'client_email', 'private_key_id',
1396
"'type' field should be defined (and have one of the '" +
1397
AUTHORIZED_USER + "' or '" + SERVICE_ACCOUNT + "' values)")
1406
access_token=None,
1407
client_id=client_credentials['client_id'],
1408
client_secret=client_credentials['client_secret'],
1409
refresh_token=client_credentials['refresh_token'],
1410
token_expiry=None,
1414
from oauth2client import service_account
1415
return service_account._JWTAccessCredentials.from_json_keyfile_dict(
1422
1423
1424
def _raise_exception_for_reading_json(credential_file,
1425
extra_help,
1426
error):
1428
'An error was encountered while reading json file: ' +
1429
credential_file + extra_help + ': ' + str(error))
1447
This credential does not require a flow to instantiate because it
1448
represents a two legged flow, and therefore has all of the required
1449
information to generate and refresh its own access tokens. It must
1450
be subclassed to generate the appropriate assertion string.
1457
token_uri=oauth2client.GOOGLE_TOKEN_URI,
1458
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
1462
Args:
1463
assertion_type: string, assertion type that will be declared to the
1464
auth server
1465
user_agent: string, The HTTP User-Agent to provide for this
1466
application.
1467
token_uri: string, URI for token endpoint. For convenience defaults
1468
to Google's endpoints but any OAuth 2.0 provider can be
1469
used.
1470
revoke_uri: string, URI for revoke endpoint.
1471
"""
1473
None,
1474
None,
1475
None,
1476
None,
1477
None,
1478
token_uri,
1479
user_agent,
1480
revoke_uri=revoke_uri)
1483
def _generate_refresh_request_body(self):
1484
assertion = self._generate_assertion()
1487
'assertion': assertion,
1488
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
1505
def sign_blob(self, blob):
1506
"""Cryptographically sign a blob (of bytes).
1507
1508
Args:
1509
blob: bytes, Message to be signed.
1510
1511
Returns:
1512
tuple, A pair of the private key ID used to sign the blob and
1513
the signed contents.
1514
"""
1515
raise NotImplementedError('This method is abstract.')
1516
1521
The oauth2client.crypt module requires either PyCrypto or PyOpenSSL
1522
to be available in order to function, but these are optional
1523
dependencies.
1524
"""
1525
if not HAS_CRYPTO:
1526
raise CryptoUnavailableError('No crypto library available')
1530
def verify_id_token(id_token, audience, http=None,
1531
cert_uri=ID_TOKEN_VERIFICATION_CERTS):
1534
This function requires PyOpenSSL and because of that it does not work on
1535
App Engine.
1537
Args:
1538
id_token: string, A Signed JWT.
1539
audience: string, The audience 'aud' that the token should be for.
1540
http: httplib2.Http, instance to use to make the HTTP request. Callers
1541
should supply an instance that has caching enabled.
1542
cert_uri: string, URI of the certificates in JSON format to
1543
verify the JWT against.
1548
Raises:
1549
oauth2client.crypt.AppIdentityError: if the JWT fails to verify.
1550
CryptoUnavailableError: if no crypto library is available.
1551
"""
1575
if type(id_token) == bytes:
1576
segments = id_token.split(b'.')
1577
else:
1578
segments = id_token.split(u'.')
1584
return json.loads(
1585
_helpers._from_bytes(_helpers._urlsafe_b64decode(segments[1])))
1591
Most providers return JSON but some (e.g. Facebook) return a
1592
url-encoded string.
1597
Returns:
1598
Content as a dictionary object. Note that the dict could be empty,
1599
i.e. {}. That basically indicates a failure.
1600
"""
1603
try:
1604
resp = json.loads(content)
1605
except Exception:
1606
# different JSON libs raise different exceptions,
1607
# so we just do a catch-all here
1610
# some providers respond with 'expires', others with 'expires_in'
1611
if resp and 'expires' in resp:
1612
resp['expires_in'] = resp.pop('expires')
1620
user_agent=None,
1621
token_uri=oauth2client.GOOGLE_TOKEN_URI,
1622
auth_uri=oauth2client.GOOGLE_AUTH_URI,
1623
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
1624
device_uri=oauth2client.GOOGLE_DEVICE_URI,
1625
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
1626
pkce=False,
1627
code_verifier=None):
1630
Args:
1631
client_id: string, client identifier.
1632
client_secret: string, client secret.
1633
scope: string or iterable of strings, scope(s) to request.
1634
code: string, An authorization code, most likely passed down from
1635
the client
1636
redirect_uri: string, this is generally set to 'postmessage' to match
1637
the redirect_uri that the client specified
1638
http: httplib2.Http, optional http instance to use to do the fetch
1639
token_uri: string, URI for token endpoint. For convenience defaults
1640
to Google's endpoints but any OAuth 2.0 provider can be
1641
used.
1642
auth_uri: string, URI for authorization endpoint. For convenience
1643
defaults to Google's endpoints but any OAuth 2.0 provider
1644
can be used.
1645
revoke_uri: string, URI for revoke endpoint. For convenience
1646
defaults to Google's endpoints but any OAuth 2.0 provider
1647
can be used.
1648
device_uri: string, URI for device authorization endpoint. For
1649
convenience defaults to Google's endpoints but any OAuth
1650
2.0 provider can be used.
1651
pkce: boolean, default: False, Generate and include a "Proof Key
1652
for Code Exchange" (PKCE) with your authorization and token
1653
requests. This adds security for installed applications that
1654
cannot protect a client_secret. See RFC 7636 for details.
1655
code_verifier: bytestring or None, default: None, parameter passed
1656
as part of the code exchange when pkce=True. If
1657
None, a code_verifier will automatically be
1658
generated as part of step1_get_authorize_url(). See
1659
RFC 7636 for details.
1660
1661
Returns:
1662
An OAuth2Credentials object.
1663
1664
Raises:
1665
FlowExchangeError if the authorization code cannot be exchanged for an
1666
access token
1667
"""
1670
user_agent=user_agent,
1671
auth_uri=auth_uri,
1672
token_uri=token_uri,
1673
revoke_uri=revoke_uri,
1692
Will create the right kind of Flow based on the contents of the
1693
clientsecrets file or will raise InvalidClientSecretsError for unknown
1694
types of Flows.
1695
1696
Args:
1697
filename: string, File name of clientsecrets.
1698
scope: string or iterable of strings, scope(s) to request.
1699
code: string, An authorization code, most likely passed down from
1700
the client
1701
message: string, A friendly string to display to the user if the
1702
clientsecrets file is missing or invalid. If message is
1703
provided then sys.exit will be called in the case of an error.
1704
If message in not provided then
1705
clientsecrets.InvalidClientSecretsError will be raised.
1706
redirect_uri: string, this is generally set to 'postmessage' to match
1707
the redirect_uri that the client specified
1708
http: httplib2.Http, optional http instance to use to do the fetch
1709
cache: An optional cache service client that implements get() and set()
1710
methods. See clientsecrets.loadfile() for details.
1711
device_uri: string, OAuth 2.0 device authorization endpoint
1712
pkce: boolean, default: False, Generate and include a "Proof Key
1713
for Code Exchange" (PKCE) with your authorization and token
1714
requests. This adds security for installed applications that
1715
cannot protect a client_secret. See RFC 7636 for details.
1716
code_verifier: bytestring or None, default: None, parameter passed
1717
as part of the code exchange when pkce=True. If
1718
None, a code_verifier will automatically be
1719
generated as part of step1_get_authorize_url(). See
1720
RFC 7636 for details.
1721
1722
Returns:
1723
An OAuth2Credentials object.
1724
1725
Raises:
1726
FlowExchangeError: if the authorization code cannot be exchanged for an
1727
access token
1728
UnknownClientSecretsFlowError: if the file describes an unknown kind
1729
of Flow.
1730
clientsecrets.InvalidClientSecretsError: if the clientsecrets file is
1731
invalid.
1732
"""
1733
flow = flow_from_clientsecrets(filename, scope, message=message,
1734
cache=cache, redirect_uri=redirect_uri,
1735
device_uri=device_uri)
1741
'device_code', 'user_code', 'interval', 'verification_url',
1742
'user_code_expiry'))):
1745
@classmethod
1746
def FromResponse(cls, response):
1747
"""Create a DeviceFlowInfo from a server response.
1757
}
1758
# The response may list the verification address as either
1759
# verification_url or verification_uri, so we check for both.
1760
verification_url = response.get(
1765
kwargs['verification_url'] = verification_url
1766
# expires_in and interval are optional.
1767
kwargs.update({
1778
def _oauth2_web_server_flow_params(kwargs):
1779
"""Configures redirect URI parameters for OAuth2WebServerFlow."""
1780
params = {
1781
'access_type': 'offline',
1782
'response_type': 'code',
1783
}
1784
1785
params.update(kwargs)
1786
1787
# Check for the presence of the deprecated approval_prompt param and
1788
# warn appropriately.
1789
approval_prompt = params.get('approval_prompt')
1790
if approval_prompt is not None:
1791
logger.warning(
1792
'The approval_prompt parameter for OAuth2WebServerFlow is '
1793
'deprecated. Please use the prompt parameter instead.')
1794
1795
if approval_prompt == 'force':
1796
logger.warning(
1797
'approval_prompt="force" has been adjusted to '
1798
'prompt="consent"')
1799
params['prompt'] = 'consent'
1800
del params['approval_prompt']
1801
1802
return params
1803
1804
1813
client_secret=None,
1814
scope=None,
1815
redirect_uri=None,
1816
user_agent=None,
1817
auth_uri=oauth2client.GOOGLE_AUTH_URI,
1818
token_uri=oauth2client.GOOGLE_TOKEN_URI,
1819
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
1821
device_uri=oauth2client.GOOGLE_DEVICE_URI,
1822
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
1831
query parameters can be set via kwargs.
1832
1833
Args:
1834
client_id: string, client identifier.
1835
client_secret: string client secret.
1836
scope: string or iterable of strings, scope(s) of the credentials
1837
being requested.
1838
redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob'
1839
for a non-web-based application, or a URI that
1840
handles the callback from the authorization server.
1843
auth_uri: string, URI for authorization endpoint. For convenience
1844
defaults to Google's endpoints but any OAuth 2.0 provider
1845
can be used.
1846
token_uri: string, URI for token endpoint. For convenience
1847
defaults to Google's endpoints but any OAuth 2.0
1848
provider can be used.
1849
revoke_uri: string, URI for revoke endpoint. For convenience
1850
defaults to Google's endpoints but any OAuth 2.0
1851
provider can be used.
1852
login_hint: string, Either an email address or domain. Passing this
1853
hint will either pre-fill the email box on the sign-in
1854
form or select the proper multi-login session, thereby
1855
simplifying the login flow.
1856
device_uri: string, URI for device authorization endpoint. For
1857
convenience defaults to Google's endpoints but any
1858
OAuth 2.0 provider can be used.
1859
authorization_header: string, For use with OAuth 2.0 providers that
1860
require a client to authenticate using a
1861
header value instead of passing client_secret
1862
in the POST body.
1863
pkce: boolean, default: False, Generate and include a "Proof Key
1864
for Code Exchange" (PKCE) with your authorization and token
1865
requests. This adds security for installed applications that
1866
cannot protect a client_secret. See RFC 7636 for details.
1867
code_verifier: bytestring or None, default: None, parameter passed
1868
as part of the code exchange when pkce=True. If
1869
None, a code_verifier will automatically be
1870
generated as part of step1_get_authorize_url(). See
1871
RFC 7636 for details.
1872
**kwargs: dict, The keyword arguments are all optional and required
1873
parameters for the OAuth calls.
1874
"""
1875
# scope is a required argument, but to preserve backwards-compatibility
1876
# we don't want to rearrange the positional arguments
1877
if scope is None:
1878
raise TypeError("The value of scope must not be None")
1879
self.client_id = client_id
1880
self.client_secret = client_secret
1882
self.redirect_uri = redirect_uri
1883
self.login_hint = login_hint
1884
self.user_agent = user_agent
1885
self.auth_uri = auth_uri
1886
self.token_uri = token_uri
1887
self.revoke_uri = revoke_uri
1888
self.device_uri = device_uri
1889
self.token_info_uri = token_info_uri
1890
self.authorization_header = authorization_header
1896
def step1_get_authorize_url(self, redirect_uri=None, state=None):
1897
"""Returns a URI to redirect to the provider.
1899
Args:
1900
redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob'
1901
for a non-web-based application, or a URI that
1902
handles the callback from the authorization server.
1903
This parameter is deprecated, please move to passing
1904
the redirect_uri in via the constructor.
1905
state: string, Opaque state string which is passed through the
1906
OAuth2 flow and returned to the client as a query parameter
1907
in the callback.
1908
1909
Returns:
1910
A URI as a string to redirect the user to begin the authorization
1911
flow.
1912
"""
1915
'The redirect_uri parameter for '
1916
'OAuth2WebServerFlow.step1_get_authorize_url is deprecated. '
1917
'Please move to passing the redirect_uri in via the '
1918
'constructor.'))
1921
if self.redirect_uri is None:
1922
raise ValueError('The value of redirect_uri must not be None.')
1925
'client_id': self.client_id,
1926
'redirect_uri': self.redirect_uri,
1927
'scope': self.scope,
1928
}
1929
if state is not None:
1930
query_params['state'] = state
1931
if self.login_hint is not None:
1932
query_params['login_hint'] = self.login_hint
1933
if self._pkce:
1934
if not self.code_verifier:
1935
self.code_verifier = _pkce.code_verifier()
1936
challenge = _pkce.code_challenge(self.code_verifier)
1937
query_params['code_challenge'] = challenge
1938
query_params['code_challenge_method'] = 'S256'
1939
1944
def step1_get_device_and_user_codes(self, http=None):
1945
"""Returns a user code and the verification URL where to enter it
1947
Returns:
1948
A user code as a string for the user to authorize the application
1949
An URL as a string where the user has to enter the code
1950
"""
1951
if self.device_uri is None:
1952
raise ValueError('The value of device_uri must not be None.')
1968
resp, content = transport.request(
1969
http, self.device_uri, method='POST', body=body, headers=headers)
1976
'Could not parse server response as JSON: "{0}", '
1977
'error: "{1}"'.format(content, exc))
1995
Args:
1996
code: string, a dict-like object, or None. For a non-device
1997
flow, this is either the response code as a string, or a
1998
dictionary of query parameters to the redirect_uri. For a
1999
device flow, this should be None.
2000
http: httplib2.Http, optional http instance to use when fetching
2001
credentials.
2002
device_flow_info: DeviceFlowInfo, return value from step1 in the
2003
case of a device flow.
2004
2005
Returns:
2006
An OAuth2Credentials object that can be used to authorize requests.
2007
2008
Raises:
2009
FlowExchangeError: if a problem occurred exchanging the code for a
2010
refresh_token.
2011
ValueError: if code and device_flow_info are both provided or both
2012
missing.
2013
"""
2014
if code is None and device_flow_info is None:
2015
raise ValueError('No code or device_flow_info provided.')
2016
if code is not None and device_flow_info is not None:
2017
raise ValueError('Cannot provide both code and device_flow_info.')
2018
2019
if code is None:
2020
code = device_flow_info.device_code
2031
}
2032
if self.client_secret is not None:
2033
post_data['client_secret'] = self.client_secret
2036
if device_flow_info is not None:
2037
post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
2038
else:
2039
post_data['grant_type'] = 'authorization_code'
2040
post_data['redirect_uri'] = self.redirect_uri
2041
body = urllib.parse.urlencode(post_data)
2042
headers = {
2044
}
2045
if self.authorization_header is not None:
2046
headers['Authorization'] = self.authorization_header
2047
if self.user_agent is not None:
2048
headers['user-agent'] = self.user_agent
2053
resp, content = transport.request(
2054
http, self.token_uri, method='POST', body=body, headers=headers)
2057
access_token = d['access_token']
2058
refresh_token = d.get('refresh_token', None)
2059
if not refresh_token:
2060
logger.info(
2065
delta = datetime.timedelta(seconds=int(d['expires_in']))
2066
token_expiry = delta + _UTCNOW()
2075
return OAuth2Credentials(
2076
access_token, self.client_id, self.client_secret,
2077
refresh_token, token_expiry, self.token_uri, self.user_agent,
2078
revoke_uri=self.revoke_uri, id_token=extracted_id_token,
2081
else:
2082
logger.info('Failed to retrieve access token: %s', content)
2083
if 'error' in d:
2084
# you never know what those providers got to say
2099
Will create the right kind of Flow based on the contents of the
2100
clientsecrets file or will raise InvalidClientSecretsError for unknown
2101
types of Flows.
2102
2103
Args:
2104
filename: string, File name of client secrets.
2105
scope: string or iterable of strings, scope(s) to request.
2106
redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
2107
a non-web-based application, or a URI that handles the
2108
callback from the authorization server.
2109
message: string, A friendly string to display to the user if the
2110
clientsecrets file is missing or invalid. If message is
2111
provided then sys.exit will be called in the case of an error.
2112
If message in not provided then
2113
clientsecrets.InvalidClientSecretsError will be raised.
2114
cache: An optional cache service client that implements get() and set()
2115
methods. See clientsecrets.loadfile() for details.
2116
login_hint: string, Either an email address or domain. Passing this
2117
hint will either pre-fill the email box on the sign-in form
2118
or select the proper multi-login session, thereby
2119
simplifying the login flow.
2120
device_uri: string, URI for device authorization endpoint. For
2121
convenience defaults to Google's endpoints but any
2122
OAuth 2.0 provider can be used.
2123
2124
Returns:
2125
A Flow object.
2126
2127
Raises:
2128
UnknownClientSecretsFlowError: if the file describes an unknown kind of
2129
Flow.
2130
clientsecrets.InvalidClientSecretsError: if the clientsecrets file is
2131
invalid.
2132
"""
2134
client_type, client_info = clientsecrets.loadfile(filename,
2135
cache=cache)
2136
if client_type in (clientsecrets.TYPE_WEB,
2137
clientsecrets.TYPE_INSTALLED):
2139
'redirect_uri': redirect_uri,
2140
'auth_uri': client_info['auth_uri'],
2141
'token_uri': client_info['token_uri'],
2142
'login_hint': login_hint,
2145
optional = (
2146
'revoke_uri',
2147
'device_uri',
2148
'pkce',
2149
'code_verifier',
2150
'prompt'
2151
)
2152
for param in optional:
2153
if locals()[param] is not None:
2154
constructor_kwargs[param] = locals()[param]
2155
2157
client_info['client_id'], client_info['client_secret'],
2158
scope, **constructor_kwargs)
2160
except clientsecrets.InvalidClientSecretsError as e:
2161
if message is not None:
2162
if e.args:
2163
message = ('The client secrets were invalid: '
2164
'\n{0}\n{1}'.format(e, message))