blob: 5d96ea4935f72f044e851105f58d7e86742e439b [file] [log] [blame]
Joe Gregorio695fdc12011-01-16 16:46:55 -05001# Copyright (C) 2010 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Command-line tools for authenticating via OAuth 2.0
16
Joe Gregorio7c22ab22011-02-16 15:32:39 -050017Do the OAuth 2.0 Web Server dance for a command line application. Stores the
18generated credentials in a common file that is used by other example apps in
19the same directory.
Joe Gregorio695fdc12011-01-16 16:46:55 -050020"""
21
22__author__ = 'jcgregorio@google.com (Joe Gregorio)'
Joe Gregorio7c22ab22011-02-16 15:32:39 -050023__all__ = ['run']
Joe Gregorio695fdc12011-01-16 16:46:55 -050024
Joe Gregorio695fdc12011-01-16 16:46:55 -050025
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050026import BaseHTTPServer
27import gflags
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050028import socket
29import sys
Joe Gregorio2fdd2952012-01-17 09:03:28 -050030import webbrowser
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050031
Joe Gregorio06d852b2011-03-25 15:03:10 -040032from client import FlowExchangeError
Joe Gregoriof2326c02012-02-09 12:18:44 -050033from client import OOB_CALLBACK_URN
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050034
35try:
Joe Gregorio9da2ad82011-09-11 14:04:44 -040036 from urlparse import parse_qsl
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050037except ImportError:
Joe Gregorio9da2ad82011-09-11 14:04:44 -040038 from cgi import parse_qsl
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050039
40
41FLAGS = gflags.FLAGS
42
43gflags.DEFINE_boolean('auth_local_webserver', True,
Joe Gregorio9da2ad82011-09-11 14:04:44 -040044 ('Run a local web server to handle redirects during '
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050045 'OAuth authorization.'))
46
47gflags.DEFINE_string('auth_host_name', 'localhost',
48 ('Host name to use when running a local web server to '
Joe Gregorio9da2ad82011-09-11 14:04:44 -040049 'handle redirects during OAuth authorization.'))
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050050
51gflags.DEFINE_multi_int('auth_host_port', [8080, 8090],
Joe Gregorio9da2ad82011-09-11 14:04:44 -040052 ('Port to use when running a local web server to '
53 'handle redirects during OAuth authorization.'))
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050054
55
56class ClientRedirectServer(BaseHTTPServer.HTTPServer):
57 """A server to handle OAuth 2.0 redirects back to localhost.
58
59 Waits for a single request and parses the query parameters
60 into query_params and then stops serving.
61 """
62 query_params = {}
63
64
65class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
66 """A handler for OAuth 2.0 redirects back to localhost.
67
68 Waits for a single request and parses the query parameters
69 into the servers query_params and then stops serving.
70 """
71
72 def do_GET(s):
Joe Gregorio9da2ad82011-09-11 14:04:44 -040073 """Handle a GET request.
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050074
75 Parses the query parameters and prints a message
76 if the flow has completed. Note that we can't detect
77 if an error occurred.
78 """
79 s.send_response(200)
80 s.send_header("Content-type", "text/html")
81 s.end_headers()
82 query = s.path.split('?', 1)[-1]
83 query = dict(parse_qsl(query))
84 s.server.query_params = query
85 s.wfile.write("<html><head><title>Authentication Status</title></head>")
86 s.wfile.write("<body><p>The authentication flow has completed.</p>")
87 s.wfile.write("</body></html>")
88
89 def log_message(self, format, *args):
90 """Do not log messages to stdout while running as command line program."""
91 pass
92
93
Joe Gregorio8e000ed2012-02-07 15:41:44 -050094def run(flow, storage, http=None):
Joe Gregorio695fdc12011-01-16 16:46:55 -050095 """Core code for a command-line application.
Joe Gregoriofffa7d72011-02-18 17:20:39 -050096
97 Args:
98 flow: Flow, an OAuth 2.0 Flow to step through.
99 storage: Storage, a Storage to store the credential in.
Joe Gregorio8e000ed2012-02-07 15:41:44 -0500100 http: An instance of httplib2.Http.request
101 or something that acts like it.
Joe Gregoriofffa7d72011-02-18 17:20:39 -0500102
103 Returns:
104 Credentials, the obtained credential.
Joe Gregorio695fdc12011-01-16 16:46:55 -0500105 """
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500106 if FLAGS.auth_local_webserver:
107 success = False
108 port_number = 0
109 for port in FLAGS.auth_host_port:
110 port_number = port
111 try:
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400112 httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
113 ClientRedirectHandler)
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500114 except socket.error, e:
115 pass
116 else:
117 success = True
118 break
119 FLAGS.auth_local_webserver = success
Joe Gregorio01c86b12012-06-07 14:31:32 -0400120 if not success:
121 print 'Failed to start a local webserver listening on either port 8080'
122 print 'or port 9090. Please check your firewall settings and locally'
123 print 'running programs that may be blocking or using those ports.'
124 print
125 print 'Falling back to --noauth_local_webserver and continuing with',
126 print 'authorization.'
127 print
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500128
129 if FLAGS.auth_local_webserver:
130 oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
131 else:
Joe Gregoriof2326c02012-02-09 12:18:44 -0500132 oauth_callback = OOB_CALLBACK_URN
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500133 authorize_url = flow.step1_get_authorize_url(oauth_callback)
Joe Gregorio695fdc12011-01-16 16:46:55 -0500134
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400135 if FLAGS.auth_local_webserver:
Joe Gregorio2fdd2952012-01-17 09:03:28 -0500136 webbrowser.open(authorize_url, new=1, autoraise=True)
137 print 'Your browser has been opened to visit:'
138 print
139 print ' ' + authorize_url
140 print
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400141 print 'If your browser is on a different machine then exit and re-run this'
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400142 print 'application with the command-line parameter '
Joe Gregorio2fdd2952012-01-17 09:03:28 -0500143 print
144 print ' --noauth_local_webserver'
145 print
146 else:
147 print 'Go to the following link in your browser:'
148 print
149 print ' ' + authorize_url
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400150 print
151
Joe Gregorio562b7312011-09-15 09:06:38 -0400152 code = None
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500153 if FLAGS.auth_local_webserver:
154 httpd.handle_request()
155 if 'error' in httpd.query_params:
156 sys.exit('Authentication request was rejected.')
157 if 'code' in httpd.query_params:
158 code = httpd.query_params['code']
Joe Gregorio562b7312011-09-15 09:06:38 -0400159 else:
160 print 'Failed to find "code" in the query parameters of the redirect.'
161 sys.exit('Try running with --noauth_local_webserver.')
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500162 else:
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400163 code = raw_input('Enter verification code: ').strip()
Joe Gregorio695fdc12011-01-16 16:46:55 -0500164
Joe Gregoriofffa7d72011-02-18 17:20:39 -0500165 try:
Joe Gregorio8e000ed2012-02-07 15:41:44 -0500166 credential = flow.step2_exchange(code, http)
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400167 except FlowExchangeError, e:
168 sys.exit('Authentication has failed: %s' % e)
Joe Gregorio695fdc12011-01-16 16:46:55 -0500169
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400170 storage.put(credential)
171 credential.set_store(storage)
172 print 'Authentication successful.'
Joe Gregoriodeeb0202011-02-15 14:49:57 -0500173
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400174 return credential