blob: 93b0171dcbedd09240dfa883b81e5ce604c36cab [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 Gregorio68a8cfe2012-08-03 16:17:40 -040032from oauth2client.client import FlowExchangeError
33from oauth2client.client import OOB_CALLBACK_URN
34from oauth2client import util
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050035
36try:
Joe Gregorio9da2ad82011-09-11 14:04:44 -040037 from urlparse import parse_qsl
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050038except ImportError:
Joe Gregorio9da2ad82011-09-11 14:04:44 -040039 from cgi import parse_qsl
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050040
41
42FLAGS = gflags.FLAGS
43
44gflags.DEFINE_boolean('auth_local_webserver', True,
Joe Gregorio9da2ad82011-09-11 14:04:44 -040045 ('Run a local web server to handle redirects during '
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050046 'OAuth authorization.'))
47
48gflags.DEFINE_string('auth_host_name', 'localhost',
49 ('Host name to use when running a local web server to '
Joe Gregorio9da2ad82011-09-11 14:04:44 -040050 'handle redirects during OAuth authorization.'))
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050051
52gflags.DEFINE_multi_int('auth_host_port', [8080, 8090],
Joe Gregorio9da2ad82011-09-11 14:04:44 -040053 ('Port to use when running a local web server to '
54 'handle redirects during OAuth authorization.'))
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050055
56
57class ClientRedirectServer(BaseHTTPServer.HTTPServer):
58 """A server to handle OAuth 2.0 redirects back to localhost.
59
60 Waits for a single request and parses the query parameters
61 into query_params and then stops serving.
62 """
63 query_params = {}
64
65
66class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
67 """A handler for OAuth 2.0 redirects back to localhost.
68
69 Waits for a single request and parses the query parameters
70 into the servers query_params and then stops serving.
71 """
72
73 def do_GET(s):
Joe Gregorio9da2ad82011-09-11 14:04:44 -040074 """Handle a GET request.
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -050075
76 Parses the query parameters and prints a message
77 if the flow has completed. Note that we can't detect
78 if an error occurred.
79 """
80 s.send_response(200)
81 s.send_header("Content-type", "text/html")
82 s.end_headers()
83 query = s.path.split('?', 1)[-1]
84 query = dict(parse_qsl(query))
85 s.server.query_params = query
86 s.wfile.write("<html><head><title>Authentication Status</title></head>")
87 s.wfile.write("<body><p>The authentication flow has completed.</p>")
88 s.wfile.write("</body></html>")
89
90 def log_message(self, format, *args):
91 """Do not log messages to stdout while running as command line program."""
92 pass
93
94
Joe Gregorio68a8cfe2012-08-03 16:17:40 -040095@util.positional(2)
Joe Gregorio8e000ed2012-02-07 15:41:44 -050096def run(flow, storage, http=None):
Joe Gregorio695fdc12011-01-16 16:46:55 -050097 """Core code for a command-line application.
Joe Gregoriofffa7d72011-02-18 17:20:39 -050098
Joe Gregoriod98b2482012-10-24 08:49:12 -040099 The run() function is called from your application and runs through all the
100 steps to obtain credentials. It takes a Flow argument and attempts to open an
101 authorization server page in the user's default web browser. The server asks
102 the user to grant your application access to the user's data. If the user
103 grants access, the run() function returns new credentials. The new credentials
104 are also stored in the Storage argument, which updates the file associated
105 with the Storage object.
106
107 It presumes it is run from a command-line application and supports the
108 following flags:
109
Joe Gregorio7a5f3e42012-11-06 10:12:52 -0500110 --auth_host_name: Host name to use when running a local web server
111 to handle redirects during OAuth authorization.
112 (default: 'localhost')
Joe Gregoriod98b2482012-10-24 08:49:12 -0400113
Joe Gregorio7a5f3e42012-11-06 10:12:52 -0500114 --auth_host_port: Port to use when running a local web server to handle
115 redirects during OAuth authorization.;
116 repeat this option to specify a list of values
117 (default: '[8080, 8090]')
118 (an integer)
Joe Gregoriod98b2482012-10-24 08:49:12 -0400119
Joe Gregorio7a5f3e42012-11-06 10:12:52 -0500120 --[no]auth_local_webserver: Run a local web server to handle redirects
Joe Gregoriod98b2482012-10-24 08:49:12 -0400121 during OAuth authorization.
Joe Gregorio7a5f3e42012-11-06 10:12:52 -0500122 (default: 'true')
Joe Gregoriod98b2482012-10-24 08:49:12 -0400123
124 Since it uses flags make sure to initialize the gflags module before calling
125 run().
126
Joe Gregoriofffa7d72011-02-18 17:20:39 -0500127 Args:
128 flow: Flow, an OAuth 2.0 Flow to step through.
129 storage: Storage, a Storage to store the credential in.
Joe Gregorio8e000ed2012-02-07 15:41:44 -0500130 http: An instance of httplib2.Http.request
131 or something that acts like it.
Joe Gregoriofffa7d72011-02-18 17:20:39 -0500132
133 Returns:
134 Credentials, the obtained credential.
Joe Gregorio695fdc12011-01-16 16:46:55 -0500135 """
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500136 if FLAGS.auth_local_webserver:
137 success = False
138 port_number = 0
139 for port in FLAGS.auth_host_port:
140 port_number = port
141 try:
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400142 httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
143 ClientRedirectHandler)
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500144 except socket.error, e:
145 pass
146 else:
147 success = True
148 break
149 FLAGS.auth_local_webserver = success
Joe Gregorio01c86b12012-06-07 14:31:32 -0400150 if not success:
151 print 'Failed to start a local webserver listening on either port 8080'
152 print 'or port 9090. Please check your firewall settings and locally'
153 print 'running programs that may be blocking or using those ports.'
154 print
155 print 'Falling back to --noauth_local_webserver and continuing with',
156 print 'authorization.'
157 print
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500158
159 if FLAGS.auth_local_webserver:
160 oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
161 else:
Joe Gregoriof2326c02012-02-09 12:18:44 -0500162 oauth_callback = OOB_CALLBACK_URN
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400163 flow.redirect_uri = oauth_callback
164 authorize_url = flow.step1_get_authorize_url()
Joe Gregorio695fdc12011-01-16 16:46:55 -0500165
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400166 if FLAGS.auth_local_webserver:
Joe Gregorio2fdd2952012-01-17 09:03:28 -0500167 webbrowser.open(authorize_url, new=1, autoraise=True)
168 print 'Your browser has been opened to visit:'
169 print
170 print ' ' + authorize_url
171 print
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400172 print 'If your browser is on a different machine then exit and re-run this'
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400173 print 'application with the command-line parameter '
Joe Gregorio2fdd2952012-01-17 09:03:28 -0500174 print
175 print ' --noauth_local_webserver'
176 print
177 else:
178 print 'Go to the following link in your browser:'
179 print
180 print ' ' + authorize_url
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400181 print
182
Joe Gregorio562b7312011-09-15 09:06:38 -0400183 code = None
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500184 if FLAGS.auth_local_webserver:
185 httpd.handle_request()
186 if 'error' in httpd.query_params:
187 sys.exit('Authentication request was rejected.')
188 if 'code' in httpd.query_params:
189 code = httpd.query_params['code']
Joe Gregorio562b7312011-09-15 09:06:38 -0400190 else:
191 print 'Failed to find "code" in the query parameters of the redirect.'
192 sys.exit('Try running with --noauth_local_webserver.')
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500193 else:
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400194 code = raw_input('Enter verification code: ').strip()
Joe Gregorio695fdc12011-01-16 16:46:55 -0500195
Joe Gregoriofffa7d72011-02-18 17:20:39 -0500196 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400197 credential = flow.step2_exchange(code, http=http)
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400198 except FlowExchangeError, e:
199 sys.exit('Authentication has failed: %s' % e)
Joe Gregorio695fdc12011-01-16 16:46:55 -0500200
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400201 storage.put(credential)
202 credential.set_store(storage)
203 print 'Authentication successful.'
Joe Gregoriodeeb0202011-02-15 14:49:57 -0500204
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400205 return credential