| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # coding: utf-8 | 2 # coding: utf-8 |
| 3 # | 3 # |
| 4 # Copyright 2007 Google Inc. | 4 # Copyright 2007 Google Inc. |
| 5 # | 5 # |
| 6 # Licensed under the Apache License, Version 2.0 (the "License"); | 6 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 # you may not use this file except in compliance with the License. | 7 # you may not use this file except in compliance with the License. |
| 8 # You may obtain a copy of the License at | 8 # You may obtain a copy of the License at |
| 9 # | 9 # |
| 10 # http://www.apache.org/licenses/LICENSE-2.0 | 10 # http://www.apache.org/licenses/LICENSE-2.0 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 27 Subversion | 27 Subversion |
| 28 Perforce | 28 Perforce |
| 29 CVS | 29 CVS |
| 30 | 30 |
| 31 It is important for Git/Mercurial users to specify a tree/node/branch to diff | 31 It is important for Git/Mercurial users to specify a tree/node/branch to diff |
| 32 against by using the '--rev' option. | 32 against by using the '--rev' option. |
| 33 """ | 33 """ |
| 34 # This code is derived from appcfg.py in the App Engine SDK (open source), | 34 # This code is derived from appcfg.py in the App Engine SDK (open source), |
| 35 # and from ASPN recipe #146306. | 35 # and from ASPN recipe #146306. |
| 36 | 36 |
| 37 import BaseHTTPServer | |
| 38 import ConfigParser | 37 import ConfigParser |
| 39 import cookielib | 38 import cookielib |
| 40 import errno | 39 import errno |
| 41 import fnmatch | 40 import fnmatch |
| 42 import getpass | 41 import getpass |
| 43 import logging | 42 import logging |
| 44 import marshal | 43 import marshal |
| 45 import mimetypes | 44 import mimetypes |
| 46 import optparse | 45 import optparse |
| 47 import os | 46 import os |
| 48 import re | 47 import re |
| 49 import socket | 48 import socket |
| 50 import subprocess | 49 import subprocess |
| 51 import sys | 50 import sys |
| 52 import urllib | 51 import urllib |
| 53 import urllib2 | 52 import urllib2 |
| 54 import urlparse | 53 import urlparse |
| 55 import webbrowser | |
| 56 | 54 |
| 57 from multiprocessing.pool import ThreadPool | 55 from multiprocessing.pool import ThreadPool |
| 58 | 56 |
| 59 # The md5 module was deprecated in Python 2.5. | 57 # The md5 module was deprecated in Python 2.5. |
| 60 try: | 58 try: |
| 61 from hashlib import md5 | 59 from hashlib import md5 |
| 62 except ImportError: | 60 except ImportError: |
| 63 from md5 import md5 | 61 from md5 import md5 |
| 64 | 62 |
| 65 try: | 63 try: |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 119 ] | 117 ] |
| 120 | 118 |
| 121 | 119 |
| 122 VCS_SHORT_NAMES = [] # hg, svn, ... | 120 VCS_SHORT_NAMES = [] # hg, svn, ... |
| 123 VCS_ABBREVIATIONS = {} # alias: name, ... | 121 VCS_ABBREVIATIONS = {} # alias: name, ... |
| 124 for vcs in VCS: | 122 for vcs in VCS: |
| 125 VCS_SHORT_NAMES.append(min(vcs['aliases'], key=len)) | 123 VCS_SHORT_NAMES.append(min(vcs['aliases'], key=len)) |
| 126 VCS_ABBREVIATIONS.update((alias, vcs['name']) for alias in vcs['aliases']) | 124 VCS_ABBREVIATIONS.update((alias, vcs['name']) for alias in vcs['aliases']) |
| 127 | 125 |
| 128 | 126 |
| 129 # OAuth 2.0-Related Constants | |
| 130 LOCALHOST_IP = '127.0.0.1' | |
| 131 DEFAULT_OAUTH2_PORT = 8001 | |
| 132 ACCESS_TOKEN_PARAM = 'access_token' | |
| 133 ERROR_PARAM = 'error' | |
| 134 OAUTH_DEFAULT_ERROR_MESSAGE = 'OAuth 2.0 error occurred.' | |
| 135 OAUTH_PATH = '/get-access-token' | |
| 136 OAUTH_PATH_PORT_TEMPLATE = OAUTH_PATH + '?port=%(port)d' | |
| 137 AUTH_HANDLER_RESPONSE = """\ | |
| 138 <html> | |
| 139 <head> | |
| 140 <title>Authentication Status</title> | |
| 141 <script> | |
| 142 window.onload = function() { | |
| 143 window.close(); | |
| 144 } | |
| 145 </script> | |
| 146 </head> | |
| 147 <body> | |
| 148 <p>The authentication flow has completed.</p> | |
| 149 </body> | |
| 150 </html> | |
| 151 """ | |
| 152 # Borrowed from google-api-python-client | |
| 153 OPEN_LOCAL_MESSAGE_TEMPLATE = """\ | |
| 154 Your browser has been opened to visit: | |
| 155 | |
| 156 %s | |
| 157 | |
| 158 If your browser is on a different machine then exit and re-run | |
| 159 upload.py with the command-line parameter | |
| 160 | |
| 161 --no_oauth2_webbrowser | |
| 162 """ | |
| 163 NO_OPEN_LOCAL_MESSAGE_TEMPLATE = """\ | |
| 164 Go to the following link in your browser: | |
| 165 | |
| 166 %s | |
| 167 | |
| 168 and copy the access token. | |
| 169 """ | |
| 170 | |
| 171 # The result of parsing Subversion's [auto-props] setting. | 127 # The result of parsing Subversion's [auto-props] setting. |
| 172 svn_auto_props_map = None | 128 svn_auto_props_map = None |
| 173 | 129 |
| 174 def GetEmail(prompt): | 130 def GetEmail(prompt): |
| 175 """Prompts the user for their email address and returns it. | 131 """Prompts the user for their email address and returns it. |
| 176 | 132 |
| 177 The last used email address is saved to a file and offered up as a suggestion | 133 The last used email address is saved to a file and offered up as a suggestion |
| 178 to the user. If the user presses enter without typing in anything the last | 134 to the user. If the user presses enter without typing in anything the last |
| 179 used email address is used. If the user enters a new address, it is saved | 135 used email address is used. If the user enters a new address, it is saved |
| 180 for next time we prompt. | 136 for next time we prompt. |
| (...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 354 try: | 310 try: |
| 355 response = self.opener.open(req) | 311 response = self.opener.open(req) |
| 356 except urllib2.HTTPError, e: | 312 except urllib2.HTTPError, e: |
| 357 response = e | 313 response = e |
| 358 if (response.code != 302 or | 314 if (response.code != 302 or |
| 359 response.info()["location"] != continue_location): | 315 response.info()["location"] != continue_location): |
| 360 raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, | 316 raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, |
| 361 response.headers, response.fp) | 317 response.headers, response.fp) |
| 362 self.authenticated = True | 318 self.authenticated = True |
| 363 | 319 |
| 364 def _Authenticate(self): | 320 def _Authenticate(self, force_refresh): |
| 365 """Authenticates the user. | 321 """Authenticates the user. |
| 366 | 322 |
| 367 The authentication process works as follows: | 323 The authentication process works as follows: |
| 368 1) We get a username and password from the user | 324 1) We get a username and password from the user |
| 369 2) We use ClientLogin to obtain an AUTH token for the user | 325 2) We use ClientLogin to obtain an AUTH token for the user |
| 370 (see http://code.google.com/apis/accounts/AuthForInstalledApps.html). | 326 (see http://code.google.com/apis/accounts/AuthForInstalledApps.html). |
| 371 3) We pass the auth token to /_ah/login on the server to obtain an | 327 3) We pass the auth token to /_ah/login on the server to obtain an |
| 372 authentication cookie. If login was successful, it tries to redirect | 328 authentication cookie. If login was successful, it tries to redirect |
| 373 us to the URL we provided. | 329 us to the URL we provided. |
| 374 | 330 |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 459 included in the request (string header names mapped to their values), | 415 included in the request (string header names mapped to their values), |
| 460 or None to not include any additional headers. | 416 or None to not include any additional headers. |
| 461 kwargs: Any keyword arguments are converted into query string parameters. | 417 kwargs: Any keyword arguments are converted into query string parameters. |
| 462 | 418 |
| 463 Returns: | 419 Returns: |
| 464 The response body, as a string. | 420 The response body, as a string. |
| 465 """ | 421 """ |
| 466 # TODO: Don't require authentication. Let the server say | 422 # TODO: Don't require authentication. Let the server say |
| 467 # whether it is necessary. | 423 # whether it is necessary. |
| 468 if not self.authenticated and self.auth_function: | 424 if not self.authenticated and self.auth_function: |
| 469 self._Authenticate() | 425 self._Authenticate(force_refresh=False) |
| 470 | 426 |
| 471 old_timeout = socket.getdefaulttimeout() | 427 old_timeout = socket.getdefaulttimeout() |
| 472 socket.setdefaulttimeout(timeout) | 428 socket.setdefaulttimeout(timeout) |
| 429 auth_attempted = False |
| 473 try: | 430 try: |
| 474 tries = 0 | 431 tries = 0 |
| 475 while True: | 432 while True: |
| 476 tries += 1 | 433 tries += 1 |
| 477 args = dict(kwargs) | 434 args = dict(kwargs) |
| 478 url = "%s%s" % (self.host, request_path) | 435 url = "%s%s" % (self.host, request_path) |
| 479 if args: | 436 if args: |
| 480 url += "?" + urllib.urlencode(args) | 437 url += "?" + urllib.urlencode(args) |
| 481 req = self._CreateRequest(url=url, data=payload) | 438 req = self._CreateRequest(url=url, data=payload) |
| 482 req.add_header("Content-Type", content_type) | 439 req.add_header("Content-Type", content_type) |
| 483 if extra_headers: | 440 if extra_headers: |
| 484 for header, value in extra_headers.items(): | 441 for header, value in extra_headers.items(): |
| 485 req.add_header(header, value) | 442 req.add_header(header, value) |
| 486 try: | 443 try: |
| 487 f = self.opener.open(req, timeout=70) | 444 f = self.opener.open(req, timeout=70) |
| 488 response = f.read() | 445 response = f.read() |
| 489 f.close() | 446 f.close() |
| 490 return response | 447 return response |
| 491 except urllib2.HTTPError, e: | 448 except urllib2.HTTPError, e: |
| 492 if tries > 3: | 449 if tries > 3: |
| 493 raise | 450 raise |
| 494 elif e.code == 401 or e.code == 302: | 451 elif e.code in (302, 401, 403): |
| 495 if not self.auth_function: | 452 if not self.auth_function: |
| 496 raise | 453 raise |
| 497 self._Authenticate() | 454 # Already tried force refresh, didn't help -> give up with error. |
| 455 if auth_attempted: |
| 456 raise auth.AuthenticationError( |
| 457 'Access to %s is denied (server returned HTTP %d).' |
| 458 % (self.host, e.code)) |
| 459 self._Authenticate(force_refresh=True) |
| 460 auth_attempted = True |
| 498 elif e.code == 301: | 461 elif e.code == 301: |
| 499 # Handle permanent redirect manually. | 462 # Handle permanent redirect manually. |
| 500 url = e.info()["location"] | 463 url = e.info()["location"] |
| 501 url_loc = urlparse.urlparse(url) | 464 url_loc = urlparse.urlparse(url) |
| 502 self.host = '%s://%s' % (url_loc[0], url_loc[1]) | 465 self.host = '%s://%s' % (url_loc[0], url_loc[1]) |
| 503 elif e.code >= 500: | 466 elif e.code >= 500: |
| 504 # TODO: We should error out on a 500, but the server is too flaky | 467 # TODO: We should error out on a 500, but the server is too flaky |
| 505 # for that at the moment. | 468 # for that at the moment. |
| 506 StatusUpdate('Upload got a 500 response: %d' % e.code) | 469 StatusUpdate('Upload got a 500 response: %d' % e.code) |
| 507 else: | 470 else: |
| 508 raise | 471 raise |
| 509 finally: | 472 finally: |
| 510 socket.setdefaulttimeout(old_timeout) | 473 socket.setdefaulttimeout(old_timeout) |
| 511 | 474 |
| 512 | 475 |
| 513 class HttpRpcServer(AbstractRpcServer): | 476 class HttpRpcServer(AbstractRpcServer): |
| 514 """Provides a simplified RPC-style interface for HTTP requests.""" | 477 """Provides a simplified RPC-style interface for HTTP requests.""" |
| 515 | 478 |
| 516 def _Authenticate(self): | 479 def _Authenticate(self, force_refresh): |
| 517 """Save the cookie jar after authentication.""" | 480 """Save the cookie jar after authentication.""" |
| 518 if isinstance(self.auth_function, OAuth2Creds): | 481 if isinstance(self.auth_function, auth.Authenticator): |
| 519 access_token = self.auth_function() | 482 try: |
| 520 if access_token is not None: | 483 access_token = self.auth_function.get_access_token(force_refresh) |
| 521 self.extra_headers['Authorization'] = 'OAuth %s' % (access_token,) | 484 except auth.LoginRequiredError: |
| 522 self.authenticated = True | 485 # Attempt to make unauthenticated request first if there's no cached |
| 486 # credentials. HttpRpcServer calls __Authenticate(force_refresh=True) |
| 487 # again if unauthenticated request doesn't work. |
| 488 if not force_refresh: |
| 489 return |
| 490 raise |
| 491 self.extra_headers['Authorization'] = 'Bearer %s' % ( |
| 492 access_token.token,) |
| 523 else: | 493 else: |
| 524 super(HttpRpcServer, self)._Authenticate() | 494 super(HttpRpcServer, self)._Authenticate(force_refresh) |
| 525 if self.save_cookies: | 495 if self.save_cookies: |
| 526 StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) | 496 StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) |
| 527 self.cookie_jar.save() | 497 self.cookie_jar.save() |
| 528 | 498 |
| 529 def _GetOpener(self): | 499 def _GetOpener(self): |
| 530 """Returns an OpenerDirector that supports cookies and ignores redirects. | 500 """Returns an OpenerDirector that supports cookies and ignores redirects. |
| 531 | 501 |
| 532 Returns: | 502 Returns: |
| 533 A urllib2.OpenerDirector object. | 503 A urllib2.OpenerDirector object. |
| 534 """ | 504 """ |
| (...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 707 metavar="P4_CHANGELIST", default=None, | 677 metavar="P4_CHANGELIST", default=None, |
| 708 help=("Perforce changelist id")) | 678 help=("Perforce changelist id")) |
| 709 group.add_option("--p4_client", action="store", dest="p4_client", | 679 group.add_option("--p4_client", action="store", dest="p4_client", |
| 710 metavar="P4_CLIENT", default=None, | 680 metavar="P4_CLIENT", default=None, |
| 711 help=("Perforce client/workspace")) | 681 help=("Perforce client/workspace")) |
| 712 group.add_option("--p4_user", action="store", dest="p4_user", | 682 group.add_option("--p4_user", action="store", dest="p4_user", |
| 713 metavar="P4_USER", default=None, | 683 metavar="P4_USER", default=None, |
| 714 help=("Perforce user")) | 684 help=("Perforce user")) |
| 715 | 685 |
| 716 | 686 |
| 717 # OAuth 2.0 Methods and Helpers | |
| 718 class ClientRedirectServer(BaseHTTPServer.HTTPServer): | |
| 719 """A server for redirects back to localhost from the associated server. | |
| 720 | |
| 721 Waits for a single request and parses the query parameters for an access token | |
| 722 or an error and then stops serving. | |
| 723 """ | |
| 724 access_token = None | |
| 725 error = None | |
| 726 | |
| 727 | |
| 728 class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): | |
| 729 """A handler for redirects back to localhost from the associated server. | |
| 730 | |
| 731 Waits for a single request and parses the query parameters into the server's | |
| 732 access_token or error and then stops serving. | |
| 733 """ | |
| 734 | |
| 735 def SetResponseValue(self): | |
| 736 """Stores the access token or error from the request on the server. | |
| 737 | |
| 738 Will only do this if exactly one query parameter was passed in to the | |
| 739 request and that query parameter used 'access_token' or 'error' as the key. | |
| 740 """ | |
| 741 query_string = urlparse.urlparse(self.path).query | |
| 742 query_params = urlparse.parse_qs(query_string) | |
| 743 | |
| 744 if len(query_params) == 1: | |
| 745 if query_params.has_key(ACCESS_TOKEN_PARAM): | |
| 746 access_token_list = query_params[ACCESS_TOKEN_PARAM] | |
| 747 if len(access_token_list) == 1: | |
| 748 self.server.access_token = access_token_list[0] | |
| 749 else: | |
| 750 error_list = query_params.get(ERROR_PARAM, []) | |
| 751 if len(error_list) == 1: | |
| 752 self.server.error = error_list[0] | |
| 753 | |
| 754 def do_GET(self): | |
| 755 """Handle a GET request. | |
| 756 | |
| 757 Parses and saves the query parameters and prints a message that the server | |
| 758 has completed its lone task (handling a redirect). | |
| 759 | |
| 760 Note that we can't detect if an error occurred. | |
| 761 """ | |
| 762 self.send_response(200) | |
| 763 self.send_header('Content-type', 'text/html') | |
| 764 self.end_headers() | |
| 765 self.SetResponseValue() | |
| 766 self.wfile.write(AUTH_HANDLER_RESPONSE) | |
| 767 | |
| 768 def log_message(self, format, *args): | |
| 769 """Do not log messages to stdout while running as command line program.""" | |
| 770 pass | |
| 771 | |
| 772 | |
| 773 def OpenOAuth2ConsentPage(server=DEFAULT_REVIEW_SERVER, | |
| 774 port=DEFAULT_OAUTH2_PORT): | |
| 775 """Opens the OAuth 2.0 consent page or prints instructions how to. | |
| 776 | |
| 777 Uses the webbrowser module to open the OAuth server side page in a browser. | |
| 778 | |
| 779 Args: | |
| 780 server: String containing the review server URL. Defaults to | |
| 781 DEFAULT_REVIEW_SERVER. | |
| 782 port: Integer, the port where the localhost server receiving the redirect | |
| 783 is serving. Defaults to DEFAULT_OAUTH2_PORT. | |
| 784 | |
| 785 Returns: | |
| 786 A boolean indicating whether the page opened successfully. | |
| 787 """ | |
| 788 path = OAUTH_PATH_PORT_TEMPLATE % {'port': port} | |
| 789 parsed_url = urlparse.urlparse(server) | |
| 790 scheme = parsed_url[0] or 'https' | |
| 791 if scheme != 'https': | |
| 792 ErrorExit('Using OAuth requires a review server with SSL enabled.') | |
| 793 # If no scheme was given on command line the server address ends up in | |
| 794 # parsed_url.path otherwise in netloc. | |
| 795 host = parsed_url[1] or parsed_url[2] | |
| 796 page = '%s://%s%s' % (scheme, host, path) | |
| 797 page_opened = webbrowser.open(page, new=1, autoraise=True) | |
| 798 if page_opened: | |
| 799 print OPEN_LOCAL_MESSAGE_TEMPLATE % (page,) | |
| 800 return page_opened | |
| 801 | |
| 802 | |
| 803 def WaitForAccessToken(port=DEFAULT_OAUTH2_PORT): | |
| 804 """Spins up a simple HTTP Server to handle a single request. | |
| 805 | |
| 806 Intended to handle a single redirect from the production server after the | |
| 807 user authenticated via OAuth 2.0 with the server. | |
| 808 | |
| 809 Args: | |
| 810 port: Integer, the port where the localhost server receiving the redirect | |
| 811 is serving. Defaults to DEFAULT_OAUTH2_PORT. | |
| 812 | |
| 813 Returns: | |
| 814 The access token passed to the localhost server, or None if no access token | |
| 815 was passed. | |
| 816 """ | |
| 817 httpd = ClientRedirectServer((LOCALHOST_IP, port), ClientRedirectHandler) | |
| 818 # Wait to serve just one request before deferring control back | |
| 819 # to the caller of wait_for_refresh_token | |
| 820 httpd.handle_request() | |
| 821 if httpd.access_token is None: | |
| 822 ErrorExit(httpd.error or OAUTH_DEFAULT_ERROR_MESSAGE) | |
| 823 return httpd.access_token | |
| 824 | |
| 825 | |
| 826 def GetAccessToken(server=DEFAULT_REVIEW_SERVER, port=DEFAULT_OAUTH2_PORT, | |
| 827 open_local_webbrowser=True): | |
| 828 """Gets an Access Token for the current user. | |
| 829 | |
| 830 Args: | |
| 831 server: String containing the review server URL. Defaults to | |
| 832 DEFAULT_REVIEW_SERVER. | |
| 833 port: Integer, the port where the localhost server receiving the redirect | |
| 834 is serving. Defaults to DEFAULT_OAUTH2_PORT. | |
| 835 open_local_webbrowser: Boolean, defaults to True. If set, opens a page in | |
| 836 the user's browser. | |
| 837 | |
| 838 Returns: | |
| 839 A string access token that was sent to the local server. If the serving page | |
| 840 via WaitForAccessToken does not receive an access token, this method | |
| 841 returns None. | |
| 842 """ | |
| 843 access_token = None | |
| 844 if open_local_webbrowser: | |
| 845 page_opened = OpenOAuth2ConsentPage(server=server, port=port) | |
| 846 if page_opened: | |
| 847 try: | |
| 848 access_token = WaitForAccessToken(port=port) | |
| 849 except socket.error, e: | |
| 850 print 'Can\'t start local webserver. Socket Error: %s\n' % (e.strerror,) | |
| 851 | |
| 852 if access_token is None: | |
| 853 # TODO(dhermes): Offer to add to clipboard using xsel, xclip, pbcopy, etc. | |
| 854 page = 'https://%s%s' % (server, OAUTH_PATH) | |
| 855 print NO_OPEN_LOCAL_MESSAGE_TEMPLATE % (page,) | |
| 856 access_token = raw_input('Enter access token: ').strip() | |
| 857 | |
| 858 return access_token | |
| 859 | |
| 860 | |
| 861 class KeyringCreds(object): | 687 class KeyringCreds(object): |
| 862 def __init__(self, server, host, email): | 688 def __init__(self, server, host, email): |
| 863 self.server = server | 689 self.server = server |
| 864 # Explicitly cast host to str to work around bug in old versions of Keyring | 690 # Explicitly cast host to str to work around bug in old versions of Keyring |
| 865 # (versions before 0.10). Even though newer versions of Keyring fix this, | 691 # (versions before 0.10). Even though newer versions of Keyring fix this, |
| 866 # some modern linuxes (such as Ubuntu 12.04) still bundle a version with | 692 # some modern linuxes (such as Ubuntu 12.04) still bundle a version with |
| 867 # the bug. | 693 # the bug. |
| 868 self.host = str(host) | 694 self.host = str(host) |
| 869 self.email = email | 695 self.email = email |
| 870 self.accounts_seen = set() | 696 self.accounts_seen = set() |
| (...skipping 25 matching lines...) Expand all Loading... |
| 896 else: | 722 else: |
| 897 password = getpass.getpass("Password for %s: " % email) | 723 password = getpass.getpass("Password for %s: " % email) |
| 898 if keyring: | 724 if keyring: |
| 899 answer = raw_input("Store password in system keyring?(y/N) ").strip() | 725 answer = raw_input("Store password in system keyring?(y/N) ").strip() |
| 900 if answer == "y": | 726 if answer == "y": |
| 901 keyring.set_password(self.host, email, password) | 727 keyring.set_password(self.host, email, password) |
| 902 self.accounts_seen.add(email) | 728 self.accounts_seen.add(email) |
| 903 return (email, password) | 729 return (email, password) |
| 904 | 730 |
| 905 | 731 |
| 906 class OAuth2Creds(object): | |
| 907 """Simple object to hold server and port to be passed to GetAccessToken.""" | |
| 908 | |
| 909 def __init__(self, server, port, open_local_webbrowser=True): | |
| 910 self.server = server | |
| 911 self.port = port | |
| 912 self.open_local_webbrowser = open_local_webbrowser | |
| 913 | |
| 914 def __call__(self): | |
| 915 """Uses stored server and port to retrieve OAuth 2.0 access token.""" | |
| 916 return GetAccessToken(server=self.server, port=self.port, | |
| 917 open_local_webbrowser=self.open_local_webbrowser) | |
| 918 | |
| 919 | |
| 920 def GetRpcServer(server, auth_config=None, email=None): | 732 def GetRpcServer(server, auth_config=None, email=None): |
| 921 """Returns an instance of an AbstractRpcServer. | 733 """Returns an instance of an AbstractRpcServer. |
| 922 | 734 |
| 923 Args: | 735 Args: |
| 924 server: String containing the review server URL. | 736 server: String containing the review server URL. |
| 925 auth_config: auth.AuthConfig tuple with OAuth2 configuration. | 737 auth_config: auth.AuthConfig tuple with OAuth2 configuration. |
| 926 email: String containing user's email address [deprecated]. | 738 email: String containing user's email address [deprecated]. |
| 927 | 739 |
| 928 Returns: | 740 Returns: |
| 929 A new HttpRpcServer, on which RPC calls can be made. | 741 A new HttpRpcServer, on which RPC calls can be made. |
| 930 """ | 742 """ |
| 931 # If email is given as an empty string or no auth config is passed, then | 743 # If email is given as an empty string or no auth config is passed, then |
| 932 # assume we want to make requests that do not need authentication. Bypass | 744 # assume we want to make requests that do not need authentication. Bypass |
| 933 # authentication by setting the auth_function to None. | 745 # authentication by setting the auth_function to None. |
| 934 if email == '' or not auth_config: | 746 if email == '' or not auth_config: |
| 935 return HttpRpcServer(server, None) | 747 return HttpRpcServer(server, None) |
| 936 | 748 |
| 937 if auth_config.use_oauth2: | |
| 938 raise NotImplementedError('See https://crbug.com/356813') | |
| 939 | |
| 940 # If this is the dev_appserver, use fake authentication. | 749 # If this is the dev_appserver, use fake authentication. |
| 941 host = server.lower() | 750 host = server.lower() |
| 942 if re.match(r'(http://)?localhost([:/]|$)', host): | 751 if re.match(r'(http://)?localhost([:/]|$)', host): |
| 943 if email is None: | 752 if email is None: |
| 944 email = "test@example.com" | 753 email = "test@example.com" |
| 945 LOGGER.info("Using debug user %s. Override with --email" % email) | 754 LOGGER.info("Using debug user %s. Override with --email" % email) |
| 946 server = HttpRpcServer( | 755 server = HttpRpcServer( |
| 947 server, | 756 server, |
| 948 lambda: (email, "password"), | 757 lambda: (email, "password"), |
| 949 extra_headers={"Cookie": | 758 extra_headers={"Cookie": |
| 950 'dev_appserver_login="%s:False"' % email}, | 759 'dev_appserver_login="%s:False"' % email}, |
| 951 save_cookies=auth_config.save_cookies, | 760 save_cookies=auth_config.save_cookies, |
| 952 account_type=AUTH_ACCOUNT_TYPE) | 761 account_type=AUTH_ACCOUNT_TYPE) |
| 953 # Don't try to talk to ClientLogin. | 762 # Don't try to talk to ClientLogin. |
| 954 server.authenticated = True | 763 server.authenticated = True |
| 955 return server | 764 return server |
| 956 | 765 |
| 766 if auth_config.use_oauth2: |
| 767 auth_func = auth.get_authenticator_for_host(server, auth_config) |
| 768 else: |
| 769 auth_func = KeyringCreds(server, host, email).GetUserCredentials |
| 770 |
| 957 return HttpRpcServer( | 771 return HttpRpcServer( |
| 958 server, | 772 server, |
| 959 KeyringCreds(server, host, email).GetUserCredentials, | 773 auth_func, |
| 960 save_cookies=auth_config.save_cookies, | 774 save_cookies=auth_config.save_cookies, |
| 961 account_type=AUTH_ACCOUNT_TYPE) | 775 account_type=AUTH_ACCOUNT_TYPE) |
| 962 | 776 |
| 963 | 777 |
| 964 def EncodeMultipartFormData(fields, files): | 778 def EncodeMultipartFormData(fields, files): |
| 965 """Encode form fields for multipart/form-data. | 779 """Encode form fields for multipart/form-data. |
| 966 | 780 |
| 967 Args: | 781 Args: |
| 968 fields: A sequence of (name, value) elements for regular form fields. | 782 fields: A sequence of (name, value) elements for regular form fields. |
| 969 files: A sequence of (name, filename, value) elements for data to be | 783 files: A sequence of (name, filename, value) elements for data to be |
| (...skipping 1736 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2706 def main(): | 2520 def main(): |
| 2707 try: | 2521 try: |
| 2708 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" | 2522 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" |
| 2709 "%(lineno)s %(message)s ")) | 2523 "%(lineno)s %(message)s ")) |
| 2710 os.environ['LC_ALL'] = 'C' | 2524 os.environ['LC_ALL'] = 'C' |
| 2711 RealMain(sys.argv) | 2525 RealMain(sys.argv) |
| 2712 except KeyboardInterrupt: | 2526 except KeyboardInterrupt: |
| 2713 print | 2527 print |
| 2714 StatusUpdate("Interrupted.") | 2528 StatusUpdate("Interrupted.") |
| 2715 sys.exit(1) | 2529 sys.exit(1) |
| 2530 except auth.AuthenticationError as e: |
| 2531 print >> sys.stderr, e |
| 2532 sys.exit(1) |
| 2716 | 2533 |
| 2717 | 2534 |
| 2718 if __name__ == "__main__": | 2535 if __name__ == "__main__": |
| 2719 main() | 2536 main() |
| OLD | NEW |