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

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 if not credentials:
397 logging.debug('No cached token')
398 else:
399 _log_credentials_info('cached token', credentials)
400
392 # Is using --auth-refresh-token-json? 401 # Is using --auth-refresh-token-json?
393 if self._external_token: 402 if self._external_token:
394 # Cached credentials are valid and match external token -> use them. It is 403 # Cached credentials are valid and match external token -> use them. It is
395 # important to reuse credentials from the storage because they contain 404 # important to reuse credentials from the storage because they contain
396 # cached access token. 405 # cached access token.
397 valid = ( 406 valid = (
398 credentials and not credentials.invalid and 407 credentials and not credentials.invalid and
399 credentials.refresh_token == self._external_token.refresh_token and 408 credentials.refresh_token == self._external_token.refresh_token and
400 credentials.client_id == self._external_token.client_id and 409 credentials.client_id == self._external_token.client_id and
401 credentials.client_secret == self._external_token.client_secret) 410 credentials.client_secret == self._external_token.client_secret)
402 if valid: 411 if valid:
412 logging.debug('Cached credentials match external refresh token')
403 return credentials 413 return credentials
404 # Construct new credentials from externally provided refresh token, 414 # Construct new credentials from externally provided refresh token,
405 # associate them with cache storage (so that access_token will be placed 415 # associate them with cache storage (so that access_token will be placed
406 # in the cache later too). 416 # in the cache later too).
417 logging.debug('Putting external refresh token into the cache')
407 credentials = client.OAuth2Credentials( 418 credentials = client.OAuth2Credentials(
408 access_token=None, 419 access_token=None,
409 client_id=self._external_token.client_id, 420 client_id=self._external_token.client_id,
410 client_secret=self._external_token.client_secret, 421 client_secret=self._external_token.client_secret,
411 refresh_token=self._external_token.refresh_token, 422 refresh_token=self._external_token.refresh_token,
412 token_expiry=None, 423 token_expiry=None,
413 token_uri='https://accounts.google.com/o/oauth2/token', 424 token_uri='https://accounts.google.com/o/oauth2/token',
414 user_agent=None, 425 user_agent=None,
415 revoke_uri=None) 426 revoke_uri=None)
416 credentials.set_store(storage) 427 credentials.set_store(storage)
417 storage.put(credentials) 428 storage.put(credentials)
418 return credentials 429 return credentials
419 430
420 # Not using external refresh token -> return whatever is cached. 431 # Not using external refresh token -> return whatever is cached.
421 return credentials if (credentials and not credentials.invalid) else None 432 return credentials if (credentials and not credentials.invalid) else None
422 433
423 def _load_access_token(self): 434 def _load_access_token(self):
424 """Returns cached AccessToken if it is not expired yet.""" 435 """Returns cached AccessToken if it is not expired yet."""
436 logging.debug('Reloading access token from cache')
425 creds = self._get_cached_credentials() 437 creds = self._get_cached_credentials()
426 if not creds or not creds.access_token or creds.access_token_expired: 438 if not creds or not creds.access_token or creds.access_token_expired:
439 logging.debug('Access token is missing or expired')
427 return None 440 return None
428 return AccessToken(str(creds.access_token), creds.token_expiry) 441 return AccessToken(str(creds.access_token), creds.token_expiry)
429 442
430 def _create_access_token(self, allow_user_interaction=False): 443 def _create_access_token(self, allow_user_interaction=False):
431 """Mints and caches a new access token, launching OAuth2 dance if necessary. 444 """Mints and caches a new access token, launching OAuth2 dance if necessary.
432 445
433 Uses cached refresh token, if present. In that case user interaction is not 446 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 447 required and function will finish quietly. Otherwise it will launch 3-legged
435 OAuth2 flow, that needs user interaction. 448 OAuth2 flow, that needs user interaction.
436 449
437 Args: 450 Args:
438 allow_user_interaction: if True, allow interaction with the user (e.g. 451 allow_user_interaction: if True, allow interaction with the user (e.g.
439 reading standard input, or launching a browser). 452 reading standard input, or launching a browser).
440 453
441 Returns: 454 Returns:
442 AccessToken. 455 AccessToken.
443 456
444 Raises: 457 Raises:
445 AuthenticationError on error or if authentication flow was interrupted. 458 AuthenticationError on error or if authentication flow was interrupted.
446 LoginRequiredError if user interaction is required, but 459 LoginRequiredError if user interaction is required, but
447 allow_user_interaction is False. 460 allow_user_interaction is False.
448 """ 461 """
462 logging.debug(
463 'Making new access token (allow_user_interaction=%r)',
464 allow_user_interaction)
449 credentials = self._get_cached_credentials() 465 credentials = self._get_cached_credentials()
450 466
451 # 3-legged flow with (perhaps cached) refresh token. 467 # 3-legged flow with (perhaps cached) refresh token.
452 refreshed = False 468 refreshed = False
453 if credentials and not credentials.invalid: 469 if credentials and not credentials.invalid:
454 try: 470 try:
471 logging.debug('Attempting to refresh access_token')
455 credentials.refresh(httplib2.Http()) 472 credentials.refresh(httplib2.Http())
473 _log_credentials_info('refreshed token', credentials)
456 refreshed = True 474 refreshed = True
457 except client.Error as err: 475 except client.Error as err:
458 logging.warning( 476 logging.warning(
459 'OAuth error during access token refresh (%s). ' 477 'OAuth error during access token refresh (%s). '
460 'Attempting a full authentication flow.', err) 478 'Attempting a full authentication flow.', err)
461 479
462 # Refresh token is missing or invalid, go through the full flow. 480 # Refresh token is missing or invalid, go through the full flow.
463 if not refreshed: 481 if not refreshed:
464 # Can't refresh externally provided token. 482 # Can't refresh externally provided token.
465 if self._external_token: 483 if self._external_token:
466 raise AuthenticationError( 484 raise AuthenticationError(
467 'Token provided via --auth-refresh-token-json is no longer valid.') 485 'Token provided via --auth-refresh-token-json is no longer valid.')
468 if not allow_user_interaction: 486 if not allow_user_interaction:
487 logging.debug('Requesting user to login')
469 raise LoginRequiredError(self._token_cache_key) 488 raise LoginRequiredError(self._token_cache_key)
489 logging.debug('Launching OAuth browser flow')
470 credentials = _run_oauth_dance(self._config) 490 credentials = _run_oauth_dance(self._config)
491 _log_credentials_info('new token', credentials)
471 492
472 logging.info( 493 logging.info(
473 'OAuth access_token refreshed. Expires in %s.', 494 'OAuth access_token refreshed. Expires in %s.',
474 credentials.token_expiry - datetime.datetime.utcnow()) 495 credentials.token_expiry - datetime.datetime.utcnow())
475 storage = self._get_storage() 496 storage = self._get_storage()
476 credentials.set_store(storage) 497 credentials.set_store(storage)
477 storage.put(credentials) 498 storage.put(credentials)
478 return AccessToken(str(credentials.access_token), credentials.token_expiry) 499 return AccessToken(str(credentials.access_token), credentials.token_expiry)
479 500
480 501
(...skipping 25 matching lines...) Expand all
506 def _needs_refresh(access_token): 527 def _needs_refresh(access_token):
507 """True if AccessToken should be refreshed.""" 528 """True if AccessToken should be refreshed."""
508 if access_token.expires_at is not None: 529 if access_token.expires_at is not None:
509 # Allow 5 min of clock skew between client and backend. 530 # Allow 5 min of clock skew between client and backend.
510 now = datetime.datetime.utcnow() + datetime.timedelta(seconds=300) 531 now = datetime.datetime.utcnow() + datetime.timedelta(seconds=300)
511 return now >= access_token.expires_at 532 return now >= access_token.expires_at
512 # Token without expiration time never expires. 533 # Token without expiration time never expires.
513 return False 534 return False
514 535
515 536
537 def _log_credentials_info(title, credentials):
538 """Dumps (non sensitive) part of client.Credentials object to debug log."""
539 if credentials:
540 logging.debug('%s info: %r', title, {
541 'access_token_expired': credentials.access_token_expired,
542 'has_access_token': bool(credentials.access_token),
543 'invalid': credentials.invalid,
544 'utcnow': datetime.datetime.utcnow(),
545 'token_expiry': credentials.token_expiry,
546 })
547
548
516 def _run_oauth_dance(config): 549 def _run_oauth_dance(config):
517 """Perform full 3-legged OAuth2 flow with the browser. 550 """Perform full 3-legged OAuth2 flow with the browser.
518 551
519 Returns: 552 Returns:
520 oauth2client.Credentials. 553 oauth2client.Credentials.
521 554
522 Raises: 555 Raises:
523 AuthenticationError on errors. 556 AuthenticationError on errors.
524 """ 557 """
525 flow = client.OAuth2WebServerFlow( 558 flow = client.OAuth2WebServerFlow(
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
619 self.end_headers() 652 self.end_headers()
620 query = self.path.split('?', 1)[-1] 653 query = self.path.split('?', 1)[-1]
621 query = dict(urlparse.parse_qsl(query)) 654 query = dict(urlparse.parse_qsl(query))
622 self.server.query_params = query 655 self.server.query_params = query
623 self.wfile.write('<html><head><title>Authentication Status</title></head>') 656 self.wfile.write('<html><head><title>Authentication Status</title></head>')
624 self.wfile.write('<body><p>The authentication flow has completed.</p>') 657 self.wfile.write('<body><p>The authentication flow has completed.</p>')
625 self.wfile.write('</body></html>') 658 self.wfile.write('</body></html>')
626 659
627 def log_message(self, _format, *args): 660 def log_message(self, _format, *args):
628 """Do not log messages to stdout while running as command line program.""" 661 """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