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 |
(...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
212 a cache key for token cache. | 212 a cache key for token cache. |
213 config: AuthConfig instance. | 213 config: AuthConfig instance. |
214 | 214 |
215 Returns: | 215 Returns: |
216 Authenticator object. | 216 Authenticator object. |
217 """ | 217 """ |
218 hostname = hostname.lower().rstrip('/') | 218 hostname = hostname.lower().rstrip('/') |
219 # Append some scheme, otherwise urlparse puts hostname into parsed.path. | 219 # Append some scheme, otherwise urlparse puts hostname into parsed.path. |
220 if '://' not in hostname: | 220 if '://' not in hostname: |
221 hostname = 'https://' + hostname | 221 hostname = 'https://' + hostname |
222 scopes = OAUTH_SCOPES | |
223 if hostname == 'https://code.google.com': | |
Vadim Sh.
2015/06/11 01:37:31
make it a top-level config-like dict, so it looks
seanmccullough
2015/06/11 02:05:22
Done.
| |
224 scopes = "%s https://www.googleapis.com/auth/projecthosting" % scopes | |
222 parsed = urlparse.urlparse(hostname) | 225 parsed = urlparse.urlparse(hostname) |
223 if parsed.path or parsed.params or parsed.query or parsed.fragment: | 226 if parsed.path or parsed.params or parsed.query or parsed.fragment: |
224 raise AuthenticationError( | 227 raise AuthenticationError( |
225 'Expecting a hostname or root host URL, got %s instead' % hostname) | 228 'Expecting a hostname or root host URL, got %s instead' % hostname) |
226 return Authenticator(parsed.netloc, config) | 229 return Authenticator(parsed.netloc, config, scopes) |
227 | 230 |
228 | 231 |
229 class Authenticator(object): | 232 class Authenticator(object): |
230 """Object that knows how to refresh access tokens when needed. | 233 """Object that knows how to refresh access tokens when needed. |
231 | 234 |
232 Args: | 235 Args: |
233 token_cache_key: string key of a section of the token cache file to use | 236 token_cache_key: string key of a section of the token cache file to use |
234 to keep the tokens. See hostname_to_token_cache_key. | 237 to keep the tokens. See hostname_to_token_cache_key. |
235 config: AuthConfig object that holds authentication configuration. | 238 config: AuthConfig object that holds authentication configuration. |
236 """ | 239 """ |
237 | 240 |
238 def __init__(self, token_cache_key, config): | 241 def __init__(self, token_cache_key, config, scopes): |
239 assert isinstance(config, AuthConfig) | 242 assert isinstance(config, AuthConfig) |
240 assert config.use_oauth2 | 243 assert config.use_oauth2 |
241 self._access_token = None | 244 self._access_token = None |
242 self._config = config | 245 self._config = config |
243 self._lock = threading.Lock() | 246 self._lock = threading.Lock() |
244 self._token_cache_key = token_cache_key | 247 self._token_cache_key = token_cache_key |
245 self._external_token = None | 248 self._external_token = None |
249 self._scopes = scopes | |
246 if config.refresh_token_json: | 250 if config.refresh_token_json: |
247 self._external_token = _read_refresh_token_json(config.refresh_token_json) | 251 self._external_token = _read_refresh_token_json(config.refresh_token_json) |
248 logging.debug('Using auth config %r', config) | 252 logging.debug('Using auth config %r', config) |
249 | 253 |
250 def login(self): | 254 def login(self): |
251 """Performs interactive login flow if necessary. | 255 """Performs interactive login flow if necessary. |
252 | 256 |
253 Raises: | 257 Raises: |
254 AuthenticationError on error or if interrupted. | 258 AuthenticationError on error or if interrupted. |
255 """ | 259 """ |
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
480 # Refresh token is missing or invalid, go through the full flow. | 484 # Refresh token is missing or invalid, go through the full flow. |
481 if not refreshed: | 485 if not refreshed: |
482 # Can't refresh externally provided token. | 486 # Can't refresh externally provided token. |
483 if self._external_token: | 487 if self._external_token: |
484 raise AuthenticationError( | 488 raise AuthenticationError( |
485 'Token provided via --auth-refresh-token-json is no longer valid.') | 489 'Token provided via --auth-refresh-token-json is no longer valid.') |
486 if not allow_user_interaction: | 490 if not allow_user_interaction: |
487 logging.debug('Requesting user to login') | 491 logging.debug('Requesting user to login') |
488 raise LoginRequiredError(self._token_cache_key) | 492 raise LoginRequiredError(self._token_cache_key) |
489 logging.debug('Launching OAuth browser flow') | 493 logging.debug('Launching OAuth browser flow') |
490 credentials = _run_oauth_dance(self._config) | 494 credentials = _run_oauth_dance(self._config, self._scopes) |
491 _log_credentials_info('new token', credentials) | 495 _log_credentials_info('new token', credentials) |
492 | 496 |
493 logging.info( | 497 logging.info( |
494 'OAuth access_token refreshed. Expires in %s.', | 498 'OAuth access_token refreshed. Expires in %s.', |
495 credentials.token_expiry - datetime.datetime.utcnow()) | 499 credentials.token_expiry - datetime.datetime.utcnow()) |
496 storage = self._get_storage() | 500 storage = self._get_storage() |
497 credentials.set_store(storage) | 501 credentials.set_store(storage) |
498 storage.put(credentials) | 502 storage.put(credentials) |
499 return AccessToken(str(credentials.access_token), credentials.token_expiry) | 503 return AccessToken(str(credentials.access_token), credentials.token_expiry) |
500 | 504 |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
553 if credentials: | 557 if credentials: |
554 logging.debug('%s info: %r', title, { | 558 logging.debug('%s info: %r', title, { |
555 'access_token_expired': credentials.access_token_expired, | 559 'access_token_expired': credentials.access_token_expired, |
556 'has_access_token': bool(credentials.access_token), | 560 'has_access_token': bool(credentials.access_token), |
557 'invalid': credentials.invalid, | 561 'invalid': credentials.invalid, |
558 'utcnow': datetime.datetime.utcnow(), | 562 'utcnow': datetime.datetime.utcnow(), |
559 'token_expiry': credentials.token_expiry, | 563 'token_expiry': credentials.token_expiry, |
560 }) | 564 }) |
561 | 565 |
562 | 566 |
563 def _run_oauth_dance(config): | 567 def _run_oauth_dance(config, scopes): |
564 """Perform full 3-legged OAuth2 flow with the browser. | 568 """Perform full 3-legged OAuth2 flow with the browser. |
565 | 569 |
566 Returns: | 570 Returns: |
567 oauth2client.Credentials. | 571 oauth2client.Credentials. |
568 | 572 |
569 Raises: | 573 Raises: |
570 AuthenticationError on errors. | 574 AuthenticationError on errors. |
571 """ | 575 """ |
572 flow = client.OAuth2WebServerFlow( | 576 flow = client.OAuth2WebServerFlow( |
573 OAUTH_CLIENT_ID, | 577 OAUTH_CLIENT_ID, |
574 OAUTH_CLIENT_SECRET, | 578 OAUTH_CLIENT_SECRET, |
575 OAUTH_SCOPES, | 579 scopes, |
576 approval_prompt='force') | 580 approval_prompt='force') |
577 | 581 |
578 use_local_webserver = config.use_local_webserver | 582 use_local_webserver = config.use_local_webserver |
579 port = config.webserver_port | 583 port = config.webserver_port |
580 if config.use_local_webserver: | 584 if config.use_local_webserver: |
581 success = False | 585 success = False |
582 try: | 586 try: |
583 httpd = _ClientRedirectServer(('localhost', port), _ClientRedirectHandler) | 587 httpd = _ClientRedirectServer(('localhost', port), _ClientRedirectHandler) |
584 except socket.error: | 588 except socket.error: |
585 pass | 589 pass |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
666 self.end_headers() | 670 self.end_headers() |
667 query = self.path.split('?', 1)[-1] | 671 query = self.path.split('?', 1)[-1] |
668 query = dict(urlparse.parse_qsl(query)) | 672 query = dict(urlparse.parse_qsl(query)) |
669 self.server.query_params = query | 673 self.server.query_params = query |
670 self.wfile.write('<html><head><title>Authentication Status</title></head>') | 674 self.wfile.write('<html><head><title>Authentication Status</title></head>') |
671 self.wfile.write('<body><p>The authentication flow has completed.</p>') | 675 self.wfile.write('<body><p>The authentication flow has completed.</p>') |
672 self.wfile.write('</body></html>') | 676 self.wfile.write('</body></html>') |
673 | 677 |
674 def log_message(self, _format, *args): | 678 def log_message(self, _format, *args): |
675 """Do not log messages to stdout while running as command line program.""" | 679 """Do not log messages to stdout while running as command line program.""" |
OLD | NEW |