| OLD | NEW |
| 1 # Copyright 2014 Google Inc. All rights reserved. | 1 # Copyright 2014 Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with 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 | 5 # You may obtain a copy of the License at |
| 6 # | 6 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 | 7 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # | 8 # |
| 9 # Unless required by applicable law or agreed to in writing, software | 9 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, | 10 # distributed under the License is distributed on an "AS IS" BASIS, |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 import datetime | 23 import datetime |
| 24 import json | 24 import json |
| 25 import logging | 25 import logging |
| 26 import os | 26 import os |
| 27 import socket | 27 import socket |
| 28 import sys | 28 import sys |
| 29 import tempfile | 29 import tempfile |
| 30 import time | 30 import time |
| 31 import shutil | 31 import shutil |
| 32 import six | 32 import six |
| 33 from six.moves import http_client |
| 33 from six.moves import urllib | 34 from six.moves import urllib |
| 34 | 35 |
| 35 import httplib2 | 36 import httplib2 |
| 36 from oauth2client import GOOGLE_AUTH_URI | 37 from oauth2client import GOOGLE_AUTH_URI |
| 37 from oauth2client import GOOGLE_DEVICE_URI | 38 from oauth2client import GOOGLE_DEVICE_URI |
| 38 from oauth2client import GOOGLE_REVOKE_URI | 39 from oauth2client import GOOGLE_REVOKE_URI |
| 39 from oauth2client import GOOGLE_TOKEN_URI | 40 from oauth2client import GOOGLE_TOKEN_URI |
| 40 from oauth2client import GOOGLE_TOKEN_INFO_URI | 41 from oauth2client import GOOGLE_TOKEN_INFO_URI |
| 41 from oauth2client._helpers import _from_bytes | 42 from oauth2client._helpers import _from_bytes |
| 42 from oauth2client._helpers import _to_bytes | 43 from oauth2client._helpers import _to_bytes |
| (...skipping 23 matching lines...) Expand all Loading... |
| 66 # Which certs to use to validate id_tokens received. | 67 # Which certs to use to validate id_tokens received. |
| 67 ID_TOKEN_VERIFICATION_CERTS = 'https://www.googleapis.com/oauth2/v1/certs' | 68 ID_TOKEN_VERIFICATION_CERTS = 'https://www.googleapis.com/oauth2/v1/certs' |
| 68 # This symbol previously had a typo in the name; we keep the old name | 69 # This symbol previously had a typo in the name; we keep the old name |
| 69 # around for now, but will remove it in the future. | 70 # around for now, but will remove it in the future. |
| 70 ID_TOKEN_VERIFICATON_CERTS = ID_TOKEN_VERIFICATION_CERTS | 71 ID_TOKEN_VERIFICATON_CERTS = ID_TOKEN_VERIFICATION_CERTS |
| 71 | 72 |
| 72 # Constant to use for the out of band OAuth 2.0 flow. | 73 # Constant to use for the out of band OAuth 2.0 flow. |
| 73 OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob' | 74 OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob' |
| 74 | 75 |
| 75 # Google Data client libraries may need to set this to [401, 403]. | 76 # Google Data client libraries may need to set this to [401, 403]. |
| 76 REFRESH_STATUS_CODES = [401] | 77 REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,) |
| 77 | 78 |
| 78 # The value representing user credentials. | 79 # The value representing user credentials. |
| 79 AUTHORIZED_USER = 'authorized_user' | 80 AUTHORIZED_USER = 'authorized_user' |
| 80 | 81 |
| 81 # The value representing service account credentials. | 82 # The value representing service account credentials. |
| 82 SERVICE_ACCOUNT = 'service_account' | 83 SERVICE_ACCOUNT = 'service_account' |
| 83 | 84 |
| 84 # The environment variable pointing the file with local | 85 # The environment variable pointing the file with local |
| 85 # Application Default Credentials. | 86 # Application Default Credentials. |
| 86 GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS' | 87 GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS' |
| 87 # The ~/.config subdirectory containing gcloud credentials. Intended | 88 # The ~/.config subdirectory containing gcloud credentials. Intended |
| 88 # to be swapped out in tests. | 89 # to be swapped out in tests. |
| 89 _CLOUDSDK_CONFIG_DIRECTORY = 'gcloud' | 90 _CLOUDSDK_CONFIG_DIRECTORY = 'gcloud' |
| 90 # The environment variable name which can replace ~/.config if set. | 91 # The environment variable name which can replace ~/.config if set. |
| 91 _CLOUDSDK_CONFIG_ENV_VAR = 'CLOUDSDK_CONFIG' | 92 _CLOUDSDK_CONFIG_ENV_VAR = 'CLOUDSDK_CONFIG' |
| 92 | 93 |
| 93 # The error message we show users when we can't find the Application | 94 # The error message we show users when we can't find the Application |
| 94 # Default Credentials. | 95 # Default Credentials. |
| 95 ADC_HELP_MSG = ( | 96 ADC_HELP_MSG = ( |
| 96 'The Application Default Credentials are not available. They are ' | 97 'The Application Default Credentials are not available. They are ' |
| 97 'available if running in Google Compute Engine. Otherwise, the ' | 98 'available if running in Google Compute Engine. Otherwise, the ' |
| 98 'environment variable ' + | 99 'environment variable ' + |
| 99 GOOGLE_APPLICATION_CREDENTIALS + | 100 GOOGLE_APPLICATION_CREDENTIALS + |
| 100 ' must be defined pointing to a file defining the credentials. See ' | 101 ' must be defined pointing to a file defining the credentials. See ' |
| 101 'https://developers.google.com/accounts/docs/' | 102 'https://developers.google.com/accounts/docs/' |
| 102 'application-default-credentials for more information.') | 103 'application-default-credentials for more information.') |
| 103 | 104 |
| 105 _WELL_KNOWN_CREDENTIALS_FILE = 'application_default_credentials.json' |
| 106 |
| 104 # The access token along with the seconds in which it expires. | 107 # The access token along with the seconds in which it expires. |
| 105 AccessTokenInfo = collections.namedtuple( | 108 AccessTokenInfo = collections.namedtuple( |
| 106 'AccessTokenInfo', ['access_token', 'expires_in']) | 109 'AccessTokenInfo', ['access_token', 'expires_in']) |
| 107 | 110 |
| 108 DEFAULT_ENV_NAME = 'UNKNOWN' | 111 DEFAULT_ENV_NAME = 'UNKNOWN' |
| 109 | 112 |
| 110 # If set to True _get_environment avoid GCE check (_detect_gce_environment) | 113 # If set to True _get_environment avoid GCE check (_detect_gce_environment) |
| 111 NO_GCE_CHECK = os.environ.setdefault('NO_GCE_CHECK', 'False') | 114 NO_GCE_CHECK = os.environ.setdefault('NO_GCE_CHECK', 'False') |
| 112 | 115 |
| 113 _SERVER_SOFTWARE = 'SERVER_SOFTWARE' | 116 _SERVER_SOFTWARE = 'SERVER_SOFTWARE' |
| 114 _GCE_METADATA_HOST = '169.254.169.254' | 117 _GCE_METADATA_HOST = '169.254.169.254' |
| 115 _METADATA_FLAVOR_HEADER = 'Metadata-Flavor' | 118 _METADATA_FLAVOR_HEADER = 'Metadata-Flavor' |
| 116 _DESIRED_METADATA_FLAVOR = 'Google' | 119 _DESIRED_METADATA_FLAVOR = 'Google' |
| 117 | 120 |
| 121 # Expose utcnow() at module level to allow for |
| 122 # easier testing (by replacing with a stub). |
| 123 _UTCNOW = datetime.datetime.utcnow |
| 124 |
| 118 | 125 |
| 119 class SETTINGS(object): | 126 class SETTINGS(object): |
| 120 """Settings namespace for globally defined values.""" | 127 """Settings namespace for globally defined values.""" |
| 121 env_name = None | 128 env_name = None |
| 122 | 129 |
| 123 | 130 |
| 124 class Error(Exception): | 131 class Error(Exception): |
| 125 """Base error for this module.""" | 132 """Base error for this module.""" |
| 126 | 133 |
| 127 | 134 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 185 def get(self, key): | 192 def get(self, key): |
| 186 return self.cache.get(key) | 193 return self.cache.get(key) |
| 187 | 194 |
| 188 def set(self, key, value): | 195 def set(self, key, value): |
| 189 self.cache[key] = value | 196 self.cache[key] = value |
| 190 | 197 |
| 191 def delete(self, key): | 198 def delete(self, key): |
| 192 self.cache.pop(key, None) | 199 self.cache.pop(key, None) |
| 193 | 200 |
| 194 | 201 |
| 202 def _parse_expiry(expiry): |
| 203 if expiry and isinstance(expiry, datetime.datetime): |
| 204 return expiry.strftime(EXPIRY_FORMAT) |
| 205 else: |
| 206 return None |
| 207 |
| 208 |
| 195 class Credentials(object): | 209 class Credentials(object): |
| 196 """Base class for all Credentials objects. | 210 """Base class for all Credentials objects. |
| 197 | 211 |
| 198 Subclasses must define an authorize() method that applies the credentials | 212 Subclasses must define an authorize() method that applies the credentials |
| 199 to an HTTP transport. | 213 to an HTTP transport. |
| 200 | 214 |
| 201 Subclasses must also specify a classmethod named 'from_json' that takes a | 215 Subclasses must also specify a classmethod named 'from_json' that takes a |
| 202 JSON string as input and returns an instantiated Credentials object. | 216 JSON string as input and returns an instantiated Credentials object. |
| 203 """ | 217 """ |
| 204 | 218 |
| 205 NON_SERIALIZED_MEMBERS = ['store'] | 219 NON_SERIALIZED_MEMBERS = frozenset(['store']) |
| 206 | 220 |
| 207 def authorize(self, http): | 221 def authorize(self, http): |
| 208 """Take an httplib2.Http instance (or equivalent) and authorizes it. | 222 """Take an httplib2.Http instance (or equivalent) and authorizes it. |
| 209 | 223 |
| 210 Authorizes it for the set of credentials, usually by replacing | 224 Authorizes it for the set of credentials, usually by replacing |
| 211 http.request() with a method that adds in the appropriate headers and | 225 http.request() with a method that adds in the appropriate headers and |
| 212 then delegates to the original Http.request() method. | 226 then delegates to the original Http.request() method. |
| 213 | 227 |
| 214 Args: | 228 Args: |
| 215 http: httplib2.Http, an http object to be used to make the refresh | 229 http: httplib2.Http, an http object to be used to make the refresh |
| (...skipping 20 matching lines...) Expand all Loading... |
| 236 _abstract() | 250 _abstract() |
| 237 | 251 |
| 238 def apply(self, headers): | 252 def apply(self, headers): |
| 239 """Add the authorization to the headers. | 253 """Add the authorization to the headers. |
| 240 | 254 |
| 241 Args: | 255 Args: |
| 242 headers: dict, the headers to add the Authorization header to. | 256 headers: dict, the headers to add the Authorization header to. |
| 243 """ | 257 """ |
| 244 _abstract() | 258 _abstract() |
| 245 | 259 |
| 246 def _to_json(self, strip): | 260 def _to_json(self, strip, to_serialize=None): |
| 247 """Utility function that creates JSON repr. of a Credentials object. | 261 """Utility function that creates JSON repr. of a Credentials object. |
| 248 | 262 |
| 249 Args: | 263 Args: |
| 250 strip: array, An array of names of members to not include in the | 264 strip: array, An array of names of members to exclude from the |
| 251 JSON. | 265 JSON. |
| 266 to_serialize: dict, (Optional) The properties for this object |
| 267 that will be serialized. This allows callers to modify |
| 268 before serializing. |
| 252 | 269 |
| 253 Returns: | 270 Returns: |
| 254 string, a JSON representation of this instance, suitable to pass to | 271 string, a JSON representation of this instance, suitable to pass to |
| 255 from_json(). | 272 from_json(). |
| 256 """ | 273 """ |
| 257 t = type(self) | 274 curr_type = self.__class__ |
| 258 d = copy.copy(self.__dict__) | 275 if to_serialize is None: |
| 276 to_serialize = copy.copy(self.__dict__) |
| 259 for member in strip: | 277 for member in strip: |
| 260 if member in d: | 278 if member in to_serialize: |
| 261 del d[member] | 279 del to_serialize[member] |
| 262 if (d.get('token_expiry') and | 280 to_serialize['token_expiry'] = _parse_expiry( |
| 263 isinstance(d['token_expiry'], datetime.datetime)): | 281 to_serialize.get('token_expiry')) |
| 264 d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT) | 282 # Add in information we will need later to reconstitute this instance. |
| 265 # Add in information we will need later to reconsistitue this instance. | 283 to_serialize['_class'] = curr_type.__name__ |
| 266 d['_class'] = t.__name__ | 284 to_serialize['_module'] = curr_type.__module__ |
| 267 d['_module'] = t.__module__ | 285 for key, val in to_serialize.items(): |
| 268 for key, val in d.items(): | |
| 269 if isinstance(val, bytes): | 286 if isinstance(val, bytes): |
| 270 d[key] = val.decode('utf-8') | 287 to_serialize[key] = val.decode('utf-8') |
| 271 if isinstance(val, set): | 288 if isinstance(val, set): |
| 272 d[key] = list(val) | 289 to_serialize[key] = list(val) |
| 273 return json.dumps(d) | 290 return json.dumps(to_serialize) |
| 274 | 291 |
| 275 def to_json(self): | 292 def to_json(self): |
| 276 """Creating a JSON representation of an instance of Credentials. | 293 """Creating a JSON representation of an instance of Credentials. |
| 277 | 294 |
| 278 Returns: | 295 Returns: |
| 279 string, a JSON representation of this instance, suitable to pass to | 296 string, a JSON representation of this instance, suitable to pass to |
| 280 from_json(). | 297 from_json(). |
| 281 """ | 298 """ |
| 282 return self._to_json(Credentials.NON_SERIALIZED_MEMBERS) | 299 return self._to_json(self.NON_SERIALIZED_MEMBERS) |
| 283 | 300 |
| 284 @classmethod | 301 @classmethod |
| 285 def new_from_json(cls, s): | 302 def new_from_json(cls, json_data): |
| 286 """Utility class method to instantiate a Credentials subclass from JSON. | 303 """Utility class method to instantiate a Credentials subclass from JSON. |
| 287 | 304 |
| 288 Expects the JSON string to have been produced by to_json(). | 305 Expects the JSON string to have been produced by to_json(). |
| 289 | 306 |
| 290 Args: | 307 Args: |
| 291 s: string or bytes, JSON from to_json(). | 308 json_data: string or bytes, JSON from to_json(). |
| 292 | 309 |
| 293 Returns: | 310 Returns: |
| 294 An instance of the subclass of Credentials that was serialized with | 311 An instance of the subclass of Credentials that was serialized with |
| 295 to_json(). | 312 to_json(). |
| 296 """ | 313 """ |
| 297 json_string_as_unicode = _from_bytes(s) | 314 json_data_as_unicode = _from_bytes(json_data) |
| 298 data = json.loads(json_string_as_unicode) | 315 data = json.loads(json_data_as_unicode) |
| 299 # Find and call the right classmethod from_json() to restore | 316 # Find and call the right classmethod from_json() to restore |
| 300 # the object. | 317 # the object. |
| 301 module_name = data['_module'] | 318 module_name = data['_module'] |
| 302 try: | 319 try: |
| 303 module_obj = __import__(module_name) | 320 module_obj = __import__(module_name) |
| 304 except ImportError: | 321 except ImportError: |
| 305 # In case there's an object from the old package structure, | 322 # In case there's an object from the old package structure, |
| 306 # update it | 323 # update it |
| 307 module_name = module_name.replace('.googleapiclient', '') | 324 module_name = module_name.replace('.googleapiclient', '') |
| 308 module_obj = __import__(module_name) | 325 module_obj = __import__(module_name) |
| 309 | 326 |
| 310 module_obj = __import__(module_name, | 327 module_obj = __import__(module_name, |
| 311 fromlist=module_name.split('.')[:-1]) | 328 fromlist=module_name.split('.')[:-1]) |
| 312 kls = getattr(module_obj, data['_class']) | 329 kls = getattr(module_obj, data['_class']) |
| 313 from_json = getattr(kls, 'from_json') | 330 return kls.from_json(json_data_as_unicode) |
| 314 return from_json(json_string_as_unicode) | |
| 315 | 331 |
| 316 @classmethod | 332 @classmethod |
| 317 def from_json(cls, unused_data): | 333 def from_json(cls, unused_data): |
| 318 """Instantiate a Credentials object from a JSON description of it. | 334 """Instantiate a Credentials object from a JSON description of it. |
| 319 | 335 |
| 320 The JSON should have been produced by calling .to_json() on the object. | 336 The JSON should have been produced by calling .to_json() on the object. |
| 321 | 337 |
| 322 Args: | 338 Args: |
| 323 unused_data: dict, A deserialized JSON object. | 339 unused_data: dict, A deserialized JSON object. |
| 324 | 340 |
| 325 Returns: | 341 Returns: |
| 326 An instance of a Credentials subclass. | 342 An instance of a Credentials subclass. |
| 327 """ | 343 """ |
| 328 return Credentials() | 344 return Credentials() |
| 329 | 345 |
| 330 | 346 |
| 331 class Flow(object): | 347 class Flow(object): |
| 332 """Base class for all Flow objects.""" | 348 """Base class for all Flow objects.""" |
| 333 pass | 349 pass |
| 334 | 350 |
| 335 | 351 |
| 336 class Storage(object): | 352 class Storage(object): |
| 337 """Base class for all Storage objects. | 353 """Base class for all Storage objects. |
| 338 | 354 |
| 339 Store and retrieve a single credential. This class supports locking | 355 Store and retrieve a single credential. This class supports locking |
| 340 such that multiple processes and threads can operate on a single | 356 such that multiple processes and threads can operate on a single |
| 341 store. | 357 store. |
| 342 """ | 358 """ |
| 359 def __init__(self, lock=None): |
| 360 """Create a Storage instance. |
| 361 |
| 362 Args: |
| 363 lock: An optional threading.Lock-like object. Must implement at |
| 364 least acquire() and release(). Does not need to be re-entrant. |
| 365 """ |
| 366 self._lock = lock |
| 343 | 367 |
| 344 def acquire_lock(self): | 368 def acquire_lock(self): |
| 345 """Acquires any lock necessary to access this Storage. | 369 """Acquires any lock necessary to access this Storage. |
| 346 | 370 |
| 347 This lock is not reentrant. | 371 This lock is not reentrant. |
| 348 """ | 372 """ |
| 349 pass | 373 if self._lock is not None: |
| 374 self._lock.acquire() |
| 350 | 375 |
| 351 def release_lock(self): | 376 def release_lock(self): |
| 352 """Release the Storage lock. | 377 """Release the Storage lock. |
| 353 | 378 |
| 354 Trying to release a lock that isn't held will result in a | 379 Trying to release a lock that isn't held will result in a |
| 355 RuntimeError. | 380 RuntimeError in the case of a threading.Lock or multiprocessing.Lock. |
| 356 """ | 381 """ |
| 357 pass | 382 if self._lock is not None: |
| 383 self._lock.release() |
| 358 | 384 |
| 359 def locked_get(self): | 385 def locked_get(self): |
| 360 """Retrieve credential. | 386 """Retrieve credential. |
| 361 | 387 |
| 362 The Storage lock must be held when this is called. | 388 The Storage lock must be held when this is called. |
| 363 | 389 |
| 364 Returns: | 390 Returns: |
| 365 oauth2client.client.Credentials | 391 oauth2client.client.Credentials |
| 366 """ | 392 """ |
| 367 _abstract() | 393 _abstract() |
| (...skipping 308 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 676 Args: | 702 Args: |
| 677 http: httplib2.Http, an http object to be used to make the refresh | 703 http: httplib2.Http, an http object to be used to make the refresh |
| 678 request. | 704 request. |
| 679 | 705 |
| 680 Returns: | 706 Returns: |
| 681 A set of strings containing the canonical list of scopes. | 707 A set of strings containing the canonical list of scopes. |
| 682 """ | 708 """ |
| 683 self._retrieve_scopes(http.request) | 709 self._retrieve_scopes(http.request) |
| 684 return self.scopes | 710 return self.scopes |
| 685 | 711 |
| 686 def to_json(self): | |
| 687 return self._to_json(Credentials.NON_SERIALIZED_MEMBERS) | |
| 688 | |
| 689 @classmethod | 712 @classmethod |
| 690 def from_json(cls, s): | 713 def from_json(cls, json_data): |
| 691 """Instantiate a Credentials object from a JSON description of it. | 714 """Instantiate a Credentials object from a JSON description of it. |
| 692 | 715 |
| 693 The JSON should have been produced by calling .to_json() on the object. | 716 The JSON should have been produced by calling .to_json() on the object. |
| 694 | 717 |
| 695 Args: | 718 Args: |
| 696 data: dict, A deserialized JSON object. | 719 json_data: string or bytes, JSON to deserialize. |
| 697 | 720 |
| 698 Returns: | 721 Returns: |
| 699 An instance of a Credentials subclass. | 722 An instance of a Credentials subclass. |
| 700 """ | 723 """ |
| 701 s = _from_bytes(s) | 724 data = json.loads(_from_bytes(json_data)) |
| 702 data = json.loads(s) | |
| 703 if (data.get('token_expiry') and | 725 if (data.get('token_expiry') and |
| 704 not isinstance(data['token_expiry'], datetime.datetime)): | 726 not isinstance(data['token_expiry'], datetime.datetime)): |
| 705 try: | 727 try: |
| 706 data['token_expiry'] = datetime.datetime.strptime( | 728 data['token_expiry'] = datetime.datetime.strptime( |
| 707 data['token_expiry'], EXPIRY_FORMAT) | 729 data['token_expiry'], EXPIRY_FORMAT) |
| 708 except ValueError: | 730 except ValueError: |
| 709 data['token_expiry'] = None | 731 data['token_expiry'] = None |
| 710 retval = cls( | 732 retval = cls( |
| 711 data['access_token'], | 733 data['access_token'], |
| 712 data['client_id'], | 734 data['client_id'], |
| (...skipping 15 matching lines...) Expand all Loading... |
| 728 """True if the credential is expired or invalid. | 750 """True if the credential is expired or invalid. |
| 729 | 751 |
| 730 If the token_expiry isn't set, we assume the token doesn't expire. | 752 If the token_expiry isn't set, we assume the token doesn't expire. |
| 731 """ | 753 """ |
| 732 if self.invalid: | 754 if self.invalid: |
| 733 return True | 755 return True |
| 734 | 756 |
| 735 if not self.token_expiry: | 757 if not self.token_expiry: |
| 736 return False | 758 return False |
| 737 | 759 |
| 738 now = datetime.datetime.utcnow() | 760 now = _UTCNOW() |
| 739 if now >= self.token_expiry: | 761 if now >= self.token_expiry: |
| 740 logger.info('access_token is expired. Now: %s, token_expiry: %s', | 762 logger.info('access_token is expired. Now: %s, token_expiry: %s', |
| 741 now, self.token_expiry) | 763 now, self.token_expiry) |
| 742 return True | 764 return True |
| 743 return False | 765 return False |
| 744 | 766 |
| 745 def get_access_token(self, http=None): | 767 def get_access_token(self, http=None): |
| 746 """Return the access token and its expiration information. | 768 """Return the access token and its expiration information. |
| 747 | 769 |
| 748 If the token does not exist, get one. | 770 If the token does not exist, get one. |
| (...skipping 22 matching lines...) Expand all Loading... |
| 771 """Return the number of seconds until this token expires. | 793 """Return the number of seconds until this token expires. |
| 772 | 794 |
| 773 If token_expiry is in the past, this method will return 0, meaning the | 795 If token_expiry is in the past, this method will return 0, meaning the |
| 774 token has already expired. | 796 token has already expired. |
| 775 | 797 |
| 776 If token_expiry is None, this method will return None. Note that | 798 If token_expiry is None, this method will return None. Note that |
| 777 returning 0 in such a case would not be fair: the token may still be | 799 returning 0 in such a case would not be fair: the token may still be |
| 778 valid; we just don't know anything about it. | 800 valid; we just don't know anything about it. |
| 779 """ | 801 """ |
| 780 if self.token_expiry: | 802 if self.token_expiry: |
| 781 now = datetime.datetime.utcnow() | 803 now = _UTCNOW() |
| 782 if self.token_expiry > now: | 804 if self.token_expiry > now: |
| 783 time_delta = self.token_expiry - now | 805 time_delta = self.token_expiry - now |
| 784 # TODO(orestica): return time_delta.total_seconds() | 806 # TODO(orestica): return time_delta.total_seconds() |
| 785 # once dropping support for Python 2.6 | 807 # once dropping support for Python 2.6 |
| 786 return time_delta.days * 86400 + time_delta.seconds | 808 return time_delta.days * 86400 + time_delta.seconds |
| 787 else: | 809 else: |
| 788 return 0 | 810 return 0 |
| 789 | 811 |
| 790 def _updateFromCredential(self, other): | 812 def _updateFromCredential(self, other): |
| 791 """Update this Credential from another instance.""" | 813 """Update this Credential from another instance.""" |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 866 Raises: | 888 Raises: |
| 867 HttpAccessTokenRefreshError: When the refresh fails. | 889 HttpAccessTokenRefreshError: When the refresh fails. |
| 868 """ | 890 """ |
| 869 body = self._generate_refresh_request_body() | 891 body = self._generate_refresh_request_body() |
| 870 headers = self._generate_refresh_request_headers() | 892 headers = self._generate_refresh_request_headers() |
| 871 | 893 |
| 872 logger.info('Refreshing access_token') | 894 logger.info('Refreshing access_token') |
| 873 resp, content = http_request( | 895 resp, content = http_request( |
| 874 self.token_uri, method='POST', body=body, headers=headers) | 896 self.token_uri, method='POST', body=body, headers=headers) |
| 875 content = _from_bytes(content) | 897 content = _from_bytes(content) |
| 876 if resp.status == 200: | 898 if resp.status == http_client.OK: |
| 877 d = json.loads(content) | 899 d = json.loads(content) |
| 878 self.token_response = d | 900 self.token_response = d |
| 879 self.access_token = d['access_token'] | 901 self.access_token = d['access_token'] |
| 880 self.refresh_token = d.get('refresh_token', self.refresh_token) | 902 self.refresh_token = d.get('refresh_token', self.refresh_token) |
| 881 if 'expires_in' in d: | 903 if 'expires_in' in d: |
| 882 self.token_expiry = datetime.timedelta( | 904 delta = datetime.timedelta(seconds=int(d['expires_in'])) |
| 883 seconds=int(d['expires_in'])) + datetime.datetime.utcnow() | 905 self.token_expiry = delta + _UTCNOW() |
| 884 else: | 906 else: |
| 885 self.token_expiry = None | 907 self.token_expiry = None |
| 908 if 'id_token' in d: |
| 909 self.id_token = _extract_id_token(d['id_token']) |
| 910 else: |
| 911 self.id_token = None |
| 886 # On temporary refresh errors, the user does not actually have to | 912 # On temporary refresh errors, the user does not actually have to |
| 887 # re-authorize, so we unflag here. | 913 # re-authorize, so we unflag here. |
| 888 self.invalid = False | 914 self.invalid = False |
| 889 if self.store: | 915 if self.store: |
| 890 self.store.locked_put(self) | 916 self.store.locked_put(self) |
| 891 else: | 917 else: |
| 892 # An {'error':...} response body means the token is expired or | 918 # An {'error':...} response body means the token is expired or |
| 893 # revoked, so we flag the credentials as such. | 919 # revoked, so we flag the credentials as such. |
| 894 logger.info('Failed to retrieve access token: %s', content) | 920 logger.info('Failed to retrieve access token: %s', content) |
| 895 error_msg = 'Invalid response %s.' % resp['status'] | 921 error_msg = 'Invalid response %s.' % resp['status'] |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 927 access_token or refresh_token. | 953 access_token or refresh_token. |
| 928 | 954 |
| 929 Raises: | 955 Raises: |
| 930 TokenRevokeError: If the revoke request does not return with a | 956 TokenRevokeError: If the revoke request does not return with a |
| 931 200 OK. | 957 200 OK. |
| 932 """ | 958 """ |
| 933 logger.info('Revoking token') | 959 logger.info('Revoking token') |
| 934 query_params = {'token': token} | 960 query_params = {'token': token} |
| 935 token_revoke_uri = _update_query_params(self.revoke_uri, query_params) | 961 token_revoke_uri = _update_query_params(self.revoke_uri, query_params) |
| 936 resp, content = http_request(token_revoke_uri) | 962 resp, content = http_request(token_revoke_uri) |
| 937 if resp.status == 200: | 963 if resp.status == http_client.OK: |
| 938 self.invalid = True | 964 self.invalid = True |
| 939 else: | 965 else: |
| 940 error_msg = 'Invalid response %s.' % resp.status | 966 error_msg = 'Invalid response %s.' % resp.status |
| 941 try: | 967 try: |
| 942 d = json.loads(_from_bytes(content)) | 968 d = json.loads(_from_bytes(content)) |
| 943 if 'error' in d: | 969 if 'error' in d: |
| 944 error_msg = d['error'] | 970 error_msg = d['error'] |
| 945 except (TypeError, ValueError): | 971 except (TypeError, ValueError): |
| 946 pass | 972 pass |
| 947 raise TokenRevokeError(error_msg) | 973 raise TokenRevokeError(error_msg) |
| (...skipping 24 matching lines...) Expand all Loading... |
| 972 Raises: | 998 Raises: |
| 973 Error: When refresh fails, indicating the the access token is | 999 Error: When refresh fails, indicating the the access token is |
| 974 invalid. | 1000 invalid. |
| 975 """ | 1001 """ |
| 976 logger.info('Refreshing scopes') | 1002 logger.info('Refreshing scopes') |
| 977 query_params = {'access_token': token, 'fields': 'scope'} | 1003 query_params = {'access_token': token, 'fields': 'scope'} |
| 978 token_info_uri = _update_query_params(self.token_info_uri, | 1004 token_info_uri = _update_query_params(self.token_info_uri, |
| 979 query_params) | 1005 query_params) |
| 980 resp, content = http_request(token_info_uri) | 1006 resp, content = http_request(token_info_uri) |
| 981 content = _from_bytes(content) | 1007 content = _from_bytes(content) |
| 982 if resp.status == 200: | 1008 if resp.status == http_client.OK: |
| 983 d = json.loads(content) | 1009 d = json.loads(content) |
| 984 self.scopes = set(util.string_to_scopes(d.get('scope', ''))) | 1010 self.scopes = set(util.string_to_scopes(d.get('scope', ''))) |
| 985 else: | 1011 else: |
| 986 error_msg = 'Invalid response %s.' % (resp.status,) | 1012 error_msg = 'Invalid response %s.' % (resp.status,) |
| 987 try: | 1013 try: |
| 988 d = json.loads(content) | 1014 d = json.loads(content) |
| 989 if 'error_description' in d: | 1015 if 'error_description' in d: |
| 990 error_msg = d['error_description'] | 1016 error_msg = d['error_description'] |
| 991 except (TypeError, ValueError): | 1017 except (TypeError, ValueError): |
| 992 pass | 1018 pass |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1036 access_token, | 1062 access_token, |
| 1037 None, | 1063 None, |
| 1038 None, | 1064 None, |
| 1039 None, | 1065 None, |
| 1040 None, | 1066 None, |
| 1041 None, | 1067 None, |
| 1042 user_agent, | 1068 user_agent, |
| 1043 revoke_uri=revoke_uri) | 1069 revoke_uri=revoke_uri) |
| 1044 | 1070 |
| 1045 @classmethod | 1071 @classmethod |
| 1046 def from_json(cls, s): | 1072 def from_json(cls, json_data): |
| 1047 data = json.loads(_from_bytes(s)) | 1073 data = json.loads(_from_bytes(json_data)) |
| 1048 retval = AccessTokenCredentials( | 1074 retval = AccessTokenCredentials( |
| 1049 data['access_token'], | 1075 data['access_token'], |
| 1050 data['user_agent']) | 1076 data['user_agent']) |
| 1051 return retval | 1077 return retval |
| 1052 | 1078 |
| 1053 def _refresh(self, http_request): | 1079 def _refresh(self, http_request): |
| 1054 raise AccessTokenCredentialsError( | 1080 raise AccessTokenCredentialsError( |
| 1055 'The access_token is expired or invalid and can\'t be refreshed.') | 1081 'The access_token is expired or invalid and can\'t be refreshed.') |
| 1056 | 1082 |
| 1057 def _revoke(self, http_request): | 1083 def _revoke(self, http_request): |
| (...skipping 20 matching lines...) Expand all Loading... |
| 1078 # could lead to false negatives in the event that we are on GCE, but | 1104 # could lead to false negatives in the event that we are on GCE, but |
| 1079 # the metadata resolution was particularly slow. The latter case is | 1105 # the metadata resolution was particularly slow. The latter case is |
| 1080 # "unlikely". | 1106 # "unlikely". |
| 1081 connection = six.moves.http_client.HTTPConnection( | 1107 connection = six.moves.http_client.HTTPConnection( |
| 1082 _GCE_METADATA_HOST, timeout=1) | 1108 _GCE_METADATA_HOST, timeout=1) |
| 1083 | 1109 |
| 1084 try: | 1110 try: |
| 1085 headers = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR} | 1111 headers = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR} |
| 1086 connection.request('GET', '/', headers=headers) | 1112 connection.request('GET', '/', headers=headers) |
| 1087 response = connection.getresponse() | 1113 response = connection.getresponse() |
| 1088 if response.status == 200: | 1114 if response.status == http_client.OK: |
| 1089 return (response.getheader(_METADATA_FLAVOR_HEADER) == | 1115 return (response.getheader(_METADATA_FLAVOR_HEADER) == |
| 1090 _DESIRED_METADATA_FLAVOR) | 1116 _DESIRED_METADATA_FLAVOR) |
| 1091 except socket.error: # socket.timeout or socket.error(64, 'Host is down') | 1117 except socket.error: # socket.timeout or socket.error(64, 'Host is down') |
| 1092 logger.info('Timeout attempting to reach GCE metadata service.') | 1118 logger.info('Timeout attempting to reach GCE metadata service.') |
| 1093 return False | 1119 return False |
| 1094 finally: | 1120 finally: |
| 1095 connection.close() | 1121 connection.close() |
| 1096 | 1122 |
| 1097 | 1123 |
| 1098 def _in_gae_environment(): | 1124 def _in_gae_environment(): |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1153 service = build('compute', 'v1', credentials=credentials) | 1179 service = build('compute', 'v1', credentials=credentials) |
| 1154 | 1180 |
| 1155 PROJECT = 'bamboo-machine-422' | 1181 PROJECT = 'bamboo-machine-422' |
| 1156 ZONE = 'us-central1-a' | 1182 ZONE = 'us-central1-a' |
| 1157 request = service.instances().list(project=PROJECT, zone=ZONE) | 1183 request = service.instances().list(project=PROJECT, zone=ZONE) |
| 1158 response = request.execute() | 1184 response = request.execute() |
| 1159 | 1185 |
| 1160 print(response) | 1186 print(response) |
| 1161 """ | 1187 """ |
| 1162 | 1188 |
| 1189 NON_SERIALIZED_MEMBERS = ( |
| 1190 frozenset(['_private_key']) | |
| 1191 OAuth2Credentials.NON_SERIALIZED_MEMBERS) |
| 1192 """Members that aren't serialized when object is converted to JSON.""" |
| 1193 |
| 1163 def __init__(self, access_token, client_id, client_secret, refresh_token, | 1194 def __init__(self, access_token, client_id, client_secret, refresh_token, |
| 1164 token_expiry, token_uri, user_agent, | 1195 token_expiry, token_uri, user_agent, |
| 1165 revoke_uri=GOOGLE_REVOKE_URI): | 1196 revoke_uri=GOOGLE_REVOKE_URI): |
| 1166 """Create an instance of GoogleCredentials. | 1197 """Create an instance of GoogleCredentials. |
| 1167 | 1198 |
| 1168 This constructor is not usually called by the user, instead | 1199 This constructor is not usually called by the user, instead |
| 1169 GoogleCredentials objects are instantiated by | 1200 GoogleCredentials objects are instantiated by |
| 1170 GoogleCredentials.from_stream() or | 1201 GoogleCredentials.from_stream() or |
| 1171 GoogleCredentials.get_application_default(). | 1202 GoogleCredentials.get_application_default(). |
| 1172 | 1203 |
| (...skipping 22 matching lines...) Expand all Loading... |
| 1195 """ | 1226 """ |
| 1196 return False | 1227 return False |
| 1197 | 1228 |
| 1198 def create_scoped(self, scopes): | 1229 def create_scoped(self, scopes): |
| 1199 """Create a Credentials object for the given scopes. | 1230 """Create a Credentials object for the given scopes. |
| 1200 | 1231 |
| 1201 The Credentials type is preserved. | 1232 The Credentials type is preserved. |
| 1202 """ | 1233 """ |
| 1203 return self | 1234 return self |
| 1204 | 1235 |
| 1236 @classmethod |
| 1237 def from_json(cls, json_data): |
| 1238 # TODO(issue 388): eliminate the circularity that is the reason for |
| 1239 # this non-top-level import. |
| 1240 from oauth2client.service_account import ServiceAccountCredentials |
| 1241 data = json.loads(_from_bytes(json_data)) |
| 1242 |
| 1243 # We handle service_account.ServiceAccountCredentials since it is a |
| 1244 # possible return type of GoogleCredentials.get_application_default() |
| 1245 if (data['_module'] == 'oauth2client.service_account' and |
| 1246 data['_class'] == 'ServiceAccountCredentials'): |
| 1247 return ServiceAccountCredentials.from_json(data) |
| 1248 |
| 1249 token_expiry = _parse_expiry(data.get('token_expiry')) |
| 1250 google_credentials = cls( |
| 1251 data['access_token'], |
| 1252 data['client_id'], |
| 1253 data['client_secret'], |
| 1254 data['refresh_token'], |
| 1255 token_expiry, |
| 1256 data['token_uri'], |
| 1257 data['user_agent'], |
| 1258 revoke_uri=data.get('revoke_uri', None)) |
| 1259 google_credentials.invalid = data['invalid'] |
| 1260 return google_credentials |
| 1261 |
| 1205 @property | 1262 @property |
| 1206 def serialization_data(self): | 1263 def serialization_data(self): |
| 1207 """Get the fields and values identifying the current credentials.""" | 1264 """Get the fields and values identifying the current credentials.""" |
| 1208 return { | 1265 return { |
| 1209 'type': 'authorized_user', | 1266 'type': 'authorized_user', |
| 1210 'client_id': self.client_id, | 1267 'client_id': self.client_id, |
| 1211 'client_secret': self.client_secret, | 1268 'client_secret': self.client_secret, |
| 1212 'refresh_token': self.refresh_token | 1269 'refresh_token': self.refresh_token |
| 1213 } | 1270 } |
| 1214 | 1271 |
| (...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1408 'File ' + application_default_credential_filename + | 1465 'File ' + application_default_credential_filename + |
| 1409 ' (pointed by ' + | 1466 ' (pointed by ' + |
| 1410 GOOGLE_APPLICATION_CREDENTIALS + | 1467 GOOGLE_APPLICATION_CREDENTIALS + |
| 1411 ' environment variable) does not exist!') | 1468 ' environment variable) does not exist!') |
| 1412 | 1469 |
| 1413 | 1470 |
| 1414 def _get_well_known_file(): | 1471 def _get_well_known_file(): |
| 1415 """Get the well known file produced by command 'gcloud auth login'.""" | 1472 """Get the well known file produced by command 'gcloud auth login'.""" |
| 1416 # TODO(orestica): Revisit this method once gcloud provides a better way | 1473 # TODO(orestica): Revisit this method once gcloud provides a better way |
| 1417 # of pinpointing the exact location of the file. | 1474 # of pinpointing the exact location of the file. |
| 1418 | |
| 1419 WELL_KNOWN_CREDENTIALS_FILE = 'application_default_credentials.json' | |
| 1420 | |
| 1421 default_config_dir = os.getenv(_CLOUDSDK_CONFIG_ENV_VAR) | 1475 default_config_dir = os.getenv(_CLOUDSDK_CONFIG_ENV_VAR) |
| 1422 if default_config_dir is None: | 1476 if default_config_dir is None: |
| 1423 if os.name == 'nt': | 1477 if os.name == 'nt': |
| 1424 try: | 1478 try: |
| 1425 default_config_dir = os.path.join(os.environ['APPDATA'], | 1479 default_config_dir = os.path.join(os.environ['APPDATA'], |
| 1426 _CLOUDSDK_CONFIG_DIRECTORY) | 1480 _CLOUDSDK_CONFIG_DIRECTORY) |
| 1427 except KeyError: | 1481 except KeyError: |
| 1428 # This should never happen unless someone is really | 1482 # This should never happen unless someone is really |
| 1429 # messing with things. | 1483 # messing with things. |
| 1430 drive = os.environ.get('SystemDrive', 'C:') | 1484 drive = os.environ.get('SystemDrive', 'C:') |
| 1431 default_config_dir = os.path.join(drive, '\\', | 1485 default_config_dir = os.path.join(drive, '\\', |
| 1432 _CLOUDSDK_CONFIG_DIRECTORY) | 1486 _CLOUDSDK_CONFIG_DIRECTORY) |
| 1433 else: | 1487 else: |
| 1434 default_config_dir = os.path.join(os.path.expanduser('~'), | 1488 default_config_dir = os.path.join(os.path.expanduser('~'), |
| 1435 '.config', | 1489 '.config', |
| 1436 _CLOUDSDK_CONFIG_DIRECTORY) | 1490 _CLOUDSDK_CONFIG_DIRECTORY) |
| 1437 | 1491 |
| 1438 return os.path.join(default_config_dir, WELL_KNOWN_CREDENTIALS_FILE) | 1492 return os.path.join(default_config_dir, _WELL_KNOWN_CREDENTIALS_FILE) |
| 1439 | 1493 |
| 1440 | 1494 |
| 1441 def _get_application_default_credential_from_file(filename): | 1495 def _get_application_default_credential_from_file(filename): |
| 1442 """Build the Application Default Credentials from file.""" | 1496 """Build the Application Default Credentials from file.""" |
| 1443 | |
| 1444 from oauth2client import service_account | |
| 1445 | |
| 1446 # read the credentials from the file | 1497 # read the credentials from the file |
| 1447 with open(filename) as file_obj: | 1498 with open(filename) as file_obj: |
| 1448 client_credentials = json.load(file_obj) | 1499 client_credentials = json.load(file_obj) |
| 1449 | 1500 |
| 1450 credentials_type = client_credentials.get('type') | 1501 credentials_type = client_credentials.get('type') |
| 1451 if credentials_type == AUTHORIZED_USER: | 1502 if credentials_type == AUTHORIZED_USER: |
| 1452 required_fields = set(['client_id', 'client_secret', 'refresh_token']) | 1503 required_fields = set(['client_id', 'client_secret', 'refresh_token']) |
| 1453 elif credentials_type == SERVICE_ACCOUNT: | 1504 elif credentials_type == SERVICE_ACCOUNT: |
| 1454 required_fields = set(['client_id', 'client_email', 'private_key_id', | 1505 required_fields = set(['client_id', 'client_email', 'private_key_id', |
| 1455 'private_key']) | 1506 'private_key']) |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1466 if client_credentials['type'] == AUTHORIZED_USER: | 1517 if client_credentials['type'] == AUTHORIZED_USER: |
| 1467 return GoogleCredentials( | 1518 return GoogleCredentials( |
| 1468 access_token=None, | 1519 access_token=None, |
| 1469 client_id=client_credentials['client_id'], | 1520 client_id=client_credentials['client_id'], |
| 1470 client_secret=client_credentials['client_secret'], | 1521 client_secret=client_credentials['client_secret'], |
| 1471 refresh_token=client_credentials['refresh_token'], | 1522 refresh_token=client_credentials['refresh_token'], |
| 1472 token_expiry=None, | 1523 token_expiry=None, |
| 1473 token_uri=GOOGLE_TOKEN_URI, | 1524 token_uri=GOOGLE_TOKEN_URI, |
| 1474 user_agent='Python client library') | 1525 user_agent='Python client library') |
| 1475 else: # client_credentials['type'] == SERVICE_ACCOUNT | 1526 else: # client_credentials['type'] == SERVICE_ACCOUNT |
| 1476 return service_account._ServiceAccountCredentials( | 1527 from oauth2client.service_account import ServiceAccountCredentials |
| 1477 service_account_id=client_credentials['client_id'], | 1528 return ServiceAccountCredentials.from_json_keyfile_dict( |
| 1478 service_account_email=client_credentials['client_email'], | 1529 client_credentials) |
| 1479 private_key_id=client_credentials['private_key_id'], | |
| 1480 private_key_pkcs8_text=client_credentials['private_key'], | |
| 1481 scopes=[]) | |
| 1482 | 1530 |
| 1483 | 1531 |
| 1484 def _raise_exception_for_missing_fields(missing_fields): | 1532 def _raise_exception_for_missing_fields(missing_fields): |
| 1485 raise ApplicationDefaultCredentialsError( | 1533 raise ApplicationDefaultCredentialsError( |
| 1486 'The following field(s) must be defined: ' + ', '.join(missing_fields)) | 1534 'The following field(s) must be defined: ' + ', '.join(missing_fields)) |
| 1487 | 1535 |
| 1488 | 1536 |
| 1489 def _raise_exception_for_reading_json(credential_file, | 1537 def _raise_exception_for_reading_json(credential_file, |
| 1490 extra_help, | 1538 extra_help, |
| 1491 error): | 1539 error): |
| 1492 raise ApplicationDefaultCredentialsError( | 1540 raise ApplicationDefaultCredentialsError( |
| 1493 'An error was encountered while reading json file: ' + | 1541 'An error was encountered while reading json file: ' + |
| 1494 credential_file + extra_help + ': ' + str(error)) | 1542 credential_file + extra_help + ': ' + str(error)) |
| 1495 | 1543 |
| 1496 | 1544 |
| 1497 def _get_application_default_credential_GAE(): | 1545 def _get_application_default_credential_GAE(): |
| 1498 from oauth2client.appengine import AppAssertionCredentials | 1546 from oauth2client.contrib.appengine import AppAssertionCredentials |
| 1499 | 1547 |
| 1500 return AppAssertionCredentials([]) | 1548 return AppAssertionCredentials([]) |
| 1501 | 1549 |
| 1502 | 1550 |
| 1503 def _get_application_default_credential_GCE(): | 1551 def _get_application_default_credential_GCE(): |
| 1504 from oauth2client.gce import AppAssertionCredentials | 1552 from oauth2client.contrib.gce import AppAssertionCredentials |
| 1505 | 1553 |
| 1506 return AppAssertionCredentials([]) | 1554 return AppAssertionCredentials() |
| 1507 | 1555 |
| 1508 | 1556 |
| 1509 class AssertionCredentials(GoogleCredentials): | 1557 class AssertionCredentials(GoogleCredentials): |
| 1510 """Abstract Credentials object used for OAuth 2.0 assertion grants. | 1558 """Abstract Credentials object used for OAuth 2.0 assertion grants. |
| 1511 | 1559 |
| 1512 This credential does not require a flow to instantiate because it | 1560 This credential does not require a flow to instantiate because it |
| 1513 represents a two legged flow, and therefore has all of the required | 1561 represents a two legged flow, and therefore has all of the required |
| 1514 information to generate and refresh its own access tokens. It must | 1562 information to generate and refresh its own access tokens. It must |
| 1515 be subclassed to generate the appropriate assertion string. | 1563 be subclassed to generate the appropriate assertion string. |
| 1516 | 1564 |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1562 def _revoke(self, http_request): | 1610 def _revoke(self, http_request): |
| 1563 """Revokes the access_token and deletes the store if available. | 1611 """Revokes the access_token and deletes the store if available. |
| 1564 | 1612 |
| 1565 Args: | 1613 Args: |
| 1566 http_request: callable, a callable that matches the method | 1614 http_request: callable, a callable that matches the method |
| 1567 signature of httplib2.Http.request, used to make the | 1615 signature of httplib2.Http.request, used to make the |
| 1568 revoke request. | 1616 revoke request. |
| 1569 """ | 1617 """ |
| 1570 self._do_revoke(http_request, self.access_token) | 1618 self._do_revoke(http_request, self.access_token) |
| 1571 | 1619 |
| 1620 def sign_blob(self, blob): |
| 1621 """Cryptographically sign a blob (of bytes). |
| 1622 |
| 1623 Args: |
| 1624 blob: bytes, Message to be signed. |
| 1625 |
| 1626 Returns: |
| 1627 tuple, A pair of the private key ID used to sign the blob and |
| 1628 the signed contents. |
| 1629 """ |
| 1630 raise NotImplementedError('This method is abstract.') |
| 1631 |
| 1572 | 1632 |
| 1573 def _RequireCryptoOrDie(): | 1633 def _RequireCryptoOrDie(): |
| 1574 """Ensure we have a crypto library, or throw CryptoUnavailableError. | 1634 """Ensure we have a crypto library, or throw CryptoUnavailableError. |
| 1575 | 1635 |
| 1576 The oauth2client.crypt module requires either PyCrypto or PyOpenSSL | 1636 The oauth2client.crypt module requires either PyCrypto or PyOpenSSL |
| 1577 to be available in order to function, but these are optional | 1637 to be available in order to function, but these are optional |
| 1578 dependencies. | 1638 dependencies. |
| 1579 """ | 1639 """ |
| 1580 if not HAS_CRYPTO: | 1640 if not HAS_CRYPTO: |
| 1581 raise CryptoUnavailableError('No crypto library available') | 1641 raise CryptoUnavailableError('No crypto library available') |
| 1582 | 1642 |
| 1583 | 1643 |
| 1584 class SignedJwtAssertionCredentials(AssertionCredentials): | |
| 1585 """Credentials object used for OAuth 2.0 Signed JWT assertion grants. | |
| 1586 | |
| 1587 This credential does not require a flow to instantiate because it | |
| 1588 represents a two legged flow, and therefore has all of the required | |
| 1589 information to generate and refresh its own access tokens. | |
| 1590 | |
| 1591 SignedJwtAssertionCredentials requires either PyOpenSSL, or PyCrypto | |
| 1592 2.6 or later. For App Engine you may also consider using | |
| 1593 AppAssertionCredentials. | |
| 1594 """ | |
| 1595 | |
| 1596 MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds | |
| 1597 | |
| 1598 @util.positional(4) | |
| 1599 def __init__(self, | |
| 1600 service_account_name, | |
| 1601 private_key, | |
| 1602 scope, | |
| 1603 private_key_password='notasecret', | |
| 1604 user_agent=None, | |
| 1605 token_uri=GOOGLE_TOKEN_URI, | |
| 1606 revoke_uri=GOOGLE_REVOKE_URI, | |
| 1607 **kwargs): | |
| 1608 """Constructor for SignedJwtAssertionCredentials. | |
| 1609 | |
| 1610 Args: | |
| 1611 service_account_name: string, id for account, usually an email | |
| 1612 address. | |
| 1613 private_key: string or bytes, private key in PKCS12 or PEM format. | |
| 1614 scope: string or iterable of strings, scope(s) of the credentials | |
| 1615 being requested. | |
| 1616 private_key_password: string, password for private_key, unused if | |
| 1617 private_key is in PEM format. | |
| 1618 user_agent: string, HTTP User-Agent to provide for this | |
| 1619 application. | |
| 1620 token_uri: string, URI for token endpoint. For convenience defaults | |
| 1621 to Google's endpoints but any OAuth 2.0 provider can be | |
| 1622 used. | |
| 1623 revoke_uri: string, URI for revoke endpoint. | |
| 1624 kwargs: kwargs, Additional parameters to add to the JWT token, for | |
| 1625 example sub=joe@xample.org. | |
| 1626 | |
| 1627 Raises: | |
| 1628 CryptoUnavailableError if no crypto library is available. | |
| 1629 """ | |
| 1630 _RequireCryptoOrDie() | |
| 1631 super(SignedJwtAssertionCredentials, self).__init__( | |
| 1632 None, | |
| 1633 user_agent=user_agent, | |
| 1634 token_uri=token_uri, | |
| 1635 revoke_uri=revoke_uri, | |
| 1636 ) | |
| 1637 | |
| 1638 self.scope = util.scopes_to_string(scope) | |
| 1639 | |
| 1640 # Keep base64 encoded so it can be stored in JSON. | |
| 1641 self.private_key = base64.b64encode(_to_bytes(private_key)) | |
| 1642 self.private_key_password = private_key_password | |
| 1643 self.service_account_name = service_account_name | |
| 1644 self.kwargs = kwargs | |
| 1645 | |
| 1646 @classmethod | |
| 1647 def from_json(cls, s): | |
| 1648 data = json.loads(_from_bytes(s)) | |
| 1649 retval = SignedJwtAssertionCredentials( | |
| 1650 data['service_account_name'], | |
| 1651 base64.b64decode(data['private_key']), | |
| 1652 data['scope'], | |
| 1653 private_key_password=data['private_key_password'], | |
| 1654 user_agent=data['user_agent'], | |
| 1655 token_uri=data['token_uri'], | |
| 1656 **data['kwargs'] | |
| 1657 ) | |
| 1658 retval.invalid = data['invalid'] | |
| 1659 retval.access_token = data['access_token'] | |
| 1660 return retval | |
| 1661 | |
| 1662 def _generate_assertion(self): | |
| 1663 """Generate the assertion that will be used in the request.""" | |
| 1664 now = int(time.time()) | |
| 1665 payload = { | |
| 1666 'aud': self.token_uri, | |
| 1667 'scope': self.scope, | |
| 1668 'iat': now, | |
| 1669 'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS, | |
| 1670 'iss': self.service_account_name | |
| 1671 } | |
| 1672 payload.update(self.kwargs) | |
| 1673 logger.debug(str(payload)) | |
| 1674 | |
| 1675 private_key = base64.b64decode(self.private_key) | |
| 1676 return crypt.make_signed_jwt(crypt.Signer.from_string( | |
| 1677 private_key, self.private_key_password), payload) | |
| 1678 | |
| 1679 # Only used in verify_id_token(), which is always calling to the same URI | 1644 # Only used in verify_id_token(), which is always calling to the same URI |
| 1680 # for the certs. | 1645 # for the certs. |
| 1681 _cached_http = httplib2.Http(MemoryCache()) | 1646 _cached_http = httplib2.Http(MemoryCache()) |
| 1682 | 1647 |
| 1683 | 1648 |
| 1684 @util.positional(2) | 1649 @util.positional(2) |
| 1685 def verify_id_token(id_token, audience, http=None, | 1650 def verify_id_token(id_token, audience, http=None, |
| 1686 cert_uri=ID_TOKEN_VERIFICATION_CERTS): | 1651 cert_uri=ID_TOKEN_VERIFICATION_CERTS): |
| 1687 """Verifies a signed JWT id_token. | 1652 """Verifies a signed JWT id_token. |
| 1688 | 1653 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 1702 | 1667 |
| 1703 Raises: | 1668 Raises: |
| 1704 oauth2client.crypt.AppIdentityError: if the JWT fails to verify. | 1669 oauth2client.crypt.AppIdentityError: if the JWT fails to verify. |
| 1705 CryptoUnavailableError: if no crypto library is available. | 1670 CryptoUnavailableError: if no crypto library is available. |
| 1706 """ | 1671 """ |
| 1707 _RequireCryptoOrDie() | 1672 _RequireCryptoOrDie() |
| 1708 if http is None: | 1673 if http is None: |
| 1709 http = _cached_http | 1674 http = _cached_http |
| 1710 | 1675 |
| 1711 resp, content = http.request(cert_uri) | 1676 resp, content = http.request(cert_uri) |
| 1712 if resp.status == 200: | 1677 if resp.status == http_client.OK: |
| 1713 certs = json.loads(_from_bytes(content)) | 1678 certs = json.loads(_from_bytes(content)) |
| 1714 return crypt.verify_signed_jwt_with_certs(id_token, certs, audience) | 1679 return crypt.verify_signed_jwt_with_certs(id_token, certs, audience) |
| 1715 else: | 1680 else: |
| 1716 raise VerifyJwtTokenError('Status code: %d' % resp.status) | 1681 raise VerifyJwtTokenError('Status code: %d' % resp.status) |
| 1717 | 1682 |
| 1718 | 1683 |
| 1719 def _extract_id_token(id_token): | 1684 def _extract_id_token(id_token): |
| 1720 """Extract the JSON payload from a JWT. | 1685 """Extract the JSON payload from a JWT. |
| 1721 | 1686 |
| 1722 Does the extraction w/o checking the signature. | 1687 Does the extraction w/o checking the signature. |
| (...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2047 | 2012 |
| 2048 if self.user_agent is not None: | 2013 if self.user_agent is not None: |
| 2049 headers['user-agent'] = self.user_agent | 2014 headers['user-agent'] = self.user_agent |
| 2050 | 2015 |
| 2051 if http is None: | 2016 if http is None: |
| 2052 http = httplib2.Http() | 2017 http = httplib2.Http() |
| 2053 | 2018 |
| 2054 resp, content = http.request(self.device_uri, method='POST', body=body, | 2019 resp, content = http.request(self.device_uri, method='POST', body=body, |
| 2055 headers=headers) | 2020 headers=headers) |
| 2056 content = _from_bytes(content) | 2021 content = _from_bytes(content) |
| 2057 if resp.status == 200: | 2022 if resp.status == http_client.OK: |
| 2058 try: | 2023 try: |
| 2059 flow_info = json.loads(content) | 2024 flow_info = json.loads(content) |
| 2060 except ValueError as e: | 2025 except ValueError as e: |
| 2061 raise OAuth2DeviceCodeError( | 2026 raise OAuth2DeviceCodeError( |
| 2062 'Could not parse server response as JSON: "%s", ' | 2027 'Could not parse server response as JSON: "%s", ' |
| 2063 'error: "%s"' % (content, e)) | 2028 'error: "%s"' % (content, e)) |
| 2064 return DeviceFlowInfo.FromResponse(flow_info) | 2029 return DeviceFlowInfo.FromResponse(flow_info) |
| 2065 else: | 2030 else: |
| 2066 error_msg = 'Invalid response %s.' % resp.status | 2031 error_msg = 'Invalid response %s.' % resp.status |
| 2067 try: | 2032 try: |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2130 headers['Authorization'] = self.authorization_header | 2095 headers['Authorization'] = self.authorization_header |
| 2131 if self.user_agent is not None: | 2096 if self.user_agent is not None: |
| 2132 headers['user-agent'] = self.user_agent | 2097 headers['user-agent'] = self.user_agent |
| 2133 | 2098 |
| 2134 if http is None: | 2099 if http is None: |
| 2135 http = httplib2.Http() | 2100 http = httplib2.Http() |
| 2136 | 2101 |
| 2137 resp, content = http.request(self.token_uri, method='POST', body=body, | 2102 resp, content = http.request(self.token_uri, method='POST', body=body, |
| 2138 headers=headers) | 2103 headers=headers) |
| 2139 d = _parse_exchange_token_response(content) | 2104 d = _parse_exchange_token_response(content) |
| 2140 if resp.status == 200 and 'access_token' in d: | 2105 if resp.status == http_client.OK and 'access_token' in d: |
| 2141 access_token = d['access_token'] | 2106 access_token = d['access_token'] |
| 2142 refresh_token = d.get('refresh_token', None) | 2107 refresh_token = d.get('refresh_token', None) |
| 2143 if not refresh_token: | 2108 if not refresh_token: |
| 2144 logger.info( | 2109 logger.info( |
| 2145 'Received token response with no refresh_token. Consider ' | 2110 'Received token response with no refresh_token. Consider ' |
| 2146 "reauthenticating with approval_prompt='force'.") | 2111 "reauthenticating with approval_prompt='force'.") |
| 2147 token_expiry = None | 2112 token_expiry = None |
| 2148 if 'expires_in' in d: | 2113 if 'expires_in' in d: |
| 2149 token_expiry = ( | 2114 delta = datetime.timedelta(seconds=int(d['expires_in'])) |
| 2150 datetime.datetime.utcnow() + | 2115 token_expiry = delta + _UTCNOW() |
| 2151 datetime.timedelta(seconds=int(d['expires_in']))) | |
| 2152 | 2116 |
| 2153 extracted_id_token = None | 2117 extracted_id_token = None |
| 2154 if 'id_token' in d: | 2118 if 'id_token' in d: |
| 2155 extracted_id_token = _extract_id_token(d['id_token']) | 2119 extracted_id_token = _extract_id_token(d['id_token']) |
| 2156 | 2120 |
| 2157 logger.info('Successfully retrieved access token') | 2121 logger.info('Successfully retrieved access token') |
| 2158 return OAuth2Credentials( | 2122 return OAuth2Credentials( |
| 2159 access_token, self.client_id, self.client_secret, | 2123 access_token, self.client_id, self.client_secret, |
| 2160 refresh_token, token_expiry, self.token_uri, self.user_agent, | 2124 refresh_token, token_expiry, self.token_uri, self.user_agent, |
| 2161 revoke_uri=self.revoke_uri, id_token=extracted_id_token, | 2125 revoke_uri=self.revoke_uri, id_token=extracted_id_token, |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2233 scope, **constructor_kwargs) | 2197 scope, **constructor_kwargs) |
| 2234 | 2198 |
| 2235 except clientsecrets.InvalidClientSecretsError: | 2199 except clientsecrets.InvalidClientSecretsError: |
| 2236 if message: | 2200 if message: |
| 2237 sys.exit(message) | 2201 sys.exit(message) |
| 2238 else: | 2202 else: |
| 2239 raise | 2203 raise |
| 2240 else: | 2204 else: |
| 2241 raise UnknownClientSecretsFlowError( | 2205 raise UnknownClientSecretsFlowError( |
| 2242 'This OAuth 2.0 flow is unsupported: %r' % client_type) | 2206 'This OAuth 2.0 flow is unsupported: %r' % client_type) |
| OLD | NEW |