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