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