Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Google OAuth2 related functions.""" | 5 """Google OAuth2 related functions.""" |
| 6 | 6 |
| 7 import BaseHTTPServer | 7 import BaseHTTPServer |
| 8 import collections | 8 import collections |
| 9 import datetime | 9 import datetime |
| 10 import functools | 10 import functools |
| 11 import hashlib | |
| 11 import json | 12 import json |
| 12 import logging | 13 import logging |
| 13 import optparse | 14 import optparse |
| 14 import os | 15 import os |
| 15 import socket | 16 import socket |
| 16 import sys | 17 import sys |
| 17 import threading | 18 import threading |
| 18 import urllib | 19 import urllib |
| 19 import urlparse | 20 import urlparse |
| 20 import webbrowser | 21 import webbrowser |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 55 os.path.expanduser('~'), '.depot_tools_oauth2_tokens') | 56 os.path.expanduser('~'), '.depot_tools_oauth2_tokens') |
| 56 | 57 |
| 57 | 58 |
| 58 # Authentication configuration extracted from command line options. | 59 # Authentication configuration extracted from command line options. |
| 59 # See doc string for 'make_auth_config' for meaning of fields. | 60 # See doc string for 'make_auth_config' for meaning of fields. |
| 60 AuthConfig = collections.namedtuple('AuthConfig', [ | 61 AuthConfig = collections.namedtuple('AuthConfig', [ |
| 61 'use_oauth2', # deprecated, will be always True | 62 'use_oauth2', # deprecated, will be always True |
| 62 'save_cookies', # deprecated, will be removed | 63 'save_cookies', # deprecated, will be removed |
| 63 'use_local_webserver', | 64 'use_local_webserver', |
| 64 'webserver_port', | 65 'webserver_port', |
| 66 'refresh_token_json', | |
| 65 ]) | 67 ]) |
| 66 | 68 |
| 67 | 69 |
| 68 # OAuth access token with its expiration time (UTC datetime or None if unknown). | 70 # OAuth access token with its expiration time (UTC datetime or None if unknown). |
| 69 AccessToken = collections.namedtuple('AccessToken', [ | 71 AccessToken = collections.namedtuple('AccessToken', [ |
| 70 'token', | 72 'token', |
| 71 'expires_at', | 73 'expires_at', |
| 72 ]) | 74 ]) |
| 73 | 75 |
| 74 | 76 |
| 77 # Refresh token passed via --auth-refresh-token-json. | |
| 78 RefreshToken = collections.namedtuple('RefreshToken', [ | |
| 79 'client_id', | |
| 80 'client_secret', | |
| 81 'refresh_token', | |
| 82 ]) | |
| 83 | |
| 84 | |
| 75 class AuthenticationError(Exception): | 85 class AuthenticationError(Exception): |
| 76 """Raised on errors related to authentication.""" | 86 """Raised on errors related to authentication.""" |
| 77 | 87 |
| 78 | 88 |
| 79 class LoginRequiredError(AuthenticationError): | 89 class LoginRequiredError(AuthenticationError): |
| 80 """Interaction with the user is required to authenticate.""" | 90 """Interaction with the user is required to authenticate.""" |
| 81 | 91 |
| 82 def __init__(self, token_cache_key): | 92 def __init__(self, token_cache_key): |
| 83 # HACK(vadimsh): It is assumed here that the token cache key is a hostname. | 93 # HACK(vadimsh): It is assumed here that the token cache key is a hostname. |
| 84 msg = ( | 94 msg = ( |
| 85 'You are not logged in. Please login first by running:\n' | 95 'You are not logged in. Please login first by running:\n' |
| 86 ' depot-tools-auth login %s' % token_cache_key) | 96 ' depot-tools-auth login %s' % token_cache_key) |
| 87 super(LoginRequiredError, self).__init__(msg) | 97 super(LoginRequiredError, self).__init__(msg) |
| 88 | 98 |
| 89 | 99 |
| 90 def make_auth_config( | 100 def make_auth_config( |
| 91 use_oauth2=None, | 101 use_oauth2=None, |
| 92 save_cookies=None, | 102 save_cookies=None, |
| 93 use_local_webserver=None, | 103 use_local_webserver=None, |
| 94 webserver_port=None): | 104 webserver_port=None, |
| 105 refresh_token_json=None): | |
| 95 """Returns new instance of AuthConfig. | 106 """Returns new instance of AuthConfig. |
| 96 | 107 |
| 97 If some config option is None, it will be set to a reasonable default value. | 108 If some config option is None, it will be set to a reasonable default value. |
| 98 This function also acts as an authoritative place for default values of | 109 This function also acts as an authoritative place for default values of |
| 99 corresponding command line options. | 110 corresponding command line options. |
| 100 """ | 111 """ |
| 101 default = lambda val, d: val if val is not None else d | 112 default = lambda val, d: val if val is not None else d |
| 102 return AuthConfig( | 113 return AuthConfig( |
| 103 default(use_oauth2, _should_use_oauth2()), | 114 default(use_oauth2, _should_use_oauth2()), |
| 104 default(save_cookies, True), | 115 default(save_cookies, True), |
| 105 default(use_local_webserver, not _is_headless()), | 116 default(use_local_webserver, not _is_headless()), |
| 106 default(webserver_port, 8090)) | 117 default(webserver_port, 8090), |
| 118 default(refresh_token_json, '')) | |
| 107 | 119 |
| 108 | 120 |
| 109 def add_auth_options(parser, default_config=None): | 121 def add_auth_options(parser, default_config=None): |
| 110 """Appends OAuth related options to OptionParser.""" | 122 """Appends OAuth related options to OptionParser.""" |
| 111 default_config = default_config or make_auth_config() | 123 default_config = default_config or make_auth_config() |
| 112 parser.auth_group = optparse.OptionGroup(parser, 'Auth options') | 124 parser.auth_group = optparse.OptionGroup(parser, 'Auth options') |
| 113 parser.add_option_group(parser.auth_group) | 125 parser.add_option_group(parser.auth_group) |
| 114 | 126 |
| 115 # OAuth2 vs password switch. | 127 # OAuth2 vs password switch. |
| 116 auth_default = 'use OAuth2' if default_config.use_oauth2 else 'use password' | 128 auth_default = 'use OAuth2' if default_config.use_oauth2 else 'use password' |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 141 action='store_false', | 153 action='store_false', |
| 142 dest='use_local_webserver', | 154 dest='use_local_webserver', |
| 143 default=default_config.use_local_webserver, | 155 default=default_config.use_local_webserver, |
| 144 help='Do not run a local web server when performing OAuth2 login flow.') | 156 help='Do not run a local web server when performing OAuth2 login flow.') |
| 145 parser.auth_group.add_option( | 157 parser.auth_group.add_option( |
| 146 '--auth-host-port', | 158 '--auth-host-port', |
| 147 type=int, | 159 type=int, |
| 148 default=default_config.webserver_port, | 160 default=default_config.webserver_port, |
| 149 help='Port a local web server should listen on. Used only if ' | 161 help='Port a local web server should listen on. Used only if ' |
| 150 '--auth-no-local-webserver is not set. [default: %default]') | 162 '--auth-no-local-webserver is not set. [default: %default]') |
| 163 parser.auth_group.add_option( | |
| 164 '--auth-refresh-token-json', | |
| 165 default=default_config.refresh_token_json, | |
| 166 help='Path to a JSON file with role account refresh token to use.') | |
| 151 | 167 |
| 152 | 168 |
| 153 def extract_auth_config_from_options(options): | 169 def extract_auth_config_from_options(options): |
| 154 """Given OptionParser parsed options, extracts AuthConfig from it. | 170 """Given OptionParser parsed options, extracts AuthConfig from it. |
| 155 | 171 |
| 156 OptionParser should be populated with auth options by 'add_auth_options'. | 172 OptionParser should be populated with auth options by 'add_auth_options'. |
| 157 """ | 173 """ |
| 158 return make_auth_config( | 174 return make_auth_config( |
| 159 use_oauth2=options.use_oauth2, | 175 use_oauth2=options.use_oauth2, |
| 160 save_cookies=False if options.use_oauth2 else options.save_cookies, | 176 save_cookies=False if options.use_oauth2 else options.save_cookies, |
| 161 use_local_webserver=options.use_local_webserver, | 177 use_local_webserver=options.use_local_webserver, |
| 162 webserver_port=options.auth_host_port) | 178 webserver_port=options.auth_host_port, |
| 179 refresh_token_json=options.auth_refresh_token_json) | |
| 163 | 180 |
| 164 | 181 |
| 165 def auth_config_to_command_options(auth_config): | 182 def auth_config_to_command_options(auth_config): |
| 166 """AuthConfig -> list of strings with command line options. | 183 """AuthConfig -> list of strings with command line options. |
| 167 | 184 |
| 168 Omits options that are set to default values. | 185 Omits options that are set to default values. |
| 169 """ | 186 """ |
| 170 if not auth_config: | 187 if not auth_config: |
| 171 return [] | 188 return [] |
| 172 defaults = make_auth_config() | 189 defaults = make_auth_config() |
| 173 opts = [] | 190 opts = [] |
| 174 if auth_config.use_oauth2 != defaults.use_oauth2: | 191 if auth_config.use_oauth2 != defaults.use_oauth2: |
| 175 opts.append('--oauth2' if auth_config.use_oauth2 else '--no-oauth2') | 192 opts.append('--oauth2' if auth_config.use_oauth2 else '--no-oauth2') |
| 176 if auth_config.save_cookies != auth_config.save_cookies: | 193 if auth_config.save_cookies != auth_config.save_cookies: |
| 177 if not auth_config.save_cookies: | 194 if not auth_config.save_cookies: |
| 178 opts.append('--no-cookies') | 195 opts.append('--no-cookies') |
| 179 if auth_config.use_local_webserver != defaults.use_local_webserver: | 196 if auth_config.use_local_webserver != defaults.use_local_webserver: |
| 180 if not auth_config.use_local_webserver: | 197 if not auth_config.use_local_webserver: |
| 181 opts.append('--auth-no-local-webserver') | 198 opts.append('--auth-no-local-webserver') |
| 182 if auth_config.webserver_port != defaults.webserver_port: | 199 if auth_config.webserver_port != defaults.webserver_port: |
| 183 opts.extend(['--auth-host-port', str(auth_config.webserver_port)]) | 200 opts.extend(['--auth-host-port', str(auth_config.webserver_port)]) |
| 201 if auth_config.refresh_token_json != defaults.refresh_token_json: | |
| 202 opts.extend([ | |
| 203 '--auth-refresh-token-json', str(auth_config.refresh_token_json)]) | |
| 184 return opts | 204 return opts |
| 185 | 205 |
| 186 | 206 |
| 187 def get_authenticator_for_host(hostname, config): | 207 def get_authenticator_for_host(hostname, config): |
| 188 """Returns Authenticator instance to access given host. | 208 """Returns Authenticator instance to access given host. |
| 189 | 209 |
| 190 Args: | 210 Args: |
| 191 hostname: a naked hostname or http(s)://<hostname>[/] URL. Used to derive | 211 hostname: a naked hostname or http(s)://<hostname>[/] URL. Used to derive |
| 192 a cache key for token cache. | 212 a cache key for token cache. |
| 193 config: AuthConfig instance. | 213 config: AuthConfig instance. |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 215 config: AuthConfig object that holds authentication configuration. | 235 config: AuthConfig object that holds authentication configuration. |
| 216 """ | 236 """ |
| 217 | 237 |
| 218 def __init__(self, token_cache_key, config): | 238 def __init__(self, token_cache_key, config): |
| 219 assert isinstance(config, AuthConfig) | 239 assert isinstance(config, AuthConfig) |
| 220 assert config.use_oauth2 | 240 assert config.use_oauth2 |
| 221 self._access_token = None | 241 self._access_token = None |
| 222 self._config = config | 242 self._config = config |
| 223 self._lock = threading.Lock() | 243 self._lock = threading.Lock() |
| 224 self._token_cache_key = token_cache_key | 244 self._token_cache_key = token_cache_key |
| 245 self._external_token = None | |
| 246 if config.refresh_token_json: | |
| 247 self._external_token = _read_refresh_token_json(config.refresh_token_json) | |
| 225 | 248 |
| 226 def login(self): | 249 def login(self): |
| 227 """Performs interactive login flow if necessary. | 250 """Performs interactive login flow if necessary. |
| 228 | 251 |
| 229 Raises: | 252 Raises: |
| 230 AuthenticationError on error or if interrupted. | 253 AuthenticationError on error or if interrupted. |
| 231 """ | 254 """ |
| 255 if self._external_token: | |
| 256 raise AuthenticationError( | |
| 257 'Can\'t run login flow when using --auth-refresh-token-json.') | |
| 232 return self.get_access_token( | 258 return self.get_access_token( |
| 233 force_refresh=True, allow_user_interaction=True) | 259 force_refresh=True, allow_user_interaction=True) |
| 234 | 260 |
| 235 def logout(self): | 261 def logout(self): |
| 236 """Revokes the refresh token and deletes it from the cache. | 262 """Revokes the refresh token and deletes it from the cache. |
| 237 | 263 |
| 238 Returns True if actually revoked a token. | 264 Returns True if had some credentials cached. |
| 239 """ | 265 """ |
| 240 revoked = False | |
| 241 with self._lock: | 266 with self._lock: |
| 242 self._access_token = None | 267 self._access_token = None |
| 243 storage = self._get_storage() | 268 storage = self._get_storage() |
| 244 credentials = storage.get() | 269 credentials = storage.get() |
| 245 if credentials: | 270 if credentials and credentials.refresh_token and credentials.revoke_uri: |
| 246 credentials.revoke(httplib2.Http()) | 271 try: |
| 247 revoked = True | 272 credentials.revoke(httplib2.Http()) |
| 273 except client.TokenRevokeError as e: | |
| 274 logging.warning('Failed to revoke refresh token: %s', e) | |
|
M-A Ruel
2015/04/17 01:49:50
you don't set credentials to None in else / except
Vadim Sh.
2015/04/17 02:38:07
If cached refresh token is invalid (for example re
| |
| 248 storage.delete() | 275 storage.delete() |
| 249 return revoked | 276 return bool(credentials) |
| 250 | 277 |
| 251 def has_cached_credentials(self): | 278 def has_cached_credentials(self): |
| 252 """Returns True if long term credentials (refresh token) are in cache. | 279 """Returns True if long term credentials (refresh token) are in cache. |
| 253 | 280 |
| 254 Doesn't make network calls. | 281 Doesn't make network calls. |
| 255 | 282 |
| 256 If returns False, get_access_token() later will ask for interactive login by | 283 If returns False, get_access_token() later will ask for interactive login by |
| 257 raising LoginRequiredError. | 284 raising LoginRequiredError. |
| 258 | 285 |
| 259 If returns True, most probably get_access_token() won't ask for interactive | 286 If returns True, most probably get_access_token() won't ask for interactive |
| 260 login, though it is not guaranteed, since cached token can be already | 287 login, though it is not guaranteed, since cached token can be already |
| 261 revoked and there's no way to figure this out without actually trying to use | 288 revoked and there's no way to figure this out without actually trying to use |
| 262 it. | 289 it. |
| 263 """ | 290 """ |
| 264 with self._lock: | 291 with self._lock: |
| 265 credentials = self._get_storage().get() | 292 return bool(self._get_cached_credentials()) |
| 266 return credentials and not credentials.invalid | |
| 267 | 293 |
| 268 def get_access_token(self, force_refresh=False, allow_user_interaction=False): | 294 def get_access_token(self, force_refresh=False, allow_user_interaction=False): |
| 269 """Returns AccessToken, refreshing it if necessary. | 295 """Returns AccessToken, refreshing it if necessary. |
| 270 | 296 |
| 271 Args: | 297 Args: |
| 272 force_refresh: forcefully refresh access token even if it is not expired. | 298 force_refresh: forcefully refresh access token even if it is not expired. |
| 273 allow_user_interaction: True to enable blocking for user input if needed. | 299 allow_user_interaction: True to enable blocking for user input if needed. |
| 274 | 300 |
| 275 Raises: | 301 Raises: |
| 276 AuthenticationError on error or if authentication flow was interrupted. | 302 AuthenticationError on error or if authentication flow was interrupted. |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 341 else: | 367 else: |
| 342 return (resp, content) | 368 return (resp, content) |
| 343 | 369 |
| 344 http.request = new_request | 370 http.request = new_request |
| 345 return http | 371 return http |
| 346 | 372 |
| 347 ## Private methods. | 373 ## Private methods. |
| 348 | 374 |
| 349 def _get_storage(self): | 375 def _get_storage(self): |
| 350 """Returns oauth2client.Storage with cached tokens.""" | 376 """Returns oauth2client.Storage with cached tokens.""" |
| 377 # Do not mix cache keys for different externally provided tokens. | |
| 378 if self._external_token: | |
| 379 token_hash = hashlib.sha1(self._external_token.refresh_token).hexdigest() | |
| 380 cache_key = '%s:refresh_tok:%s' % (self._token_cache_key, token_hash) | |
| 381 else: | |
| 382 cache_key = self._token_cache_key | |
| 351 return multistore_file.get_credential_storage_custom_string_key( | 383 return multistore_file.get_credential_storage_custom_string_key( |
| 352 OAUTH_TOKENS_CACHE, self._token_cache_key) | 384 OAUTH_TOKENS_CACHE, cache_key) |
| 385 | |
| 386 def _get_cached_credentials(self): | |
| 387 """Returns oauth2client.Credentials loaded from storage.""" | |
| 388 storage = self._get_storage() | |
| 389 credentials = storage.get() | |
| 390 | |
| 391 # Is using --auth-refresh-token-json? | |
| 392 if self._external_token: | |
| 393 # Cached credentials are valid and match external token -> use them. It is | |
| 394 # important to reuse credentials from the storage because they contain | |
| 395 # cached access token. | |
| 396 valid = ( | |
| 397 credentials and not credentials.invalid and | |
| 398 credentials.refresh_token == self._external_token.refresh_token and | |
| 399 credentials.client_id == self._external_token.client_id and | |
| 400 credentials.client_secret == self._external_token.client_secret) | |
| 401 if valid: | |
| 402 return credentials | |
| 403 # Construct new credentials from externally provided refresh token, | |
| 404 # associate them with cache storage (so that access_token will be placed | |
| 405 # in the cache later too). | |
| 406 credentials = client.OAuth2Credentials( | |
| 407 access_token=None, | |
| 408 client_id=self._external_token.client_id, | |
| 409 client_secret=self._external_token.client_secret, | |
| 410 refresh_token=self._external_token.refresh_token, | |
| 411 token_expiry=None, | |
| 412 token_uri='https://accounts.google.com/o/oauth2/token', | |
| 413 user_agent=None, | |
| 414 revoke_uri=None) | |
| 415 credentials.set_store(storage) | |
| 416 storage.put(credentials) | |
| 417 return credentials | |
| 418 | |
| 419 # Not using external refresh token -> return whatever is cached. | |
| 420 return credentials if (credentials and not credentials.invalid) else None | |
| 353 | 421 |
| 354 def _load_access_token(self): | 422 def _load_access_token(self): |
| 355 """Returns cached AccessToken if it is not expired yet.""" | 423 """Returns cached AccessToken if it is not expired yet.""" |
| 356 credentials = self._get_storage().get() | 424 creds = self._get_cached_credentials() |
| 357 if not credentials or credentials.invalid: | 425 if not creds or not creds.access_token or creds.access_token_expired: |
| 358 return None | 426 return None |
| 359 if not credentials.access_token or credentials.access_token_expired: | 427 return AccessToken(str(creds.access_token), creds.token_expiry) |
| 360 return None | |
| 361 return AccessToken(str(credentials.access_token), credentials.token_expiry) | |
| 362 | 428 |
| 363 def _create_access_token(self, allow_user_interaction=False): | 429 def _create_access_token(self, allow_user_interaction=False): |
| 364 """Mints and caches a new access token, launching OAuth2 dance if necessary. | 430 """Mints and caches a new access token, launching OAuth2 dance if necessary. |
| 365 | 431 |
| 366 Uses cached refresh token, if present. In that case user interaction is not | 432 Uses cached refresh token, if present. In that case user interaction is not |
| 367 required and function will finish quietly. Otherwise it will launch 3-legged | 433 required and function will finish quietly. Otherwise it will launch 3-legged |
| 368 OAuth2 flow, that needs user interaction. | 434 OAuth2 flow, that needs user interaction. |
| 369 | 435 |
| 370 Args: | 436 Args: |
| 371 allow_user_interaction: if True, allow interaction with the user (e.g. | 437 allow_user_interaction: if True, allow interaction with the user (e.g. |
| 372 reading standard input, or launching a browser). | 438 reading standard input, or launching a browser). |
| 373 | 439 |
| 374 Returns: | 440 Returns: |
| 375 AccessToken. | 441 AccessToken. |
| 376 | 442 |
| 377 Raises: | 443 Raises: |
| 378 AuthenticationError on error or if authentication flow was interrupted. | 444 AuthenticationError on error or if authentication flow was interrupted. |
| 379 LoginRequiredError if user interaction is required, but | 445 LoginRequiredError if user interaction is required, but |
| 380 allow_user_interaction is False. | 446 allow_user_interaction is False. |
| 381 """ | 447 """ |
| 382 storage = self._get_storage() | 448 credentials = self._get_cached_credentials() |
| 383 credentials = None | |
| 384 | 449 |
| 385 # 3-legged flow with (perhaps cached) refresh token. | 450 # 3-legged flow with (perhaps cached) refresh token. |
| 386 credentials = storage.get() | |
| 387 refreshed = False | 451 refreshed = False |
| 388 if credentials and not credentials.invalid: | 452 if credentials and not credentials.invalid: |
| 389 try: | 453 try: |
| 390 credentials.refresh(httplib2.Http()) | 454 credentials.refresh(httplib2.Http()) |
| 391 refreshed = True | 455 refreshed = True |
| 392 except client.Error as err: | 456 except client.Error as err: |
| 393 logging.warning( | 457 logging.warning( |
| 394 'OAuth error during access token refresh: %s. ' | 458 'OAuth error during access token refresh (%s). ' |
| 395 'Attempting a full authentication flow.', err) | 459 'Attempting a full authentication flow.', err) |
| 396 | 460 |
| 397 # Refresh token is missing or invalid, go through the full flow. | 461 # Refresh token is missing or invalid, go through the full flow. |
| 398 if not refreshed: | 462 if not refreshed: |
| 463 # Can't refresh externally provided token. | |
| 464 if self._external_token: | |
| 465 raise AuthenticationError( | |
| 466 'Token provided via --auth-refresh-token-json is no longer valid.') | |
| 399 if not allow_user_interaction: | 467 if not allow_user_interaction: |
| 400 raise LoginRequiredError(self._token_cache_key) | 468 raise LoginRequiredError(self._token_cache_key) |
| 401 credentials = _run_oauth_dance(self._config) | 469 credentials = _run_oauth_dance(self._config) |
| 402 | 470 |
| 403 logging.info( | 471 logging.info( |
| 404 'OAuth access_token refreshed. Expires in %s.', | 472 'OAuth access_token refreshed. Expires in %s.', |
| 405 credentials.token_expiry - datetime.datetime.utcnow()) | 473 credentials.token_expiry - datetime.datetime.utcnow()) |
| 474 storage = self._get_storage() | |
| 406 credentials.set_store(storage) | 475 credentials.set_store(storage) |
| 407 storage.put(credentials) | 476 storage.put(credentials) |
| 408 return AccessToken(str(credentials.access_token), credentials.token_expiry) | 477 return AccessToken(str(credentials.access_token), credentials.token_expiry) |
| 409 | 478 |
| 410 | 479 |
| 411 ## Private functions. | 480 ## Private functions. |
| 412 | 481 |
| 413 | 482 |
| 414 def _should_use_oauth2(): | 483 def _should_use_oauth2(): |
| 415 """Default value for use_oauth2 config option. | 484 """Default value for use_oauth2 config option. |
| 416 | 485 |
| 417 Used to selectively enable OAuth2 by default. | 486 Used to selectively enable OAuth2 by default. |
| 418 """ | 487 """ |
| 419 return os.path.exists(os.path.join(DEPOT_TOOLS_DIR, 'USE_OAUTH2')) | 488 return os.path.exists(os.path.join(DEPOT_TOOLS_DIR, 'USE_OAUTH2')) |
| 420 | 489 |
| 421 | 490 |
| 422 def _is_headless(): | 491 def _is_headless(): |
| 423 """True if machine doesn't seem to have a display.""" | 492 """True if machine doesn't seem to have a display.""" |
| 424 return sys.platform == 'linux2' and not os.environ.get('DISPLAY') | 493 return sys.platform == 'linux2' and not os.environ.get('DISPLAY') |
| 425 | 494 |
| 426 | 495 |
| 496 def _read_refresh_token_json(path): | |
| 497 """Returns RefreshToken by reading it from the JSON file.""" | |
| 498 try: | |
| 499 with open(path, 'r') as f: | |
| 500 data = json.load(f) | |
| 501 return RefreshToken( | |
| 502 client_id=str(data.get('client_id', OAUTH_CLIENT_ID)), | |
| 503 client_secret=str(data.get('client_secret', OAUTH_CLIENT_SECRET)), | |
| 504 refresh_token=str(data['refresh_token'])) | |
| 505 except (IOError, ValueError) as e: | |
| 506 raise AuthenticationError( | |
| 507 'Failed to read refresh token from %s: %s' % (path, e)) | |
| 508 except KeyError as e: | |
| 509 raise AuthenticationError( | |
| 510 'Failed to read refresh token from %s: missing key %s' % (path, e)) | |
| 511 | |
| 512 | |
| 427 def _needs_refresh(access_token): | 513 def _needs_refresh(access_token): |
| 428 """True if AccessToken should be refreshed.""" | 514 """True if AccessToken should be refreshed.""" |
| 429 if access_token.expires_at is not None: | 515 if access_token.expires_at is not None: |
| 430 # Allow 5 min of clock skew between client and backend. | 516 # Allow 5 min of clock skew between client and backend. |
| 431 now = datetime.datetime.utcnow() + datetime.timedelta(seconds=300) | 517 now = datetime.datetime.utcnow() + datetime.timedelta(seconds=300) |
| 432 return now >= access_token.expires_at | 518 return now >= access_token.expires_at |
| 433 # Token without expiration time never expires. | 519 # Token without expiration time never expires. |
| 434 return False | 520 return False |
| 435 | 521 |
| 436 | 522 |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 540 self.end_headers() | 626 self.end_headers() |
| 541 query = self.path.split('?', 1)[-1] | 627 query = self.path.split('?', 1)[-1] |
| 542 query = dict(urlparse.parse_qsl(query)) | 628 query = dict(urlparse.parse_qsl(query)) |
| 543 self.server.query_params = query | 629 self.server.query_params = query |
| 544 self.wfile.write('<html><head><title>Authentication Status</title></head>') | 630 self.wfile.write('<html><head><title>Authentication Status</title></head>') |
| 545 self.wfile.write('<body><p>The authentication flow has completed.</p>') | 631 self.wfile.write('<body><p>The authentication flow has completed.</p>') |
| 546 self.wfile.write('</body></html>') | 632 self.wfile.write('</body></html>') |
| 547 | 633 |
| 548 def log_message(self, _format, *args): | 634 def log_message(self, _format, *args): |
| 549 """Do not log messages to stdout while running as command line program.""" | 635 """Do not log messages to stdout while running as command line program.""" |
| OLD | NEW |