1# Copyright 2014 Google Inc. All rights reserved. 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 17Do 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. 20""" 21 22from __future__ import print_function 23 24__author__ = 'jcgregorio@google.com (Joe Gregorio)' 25__all__ = ['argparser', 'run_flow', 'run', 'message_if_missing'] 26 27import logging 28import socket 29import sys 30 31from six.moves import BaseHTTPServer 32from six.moves import urllib 33from six.moves import input 34 35from oauth2client import client 36from oauth2client import util 37 38 39_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0 40 41To make this sample run you will need to populate the client_secrets.json file 42found at: 43 44 %s 45 46with information from the APIs Console <https://code.google.com/apis/console>. 47 48""" 49 50def _CreateArgumentParser(): 51 try: 52 import argparse 53 except ImportError: 54 return None 55 parser = argparse.ArgumentParser(add_help=False) 56 parser.add_argument('--auth_host_name', default='localhost', 57 help='Hostname when running a local web server.') 58 parser.add_argument('--noauth_local_webserver', action='store_true', 59 default=False, help='Do not run a local web server.') 60 parser.add_argument('--auth_host_port', default=[8080, 8090], type=int, 61 nargs='*', help='Port web server should listen on.') 62 parser.add_argument('--logging_level', default='ERROR', 63 choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], 64 help='Set the logging level of detail.') 65 return parser 66 67# argparser is an ArgumentParser that contains command-line options expected 68# by tools.run(). Pass it in as part of the 'parents' argument to your own 69# ArgumentParser. 70argparser = _CreateArgumentParser() 71 72 73class ClientRedirectServer(BaseHTTPServer.HTTPServer): 74 """A server to handle OAuth 2.0 redirects back to localhost. 75 76 Waits for a single request and parses the query parameters 77 into query_params and then stops serving. 78 """ 79 query_params = {} 80 81 82class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): 83 """A handler for OAuth 2.0 redirects back to localhost. 84 85 Waits for a single request and parses the query parameters 86 into the servers query_params and then stops serving. 87 """ 88 89 def do_GET(self): 90 """Handle a GET request. 91 92 Parses the query parameters and prints a message 93 if the flow has completed. Note that we can't detect 94 if an error occurred. 95 """ 96 self.send_response(200) 97 self.send_header("Content-type", "text/html") 98 self.end_headers() 99 query = self.path.split('?', 1)[-1] 100 query = dict(urllib.parse.parse_qsl(query)) 101 self.server.query_params = query 102 self.wfile.write(b"<html><head><title>Authentication Status</title></head>") 103 self.wfile.write(b"<body><p>The authentication flow has completed.</p>") 104 self.wfile.write(b"</body></html>") 105 106 def log_message(self, format, *args): 107 """Do not log messages to stdout while running as command line program.""" 108 109 110@util.positional(3) 111def run_flow(flow, storage, flags, http=None): 112 """Core code for a command-line application. 113 114 The ``run()`` function is called from your application and runs 115 through all the steps to obtain credentials. It takes a ``Flow`` 116 argument and attempts to open an authorization server page in the 117 user's default web browser. The server asks the user to grant your 118 application access to the user's data. If the user grants access, 119 the ``run()`` function returns new credentials. The new credentials 120 are also stored in the ``storage`` argument, which updates the file 121 associated with the ``Storage`` object. 122 123 It presumes it is run from a command-line application and supports the 124 following flags: 125 126 ``--auth_host_name`` (string, default: ``localhost``) 127 Host name to use when running a local web server to handle 128 redirects during OAuth authorization. 129 130 ``--auth_host_port`` (integer, default: ``[8080, 8090]``) 131 Port to use when running a local web server to handle redirects 132 during OAuth authorization. Repeat this option to specify a list 133 of values. 134 135 ``--[no]auth_local_webserver`` (boolean, default: ``True``) 136 Run a local web server to handle redirects during OAuth authorization. 137 138 139 140 141 The tools module defines an ``ArgumentParser`` the already contains the flag 142 definitions that ``run()`` requires. You can pass that ``ArgumentParser`` to your 143 ``ArgumentParser`` constructor:: 144 145 parser = argparse.ArgumentParser(description=__doc__, 146 formatter_class=argparse.RawDescriptionHelpFormatter, 147 parents=[tools.argparser]) 148 flags = parser.parse_args(argv) 149 150 Args: 151 flow: Flow, an OAuth 2.0 Flow to step through. 152 storage: Storage, a ``Storage`` to store the credential in. 153 flags: ``argparse.Namespace``, The command-line flags. This is the 154 object returned from calling ``parse_args()`` on 155 ``argparse.ArgumentParser`` as described above. 156 http: An instance of ``httplib2.Http.request`` or something that 157 acts like it. 158 159 Returns: 160 Credentials, the obtained credential. 161 """ 162 logging.getLogger().setLevel(getattr(logging, flags.logging_level)) 163 if not flags.noauth_local_webserver: 164 success = False 165 port_number = 0 166 for port in flags.auth_host_port: 167 port_number = port 168 try: 169 httpd = ClientRedirectServer((flags.auth_host_name, port), 170 ClientRedirectHandler) 171 except socket.error: 172 pass 173 else: 174 success = True 175 break 176 flags.noauth_local_webserver = not success 177 if not success: 178 print('Failed to start a local webserver listening on either port 8080') 179 print('or port 9090. Please check your firewall settings and locally') 180 print('running programs that may be blocking or using those ports.') 181 print() 182 print('Falling back to --noauth_local_webserver and continuing with') 183 print('authorization.') 184 print() 185 186 if not flags.noauth_local_webserver: 187 oauth_callback = 'http://%s:%s/' % (flags.auth_host_name, port_number) 188 else: 189 oauth_callback = client.OOB_CALLBACK_URN 190 flow.redirect_uri = oauth_callback 191 authorize_url = flow.step1_get_authorize_url() 192 193 if not flags.noauth_local_webserver: 194 import webbrowser 195 webbrowser.open(authorize_url, new=1, autoraise=True) 196 print('Your browser has been opened to visit:') 197 print() 198 print(' ' + authorize_url) 199 print() 200 print('If your browser is on a different machine then exit and re-run this') 201 print('application with the command-line parameter ') 202 print() 203 print(' --noauth_local_webserver') 204 print() 205 else: 206 print('Go to the following link in your browser:') 207 print() 208 print(' ' + authorize_url) 209 print() 210 211 code = None 212 if not flags.noauth_local_webserver: 213 httpd.handle_request() 214 if 'error' in httpd.query_params: 215 sys.exit('Authentication request was rejected.') 216 if 'code' in httpd.query_params: 217 code = httpd.query_params['code'] 218 else: 219 print('Failed to find "code" in the query parameters of the redirect.') 220 sys.exit('Try running with --noauth_local_webserver.') 221 else: 222 code = input('Enter verification code: ').strip() 223 224 try: 225 credential = flow.step2_exchange(code, http=http) 226 except client.FlowExchangeError as e: 227 sys.exit('Authentication has failed: %s' % e) 228 229 storage.put(credential) 230 credential.set_store(storage) 231 print('Authentication successful.') 232 233 return credential 234 235 236def message_if_missing(filename): 237 """Helpful message to display if the CLIENT_SECRETS file is missing.""" 238 239 return _CLIENT_SECRETS_MESSAGE % filename 240 241try: 242 from oauth2client.old_run import run 243 from oauth2client.old_run import FLAGS 244except ImportError: 245 def run(*args, **kwargs): 246 raise NotImplementedError( 247 'The gflags library must be installed to use tools.run(). ' 248 'Please install gflags or preferrably switch to using ' 249 'tools.run_flow().') 250