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

Side by Side Diff: third_party/upload.py

Issue 1074673002: Add OAuth2 support for end users (i.e. 3-legged flow with the browser). (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
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
OLDNEW
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
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
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
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
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)
473 try: 429 try:
474 tries = 0 430 tries = 0
475 while True: 431 while True:
476 tries += 1 432 tries += 1
477 args = dict(kwargs) 433 args = dict(kwargs)
478 url = "%s%s" % (self.host, request_path) 434 url = "%s%s" % (self.host, request_path)
479 if args: 435 if args:
480 url += "?" + urllib.urlencode(args) 436 url += "?" + urllib.urlencode(args)
481 req = self._CreateRequest(url=url, data=payload) 437 req = self._CreateRequest(url=url, data=payload)
482 req.add_header("Content-Type", content_type) 438 req.add_header("Content-Type", content_type)
483 if extra_headers: 439 if extra_headers:
484 for header, value in extra_headers.items(): 440 for header, value in extra_headers.items():
485 req.add_header(header, value) 441 req.add_header(header, value)
486 try: 442 try:
487 f = self.opener.open(req, timeout=70) 443 f = self.opener.open(req, timeout=70)
488 response = f.read() 444 response = f.read()
489 f.close() 445 f.close()
490 return response 446 return response
491 except urllib2.HTTPError, e: 447 except urllib2.HTTPError, e:
492 if tries > 3: 448 if tries > 3:
493 raise 449 raise
494 elif e.code == 401 or e.code == 302: 450 elif e.code == 401 or e.code == 302:
495 if not self.auth_function: 451 if not self.auth_function:
496 raise 452 raise
497 self._Authenticate() 453 self._Authenticate(force_refresh=True)
498 elif e.code == 301: 454 elif e.code == 301:
499 # Handle permanent redirect manually. 455 # Handle permanent redirect manually.
500 url = e.info()["location"] 456 url = e.info()["location"]
501 url_loc = urlparse.urlparse(url) 457 url_loc = urlparse.urlparse(url)
502 self.host = '%s://%s' % (url_loc[0], url_loc[1]) 458 self.host = '%s://%s' % (url_loc[0], url_loc[1])
503 elif e.code >= 500: 459 elif e.code >= 500:
504 # TODO: We should error out on a 500, but the server is too flaky 460 # TODO: We should error out on a 500, but the server is too flaky
505 # for that at the moment. 461 # for that at the moment.
506 StatusUpdate('Upload got a 500 response: %d' % e.code) 462 StatusUpdate('Upload got a 500 response: %d' % e.code)
507 else: 463 else:
508 raise 464 raise
509 finally: 465 finally:
510 socket.setdefaulttimeout(old_timeout) 466 socket.setdefaulttimeout(old_timeout)
511 467
512 468
513 class HttpRpcServer(AbstractRpcServer): 469 class HttpRpcServer(AbstractRpcServer):
514 """Provides a simplified RPC-style interface for HTTP requests.""" 470 """Provides a simplified RPC-style interface for HTTP requests."""
515 471
516 def _Authenticate(self): 472 def _Authenticate(self, force_refresh):
517 """Save the cookie jar after authentication.""" 473 """Save the cookie jar after authentication."""
518 if isinstance(self.auth_function, OAuth2Creds): 474 if isinstance(self.auth_function, auth.Authenticator):
519 access_token = self.auth_function() 475 try:
520 if access_token is not None: 476 access_token = self.auth_function.get_access_token(force_refresh)
521 self.extra_headers['Authorization'] = 'OAuth %s' % (access_token,) 477 except auth.LoginRequiredError:
522 self.authenticated = True 478 # Attempt to make unauthenticated request first if there's no cached
479 # credentials. HttpRpcServer calls __Authenticate(force_refresh=True)
480 # again if unauthenticated request doesn't work.
481 if not force_refresh:
482 return
483 raise
484 self.extra_headers['Authorization'] = 'Bearer %s' % (
485 access_token.token,)
523 else: 486 else:
524 super(HttpRpcServer, self)._Authenticate() 487 super(HttpRpcServer, self)._Authenticate(force_refresh)
525 if self.save_cookies: 488 if self.save_cookies:
526 StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) 489 StatusUpdate("Saving authentication cookies to %s" % self.cookie_file)
527 self.cookie_jar.save() 490 self.cookie_jar.save()
528 491
529 def _GetOpener(self): 492 def _GetOpener(self):
530 """Returns an OpenerDirector that supports cookies and ignores redirects. 493 """Returns an OpenerDirector that supports cookies and ignores redirects.
531 494
532 Returns: 495 Returns:
533 A urllib2.OpenerDirector object. 496 A urllib2.OpenerDirector object.
534 """ 497 """
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after
707 metavar="P4_CHANGELIST", default=None, 670 metavar="P4_CHANGELIST", default=None,
708 help=("Perforce changelist id")) 671 help=("Perforce changelist id"))
709 group.add_option("--p4_client", action="store", dest="p4_client", 672 group.add_option("--p4_client", action="store", dest="p4_client",
710 metavar="P4_CLIENT", default=None, 673 metavar="P4_CLIENT", default=None,
711 help=("Perforce client/workspace")) 674 help=("Perforce client/workspace"))
712 group.add_option("--p4_user", action="store", dest="p4_user", 675 group.add_option("--p4_user", action="store", dest="p4_user",
713 metavar="P4_USER", default=None, 676 metavar="P4_USER", default=None,
714 help=("Perforce user")) 677 help=("Perforce user"))
715 678
716 679
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): 680 class KeyringCreds(object):
862 def __init__(self, server, host, email): 681 def __init__(self, server, host, email):
863 self.server = server 682 self.server = server
864 # Explicitly cast host to str to work around bug in old versions of Keyring 683 # 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, 684 # (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 685 # some modern linuxes (such as Ubuntu 12.04) still bundle a version with
867 # the bug. 686 # the bug.
868 self.host = str(host) 687 self.host = str(host)
869 self.email = email 688 self.email = email
870 self.accounts_seen = set() 689 self.accounts_seen = set()
(...skipping 25 matching lines...) Expand all
896 else: 715 else:
897 password = getpass.getpass("Password for %s: " % email) 716 password = getpass.getpass("Password for %s: " % email)
898 if keyring: 717 if keyring:
899 answer = raw_input("Store password in system keyring?(y/N) ").strip() 718 answer = raw_input("Store password in system keyring?(y/N) ").strip()
900 if answer == "y": 719 if answer == "y":
901 keyring.set_password(self.host, email, password) 720 keyring.set_password(self.host, email, password)
902 self.accounts_seen.add(email) 721 self.accounts_seen.add(email)
903 return (email, password) 722 return (email, password)
904 723
905 724
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): 725 def GetRpcServer(server, auth_config=None, email=None):
921 """Returns an instance of an AbstractRpcServer. 726 """Returns an instance of an AbstractRpcServer.
922 727
923 Args: 728 Args:
924 server: String containing the review server URL. 729 server: String containing the review server URL.
925 auth_config: auth.AuthConfig tuple with OAuth2 configuration. 730 auth_config: auth.AuthConfig tuple with OAuth2 configuration.
926 email: String containing user's email address [deprecated]. 731 email: String containing user's email address [deprecated].
927 732
928 Returns: 733 Returns:
929 A new HttpRpcServer, on which RPC calls can be made. 734 A new HttpRpcServer, on which RPC calls can be made.
930 """ 735 """
931 # If email is given as an empty string or no auth config is passed, then 736 # 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 737 # assume we want to make requests that do not need authentication. Bypass
933 # authentication by setting the auth_function to None. 738 # authentication by setting the auth_function to None.
934 if email == '' or not auth_config: 739 if email == '' or not auth_config:
935 return HttpRpcServer(server, None) 740 return HttpRpcServer(server, None)
936 741
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. 742 # If this is the dev_appserver, use fake authentication.
941 host = server.lower() 743 host = server.lower()
942 if re.match(r'(http://)?localhost([:/]|$)', host): 744 if re.match(r'(http://)?localhost([:/]|$)', host):
943 if email is None: 745 if email is None:
944 email = "test@example.com" 746 email = "test@example.com"
945 LOGGER.info("Using debug user %s. Override with --email" % email) 747 LOGGER.info("Using debug user %s. Override with --email" % email)
946 server = HttpRpcServer( 748 server = HttpRpcServer(
947 server, 749 server,
948 lambda: (email, "password"), 750 lambda: (email, "password"),
949 extra_headers={"Cookie": 751 extra_headers={"Cookie":
950 'dev_appserver_login="%s:False"' % email}, 752 'dev_appserver_login="%s:False"' % email},
951 save_cookies=auth_config.save_cookies, 753 save_cookies=auth_config.save_cookies,
952 account_type=AUTH_ACCOUNT_TYPE) 754 account_type=AUTH_ACCOUNT_TYPE)
953 # Don't try to talk to ClientLogin. 755 # Don't try to talk to ClientLogin.
954 server.authenticated = True 756 server.authenticated = True
955 return server 757 return server
956 758
759 if auth_config.use_oauth2:
760 auth_func = auth.get_authenticator_for_host(server, auth_config)
761 else:
762 auth_func = KeyringCreds(server, host, email).GetUserCredentials,
763
957 return HttpRpcServer( 764 return HttpRpcServer(
958 server, 765 server,
959 KeyringCreds(server, host, email).GetUserCredentials, 766 auth_func,
960 save_cookies=auth_config.save_cookies, 767 save_cookies=auth_config.save_cookies,
961 account_type=AUTH_ACCOUNT_TYPE) 768 account_type=AUTH_ACCOUNT_TYPE)
962 769
963 770
964 def EncodeMultipartFormData(fields, files): 771 def EncodeMultipartFormData(fields, files):
965 """Encode form fields for multipart/form-data. 772 """Encode form fields for multipart/form-data.
966 773
967 Args: 774 Args:
968 fields: A sequence of (name, value) elements for regular form fields. 775 fields: A sequence of (name, value) elements for regular form fields.
969 files: A sequence of (name, filename, value) elements for data to be 776 files: A sequence of (name, filename, value) elements for data to be
(...skipping 1736 matching lines...) Expand 10 before | Expand all | Expand 10 after
2706 def main(): 2513 def main():
2707 try: 2514 try:
2708 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:" 2515 logging.basicConfig(format=("%(asctime).19s %(levelname)s %(filename)s:"
2709 "%(lineno)s %(message)s ")) 2516 "%(lineno)s %(message)s "))
2710 os.environ['LC_ALL'] = 'C' 2517 os.environ['LC_ALL'] = 'C'
2711 RealMain(sys.argv) 2518 RealMain(sys.argv)
2712 except KeyboardInterrupt: 2519 except KeyboardInterrupt:
2713 print 2520 print
2714 StatusUpdate("Interrupted.") 2521 StatusUpdate("Interrupted.")
2715 sys.exit(1) 2522 sys.exit(1)
2523 except auth.AuthenticationError as e:
2524 print >> sys.stderr, e
2525 sys.exit(1)
2716 2526
2717 2527
2718 if __name__ == "__main__": 2528 if __name__ == "__main__":
2719 main() 2529 main()
OLDNEW
« depot-tools-auth.bat ('K') | « third_party/oauth2client/multistore_file.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698