Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(42)

Side by Side Diff: auth.py

Issue 1051143004: Add debug logging to auth.py. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
(...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after
238 def __init__(self, token_cache_key, config): 238 def __init__(self, token_cache_key, config):
239 assert isinstance(config, AuthConfig) 239 assert isinstance(config, AuthConfig)
240 assert config.use_oauth2 240 assert config.use_oauth2
241 self._access_token = None 241 self._access_token = None
242 self._config = config 242 self._config = config
243 self._lock = threading.Lock() 243 self._lock = threading.Lock()
244 self._token_cache_key = token_cache_key 244 self._token_cache_key = token_cache_key
245 self._external_token = None 245 self._external_token = None
246 if config.refresh_token_json: 246 if config.refresh_token_json:
247 self._external_token = _read_refresh_token_json(config.refresh_token_json) 247 self._external_token = _read_refresh_token_json(config.refresh_token_json)
248 logging.debug('Using auth config %r', config)
248 249
249 def login(self): 250 def login(self):
250 """Performs interactive login flow if necessary. 251 """Performs interactive login flow if necessary.
251 252
252 Raises: 253 Raises:
253 AuthenticationError on error or if interrupted. 254 AuthenticationError on error or if interrupted.
254 """ 255 """
255 if self._external_token: 256 if self._external_token:
256 raise AuthenticationError( 257 raise AuthenticationError(
257 'Can\'t run login flow when using --auth-refresh-token-json.') 258 'Can\'t run login flow when using --auth-refresh-token-json.')
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
299 force_refresh: forcefully refresh access token even if it is not expired. 300 force_refresh: forcefully refresh access token even if it is not expired.
300 allow_user_interaction: True to enable blocking for user input if needed. 301 allow_user_interaction: True to enable blocking for user input if needed.
301 302
302 Raises: 303 Raises:
303 AuthenticationError on error or if authentication flow was interrupted. 304 AuthenticationError on error or if authentication flow was interrupted.
304 LoginRequiredError if user interaction is required, but 305 LoginRequiredError if user interaction is required, but
305 allow_user_interaction is False. 306 allow_user_interaction is False.
306 """ 307 """
307 with self._lock: 308 with self._lock:
308 if force_refresh: 309 if force_refresh:
310 logging.debug('Forcing access token refresh')
309 self._access_token = self._create_access_token(allow_user_interaction) 311 self._access_token = self._create_access_token(allow_user_interaction)
310 return self._access_token 312 return self._access_token
311 313
312 # Load from on-disk cache on a first access. 314 # Load from on-disk cache on a first access.
313 if not self._access_token: 315 if not self._access_token:
314 self._access_token = self._load_access_token() 316 self._access_token = self._load_access_token()
315 317
316 # Refresh if expired or missing. 318 # Refresh if expired or missing.
317 if not self._access_token or _needs_refresh(self._access_token): 319 if not self._access_token or _needs_refresh(self._access_token):
318 # Maybe some other process already updated it, reload from the cache. 320 # Maybe some other process already updated it, reload from the cache.
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
374 ## Private methods. 376 ## Private methods.
375 377
376 def _get_storage(self): 378 def _get_storage(self):
377 """Returns oauth2client.Storage with cached tokens.""" 379 """Returns oauth2client.Storage with cached tokens."""
378 # Do not mix cache keys for different externally provided tokens. 380 # Do not mix cache keys for different externally provided tokens.
379 if self._external_token: 381 if self._external_token:
380 token_hash = hashlib.sha1(self._external_token.refresh_token).hexdigest() 382 token_hash = hashlib.sha1(self._external_token.refresh_token).hexdigest()
381 cache_key = '%s:refresh_tok:%s' % (self._token_cache_key, token_hash) 383 cache_key = '%s:refresh_tok:%s' % (self._token_cache_key, token_hash)
382 else: 384 else:
383 cache_key = self._token_cache_key 385 cache_key = self._token_cache_key
386 logging.debug(
387 'Using token storage %r (cache key %r)', OAUTH_TOKENS_CACHE, cache_key)
384 return multistore_file.get_credential_storage_custom_string_key( 388 return multistore_file.get_credential_storage_custom_string_key(
385 OAUTH_TOKENS_CACHE, cache_key) 389 OAUTH_TOKENS_CACHE, cache_key)
386 390
387 def _get_cached_credentials(self): 391 def _get_cached_credentials(self):
388 """Returns oauth2client.Credentials loaded from storage.""" 392 """Returns oauth2client.Credentials loaded from storage."""
389 storage = self._get_storage() 393 storage = self._get_storage()
390 credentials = storage.get() 394 credentials = storage.get()
391 395
396 _log_credentials_info('cached token', credentials)
397
392 # Is using --auth-refresh-token-json? 398 # Is using --auth-refresh-token-json?
393 if self._external_token: 399 if self._external_token:
394 # Cached credentials are valid and match external token -> use them. It is 400 # Cached credentials are valid and match external token -> use them. It is
395 # important to reuse credentials from the storage because they contain 401 # important to reuse credentials from the storage because they contain
396 # cached access token. 402 # cached access token.
397 valid = ( 403 valid = (
398 credentials and not credentials.invalid and 404 credentials and not credentials.invalid and
399 credentials.refresh_token == self._external_token.refresh_token and 405 credentials.refresh_token == self._external_token.refresh_token and
400 credentials.client_id == self._external_token.client_id and 406 credentials.client_id == self._external_token.client_id and
401 credentials.client_secret == self._external_token.client_secret) 407 credentials.client_secret == self._external_token.client_secret)
402 if valid: 408 if valid:
409 logging.debug('Cached credentials match external refresh token')
403 return credentials 410 return credentials
404 # Construct new credentials from externally provided refresh token, 411 # Construct new credentials from externally provided refresh token,
405 # associate them with cache storage (so that access_token will be placed 412 # associate them with cache storage (so that access_token will be placed
406 # in the cache later too). 413 # in the cache later too).
414 logging.debug('Putting external refresh token into the cache')
407 credentials = client.OAuth2Credentials( 415 credentials = client.OAuth2Credentials(
408 access_token=None, 416 access_token=None,
409 client_id=self._external_token.client_id, 417 client_id=self._external_token.client_id,
410 client_secret=self._external_token.client_secret, 418 client_secret=self._external_token.client_secret,
411 refresh_token=self._external_token.refresh_token, 419 refresh_token=self._external_token.refresh_token,
412 token_expiry=None, 420 token_expiry=None,
413 token_uri='https://accounts.google.com/o/oauth2/token', 421 token_uri='https://accounts.google.com/o/oauth2/token',
414 user_agent=None, 422 user_agent=None,
415 revoke_uri=None) 423 revoke_uri=None)
416 credentials.set_store(storage) 424 credentials.set_store(storage)
417 storage.put(credentials) 425 storage.put(credentials)
418 return credentials 426 return credentials
419 427
420 # Not using external refresh token -> return whatever is cached. 428 # Not using external refresh token -> return whatever is cached.
421 return credentials if (credentials and not credentials.invalid) else None 429 return credentials if (credentials and not credentials.invalid) else None
422 430
423 def _load_access_token(self): 431 def _load_access_token(self):
424 """Returns cached AccessToken if it is not expired yet.""" 432 """Returns cached AccessToken if it is not expired yet."""
433 logging.debug('Reloading access token from cache')
425 creds = self._get_cached_credentials() 434 creds = self._get_cached_credentials()
426 if not creds or not creds.access_token or creds.access_token_expired: 435 if not creds or not creds.access_token or creds.access_token_expired:
436 logging.debug('Access token is missing or expired')
427 return None 437 return None
428 return AccessToken(str(creds.access_token), creds.token_expiry) 438 return AccessToken(str(creds.access_token), creds.token_expiry)
429 439
430 def _create_access_token(self, allow_user_interaction=False): 440 def _create_access_token(self, allow_user_interaction=False):
431 """Mints and caches a new access token, launching OAuth2 dance if necessary. 441 """Mints and caches a new access token, launching OAuth2 dance if necessary.
432 442
433 Uses cached refresh token, if present. In that case user interaction is not 443 Uses cached refresh token, if present. In that case user interaction is not
434 required and function will finish quietly. Otherwise it will launch 3-legged 444 required and function will finish quietly. Otherwise it will launch 3-legged
435 OAuth2 flow, that needs user interaction. 445 OAuth2 flow, that needs user interaction.
436 446
437 Args: 447 Args:
438 allow_user_interaction: if True, allow interaction with the user (e.g. 448 allow_user_interaction: if True, allow interaction with the user (e.g.
439 reading standard input, or launching a browser). 449 reading standard input, or launching a browser).
440 450
441 Returns: 451 Returns:
442 AccessToken. 452 AccessToken.
443 453
444 Raises: 454 Raises:
445 AuthenticationError on error or if authentication flow was interrupted. 455 AuthenticationError on error or if authentication flow was interrupted.
446 LoginRequiredError if user interaction is required, but 456 LoginRequiredError if user interaction is required, but
447 allow_user_interaction is False. 457 allow_user_interaction is False.
448 """ 458 """
459 logging.debug(
460 'Making new access token (allow_user_interaction=%r)',
461 allow_user_interaction)
449 credentials = self._get_cached_credentials() 462 credentials = self._get_cached_credentials()
450 463
451 # 3-legged flow with (perhaps cached) refresh token. 464 # 3-legged flow with (perhaps cached) refresh token.
452 refreshed = False 465 refreshed = False
453 if credentials and not credentials.invalid: 466 if credentials and not credentials.invalid:
454 try: 467 try:
468 logging.debug('Attempting to refresh access_token')
455 credentials.refresh(httplib2.Http()) 469 credentials.refresh(httplib2.Http())
470 _log_credentials_info('refreshed token', credentials)
456 refreshed = True 471 refreshed = True
457 except client.Error as err: 472 except client.Error as err:
458 logging.warning( 473 logging.warning(
459 'OAuth error during access token refresh (%s). ' 474 'OAuth error during access token refresh (%s). '
460 'Attempting a full authentication flow.', err) 475 'Attempting a full authentication flow.', err)
461 476
462 # Refresh token is missing or invalid, go through the full flow. 477 # Refresh token is missing or invalid, go through the full flow.
463 if not refreshed: 478 if not refreshed:
464 # Can't refresh externally provided token. 479 # Can't refresh externally provided token.
465 if self._external_token: 480 if self._external_token:
466 raise AuthenticationError( 481 raise AuthenticationError(
467 'Token provided via --auth-refresh-token-json is no longer valid.') 482 'Token provided via --auth-refresh-token-json is no longer valid.')
468 if not allow_user_interaction: 483 if not allow_user_interaction:
484 logging.debug('Requesting user to login')
469 raise LoginRequiredError(self._token_cache_key) 485 raise LoginRequiredError(self._token_cache_key)
486 logging.debug('Launching OAuth browser flow')
470 credentials = _run_oauth_dance(self._config) 487 credentials = _run_oauth_dance(self._config)
488 _log_credentials_info('new token', credentials)
471 489
472 logging.info( 490 logging.info(
473 'OAuth access_token refreshed. Expires in %s.', 491 'OAuth access_token refreshed. Expires in %s.',
474 credentials.token_expiry - datetime.datetime.utcnow()) 492 credentials.token_expiry - datetime.datetime.utcnow())
475 storage = self._get_storage() 493 storage = self._get_storage()
476 credentials.set_store(storage) 494 credentials.set_store(storage)
477 storage.put(credentials) 495 storage.put(credentials)
478 return AccessToken(str(credentials.access_token), credentials.token_expiry) 496 return AccessToken(str(credentials.access_token), credentials.token_expiry)
479 497
480 498
(...skipping 25 matching lines...) Expand all
506 def _needs_refresh(access_token): 524 def _needs_refresh(access_token):
507 """True if AccessToken should be refreshed.""" 525 """True if AccessToken should be refreshed."""
508 if access_token.expires_at is not None: 526 if access_token.expires_at is not None:
509 # Allow 5 min of clock skew between client and backend. 527 # Allow 5 min of clock skew between client and backend.
510 now = datetime.datetime.utcnow() + datetime.timedelta(seconds=300) 528 now = datetime.datetime.utcnow() + datetime.timedelta(seconds=300)
511 return now >= access_token.expires_at 529 return now >= access_token.expires_at
512 # Token without expiration time never expires. 530 # Token without expiration time never expires.
513 return False 531 return False
514 532
515 533
534 def _log_credentials_info(title, credentials):
535 """Dumps (non sensitive) part of client.Credentials object to debug log."""
536 if not credentials:
537 logging.debug('No %s', title)
nodir 2015/04/21 00:01:11 I think there is only one case that (not credentia
Vadim Sh. 2015/04/21 00:04:21 Done. Though I still prefer to be defensive and ch
538 else:
539 logging.debug('%s info: %r', title, {
540 'access_token_expired': credentials.access_token_expired,
541 'has_access_token': bool(credentials.access_token),
542 'invalid': credentials.invalid,
543 'now': datetime.datetime.utcnow(),
nodir 2015/04/21 00:01:12 Please say utcnow
Vadim Sh. 2015/04/21 00:04:21 Done.
544 'token_expiry': credentials.token_expiry,
545 })
546
547
516 def _run_oauth_dance(config): 548 def _run_oauth_dance(config):
517 """Perform full 3-legged OAuth2 flow with the browser. 549 """Perform full 3-legged OAuth2 flow with the browser.
518 550
519 Returns: 551 Returns:
520 oauth2client.Credentials. 552 oauth2client.Credentials.
521 553
522 Raises: 554 Raises:
523 AuthenticationError on errors. 555 AuthenticationError on errors.
524 """ 556 """
525 flow = client.OAuth2WebServerFlow( 557 flow = client.OAuth2WebServerFlow(
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
619 self.end_headers() 651 self.end_headers()
620 query = self.path.split('?', 1)[-1] 652 query = self.path.split('?', 1)[-1]
621 query = dict(urlparse.parse_qsl(query)) 653 query = dict(urlparse.parse_qsl(query))
622 self.server.query_params = query 654 self.server.query_params = query
623 self.wfile.write('<html><head><title>Authentication Status</title></head>') 655 self.wfile.write('<html><head><title>Authentication Status</title></head>')
624 self.wfile.write('<body><p>The authentication flow has completed.</p>') 656 self.wfile.write('<body><p>The authentication flow has completed.</p>')
625 self.wfile.write('</body></html>') 657 self.wfile.write('</body></html>')
626 658
627 def log_message(self, _format, *args): 659 def log_message(self, _format, *args):
628 """Do not log messages to stdout while running as command line program.""" 660 """Do not log messages to stdout while running as command line program."""
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698