| 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 |
| 37 import ConfigParser | 38 import ConfigParser |
| 38 import cookielib | 39 import cookielib |
| 39 import errno | 40 import errno |
| 40 import fnmatch | 41 import fnmatch |
| 41 import getpass | 42 import getpass |
| 42 import logging | 43 import logging |
| 43 import marshal | 44 import marshal |
| 44 import mimetypes | 45 import mimetypes |
| 45 import optparse | 46 import optparse |
| 46 import os | 47 import os |
| 47 import re | 48 import re |
| 48 import socket | 49 import socket |
| 49 import subprocess | 50 import subprocess |
| 50 import sys | 51 import sys |
| 51 import urllib | 52 import urllib |
| 52 import urllib2 | 53 import urllib2 |
| 53 import urlparse | 54 import urlparse |
| 55 import webbrowser |
| 54 | 56 |
| 55 # The md5 module was deprecated in Python 2.5. | 57 # The md5 module was deprecated in Python 2.5. |
| 56 try: | 58 try: |
| 57 from hashlib import md5 | 59 from hashlib import md5 |
| 58 except ImportError: | 60 except ImportError: |
| 59 from md5 import md5 | 61 from md5 import md5 |
| 60 | 62 |
| 61 try: | 63 try: |
| 62 import readline | 64 import readline |
| 63 except ImportError: | 65 except ImportError: |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 99 VCS_MERCURIAL.lower(): VCS_MERCURIAL, | 101 VCS_MERCURIAL.lower(): VCS_MERCURIAL, |
| 100 "hg": VCS_MERCURIAL, | 102 "hg": VCS_MERCURIAL, |
| 101 VCS_SUBVERSION.lower(): VCS_SUBVERSION, | 103 VCS_SUBVERSION.lower(): VCS_SUBVERSION, |
| 102 "svn": VCS_SUBVERSION, | 104 "svn": VCS_SUBVERSION, |
| 103 VCS_PERFORCE.lower(): VCS_PERFORCE, | 105 VCS_PERFORCE.lower(): VCS_PERFORCE, |
| 104 "p4": VCS_PERFORCE, | 106 "p4": VCS_PERFORCE, |
| 105 VCS_GIT.lower(): VCS_GIT, | 107 VCS_GIT.lower(): VCS_GIT, |
| 106 VCS_CVS.lower(): VCS_CVS, | 108 VCS_CVS.lower(): VCS_CVS, |
| 107 } | 109 } |
| 108 | 110 |
| 111 # OAuth 2.0-Related Constants |
| 112 LOCALHOST_IP = '127.0.0.1' |
| 113 DEFAULT_OAUTH2_PORT = 8001 |
| 114 ACCESS_TOKEN_PARAM = 'access_token' |
| 115 OAUTH_PATH = '/get-access-token' |
| 116 OAUTH_PATH_PORT_TEMPLATE = OAUTH_PATH + '?port=%(port)d' |
| 117 AUTH_HANDLER_RESPONSE = """\ |
| 118 <html> |
| 119 <head> |
| 120 <title>Authentication Status</title> |
| 121 </head> |
| 122 <body> |
| 123 <p>The authentication flow has completed.</p> |
| 124 </body> |
| 125 </html> |
| 126 """ |
| 127 # Borrowed from google-api-python-client |
| 128 OPEN_LOCAL_MESSAGE_TEMPLATE = """\ |
| 129 Your browser has been opened to visit: |
| 130 |
| 131 %s |
| 132 |
| 133 If your browser is on a different machine then exit and re-run |
| 134 upload.py with the command-line parameter |
| 135 |
| 136 --no_oauth2_webbrowser |
| 137 """ |
| 138 NO_OPEN_LOCAL_MESSAGE_TEMPLATE = """\ |
| 139 Go to the following link in your browser: |
| 140 |
| 141 %s |
| 142 |
| 143 and copy the access token. |
| 144 """ |
| 145 |
| 109 # The result of parsing Subversion's [auto-props] setting. | 146 # The result of parsing Subversion's [auto-props] setting. |
| 110 svn_auto_props_map = None | 147 svn_auto_props_map = None |
| 111 | 148 |
| 112 def GetEmail(prompt): | 149 def GetEmail(prompt): |
| 113 """Prompts the user for their email address and returns it. | 150 """Prompts the user for their email address and returns it. |
| 114 | 151 |
| 115 The last used email address is saved to a file and offered up as a suggestion | 152 The last used email address is saved to a file and offered up as a suggestion |
| 116 to the user. If the user presses enter without typing in anything the last | 153 to the user. If the user presses enter without typing in anything the last |
| 117 used email address is used. If the user enters a new address, it is saved | 154 used email address is used. If the user enters a new address, it is saved |
| 118 for next time we prompt. | 155 for next time we prompt. |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 172 def reason(self): | 209 def reason(self): |
| 173 # reason is a property on python 2.7 but a member variable on <=2.6. | 210 # reason is a property on python 2.7 but a member variable on <=2.6. |
| 174 # self.args is modified so it cannot be used as-is so save the value in | 211 # self.args is modified so it cannot be used as-is so save the value in |
| 175 # self._reason. | 212 # self._reason. |
| 176 return self._reason | 213 return self._reason |
| 177 | 214 |
| 178 | 215 |
| 179 class AbstractRpcServer(object): | 216 class AbstractRpcServer(object): |
| 180 """Provides a common interface for a simple RPC server.""" | 217 """Provides a common interface for a simple RPC server.""" |
| 181 | 218 |
| 182 def __init__(self, host, auth_function, host_override=None, extra_headers={}, | 219 def __init__(self, host, auth_function, host_override=None, |
| 183 save_cookies=False, account_type=AUTH_ACCOUNT_TYPE): | 220 extra_headers=None, save_cookies=False, |
| 221 account_type=AUTH_ACCOUNT_TYPE): |
| 184 """Creates a new AbstractRpcServer. | 222 """Creates a new AbstractRpcServer. |
| 185 | 223 |
| 186 Args: | 224 Args: |
| 187 host: The host to send requests to. | 225 host: The host to send requests to. |
| 188 auth_function: A function that takes no arguments and returns an | 226 auth_function: A function that takes no arguments and returns an |
| 189 (email, password) tuple when called. Will be called if authentication | 227 (email, password) tuple when called. Will be called if authentication |
| 190 is required. | 228 is required. |
| 191 host_override: The host header to send to the server (defaults to host). | 229 host_override: The host header to send to the server (defaults to host). |
| 192 extra_headers: A dict of extra headers to append to every request. | 230 extra_headers: A dict of extra headers to append to every request. |
| 193 save_cookies: If True, save the authentication cookies to local disk. | 231 save_cookies: If True, save the authentication cookies to local disk. |
| 194 If False, use an in-memory cookiejar instead. Subclasses must | 232 If False, use an in-memory cookiejar instead. Subclasses must |
| 195 implement this functionality. Defaults to False. | 233 implement this functionality. Defaults to False. |
| 196 account_type: Account type used for authentication. Defaults to | 234 account_type: Account type used for authentication. Defaults to |
| 197 AUTH_ACCOUNT_TYPE. | 235 AUTH_ACCOUNT_TYPE. |
| 198 """ | 236 """ |
| 199 self.host = host | 237 self.host = host |
| 200 if (not self.host.startswith("http://") and | 238 if (not self.host.startswith("http://") and |
| 201 not self.host.startswith("https://")): | 239 not self.host.startswith("https://")): |
| 202 self.host = "http://" + self.host | 240 self.host = "http://" + self.host |
| 203 self.host_override = host_override | 241 self.host_override = host_override |
| 204 self.auth_function = auth_function | 242 self.auth_function = auth_function |
| 205 self.authenticated = False | 243 self.authenticated = False |
| 206 self.extra_headers = extra_headers | 244 self.extra_headers = extra_headers or {} |
| 207 self.save_cookies = save_cookies | 245 self.save_cookies = save_cookies |
| 208 self.account_type = account_type | 246 self.account_type = account_type |
| 209 self.opener = self._GetOpener() | 247 self.opener = self._GetOpener() |
| 210 if self.host_override: | 248 if self.host_override: |
| 211 logging.info("Server: %s; Host: %s", self.host, self.host_override) | 249 logging.info("Server: %s; Host: %s", self.host, self.host_override) |
| 212 else: | 250 else: |
| 213 logging.info("Server: %s", self.host) | 251 logging.info("Server: %s", self.host) |
| 214 | 252 |
| 215 def _GetOpener(self): | 253 def _GetOpener(self): |
| 216 """Returns an OpenerDirector for making HTTP requests. | 254 """Returns an OpenerDirector for making HTTP requests. |
| (...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 418 raise | 456 raise |
| 419 finally: | 457 finally: |
| 420 socket.setdefaulttimeout(old_timeout) | 458 socket.setdefaulttimeout(old_timeout) |
| 421 | 459 |
| 422 | 460 |
| 423 class HttpRpcServer(AbstractRpcServer): | 461 class HttpRpcServer(AbstractRpcServer): |
| 424 """Provides a simplified RPC-style interface for HTTP requests.""" | 462 """Provides a simplified RPC-style interface for HTTP requests.""" |
| 425 | 463 |
| 426 def _Authenticate(self): | 464 def _Authenticate(self): |
| 427 """Save the cookie jar after authentication.""" | 465 """Save the cookie jar after authentication.""" |
| 428 super(HttpRpcServer, self)._Authenticate() | 466 if isinstance(self.auth_function, OAuth2Creds): |
| 429 if self.save_cookies: | 467 access_token = self.auth_function() |
| 430 StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) | 468 if access_token is not None: |
| 431 self.cookie_jar.save() | 469 self.extra_headers['Authorization'] = 'OAuth %s' % (access_token,) |
| 470 self.authenticated = True |
| 471 else: |
| 472 super(HttpRpcServer, self)._Authenticate() |
| 473 if self.save_cookies: |
| 474 StatusUpdate("Saving authentication cookies to %s" % self.cookie_file) |
| 475 self.cookie_jar.save() |
| 432 | 476 |
| 433 def _GetOpener(self): | 477 def _GetOpener(self): |
| 434 """Returns an OpenerDirector that supports cookies and ignores redirects. | 478 """Returns an OpenerDirector that supports cookies and ignores redirects. |
| 435 | 479 |
| 436 Returns: | 480 Returns: |
| 437 A urllib2.OpenerDirector object. | 481 A urllib2.OpenerDirector object. |
| 438 """ | 482 """ |
| 439 opener = urllib2.OpenerDirector() | 483 opener = urllib2.OpenerDirector() |
| 440 opener.add_handler(urllib2.ProxyHandler()) | 484 opener.add_handler(urllib2.ProxyHandler()) |
| 441 opener.add_handler(urllib2.UnknownHandler()) | 485 opener.add_handler(urllib2.UnknownHandler()) |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 488 optlist = optstr.split(", ") | 532 optlist = optstr.split(", ") |
| 489 if len(optlist) > 1: | 533 if len(optlist) > 1: |
| 490 if option.takes_value(): | 534 if option.takes_value(): |
| 491 # strip METAVAR from all but the last option | 535 # strip METAVAR from all but the last option |
| 492 optlist = [x.split()[0] for x in optlist[:-1]] + optlist[-1:] | 536 optlist = [x.split()[0] for x in optlist[:-1]] + optlist[-1:] |
| 493 optstr = " ".join(optlist) | 537 optstr = " ".join(optlist) |
| 494 return optstr | 538 return optstr |
| 495 | 539 |
| 496 | 540 |
| 497 parser = optparse.OptionParser( | 541 parser = optparse.OptionParser( |
| 498 usage="%prog [options] [-- diff_options] [path...]", | 542 usage=("%prog [options] [-- diff_options] [path...]\n" |
| 543 "See also: http://code.google.com/p/rietveld/wiki/UploadPyUsage"), |
| 499 add_help_option=False, | 544 add_help_option=False, |
| 500 formatter=CondensedHelpFormatter() | 545 formatter=CondensedHelpFormatter() |
| 501 ) | 546 ) |
| 502 parser.add_option("-h", "--help", action="store_true", | 547 parser.add_option("-h", "--help", action="store_true", |
| 503 help="Show this help message and exit.") | 548 help="Show this help message and exit.") |
| 504 parser.add_option("-y", "--assume_yes", action="store_true", | 549 parser.add_option("-y", "--assume_yes", action="store_true", |
| 505 dest="assume_yes", default=False, | 550 dest="assume_yes", default=False, |
| 506 help="Assume that the answer to yes/no questions is 'yes'.") | 551 help="Assume that the answer to yes/no questions is 'yes'.") |
| 507 # Logging | 552 # Logging |
| 508 group = parser.add_option_group("Logging options") | 553 group = parser.add_option_group("Logging options") |
| (...skipping 15 matching lines...) Expand all Loading... |
| 524 "Defaults to '%default'.")) | 569 "Defaults to '%default'.")) |
| 525 group.add_option("-e", "--email", action="store", dest="email", | 570 group.add_option("-e", "--email", action="store", dest="email", |
| 526 metavar="EMAIL", default=None, | 571 metavar="EMAIL", default=None, |
| 527 help="The username to use. Will prompt if omitted.") | 572 help="The username to use. Will prompt if omitted.") |
| 528 group.add_option("-H", "--host", action="store", dest="host", | 573 group.add_option("-H", "--host", action="store", dest="host", |
| 529 metavar="HOST", default=None, | 574 metavar="HOST", default=None, |
| 530 help="Overrides the Host header sent with all RPCs.") | 575 help="Overrides the Host header sent with all RPCs.") |
| 531 group.add_option("--no_cookies", action="store_false", | 576 group.add_option("--no_cookies", action="store_false", |
| 532 dest="save_cookies", default=True, | 577 dest="save_cookies", default=True, |
| 533 help="Do not save authentication cookies to local disk.") | 578 help="Do not save authentication cookies to local disk.") |
| 579 group.add_option("--oauth2", action="store_true", |
| 580 dest="use_oauth2", default=False, |
| 581 help="Use OAuth 2.0 instead of a password.") |
| 582 group.add_option("--oauth2_port", action="store", type="int", |
| 583 dest="oauth2_port", default=DEFAULT_OAUTH2_PORT, |
| 584 help=("Port to use to handle OAuth 2.0 redirect. Must be an " |
| 585 "integer in the range 1024-49151, defaults to " |
| 586 "'%default'.")) |
| 587 group.add_option("--no_oauth2_webbrowser", action="store_false", |
| 588 dest="open_oauth2_local_webbrowser", default=True, |
| 589 help="Don't open a browser window to get an access token.") |
| 534 group.add_option("--account_type", action="store", dest="account_type", | 590 group.add_option("--account_type", action="store", dest="account_type", |
| 535 metavar="TYPE", default=AUTH_ACCOUNT_TYPE, | 591 metavar="TYPE", default=AUTH_ACCOUNT_TYPE, |
| 536 choices=["GOOGLE", "HOSTED"], | 592 choices=["GOOGLE", "HOSTED"], |
| 537 help=("Override the default account type " | 593 help=("Override the default account type " |
| 538 "(defaults to '%default', " | 594 "(defaults to '%default', " |
| 539 "valid choices are 'GOOGLE' and 'HOSTED').")) | 595 "valid choices are 'GOOGLE' and 'HOSTED').")) |
| 540 # Issue | 596 # Issue |
| 541 group = parser.add_option_group("Issue options") | 597 group = parser.add_option_group("Issue options") |
| 542 group.add_option("-t", "--title", action="store", dest="title", | 598 group.add_option("-t", "--title", action="store", dest="title", |
| 543 help="New issue subject or new patch set title") | 599 help="New issue subject or new patch set title") |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 605 metavar="P4_CHANGELIST", default=None, | 661 metavar="P4_CHANGELIST", default=None, |
| 606 help=("Perforce changelist id")) | 662 help=("Perforce changelist id")) |
| 607 group.add_option("--p4_client", action="store", dest="p4_client", | 663 group.add_option("--p4_client", action="store", dest="p4_client", |
| 608 metavar="P4_CLIENT", default=None, | 664 metavar="P4_CLIENT", default=None, |
| 609 help=("Perforce client/workspace")) | 665 help=("Perforce client/workspace")) |
| 610 group.add_option("--p4_user", action="store", dest="p4_user", | 666 group.add_option("--p4_user", action="store", dest="p4_user", |
| 611 metavar="P4_USER", default=None, | 667 metavar="P4_USER", default=None, |
| 612 help=("Perforce user")) | 668 help=("Perforce user")) |
| 613 | 669 |
| 614 | 670 |
| 671 # OAuth 2.0 Methods and Helpers |
| 672 class ClientRedirectServer(BaseHTTPServer.HTTPServer): |
| 673 """A server for redirects back to localhost from the associated server. |
| 674 |
| 675 Waits for a single request and parses the query parameters for an access token |
| 676 and then stops serving. |
| 677 """ |
| 678 access_token = None |
| 679 |
| 680 |
| 681 class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
| 682 """A handler for redirects back to localhost from the associated server. |
| 683 |
| 684 Waits for a single request and parses the query parameters into the server's |
| 685 access_token and then stops serving. |
| 686 """ |
| 687 |
| 688 def SetAccessToken(self): |
| 689 """Stores the access token from the request on the server. |
| 690 |
| 691 Will only do this if exactly one query parameter was passed in to the |
| 692 request and that query parameter used 'access_token' as the key. |
| 693 """ |
| 694 query_string = urlparse.urlparse(self.path).query |
| 695 query_params = urlparse.parse_qs(query_string) |
| 696 |
| 697 if len(query_params) == 1: |
| 698 access_token_list = query_params.get(ACCESS_TOKEN_PARAM, []) |
| 699 if len(access_token_list) == 1: |
| 700 self.server.access_token = access_token_list[0] |
| 701 |
| 702 def do_GET(self): |
| 703 """Handle a GET request. |
| 704 |
| 705 Parses and saves the query parameters and prints a message that the server |
| 706 has completed its lone task (handling a redirect). |
| 707 |
| 708 Note that we can't detect if an error occurred. |
| 709 """ |
| 710 self.send_response(200) |
| 711 self.send_header('Content-type', 'text/html') |
| 712 self.end_headers() |
| 713 self.SetAccessToken() |
| 714 self.wfile.write(AUTH_HANDLER_RESPONSE) |
| 715 |
| 716 def log_message(self, format, *args): |
| 717 """Do not log messages to stdout while running as command line program.""" |
| 718 pass |
| 719 |
| 720 |
| 721 def OpenOAuth2ConsentPage(server=DEFAULT_REVIEW_SERVER, |
| 722 port=DEFAULT_OAUTH2_PORT): |
| 723 """Opens the OAuth 2.0 consent page or prints instructions how to. |
| 724 |
| 725 Uses the webbrowser module to open the OAuth server side page in a browser. |
| 726 |
| 727 Args: |
| 728 server: String containing the review server URL. Defaults to |
| 729 DEFAULT_REVIEW_SERVER. |
| 730 port: Integer, the port where the localhost server receiving the redirect |
| 731 is serving. Defaults to DEFAULT_OAUTH2_PORT. |
| 732 """ |
| 733 path = OAUTH_PATH_PORT_TEMPLATE % {'port': port} |
| 734 page = 'https://%s%s' % (server, path) |
| 735 webbrowser.open(page, new=1, autoraise=True) |
| 736 print OPEN_LOCAL_MESSAGE_TEMPLATE % (page,) |
| 737 |
| 738 |
| 739 def WaitForAccessToken(port=DEFAULT_OAUTH2_PORT): |
| 740 """Spins up a simple HTTP Server to handle a single request. |
| 741 |
| 742 Intended to handle a single redirect from the production server after the |
| 743 user authenticated via OAuth 2.0 with the server. |
| 744 |
| 745 Args: |
| 746 port: Integer, the port where the localhost server receiving the redirect |
| 747 is serving. Defaults to DEFAULT_OAUTH2_PORT. |
| 748 |
| 749 Returns: |
| 750 The access token passed to the localhost server, or None if no access token |
| 751 was passed. |
| 752 """ |
| 753 httpd = ClientRedirectServer((LOCALHOST_IP, port), ClientRedirectHandler) |
| 754 # Wait to serve just one request before deferring control back |
| 755 # to the caller of wait_for_refresh_token |
| 756 httpd.handle_request() |
| 757 return httpd.access_token |
| 758 |
| 759 |
| 760 def GetAccessToken(server=DEFAULT_REVIEW_SERVER, port=DEFAULT_OAUTH2_PORT, |
| 761 open_local_webbrowser=True): |
| 762 """Gets an Access Token for the current user. |
| 763 |
| 764 Args: |
| 765 server: String containing the review server URL. Defaults to |
| 766 DEFAULT_REVIEW_SERVER. |
| 767 port: Integer, the port where the localhost server receiving the redirect |
| 768 is serving. Defaults to DEFAULT_OAUTH2_PORT. |
| 769 open_local_webbrowser: Boolean, defaults to True. If set, opens a page in |
| 770 the user's browser. |
| 771 |
| 772 Returns: |
| 773 A string access token that was sent to the local server. If the serving page |
| 774 via WaitForAccessToken does not receive an access token, this method |
| 775 returns None. |
| 776 """ |
| 777 access_token = None |
| 778 if open_local_webbrowser: |
| 779 OpenOAuth2ConsentPage(server=server, port=port) |
| 780 try: |
| 781 access_token = WaitForAccessToken(port=port) |
| 782 except socket.error, e: |
| 783 print 'Can\'t start local webserver. Socket Error: %s\n' % (e.strerror,) |
| 784 |
| 785 if access_token is None: |
| 786 # TODO(dhermes): Offer to add to clipboard using xsel, xclip, pbcopy, etc. |
| 787 page = 'https://%s%s' % (server, OAUTH_PATH) |
| 788 print NO_OPEN_LOCAL_MESSAGE_TEMPLATE % (page,) |
| 789 access_token = raw_input('Enter access token: ').strip() |
| 790 |
| 791 return access_token |
| 792 |
| 793 |
| 615 class KeyringCreds(object): | 794 class KeyringCreds(object): |
| 616 def __init__(self, server, host, email): | 795 def __init__(self, server, host, email): |
| 617 self.server = server | 796 self.server = server |
| 618 self.host = host | 797 # Explicitly cast host to str to work around bug in old versions of Keyring |
| 798 # (versions before 0.10). Even though newer versions of Keyring fix this, |
| 799 # some modern linuxes (such as Ubuntu 12.04) still bundle a version with |
| 800 # the bug. |
| 801 self.host = str(host) |
| 619 self.email = email | 802 self.email = email |
| 620 self.accounts_seen = set() | 803 self.accounts_seen = set() |
| 621 | 804 |
| 622 def GetUserCredentials(self): | 805 def GetUserCredentials(self): |
| 623 """Prompts the user for a username and password. | 806 """Prompts the user for a username and password. |
| 624 | 807 |
| 625 Only use keyring on the initial call. If the keyring contains the wrong | 808 Only use keyring on the initial call. If the keyring contains the wrong |
| 626 password, we want to give the user a chance to enter another one. | 809 password, we want to give the user a chance to enter another one. |
| 627 """ | 810 """ |
| 628 # Create a local alias to the email variable to avoid Python's crazy | 811 # Create a local alias to the email variable to avoid Python's crazy |
| (...skipping 17 matching lines...) Expand all Loading... |
| 646 else: | 829 else: |
| 647 password = getpass.getpass("Password for %s: " % email) | 830 password = getpass.getpass("Password for %s: " % email) |
| 648 if keyring: | 831 if keyring: |
| 649 answer = raw_input("Store password in system keyring?(y/N) ").strip() | 832 answer = raw_input("Store password in system keyring?(y/N) ").strip() |
| 650 if answer == "y": | 833 if answer == "y": |
| 651 keyring.set_password(self.host, email, password) | 834 keyring.set_password(self.host, email, password) |
| 652 self.accounts_seen.add(email) | 835 self.accounts_seen.add(email) |
| 653 return (email, password) | 836 return (email, password) |
| 654 | 837 |
| 655 | 838 |
| 839 class OAuth2Creds(object): |
| 840 """Simple object to hold server and port to be passed to GetAccessToken.""" |
| 841 |
| 842 def __init__(self, server, port, open_local_webbrowser=True): |
| 843 self.server = server |
| 844 self.port = port |
| 845 self.open_local_webbrowser = open_local_webbrowser |
| 846 |
| 847 def __call__(self): |
| 848 """Uses stored server and port to retrieve OAuth 2.0 access token.""" |
| 849 return GetAccessToken(server=self.server, port=self.port, |
| 850 open_local_webbrowser=self.open_local_webbrowser) |
| 851 |
| 852 |
| 656 def GetRpcServer(server, email=None, host_override=None, save_cookies=True, | 853 def GetRpcServer(server, email=None, host_override=None, save_cookies=True, |
| 657 account_type=AUTH_ACCOUNT_TYPE): | 854 account_type=AUTH_ACCOUNT_TYPE, use_oauth2=False, |
| 855 oauth2_port=DEFAULT_OAUTH2_PORT, |
| 856 open_oauth2_local_webbrowser=True): |
| 658 """Returns an instance of an AbstractRpcServer. | 857 """Returns an instance of an AbstractRpcServer. |
| 659 | 858 |
| 660 Args: | 859 Args: |
| 661 server: String containing the review server URL. | 860 server: String containing the review server URL. |
| 662 email: String containing user's email address. | 861 email: String containing user's email address. |
| 663 host_override: If not None, string containing an alternate hostname to use | 862 host_override: If not None, string containing an alternate hostname to use |
| 664 in the host header. | 863 in the host header. |
| 665 save_cookies: Whether authentication cookies should be saved to disk. | 864 save_cookies: Whether authentication cookies should be saved to disk. |
| 666 account_type: Account type for authentication, either 'GOOGLE' | 865 account_type: Account type for authentication, either 'GOOGLE' |
| 667 or 'HOSTED'. Defaults to AUTH_ACCOUNT_TYPE. | 866 or 'HOSTED'. Defaults to AUTH_ACCOUNT_TYPE. |
| 867 use_oauth2: Boolean indicating whether OAuth 2.0 should be used for |
| 868 authentication. |
| 869 oauth2_port: Integer, the port where the localhost server receiving the |
| 870 redirect is serving. Defaults to DEFAULT_OAUTH2_PORT. |
| 871 open_oauth2_local_webbrowser: Boolean, defaults to True. If True and using |
| 872 OAuth, this opens a page in the user's browser to obtain a token. |
| 668 | 873 |
| 669 Returns: | 874 Returns: |
| 670 A new HttpRpcServer, on which RPC calls can be made. | 875 A new HttpRpcServer, on which RPC calls can be made. |
| 671 """ | 876 """ |
| 672 | |
| 673 # If this is the dev_appserver, use fake authentication. | 877 # If this is the dev_appserver, use fake authentication. |
| 674 host = (host_override or server).lower() | 878 host = (host_override or server).lower() |
| 675 if re.match(r'(http://)?localhost([:/]|$)', host): | 879 if re.match(r'(http://)?localhost([:/]|$)', host): |
| 676 if email is None: | 880 if email is None: |
| 677 email = "test@example.com" | 881 email = "test@example.com" |
| 678 logging.info("Using debug user %s. Override with --email" % email) | 882 logging.info("Using debug user %s. Override with --email" % email) |
| 679 server = HttpRpcServer( | 883 server = HttpRpcServer( |
| 680 server, | 884 server, |
| 681 lambda: (email, "password"), | 885 lambda: (email, "password"), |
| 682 host_override=host_override, | 886 host_override=host_override, |
| 683 extra_headers={"Cookie": | 887 extra_headers={"Cookie": |
| 684 'dev_appserver_login="%s:False"' % email}, | 888 'dev_appserver_login="%s:False"' % email}, |
| 685 save_cookies=save_cookies, | 889 save_cookies=save_cookies, |
| 686 account_type=account_type) | 890 account_type=account_type) |
| 687 # Don't try to talk to ClientLogin. | 891 # Don't try to talk to ClientLogin. |
| 688 server.authenticated = True | 892 server.authenticated = True |
| 689 return server | 893 return server |
| 690 | 894 |
| 691 return HttpRpcServer(server, | 895 positional_args = [server] |
| 692 KeyringCreds(server, host, email).GetUserCredentials, | 896 if use_oauth2: |
| 897 positional_args.append( |
| 898 OAuth2Creds(server, oauth2_port, open_oauth2_local_webbrowser)) |
| 899 else: |
| 900 positional_args.append(KeyringCreds(server, host, email).GetUserCredentials) |
| 901 return HttpRpcServer(*positional_args, |
| 693 host_override=host_override, | 902 host_override=host_override, |
| 694 save_cookies=save_cookies) | 903 save_cookies=save_cookies, |
| 904 account_type=account_type) |
| 695 | 905 |
| 696 | 906 |
| 697 def EncodeMultipartFormData(fields, files): | 907 def EncodeMultipartFormData(fields, files): |
| 698 """Encode form fields for multipart/form-data. | 908 """Encode form fields for multipart/form-data. |
| 699 | 909 |
| 700 Args: | 910 Args: |
| 701 fields: A sequence of (name, value) elements for regular form fields. | 911 fields: A sequence of (name, value) elements for regular form fields. |
| 702 files: A sequence of (name, filename, value) elements for data to be | 912 files: A sequence of (name, filename, value) elements for data to be |
| 703 uploaded as files. | 913 uploaded as files. |
| 704 Returns: | 914 Returns: |
| (...skipping 1497 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2202 | 2412 |
| 2203 Returns: | 2413 Returns: |
| 2204 A 2-tuple (issue id, patchset id). | 2414 A 2-tuple (issue id, patchset id). |
| 2205 The patchset id is None if the base files are not uploaded by this | 2415 The patchset id is None if the base files are not uploaded by this |
| 2206 script (applies only to SVN checkouts). | 2416 script (applies only to SVN checkouts). |
| 2207 """ | 2417 """ |
| 2208 options, args = parser.parse_args(argv[1:]) | 2418 options, args = parser.parse_args(argv[1:]) |
| 2209 if options.help: | 2419 if options.help: |
| 2210 if options.verbose < 2: | 2420 if options.verbose < 2: |
| 2211 # hide Perforce options | 2421 # hide Perforce options |
| 2212 parser.epilog = "Use '--help -v' to show additional Perforce options." | 2422 parser.epilog = ( |
| 2423 "Use '--help -v' to show additional Perforce options. " |
| 2424 "For more help, see " |
| 2425 "http://code.google.com/p/rietveld/wiki/CodeReviewHelp" |
| 2426 ) |
| 2213 parser.option_groups.remove(parser.get_option_group('--p4_port')) | 2427 parser.option_groups.remove(parser.get_option_group('--p4_port')) |
| 2214 parser.print_help() | 2428 parser.print_help() |
| 2215 sys.exit(0) | 2429 sys.exit(0) |
| 2216 | 2430 |
| 2217 global verbosity | 2431 global verbosity |
| 2218 verbosity = options.verbose | 2432 verbosity = options.verbose |
| 2219 if verbosity >= 3: | 2433 if verbosity >= 3: |
| 2220 logging.getLogger().setLevel(logging.DEBUG) | 2434 logging.getLogger().setLevel(logging.DEBUG) |
| 2221 elif verbosity >= 2: | 2435 elif verbosity >= 2: |
| 2222 logging.getLogger().setLevel(logging.INFO) | 2436 logging.getLogger().setLevel(logging.INFO) |
| (...skipping 20 matching lines...) Expand all Loading... |
| 2243 if data is None: | 2457 if data is None: |
| 2244 data = vcs.GenerateDiff(args) | 2458 data = vcs.GenerateDiff(args) |
| 2245 data = vcs.PostProcessDiff(data) | 2459 data = vcs.PostProcessDiff(data) |
| 2246 if options.print_diffs: | 2460 if options.print_diffs: |
| 2247 print "Rietveld diff start:*****" | 2461 print "Rietveld diff start:*****" |
| 2248 print data | 2462 print data |
| 2249 print "Rietveld diff end:*****" | 2463 print "Rietveld diff end:*****" |
| 2250 files = vcs.GetBaseFiles(data) | 2464 files = vcs.GetBaseFiles(data) |
| 2251 if verbosity >= 1: | 2465 if verbosity >= 1: |
| 2252 print "Upload server:", options.server, "(change with -s/--server)" | 2466 print "Upload server:", options.server, "(change with -s/--server)" |
| 2467 if options.use_oauth2: |
| 2468 options.save_cookies = False |
| 2253 rpc_server = GetRpcServer(options.server, | 2469 rpc_server = GetRpcServer(options.server, |
| 2254 options.email, | 2470 options.email, |
| 2255 options.host, | 2471 options.host, |
| 2256 options.save_cookies, | 2472 options.save_cookies, |
| 2257 options.account_type) | 2473 options.account_type, |
| 2474 options.use_oauth2, |
| 2475 options.oauth2_port, |
| 2476 options.open_oauth2_local_webbrowser) |
| 2258 form_fields = [] | 2477 form_fields = [] |
| 2259 | 2478 |
| 2260 repo_guid = vcs.GetGUID() | 2479 repo_guid = vcs.GetGUID() |
| 2261 if repo_guid: | 2480 if repo_guid: |
| 2262 form_fields.append(("repo_guid", repo_guid)) | 2481 form_fields.append(("repo_guid", repo_guid)) |
| 2263 if base: | 2482 if base: |
| 2264 b = urlparse.urlparse(base) | 2483 b = urlparse.urlparse(base) |
| 2265 username, netloc = urllib.splituser(b.netloc) | 2484 username, netloc = urllib.splituser(b.netloc) |
| 2266 if username: | 2485 if username: |
| 2267 logging.info("Removed username from base URL") | 2486 logging.info("Removed username from base URL") |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2384 os.environ['LC_ALL'] = 'C' | 2603 os.environ['LC_ALL'] = 'C' |
| 2385 RealMain(sys.argv) | 2604 RealMain(sys.argv) |
| 2386 except KeyboardInterrupt: | 2605 except KeyboardInterrupt: |
| 2387 print | 2606 print |
| 2388 StatusUpdate("Interrupted.") | 2607 StatusUpdate("Interrupted.") |
| 2389 sys.exit(1) | 2608 sys.exit(1) |
| 2390 | 2609 |
| 2391 | 2610 |
| 2392 if __name__ == "__main__": | 2611 if __name__ == "__main__": |
| 2393 main() | 2612 main() |
| OLD | NEW |