xref: /aosp_15_r20/external/autotest/utils/frozen_chromite/third_party/oauth2client/tools.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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