blob: 1faa9fffa52a3c07e93dbac0033885efb6c8c46d [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
99 Args:
100 flow: Flow, an OAuth 2.0 Flow to step through.
101 storage: Storage, a Storage to store the credential in.
Joe Gregorio8e000ed2012-02-07 15:41:44 -0500102 http: An instance of httplib2.Http.request
103 or something that acts like it.
Joe Gregoriofffa7d72011-02-18 17:20:39 -0500104
105 Returns:
106 Credentials, the obtained credential.
Joe Gregorio695fdc12011-01-16 16:46:55 -0500107 """
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500108 if FLAGS.auth_local_webserver:
109 success = False
110 port_number = 0
111 for port in FLAGS.auth_host_port:
112 port_number = port
113 try:
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400114 httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
115 ClientRedirectHandler)
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500116 except socket.error, e:
117 pass
118 else:
119 success = True
120 break
121 FLAGS.auth_local_webserver = success
Joe Gregorio01c86b12012-06-07 14:31:32 -0400122 if not success:
123 print 'Failed to start a local webserver listening on either port 8080'
124 print 'or port 9090. Please check your firewall settings and locally'
125 print 'running programs that may be blocking or using those ports.'
126 print
127 print 'Falling back to --noauth_local_webserver and continuing with',
128 print 'authorization.'
129 print
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500130
131 if FLAGS.auth_local_webserver:
132 oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
133 else:
Joe Gregoriof2326c02012-02-09 12:18:44 -0500134 oauth_callback = OOB_CALLBACK_URN
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400135 flow.redirect_uri = oauth_callback
136 authorize_url = flow.step1_get_authorize_url()
Joe Gregorio695fdc12011-01-16 16:46:55 -0500137
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400138 if FLAGS.auth_local_webserver:
Joe Gregorio2fdd2952012-01-17 09:03:28 -0500139 webbrowser.open(authorize_url, new=1, autoraise=True)
140 print 'Your browser has been opened to visit:'
141 print
142 print ' ' + authorize_url
143 print
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400144 print 'If your browser is on a different machine then exit and re-run this'
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400145 print 'application with the command-line parameter '
Joe Gregorio2fdd2952012-01-17 09:03:28 -0500146 print
147 print ' --noauth_local_webserver'
148 print
149 else:
150 print 'Go to the following link in your browser:'
151 print
152 print ' ' + authorize_url
Joe Gregorio8097e2a2011-05-17 11:11:34 -0400153 print
154
Joe Gregorio562b7312011-09-15 09:06:38 -0400155 code = None
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500156 if FLAGS.auth_local_webserver:
157 httpd.handle_request()
158 if 'error' in httpd.query_params:
159 sys.exit('Authentication request was rejected.')
160 if 'code' in httpd.query_params:
161 code = httpd.query_params['code']
Joe Gregorio562b7312011-09-15 09:06:38 -0400162 else:
163 print 'Failed to find "code" in the query parameters of the redirect.'
164 sys.exit('Try running with --noauth_local_webserver.')
Joe Gregorio9e5fe4d2011-03-10 09:33:47 -0500165 else:
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400166 code = raw_input('Enter verification code: ').strip()
Joe Gregorio695fdc12011-01-16 16:46:55 -0500167
Joe Gregoriofffa7d72011-02-18 17:20:39 -0500168 try:
Joe Gregorio68a8cfe2012-08-03 16:17:40 -0400169 credential = flow.step2_exchange(code, http=http)
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400170 except FlowExchangeError, e:
171 sys.exit('Authentication has failed: %s' % e)
Joe Gregorio695fdc12011-01-16 16:46:55 -0500172
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400173 storage.put(credential)
174 credential.set_store(storage)
175 print 'Authentication successful.'
Joe Gregoriodeeb0202011-02-15 14:49:57 -0500176
Joe Gregorio9da2ad82011-09-11 14:04:44 -0400177 return credential