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 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
105 VCS_PERFORCE.lower(): VCS_PERFORCE, | 105 VCS_PERFORCE.lower(): VCS_PERFORCE, |
106 "p4": VCS_PERFORCE, | 106 "p4": VCS_PERFORCE, |
107 VCS_GIT.lower(): VCS_GIT, | 107 VCS_GIT.lower(): VCS_GIT, |
108 VCS_CVS.lower(): VCS_CVS, | 108 VCS_CVS.lower(): VCS_CVS, |
109 } | 109 } |
110 | 110 |
111 # OAuth 2.0-Related Constants | 111 # OAuth 2.0-Related Constants |
112 LOCALHOST_IP = '127.0.0.1' | 112 LOCALHOST_IP = '127.0.0.1' |
113 DEFAULT_OAUTH2_PORT = 8001 | 113 DEFAULT_OAUTH2_PORT = 8001 |
114 ACCESS_TOKEN_PARAM = 'access_token' | 114 ACCESS_TOKEN_PARAM = 'access_token' |
| 115 ERROR_PARAM = 'error' |
| 116 OAUTH_DEFAULT_ERROR_MESSAGE = 'OAuth 2.0 error occurred.' |
115 OAUTH_PATH = '/get-access-token' | 117 OAUTH_PATH = '/get-access-token' |
116 OAUTH_PATH_PORT_TEMPLATE = OAUTH_PATH + '?port=%(port)d' | 118 OAUTH_PATH_PORT_TEMPLATE = OAUTH_PATH + '?port=%(port)d' |
117 AUTH_HANDLER_RESPONSE = """\ | 119 AUTH_HANDLER_RESPONSE = """\ |
118 <html> | 120 <html> |
119 <head> | 121 <head> |
120 <title>Authentication Status</title> | 122 <title>Authentication Status</title> |
| 123 <script> |
| 124 window.onload = function() { |
| 125 window.close(); |
| 126 } |
| 127 </script> |
121 </head> | 128 </head> |
122 <body> | 129 <body> |
123 <p>The authentication flow has completed.</p> | 130 <p>The authentication flow has completed.</p> |
124 </body> | 131 </body> |
125 </html> | 132 </html> |
126 """ | 133 """ |
127 # Borrowed from google-api-python-client | 134 # Borrowed from google-api-python-client |
128 OPEN_LOCAL_MESSAGE_TEMPLATE = """\ | 135 OPEN_LOCAL_MESSAGE_TEMPLATE = """\ |
129 Your browser has been opened to visit: | 136 Your browser has been opened to visit: |
130 | 137 |
(...skipping 535 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
666 group.add_option("--p4_user", action="store", dest="p4_user", | 673 group.add_option("--p4_user", action="store", dest="p4_user", |
667 metavar="P4_USER", default=None, | 674 metavar="P4_USER", default=None, |
668 help=("Perforce user")) | 675 help=("Perforce user")) |
669 | 676 |
670 | 677 |
671 # OAuth 2.0 Methods and Helpers | 678 # OAuth 2.0 Methods and Helpers |
672 class ClientRedirectServer(BaseHTTPServer.HTTPServer): | 679 class ClientRedirectServer(BaseHTTPServer.HTTPServer): |
673 """A server for redirects back to localhost from the associated server. | 680 """A server for redirects back to localhost from the associated server. |
674 | 681 |
675 Waits for a single request and parses the query parameters for an access token | 682 Waits for a single request and parses the query parameters for an access token |
676 and then stops serving. | 683 or an error and then stops serving. |
677 """ | 684 """ |
678 access_token = None | 685 access_token = None |
| 686 error = None |
679 | 687 |
680 | 688 |
681 class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): | 689 class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
682 """A handler for redirects back to localhost from the associated server. | 690 """A handler for redirects back to localhost from the associated server. |
683 | 691 |
684 Waits for a single request and parses the query parameters into the server's | 692 Waits for a single request and parses the query parameters into the server's |
685 access_token and then stops serving. | 693 access_token or error and then stops serving. |
686 """ | 694 """ |
687 | 695 |
688 def SetAccessToken(self): | 696 def SetResponseValue(self): |
689 """Stores the access token from the request on the server. | 697 """Stores the access token or error from the request on the server. |
690 | 698 |
691 Will only do this if exactly one query parameter was passed in to the | 699 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. | 700 request and that query parameter used 'access_token' or 'error' as the key. |
693 """ | 701 """ |
694 query_string = urlparse.urlparse(self.path).query | 702 query_string = urlparse.urlparse(self.path).query |
695 query_params = urlparse.parse_qs(query_string) | 703 query_params = urlparse.parse_qs(query_string) |
696 | 704 |
697 if len(query_params) == 1: | 705 if len(query_params) == 1: |
698 access_token_list = query_params.get(ACCESS_TOKEN_PARAM, []) | 706 if query_params.has_key(ACCESS_TOKEN_PARAM): |
699 if len(access_token_list) == 1: | 707 access_token_list = query_params[ACCESS_TOKEN_PARAM] |
700 self.server.access_token = access_token_list[0] | 708 if len(access_token_list) == 1: |
| 709 self.server.access_token = access_token_list[0] |
| 710 else: |
| 711 error_list = query_params.get(ERROR_PARAM, []) |
| 712 if len(error_list) == 1: |
| 713 self.server.error = error_list[0] |
701 | 714 |
702 def do_GET(self): | 715 def do_GET(self): |
703 """Handle a GET request. | 716 """Handle a GET request. |
704 | 717 |
705 Parses and saves the query parameters and prints a message that the server | 718 Parses and saves the query parameters and prints a message that the server |
706 has completed its lone task (handling a redirect). | 719 has completed its lone task (handling a redirect). |
707 | 720 |
708 Note that we can't detect if an error occurred. | 721 Note that we can't detect if an error occurred. |
709 """ | 722 """ |
710 self.send_response(200) | 723 self.send_response(200) |
711 self.send_header('Content-type', 'text/html') | 724 self.send_header('Content-type', 'text/html') |
712 self.end_headers() | 725 self.end_headers() |
713 self.SetAccessToken() | 726 self.SetResponseValue() |
714 self.wfile.write(AUTH_HANDLER_RESPONSE) | 727 self.wfile.write(AUTH_HANDLER_RESPONSE) |
715 | 728 |
716 def log_message(self, format, *args): | 729 def log_message(self, format, *args): |
717 """Do not log messages to stdout while running as command line program.""" | 730 """Do not log messages to stdout while running as command line program.""" |
718 pass | 731 pass |
719 | 732 |
720 | 733 |
721 def OpenOAuth2ConsentPage(server=DEFAULT_REVIEW_SERVER, | 734 def OpenOAuth2ConsentPage(server=DEFAULT_REVIEW_SERVER, |
722 port=DEFAULT_OAUTH2_PORT): | 735 port=DEFAULT_OAUTH2_PORT): |
723 """Opens the OAuth 2.0 consent page or prints instructions how to. | 736 """Opens the OAuth 2.0 consent page or prints instructions how to. |
724 | 737 |
725 Uses the webbrowser module to open the OAuth server side page in a browser. | 738 Uses the webbrowser module to open the OAuth server side page in a browser. |
726 | 739 |
727 Args: | 740 Args: |
728 server: String containing the review server URL. Defaults to | 741 server: String containing the review server URL. Defaults to |
729 DEFAULT_REVIEW_SERVER. | 742 DEFAULT_REVIEW_SERVER. |
730 port: Integer, the port where the localhost server receiving the redirect | 743 port: Integer, the port where the localhost server receiving the redirect |
731 is serving. Defaults to DEFAULT_OAUTH2_PORT. | 744 is serving. Defaults to DEFAULT_OAUTH2_PORT. |
| 745 |
| 746 Returns: |
| 747 A boolean indicating whether the page opened successfully. |
732 """ | 748 """ |
733 path = OAUTH_PATH_PORT_TEMPLATE % {'port': port} | 749 path = OAUTH_PATH_PORT_TEMPLATE % {'port': port} |
734 page = 'https://%s%s' % (server, path) | 750 parsed_url = urlparse.urlparse(server) |
735 webbrowser.open(page, new=1, autoraise=True) | 751 scheme = parsed_url[0] or 'https' |
736 print OPEN_LOCAL_MESSAGE_TEMPLATE % (page,) | 752 if scheme != 'https': |
| 753 ErrorExit('Using OAuth requires a review server with SSL enabled.') |
| 754 # If no scheme was given on command line the server address ends up in |
| 755 # parsed_url.path otherwise in netloc. |
| 756 host = parsed_url[1] or parsed_url[2] |
| 757 page = '%s://%s%s' % (scheme, host, path) |
| 758 page_opened = webbrowser.open(page, new=1, autoraise=True) |
| 759 if page_opened: |
| 760 print OPEN_LOCAL_MESSAGE_TEMPLATE % (page,) |
| 761 return page_opened |
737 | 762 |
738 | 763 |
739 def WaitForAccessToken(port=DEFAULT_OAUTH2_PORT): | 764 def WaitForAccessToken(port=DEFAULT_OAUTH2_PORT): |
740 """Spins up a simple HTTP Server to handle a single request. | 765 """Spins up a simple HTTP Server to handle a single request. |
741 | 766 |
742 Intended to handle a single redirect from the production server after the | 767 Intended to handle a single redirect from the production server after the |
743 user authenticated via OAuth 2.0 with the server. | 768 user authenticated via OAuth 2.0 with the server. |
744 | 769 |
745 Args: | 770 Args: |
746 port: Integer, the port where the localhost server receiving the redirect | 771 port: Integer, the port where the localhost server receiving the redirect |
747 is serving. Defaults to DEFAULT_OAUTH2_PORT. | 772 is serving. Defaults to DEFAULT_OAUTH2_PORT. |
748 | 773 |
749 Returns: | 774 Returns: |
750 The access token passed to the localhost server, or None if no access token | 775 The access token passed to the localhost server, or None if no access token |
751 was passed. | 776 was passed. |
752 """ | 777 """ |
753 httpd = ClientRedirectServer((LOCALHOST_IP, port), ClientRedirectHandler) | 778 httpd = ClientRedirectServer((LOCALHOST_IP, port), ClientRedirectHandler) |
754 # Wait to serve just one request before deferring control back | 779 # Wait to serve just one request before deferring control back |
755 # to the caller of wait_for_refresh_token | 780 # to the caller of wait_for_refresh_token |
756 httpd.handle_request() | 781 httpd.handle_request() |
| 782 if httpd.access_token is None: |
| 783 ErrorExit(httpd.error or OAUTH_DEFAULT_ERROR_MESSAGE) |
757 return httpd.access_token | 784 return httpd.access_token |
758 | 785 |
759 | 786 |
760 def GetAccessToken(server=DEFAULT_REVIEW_SERVER, port=DEFAULT_OAUTH2_PORT, | 787 def GetAccessToken(server=DEFAULT_REVIEW_SERVER, port=DEFAULT_OAUTH2_PORT, |
761 open_local_webbrowser=True): | 788 open_local_webbrowser=True): |
762 """Gets an Access Token for the current user. | 789 """Gets an Access Token for the current user. |
763 | 790 |
764 Args: | 791 Args: |
765 server: String containing the review server URL. Defaults to | 792 server: String containing the review server URL. Defaults to |
766 DEFAULT_REVIEW_SERVER. | 793 DEFAULT_REVIEW_SERVER. |
767 port: Integer, the port where the localhost server receiving the redirect | 794 port: Integer, the port where the localhost server receiving the redirect |
768 is serving. Defaults to DEFAULT_OAUTH2_PORT. | 795 is serving. Defaults to DEFAULT_OAUTH2_PORT. |
769 open_local_webbrowser: Boolean, defaults to True. If set, opens a page in | 796 open_local_webbrowser: Boolean, defaults to True. If set, opens a page in |
770 the user's browser. | 797 the user's browser. |
771 | 798 |
772 Returns: | 799 Returns: |
773 A string access token that was sent to the local server. If the serving page | 800 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 | 801 via WaitForAccessToken does not receive an access token, this method |
775 returns None. | 802 returns None. |
776 """ | 803 """ |
777 access_token = None | 804 access_token = None |
778 if open_local_webbrowser: | 805 if open_local_webbrowser: |
779 OpenOAuth2ConsentPage(server=server, port=port) | 806 page_opened = OpenOAuth2ConsentPage(server=server, port=port) |
780 try: | 807 if page_opened: |
781 access_token = WaitForAccessToken(port=port) | 808 try: |
782 except socket.error, e: | 809 access_token = WaitForAccessToken(port=port) |
783 print 'Can\'t start local webserver. Socket Error: %s\n' % (e.strerror,) | 810 except socket.error, e: |
| 811 print 'Can\'t start local webserver. Socket Error: %s\n' % (e.strerror,) |
784 | 812 |
785 if access_token is None: | 813 if access_token is None: |
786 # TODO(dhermes): Offer to add to clipboard using xsel, xclip, pbcopy, etc. | 814 # TODO(dhermes): Offer to add to clipboard using xsel, xclip, pbcopy, etc. |
787 page = 'https://%s%s' % (server, OAUTH_PATH) | 815 page = 'https://%s%s' % (server, OAUTH_PATH) |
788 print NO_OPEN_LOCAL_MESSAGE_TEMPLATE % (page,) | 816 print NO_OPEN_LOCAL_MESSAGE_TEMPLATE % (page,) |
789 access_token = raw_input('Enter access token: ').strip() | 817 access_token = raw_input('Enter access token: ').strip() |
790 | 818 |
791 return access_token | 819 return access_token |
792 | 820 |
793 | 821 |
(...skipping 349 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1143 if base_content != None: | 1171 if base_content != None: |
1144 UploadFile(filename, file_id, base_content, is_binary, status, True) | 1172 UploadFile(filename, file_id, base_content, is_binary, status, True) |
1145 if new_content != None: | 1173 if new_content != None: |
1146 UploadFile(filename, file_id, new_content, is_binary, status, False) | 1174 UploadFile(filename, file_id, new_content, is_binary, status, False) |
1147 | 1175 |
1148 def IsImage(self, filename): | 1176 def IsImage(self, filename): |
1149 """Returns true if the filename has an image extension.""" | 1177 """Returns true if the filename has an image extension.""" |
1150 mimetype = mimetypes.guess_type(filename)[0] | 1178 mimetype = mimetypes.guess_type(filename)[0] |
1151 if not mimetype: | 1179 if not mimetype: |
1152 return False | 1180 return False |
1153 return mimetype.startswith("image/") | 1181 return mimetype.startswith("image/") and not mimetype.startswith("image/svg"
) |
1154 | 1182 |
1155 def IsBinaryData(self, data): | 1183 def IsBinaryData(self, data): |
1156 """Returns true if data contains a null byte.""" | 1184 """Returns true if data contains a null byte.""" |
1157 # Derived from how Mercurial's heuristic, see | 1185 # Derived from how Mercurial's heuristic, see |
1158 # http://selenic.com/hg/file/848a6658069e/mercurial/util.py#l229 | 1186 # http://selenic.com/hg/file/848a6658069e/mercurial/util.py#l229 |
1159 return bool(data and "\0" in data) | 1187 return bool(data and "\0" in data) |
1160 | 1188 |
1161 | 1189 |
1162 class SubversionVCS(VersionControlSystem): | 1190 class SubversionVCS(VersionControlSystem): |
1163 """Implementation of the VersionControlSystem interface for Subversion.""" | 1191 """Implementation of the VersionControlSystem interface for Subversion.""" |
(...skipping 1439 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2603 os.environ['LC_ALL'] = 'C' | 2631 os.environ['LC_ALL'] = 'C' |
2604 RealMain(sys.argv) | 2632 RealMain(sys.argv) |
2605 except KeyboardInterrupt: | 2633 except KeyboardInterrupt: |
2606 print | 2634 print |
2607 StatusUpdate("Interrupted.") | 2635 StatusUpdate("Interrupted.") |
2608 sys.exit(1) | 2636 sys.exit(1) |
2609 | 2637 |
2610 | 2638 |
2611 if __name__ == "__main__": | 2639 if __name__ == "__main__": |
2612 main() | 2640 main() |
OLD | NEW |