././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5312295 google-auth-oauthlib-0.4.2/0002755000000000017530000000000000000000000014612 5ustar00root00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/LICENSE0000664000000000017530000002613200000000000015623 0ustar00root00000000000000 Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/MANIFEST.in0000664000000000017530000000152200000000000016350 0ustar00root00000000000000# -*- coding: utf-8 -*- # # Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Generated by synthtool. DO NOT EDIT! include README.rst LICENSE recursive-include google *.json *.proto recursive-include tests * global-exclude *.py[co] global-exclude __pycache__ # Exclude scripts for samples readmegen prune scripts/readme-gen././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5312295 google-auth-oauthlib-0.4.2/PKG-INFO0000644000000000017530000000506200000000000015710 0ustar00root00000000000000Metadata-Version: 2.1 Name: google-auth-oauthlib Version: 0.4.2 Summary: Google Authentication Library Home-page: https://github.com/GoogleCloudPlatform/google-auth-library-python-oauthlib Author: Google Cloud Platform Author-email: jonwayne+google-auth@google.com License: Apache 2.0 Description: oauthlib integration for Google Auth ==================================== |pypi| This library provides `oauthlib`_ integration with `google-auth`_. .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=master :target: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib .. |docs| image:: https://readthedocs.org/projects/google-auth-oauthlib/badge/?version=latest :target: https://google-auth-oauthlib.readthedocs.io/en/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg :target: https://pypi.python.org/pypi/google-auth-oauthlib .. _oauthlib: https://github.com/idan/oauthlib .. _google-auth: https://github.com/googleapis/google-auth-library-python Installing ---------- You can install using `pip`_:: $ pip install google-auth-oauthlib .. _pip: https://pip.pypa.io/en/stable/ Documentation ------------- The latest documentation is available at `google-auth-oauthlib.readthedocs.io`_. .. _google-auth-oauthlib.readthedocs.io: http://google-auth-oauthlib.readthedocs.io/ License ------- Apache 2.0 - See `the LICENSE`_ for more information. .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/master/LICENSE Keywords: google auth oauth client oauthlib Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Requires-Python: >=3.6 Provides-Extra: tool ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/README.rst0000664000000000017530000000236100000000000016303 0ustar00root00000000000000oauthlib integration for Google Auth ==================================== |pypi| This library provides `oauthlib`_ integration with `google-auth`_. .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=master :target: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib .. |docs| image:: https://readthedocs.org/projects/google-auth-oauthlib/badge/?version=latest :target: https://google-auth-oauthlib.readthedocs.io/en/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg :target: https://pypi.python.org/pypi/google-auth-oauthlib .. _oauthlib: https://github.com/idan/oauthlib .. _google-auth: https://github.com/googleapis/google-auth-library-python Installing ---------- You can install using `pip`_:: $ pip install google-auth-oauthlib .. _pip: https://pip.pypa.io/en/stable/ Documentation ------------- The latest documentation is available at `google-auth-oauthlib.readthedocs.io`_. .. _google-auth-oauthlib.readthedocs.io: http://google-auth-oauthlib.readthedocs.io/ License ------- Apache 2.0 - See `the LICENSE`_ for more information. .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/master/LICENSE ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5312295 google-auth-oauthlib-0.4.2/google_auth_oauthlib/0002755000000000017530000000000000000000000020776 5ustar00root00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib/__init__.py0000664000000000017530000000151600000000000023112 0ustar00root00000000000000# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """oauthlib integration for Google Auth This library provides `oauthlib `__ integration with `google-auth `__. """ from .interactive import get_user_credentials __all__ = ["get_user_credentials"] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib/flow.py0000664000000000017530000004623300000000000022327 0ustar00root00000000000000# Copyright 2016 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """OAuth 2.0 Authorization Flow This module provides integration with `requests-oauthlib`_ for running the `OAuth 2.0 Authorization Flow`_ and acquiring user credentials. Here's an example of using :class:`Flow` with the installed application authorization flow:: from google_auth_oauthlib.flow import Flow # Create the flow using the client secrets file from the Google API # Console. flow = Flow.from_client_secrets_file( 'path/to/client_secrets.json', scopes=['profile', 'email'], redirect_uri='urn:ietf:wg:oauth:2.0:oob') # Tell the user to go to the authorization URL. auth_url, _ = flow.authorization_url(prompt='consent') print('Please go to this URL: {}'.format(auth_url)) # The user will get an authorization code. This code is used to get the # access token. code = input('Enter the authorization code: ') flow.fetch_token(code=code) # You can use flow.credentials, or you can just get a requests session # using flow.authorized_session. session = flow.authorized_session() print(session.get('https://www.googleapis.com/userinfo/v2/me').json()) This particular flow can be handled entirely by using :class:`InstalledAppFlow`. .. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ .. _OAuth 2.0 Authorization Flow: https://tools.ietf.org/html/rfc6749#section-1.2 """ from base64 import urlsafe_b64encode import hashlib import json import logging try: from secrets import SystemRandom except ImportError: # pragma: NO COVER from random import SystemRandom from string import ascii_letters, digits import webbrowser import wsgiref.simple_server import wsgiref.util import google.auth.transport.requests import google.oauth2.credentials from six.moves import input import google_auth_oauthlib.helpers _LOGGER = logging.getLogger(__name__) class Flow(object): """OAuth 2.0 Authorization Flow This class uses a :class:`requests_oauthlib.OAuth2Session` instance at :attr:`oauth2session` to perform all of the OAuth 2.0 logic. This class just provides convenience methods and sane defaults for doing Google's particular flavors of OAuth 2.0. Typically you'll construct an instance of this flow using :meth:`from_client_secrets_file` and a `client secrets file`_ obtained from the `Google API Console`_. .. _client secrets file: https://developers.google.com/identity/protocols/OAuth2WebServer #creatingcred .. _Google API Console: https://console.developers.google.com/apis/credentials """ def __init__( self, oauth2session, client_type, client_config, redirect_uri=None, code_verifier=None, autogenerate_code_verifier=False, ): """ Args: oauth2session (requests_oauthlib.OAuth2Session): The OAuth 2.0 session from ``requests-oauthlib``. client_type (str): The client type, either ``web`` or ``installed``. client_config (Mapping[str, Any]): The client configuration in the Google `client secrets`_ format. redirect_uri (str): The OAuth 2.0 redirect URI if known at flow creation time. Otherwise, it will need to be set using :attr:`redirect_uri`. code_verifier (str): random string of 43-128 chars used to verify the key exchange.using PKCE. autogenerate_code_verifier (bool): If true, auto-generate a code_verifier. .. _client secrets: https://developers.google.com/api-client-library/python/guide /aaa_client_secrets """ self.client_type = client_type """str: The client type, either ``'web'`` or ``'installed'``""" self.client_config = client_config[client_type] """Mapping[str, Any]: The OAuth 2.0 client configuration.""" self.oauth2session = oauth2session """requests_oauthlib.OAuth2Session: The OAuth 2.0 session.""" self.redirect_uri = redirect_uri self.code_verifier = code_verifier self.autogenerate_code_verifier = autogenerate_code_verifier @classmethod def from_client_config(cls, client_config, scopes, **kwargs): """Creates a :class:`requests_oauthlib.OAuth2Session` from client configuration loaded from a Google-format client secrets file. Args: client_config (Mapping[str, Any]): The client configuration in the Google `client secrets`_ format. scopes (Sequence[str]): The list of scopes to request during the flow. kwargs: Any additional parameters passed to :class:`requests_oauthlib.OAuth2Session` Returns: Flow: The constructed Flow instance. Raises: ValueError: If the client configuration is not in the correct format. .. _client secrets: https://developers.google.com/api-client-library/python/guide /aaa_client_secrets """ if "web" in client_config: client_type = "web" elif "installed" in client_config: client_type = "installed" else: raise ValueError("Client secrets must be for a web or installed app.") # these args cannot be passed to requests_oauthlib.OAuth2Session code_verifier = kwargs.pop("code_verifier", None) autogenerate_code_verifier = kwargs.pop("autogenerate_code_verifier", None) ( session, client_config, ) = google_auth_oauthlib.helpers.session_from_client_config( client_config, scopes, **kwargs ) redirect_uri = kwargs.get("redirect_uri", None) return cls( session, client_type, client_config, redirect_uri, code_verifier, autogenerate_code_verifier, ) @classmethod def from_client_secrets_file(cls, client_secrets_file, scopes, **kwargs): """Creates a :class:`Flow` instance from a Google client secrets file. Args: client_secrets_file (str): The path to the client secrets .json file. scopes (Sequence[str]): The list of scopes to request during the flow. kwargs: Any additional parameters passed to :class:`requests_oauthlib.OAuth2Session` Returns: Flow: The constructed Flow instance. """ with open(client_secrets_file, "r") as json_file: client_config = json.load(json_file) return cls.from_client_config(client_config, scopes=scopes, **kwargs) @property def redirect_uri(self): """The OAuth 2.0 redirect URI. Pass-through to ``self.oauth2session.redirect_uri``.""" return self.oauth2session.redirect_uri @redirect_uri.setter def redirect_uri(self, value): self.oauth2session.redirect_uri = value def authorization_url(self, **kwargs): """Generates an authorization URL. This is the first step in the OAuth 2.0 Authorization Flow. The user's browser should be redirected to the returned URL. This method calls :meth:`requests_oauthlib.OAuth2Session.authorization_url` and specifies the client configuration's authorization URI (usually Google's authorization server) and specifies that "offline" access is desired. This is required in order to obtain a refresh token. Args: kwargs: Additional arguments passed through to :meth:`requests_oauthlib.OAuth2Session.authorization_url` Returns: Tuple[str, str]: The generated authorization URL and state. The user must visit the URL to complete the flow. The state is used when completing the flow to verify that the request originated from your application. If your application is using a different :class:`Flow` instance to obtain the token, you will need to specify the ``state`` when constructing the :class:`Flow`. """ kwargs.setdefault("access_type", "offline") if self.autogenerate_code_verifier: chars = ascii_letters + digits + "-._~" rnd = SystemRandom() random_verifier = [rnd.choice(chars) for _ in range(0, 128)] self.code_verifier = "".join(random_verifier) if self.code_verifier: code_hash = hashlib.sha256() code_hash.update(str.encode(self.code_verifier)) unencoded_challenge = code_hash.digest() b64_challenge = urlsafe_b64encode(unencoded_challenge) code_challenge = b64_challenge.decode().split("=")[0] kwargs.setdefault("code_challenge", code_challenge) kwargs.setdefault("code_challenge_method", "S256") url, state = self.oauth2session.authorization_url( self.client_config["auth_uri"], **kwargs ) return url, state def fetch_token(self, **kwargs): """Completes the Authorization Flow and obtains an access token. This is the final step in the OAuth 2.0 Authorization Flow. This is called after the user consents. This method calls :meth:`requests_oauthlib.OAuth2Session.fetch_token` and specifies the client configuration's token URI (usually Google's token server). Args: kwargs: Arguments passed through to :meth:`requests_oauthlib.OAuth2Session.fetch_token`. At least one of ``code`` or ``authorization_response`` must be specified. Returns: Mapping[str, str]: The obtained tokens. Typically, you will not use return value of this function and instead and use :meth:`credentials` to obtain a :class:`~google.auth.credentials.Credentials` instance. """ kwargs.setdefault("client_secret", self.client_config["client_secret"]) kwargs.setdefault("code_verifier", self.code_verifier) return self.oauth2session.fetch_token(self.client_config["token_uri"], **kwargs) @property def credentials(self): """Returns credentials from the OAuth 2.0 session. :meth:`fetch_token` must be called before accessing this. This method constructs a :class:`google.oauth2.credentials.Credentials` class using the session's token and the client config. Returns: google.oauth2.credentials.Credentials: The constructed credentials. Raises: ValueError: If there is no access token in the session. """ return google_auth_oauthlib.helpers.credentials_from_session( self.oauth2session, self.client_config ) def authorized_session(self): """Returns a :class:`requests.Session` authorized with credentials. :meth:`fetch_token` must be called before this method. This method constructs a :class:`google.auth.transport.requests.AuthorizedSession` class using this flow's :attr:`credentials`. Returns: google.auth.transport.requests.AuthorizedSession: The constructed session. """ return google.auth.transport.requests.AuthorizedSession(self.credentials) class InstalledAppFlow(Flow): """Authorization flow helper for installed applications. This :class:`Flow` subclass makes it easier to perform the `Installed Application Authorization Flow`_. This flow is useful for local development or applications that are installed on a desktop operating system. This flow has two strategies: The console strategy provided by :meth:`run_console` and the local server strategy provided by :meth:`run_local_server`. Example:: from google_auth_oauthlib.flow import InstalledAppFlow flow = InstalledAppFlow.from_client_secrets_file( 'client_secrets.json', scopes=['profile', 'email']) flow.run_local_server() session = flow.authorized_session() profile_info = session.get( 'https://www.googleapis.com/userinfo/v2/me').json() print(profile_info) # {'name': '...', 'email': '...', ...} Note that these aren't the only two ways to accomplish the installed application flow, they are just the most common ways. You can use the :class:`Flow` class to perform the same flow with different methods of presenting the authorization URL to the user or obtaining the authorization response, such as using an embedded web view. .. _Installed Application Authorization Flow: https://developers.google.com/api-client-library/python/auth /installed-app """ _OOB_REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob" _DEFAULT_AUTH_PROMPT_MESSAGE = ( "Please visit this URL to authorize this application: {url}" ) """str: The message to display when prompting the user for authorization.""" _DEFAULT_AUTH_CODE_MESSAGE = "Enter the authorization code: " """str: The message to display when prompting the user for the authorization code. Used only by the console strategy.""" _DEFAULT_WEB_SUCCESS_MESSAGE = ( "The authentication flow has completed. You may close this window." ) def run_console( self, authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE, authorization_code_message=_DEFAULT_AUTH_CODE_MESSAGE, **kwargs ): """Run the flow using the console strategy. The console strategy instructs the user to open the authorization URL in their browser. Once the authorization is complete the authorization server will give the user a code. The user then must copy & paste this code into the application. The code is then exchanged for a token. Args: authorization_prompt_message (str): The message to display to tell the user to navigate to the authorization URL. authorization_code_message (str): The message to display when prompting the user for the authorization code. kwargs: Additional keyword arguments passed through to :meth:`authorization_url`. Returns: google.oauth2.credentials.Credentials: The OAuth 2.0 credentials for the user. """ kwargs.setdefault("prompt", "consent") self.redirect_uri = self._OOB_REDIRECT_URI auth_url, _ = self.authorization_url(**kwargs) print(authorization_prompt_message.format(url=auth_url)) code = input(authorization_code_message) self.fetch_token(code=code) return self.credentials def run_local_server( self, host="localhost", port=8080, authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE, success_message=_DEFAULT_WEB_SUCCESS_MESSAGE, open_browser=True, **kwargs ): """Run the flow using the server strategy. The server strategy instructs the user to open the authorization URL in their browser and will attempt to automatically open the URL for them. It will start a local web server to listen for the authorization response. Once authorization is complete the authorization server will redirect the user's browser to the local web server. The web server will get the authorization code from the response and shutdown. The code is then exchanged for a token. Args: host (str): The hostname for the local redirect server. This will be served over http, not https. port (int): The port for the local redirect server. authorization_prompt_message (str): The message to display to tell the user to navigate to the authorization URL. success_message (str): The message to display in the web browser the authorization flow is complete. open_browser (bool): Whether or not to open the authorization URL in the user's browser. kwargs: Additional keyword arguments passed through to :meth:`authorization_url`. Returns: google.oauth2.credentials.Credentials: The OAuth 2.0 credentials for the user. """ wsgi_app = _RedirectWSGIApp(success_message) # Fail fast if the address is occupied wsgiref.simple_server.WSGIServer.allow_reuse_address = False local_server = wsgiref.simple_server.make_server( host, port, wsgi_app, handler_class=_WSGIRequestHandler ) self.redirect_uri = "http://{}:{}/".format(host, local_server.server_port) auth_url, _ = self.authorization_url(**kwargs) if open_browser: webbrowser.open(auth_url, new=1, autoraise=True) print(authorization_prompt_message.format(url=auth_url)) local_server.handle_request() # Note: using https here because oauthlib is very picky that # OAuth 2.0 should only occur over https. authorization_response = wsgi_app.last_request_uri.replace("http", "https") self.fetch_token(authorization_response=authorization_response) # This closes the socket local_server.server_close() return self.credentials class _WSGIRequestHandler(wsgiref.simple_server.WSGIRequestHandler): """Custom WSGIRequestHandler. Uses a named logger instead of printing to stderr. """ def log_message(self, format, *args): # pylint: disable=redefined-builtin # (format is the argument name defined in the superclass.) _LOGGER.info(format, *args) class _RedirectWSGIApp(object): """WSGI app to handle the authorization redirect. Stores the request URI and displays the given success message. """ def __init__(self, success_message): """ Args: success_message (str): The message to display in the web browser the authorization flow is complete. """ self.last_request_uri = None self._success_message = success_message def __call__(self, environ, start_response): """WSGI Callable. Args: environ (Mapping[str, Any]): The WSGI environment. start_response (Callable[str, list]): The WSGI start_response callable. Returns: Iterable[bytes]: The response body. """ start_response("200 OK", [("Content-type", "text/plain")]) self.last_request_uri = wsgiref.util.request_uri(environ) return [self._success_message.encode("utf-8")] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib/helpers.py0000664000000000017530000001173700000000000023023 0ustar00root00000000000000# Copyright 2017 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Integration helpers. This module provides helpers for integrating with `requests-oauthlib`_. Typically, you'll want to use the higher-level helpers in :mod:`google_auth_oauthlib.flow`. .. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ """ import datetime import json import google.oauth2.credentials import requests_oauthlib _REQUIRED_CONFIG_KEYS = frozenset(("auth_uri", "token_uri", "client_id")) def session_from_client_config(client_config, scopes, **kwargs): """Creates a :class:`requests_oauthlib.OAuth2Session` from client configuration loaded from a Google-format client secrets file. Args: client_config (Mapping[str, Any]): The client configuration in the Google `client secrets`_ format. scopes (Sequence[str]): The list of scopes to request during the flow. kwargs: Any additional parameters passed to :class:`requests_oauthlib.OAuth2Session` Raises: ValueError: If the client configuration is not in the correct format. Returns: Tuple[requests_oauthlib.OAuth2Session, Mapping[str, Any]]: The new oauthlib session and the validated client configuration. .. _client secrets: https://developers.google.com/api-client-library/python/guide /aaa_client_secrets """ if "web" in client_config: config = client_config["web"] elif "installed" in client_config: config = client_config["installed"] else: raise ValueError("Client secrets must be for a web or installed app.") if not _REQUIRED_CONFIG_KEYS.issubset(config.keys()): raise ValueError("Client secrets is not in the correct format.") session = requests_oauthlib.OAuth2Session( client_id=config["client_id"], scope=scopes, **kwargs ) return session, client_config def session_from_client_secrets_file(client_secrets_file, scopes, **kwargs): """Creates a :class:`requests_oauthlib.OAuth2Session` instance from a Google-format client secrets file. Args: client_secrets_file (str): The path to the `client secrets`_ .json file. scopes (Sequence[str]): The list of scopes to request during the flow. kwargs: Any additional parameters passed to :class:`requests_oauthlib.OAuth2Session` Returns: Tuple[requests_oauthlib.OAuth2Session, Mapping[str, Any]]: The new oauthlib session and the validated client configuration. .. _client secrets: https://developers.google.com/api-client-library/python/guide /aaa_client_secrets """ with open(client_secrets_file, "r") as json_file: client_config = json.load(json_file) return session_from_client_config(client_config, scopes, **kwargs) def credentials_from_session(session, client_config=None): """Creates :class:`google.oauth2.credentials.Credentials` from a :class:`requests_oauthlib.OAuth2Session`. :meth:`fetch_token` must be called on the session before before calling this. This uses the session's auth token and the provided client configuration to create :class:`google.oauth2.credentials.Credentials`. This allows you to use the credentials from the session with Google API client libraries. Args: session (requests_oauthlib.OAuth2Session): The OAuth 2.0 session. client_config (Mapping[str, Any]): The subset of the client configuration to use. For example, if you have a web client you would pass in `client_config['web']`. Returns: google.oauth2.credentials.Credentials: The constructed credentials. Raises: ValueError: If there is no access token in the session. """ client_config = client_config if client_config is not None else {} if not session.token: raise ValueError( "There is no access token for this session, did you call " "fetch_token?" ) credentials = google.oauth2.credentials.Credentials( session.token["access_token"], refresh_token=session.token.get("refresh_token"), id_token=session.token.get("id_token"), token_uri=client_config.get("token_uri"), client_id=client_config.get("client_id"), client_secret=client_config.get("client_secret"), scopes=session.scope, ) credentials.expiry = datetime.datetime.utcfromtimestamp(session.token["expires_at"]) return credentials ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib/interactive.py0000664000000000017530000001001700000000000023664 0ustar00root00000000000000# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Get user credentials from interactive code environments. This module contains helpers for getting user credentials from interactive code environments installed on a development machine, such as Jupyter notebooks. """ from __future__ import absolute_import import google_auth_oauthlib.flow def get_user_credentials(scopes, client_id, client_secret): """Gets credentials associated with your Google user account. This function authenticates using your user credentials by going through the OAuth 2.0 flow. You'll open a browser window to authenticate to your Google account. The permissions it requests correspond to the scopes you've provided. To obtain the ``client_id`` and ``client_secret``, create an **OAuth client ID** with application type **Other** from the `Credentials page on the Google Developer's Console `_. Learn more with the `Authenticating as an end user `_ guide. Args: scopes (Sequence[str]): A list of scopes to use when authenticating to Google APIs. See the `list of OAuth 2.0 scopes for Google APIs `_. client_id (str): A string that identifies your application to Google APIs. Find this value in the `Credentials page on the Google Developer's Console `_. client_secret (str): A string that verifies your application to Google APIs. Find this value in the `Credentials page on the Google Developer's Console `_. Returns: google.oauth2.credentials.Credentials: The OAuth 2.0 credentials for the user. Examples: Get credentials for your user account and use them to run a query with BigQuery:: import google_auth_oauthlib # TODO: Create a client ID for your project. client_id = "YOUR-CLIENT-ID.apps.googleusercontent.com" client_secret = "abc_ThIsIsAsEcReT" # TODO: Choose the needed scopes for your applications. scopes = ["https://www.googleapis.com/auth/cloud-platform"] credentials = google_auth_oauthlib.get_user_credentials( scopes, client_id, client_secret ) # 1. Open the link. # 2. Authorize the application to have access to your account. # 3. Copy and paste the authorization code to the prompt. # Use the credentials to construct a client for Google APIs. from google.cloud import bigquery bigquery_client = bigquery.Client( credentials=credentials, project="your-project-id" ) print(list(bigquery_client.query("SELECT 1").result())) """ client_config = { "installed": { "client_id": client_id, "client_secret": client_secret, "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob"], "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", } } app_flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_config( client_config, scopes=scopes ) return app_flow.run_console() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5312295 google-auth-oauthlib-0.4.2/google_auth_oauthlib/tool/0002755000000000017530000000000000000000000021753 5ustar00root00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib/tool/__init__.py0000664000000000017530000000000000000000000024052 0ustar00root00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib/tool/__main__.py0000664000000000017530000001024100000000000024043 0ustar00root00000000000000# Copyright (C) 2017 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Command-line tool for obtaining authorization and credentials from a user. This tool uses the OAuth 2.0 Authorization Code grant as described in `section 1.3.1 of RFC6749`_ and implemeted by :class:`google_auth_oauthlib.flow.Flow`. This tool is intended for assist developers in obtaining credentials for testing applications where it may not be possible or easy to run a complete OAuth 2.0 authorization flow, especially in the case of code samples or embedded devices without input / display capabilities. This is not intended for production use where a combination of companion and on-device applications should complete the OAuth 2.0 authorization flow to get authorization from the users. .. _section 1.3.1 of RFC6749: https://tools.ietf.org/html/rfc6749#section-1.3.1 """ import json import os import os.path import click import google_auth_oauthlib.flow APP_NAME = "google-oauthlib-tool" DEFAULT_CREDENTIALS_FILENAME = "credentials.json" @click.command() @click.option( "--client-secrets", metavar="", required=True, help="Path to OAuth2 client secret JSON file.", ) @click.option( "--scope", multiple=True, metavar="", required=True, help="API scopes to authorize access for.", ) @click.option( "--save", is_flag=True, metavar="", show_default=True, default=False, help="Save the credentials to file.", ) @click.option( "--credentials", metavar="", show_default=True, default=os.path.join(click.get_app_dir(APP_NAME), DEFAULT_CREDENTIALS_FILENAME), help="Path to store OAuth2 credentials.", ) @click.option( "--headless", is_flag=True, metavar="", show_default=True, default=False, help="Run a console based flow.", ) def main(client_secrets, scope, save, credentials, headless): """Command-line tool for obtaining authorization and credentials from a user. This tool uses the OAuth 2.0 Authorization Code grant as described in section 1.3.1 of RFC6749: https://tools.ietf.org/html/rfc6749#section-1.3.1 This tool is intended for assist developers in obtaining credentials for testing applications where it may not be possible or easy to run a complete OAuth 2.0 authorization flow, especially in the case of code samples or embedded devices without input / display capabilities. This is not intended for production use where a combination of companion and on-device applications should complete the OAuth 2.0 authorization flow to get authorization from the users. """ flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file( client_secrets, scopes=scope ) if not headless: creds = flow.run_local_server() else: creds = flow.run_console() creds_data = { "token": creds.token, "refresh_token": creds.refresh_token, "token_uri": creds.token_uri, "client_id": creds.client_id, "client_secret": creds.client_secret, "scopes": creds.scopes, } if save: del creds_data["token"] config_path = os.path.dirname(credentials) if config_path and not os.path.isdir(config_path): os.makedirs(config_path) with open(credentials, "w") as outfile: json.dump(creds_data, outfile) click.echo("credentials saved: %s" % credentials) else: click.echo(json.dumps(creds_data)) if __name__ == "__main__": # pylint doesn't realize that click has changed the function signature. main() # pylint: disable=no-value-for-parameter ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5312295 google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/0002755000000000017530000000000000000000000022470 5ustar00root00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903875.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/PKG-INFO0000644000000000017530000000506200000000000023566 0ustar00root00000000000000Metadata-Version: 2.1 Name: google-auth-oauthlib Version: 0.4.2 Summary: Google Authentication Library Home-page: https://github.com/GoogleCloudPlatform/google-auth-library-python-oauthlib Author: Google Cloud Platform Author-email: jonwayne+google-auth@google.com License: Apache 2.0 Description: oauthlib integration for Google Auth ==================================== |pypi| This library provides `oauthlib`_ integration with `google-auth`_. .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=master :target: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib .. |docs| image:: https://readthedocs.org/projects/google-auth-oauthlib/badge/?version=latest :target: https://google-auth-oauthlib.readthedocs.io/en/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg :target: https://pypi.python.org/pypi/google-auth-oauthlib .. _oauthlib: https://github.com/idan/oauthlib .. _google-auth: https://github.com/googleapis/google-auth-library-python Installing ---------- You can install using `pip`_:: $ pip install google-auth-oauthlib .. _pip: https://pip.pypa.io/en/stable/ Documentation ------------- The latest documentation is available at `google-auth-oauthlib.readthedocs.io`_. .. _google-auth-oauthlib.readthedocs.io: http://google-auth-oauthlib.readthedocs.io/ License ------- Apache 2.0 - See `the LICENSE`_ for more information. .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/master/LICENSE Keywords: google auth oauth client oauthlib Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Requires-Python: >=3.6 Provides-Extra: tool ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903875.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/SOURCES.txt0000644000000000017530000000122700000000000024354 0ustar00root00000000000000LICENSE MANIFEST.in README.rst setup.cfg setup.py google_auth_oauthlib/__init__.py google_auth_oauthlib/flow.py google_auth_oauthlib/helpers.py google_auth_oauthlib/interactive.py google_auth_oauthlib.egg-info/PKG-INFO google_auth_oauthlib.egg-info/SOURCES.txt google_auth_oauthlib.egg-info/dependency_links.txt google_auth_oauthlib.egg-info/entry_points.txt google_auth_oauthlib.egg-info/requires.txt google_auth_oauthlib.egg-info/top_level.txt google_auth_oauthlib/tool/__init__.py google_auth_oauthlib/tool/__main__.py tests/unit/test_flow.py tests/unit/test_helpers.py tests/unit/test_interactive.py tests/unit/test_tool.py tests/unit/data/client_secrets.json././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903875.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/dependency_links.txt0000644000000000017530000000000100000000000026534 0ustar00root00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903875.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/entry_points.txt0000644000000000017530000000013100000000000025757 0ustar00root00000000000000[console_scripts] google-oauthlib-tool = google_auth_oauthlib.tool.__main__:main [tool] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903875.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/requires.txt0000644000000000017530000000006300000000000025065 0ustar00root00000000000000google-auth requests-oauthlib>=0.7.0 [tool] click ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903875.0 google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/top_level.txt0000644000000000017530000000002500000000000025215 0ustar00root00000000000000google_auth_oauthlib ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5312295 google-auth-oauthlib-0.4.2/setup.cfg0000664000000000017530000000010300000000000016425 0ustar00root00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/setup.py0000664000000000017530000000421700000000000016330 0ustar00root00000000000000# Copyright 2014 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import io from setuptools import find_packages from setuptools import setup TOOL_DEPENDENCIES = "click" DEPENDENCIES = ("google-auth", "requests-oauthlib>=0.7.0") with io.open("README.rst", "r") as fh: long_description = fh.read() version = "0.4.2" setup( name="google-auth-oauthlib", version=version, author="Google Cloud Platform", author_email="jonwayne+google-auth@google.com", description="Google Authentication Library", long_description=long_description, url="https://github.com/GoogleCloudPlatform/google-auth-library-python-oauthlib", packages=find_packages(exclude=("tests*",)), install_requires=DEPENDENCIES, extras_require={"tool": TOOL_DEPENDENCIES}, entry_points={ "console_scripts": [ "google-oauthlib-tool" "=google_auth_oauthlib.tool.__main__:main [tool]" ] }, python_requires=">=3.6", license="Apache 2.0", keywords="google auth oauth client oauthlib", classifiers=[ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Operating System :: OS Independent", "Topic :: Internet :: WWW/HTTP", ], ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5272295 google-auth-oauthlib-0.4.2/tests/0002755000000000017530000000000000000000000015754 5ustar00root00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5312295 google-auth-oauthlib-0.4.2/tests/unit/0002755000000000017530000000000000000000000016733 5ustar00root00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1603903875.5312295 google-auth-oauthlib-0.4.2/tests/unit/data/0002755000000000017530000000000000000000000017644 5ustar00root00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/tests/unit/data/client_secrets.json0000664000000000017530000000067200000000000023552 0ustar00root00000000000000{ "web": { "client_id": "example.apps.googleusercontent.com", "project_id": "example", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_secret": "itsasecrettoeveryone", "redirect_uris": [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost" ] } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/tests/unit/test_flow.py0000664000000000017530000003532500000000000021323 0ustar00root00000000000000# Copyright 2017 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import concurrent.futures import datetime from functools import partial import json import os import re import random import socket import mock import pytest import requests from six.moves import urllib from google_auth_oauthlib import flow DATA_DIR = os.path.join(os.path.dirname(__file__), "data") CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json") with open(CLIENT_SECRETS_FILE, "r") as fh: CLIENT_SECRETS_INFO = json.load(fh) class TestFlow(object): def test_from_client_secrets_file(self): instance = flow.Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes ) assert instance.client_config == CLIENT_SECRETS_INFO["web"] assert ( instance.oauth2session.client_id == CLIENT_SECRETS_INFO["web"]["client_id"] ) assert instance.oauth2session.scope == mock.sentinel.scopes def test_from_client_secrets_file_with_redirect_uri(self): instance = flow.Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes, redirect_uri=mock.sentinel.redirect_uri, ) assert ( instance.redirect_uri == instance.oauth2session.redirect_uri == mock.sentinel.redirect_uri ) def test_from_client_config_installed(self): client_config = {"installed": CLIENT_SECRETS_INFO["web"]} instance = flow.Flow.from_client_config( client_config, scopes=mock.sentinel.scopes ) assert instance.client_config == client_config["installed"] assert ( instance.oauth2session.client_id == client_config["installed"]["client_id"] ) assert instance.oauth2session.scope == mock.sentinel.scopes def test_from_client_config_with_redirect_uri(self): client_config = {"installed": CLIENT_SECRETS_INFO["web"]} instance = flow.Flow.from_client_config( client_config, scopes=mock.sentinel.scopes, redirect_uri=mock.sentinel.redirect_uri, ) assert ( instance.redirect_uri == instance.oauth2session.redirect_uri == mock.sentinel.redirect_uri ) def test_from_client_config_bad_format(self): with pytest.raises(ValueError): flow.Flow.from_client_config({}, scopes=mock.sentinel.scopes) @pytest.fixture def instance(self): yield flow.Flow.from_client_config( CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes ) def test_redirect_uri(self, instance): instance.redirect_uri = mock.sentinel.redirect_uri assert ( instance.redirect_uri == instance.oauth2session.redirect_uri == mock.sentinel.redirect_uri ) def test_authorization_url(self, instance): scope = "scope_one" instance.oauth2session.scope = [scope] authorization_url_patch = mock.patch.object( instance.oauth2session, "authorization_url", wraps=instance.oauth2session.authorization_url, ) with authorization_url_patch as authorization_url_spy: url, _ = instance.authorization_url(prompt="consent") assert CLIENT_SECRETS_INFO["web"]["auth_uri"] in url assert scope in url authorization_url_spy.assert_called_with( CLIENT_SECRETS_INFO["web"]["auth_uri"], access_type="offline", prompt="consent", ) def test_authorization_url_code_verifier(self, instance): scope = "scope_one" instance.oauth2session.scope = [scope] instance.code_verifier = "amanaplanacanalpanama" authorization_url_patch = mock.patch.object( instance.oauth2session, "authorization_url", wraps=instance.oauth2session.authorization_url, ) with authorization_url_patch as authorization_url_spy: url, _ = instance.authorization_url(prompt="consent") assert CLIENT_SECRETS_INFO["web"]["auth_uri"] in url assert scope in url authorization_url_spy.assert_called_with( CLIENT_SECRETS_INFO["web"]["auth_uri"], access_type="offline", prompt="consent", code_challenge="2yN0TOdl0gkGwFOmtfx3f913tgEaLM2d2S0WlmG1Z6Q", code_challenge_method="S256", ) def test_authorization_url_access_type(self, instance): scope = "scope_one" instance.oauth2session.scope = [scope] instance.code_verifier = "amanaplanacanalpanama" authorization_url_patch = mock.patch.object( instance.oauth2session, "authorization_url", wraps=instance.oauth2session.authorization_url, ) with authorization_url_patch as authorization_url_spy: url, _ = instance.authorization_url(access_type="meep") assert CLIENT_SECRETS_INFO["web"]["auth_uri"] in url assert scope in url authorization_url_spy.assert_called_with( CLIENT_SECRETS_INFO["web"]["auth_uri"], access_type="meep", code_challenge="2yN0TOdl0gkGwFOmtfx3f913tgEaLM2d2S0WlmG1Z6Q", code_challenge_method="S256", ) def test_authorization_url_generated_verifier(self): scope = "scope_one" instance = flow.Flow.from_client_config( CLIENT_SECRETS_INFO, scopes=[scope], autogenerate_code_verifier=True ) authorization_url_path = mock.patch.object( instance.oauth2session, "authorization_url", wraps=instance.oauth2session.authorization_url, ) with authorization_url_path as authorization_url_spy: instance.authorization_url() _, kwargs = authorization_url_spy.call_args_list[0] assert kwargs["code_challenge_method"] == "S256" assert len(instance.code_verifier) == 128 assert len(kwargs["code_challenge"]) == 43 valid_verifier = r"^[A-Za-z0-9-._~]*$" valid_challenge = r"^[A-Za-z0-9-_]*$" assert re.match(valid_verifier, instance.code_verifier) assert re.match(valid_challenge, kwargs["code_challenge"]) def test_fetch_token(self, instance): instance.code_verifier = "amanaplanacanalpanama" fetch_token_patch = mock.patch.object( instance.oauth2session, "fetch_token", autospec=True, return_value=mock.sentinel.token, ) with fetch_token_patch as fetch_token_mock: token = instance.fetch_token(code=mock.sentinel.code) assert token == mock.sentinel.token fetch_token_mock.assert_called_with( CLIENT_SECRETS_INFO["web"]["token_uri"], client_secret=CLIENT_SECRETS_INFO["web"]["client_secret"], code=mock.sentinel.code, code_verifier="amanaplanacanalpanama", ) def test_credentials(self, instance): instance.oauth2session.token = { "access_token": mock.sentinel.access_token, "refresh_token": mock.sentinel.refresh_token, "id_token": mock.sentinel.id_token, "expires_at": 643969200.0, } credentials = instance.credentials assert credentials.token == mock.sentinel.access_token assert credentials.expiry == datetime.datetime(1990, 5, 29, 8, 20, 0) assert credentials._refresh_token == mock.sentinel.refresh_token assert credentials.id_token == mock.sentinel.id_token assert credentials._client_id == CLIENT_SECRETS_INFO["web"]["client_id"] assert credentials._client_secret == CLIENT_SECRETS_INFO["web"]["client_secret"] assert credentials._token_uri == CLIENT_SECRETS_INFO["web"]["token_uri"] def test_authorized_session(self, instance): instance.oauth2session.token = { "access_token": mock.sentinel.access_token, "refresh_token": mock.sentinel.refresh_token, "id_token": mock.sentinel.id_token, "expires_at": 643969200.0, } session = instance.authorized_session() assert session.credentials.token == mock.sentinel.access_token class TestInstalledAppFlow(object): SCOPES = ["email", "profile"] REDIRECT_REQUEST_PATH = "/?code=code&state=state" @pytest.fixture def instance(self): yield flow.InstalledAppFlow.from_client_config( CLIENT_SECRETS_INFO, scopes=self.SCOPES ) @pytest.fixture def port(self): # Creating a new server at the same port will result in # a 'Address already in use' error for a brief # period of time after the socket has been closed. # Work around this in the tests by choosing a random port. # https://stackoverflow.com/questions/6380057/python-binding-socket-address-already-in-use yield random.randrange(60400, 60900) @pytest.fixture def socket(self, port): s = socket.socket() s.bind(("localhost", port)) yield s s.close() @pytest.fixture def mock_fetch_token(self, instance): def set_token(*args, **kwargs): instance.oauth2session.token = { "access_token": mock.sentinel.access_token, "refresh_token": mock.sentinel.refresh_token, "id_token": mock.sentinel.id_token, "expires_at": 643969200.0, } fetch_token_patch = mock.patch.object( instance.oauth2session, "fetch_token", autospec=True, side_effect=set_token ) with fetch_token_patch as fetch_token_mock: yield fetch_token_mock @mock.patch("google_auth_oauthlib.flow.input", autospec=True) def test_run_console(self, input_mock, instance, mock_fetch_token): input_mock.return_value = mock.sentinel.code instance.code_verifier = "amanaplanacanalpanama" credentials = instance.run_console() assert credentials.token == mock.sentinel.access_token assert credentials._refresh_token == mock.sentinel.refresh_token assert credentials.id_token == mock.sentinel.id_token mock_fetch_token.assert_called_with( CLIENT_SECRETS_INFO["web"]["token_uri"], client_secret=CLIENT_SECRETS_INFO["web"]["client_secret"], code=mock.sentinel.code, code_verifier="amanaplanacanalpanama", ) @pytest.mark.webtest @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True) def test_run_local_server(self, webbrowser_mock, instance, mock_fetch_token, port): auth_redirect_url = urllib.parse.urljoin( f"http://localhost:{port}", self.REDIRECT_REQUEST_PATH ) with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: future = pool.submit(partial(instance.run_local_server, port=port)) while not future.done(): try: requests.get(auth_redirect_url) except requests.ConnectionError: # pragma: NO COVER pass credentials = future.result() assert credentials.token == mock.sentinel.access_token assert credentials._refresh_token == mock.sentinel.refresh_token assert credentials.id_token == mock.sentinel.id_token assert webbrowser_mock.open.called expected_auth_response = auth_redirect_url.replace("http", "https") mock_fetch_token.assert_called_with( CLIENT_SECRETS_INFO["web"]["token_uri"], client_secret=CLIENT_SECRETS_INFO["web"]["client_secret"], authorization_response=expected_auth_response, code_verifier=None, ) @pytest.mark.webtest @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True) def test_run_local_server_code_verifier( self, webbrowser_mock, instance, mock_fetch_token, port ): auth_redirect_url = urllib.parse.urljoin( f"http://localhost:{port}", self.REDIRECT_REQUEST_PATH ) instance.code_verifier = "amanaplanacanalpanama" with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: future = pool.submit(partial(instance.run_local_server, port=port)) while not future.done(): try: requests.get(auth_redirect_url) except requests.ConnectionError: # pragma: NO COVER pass credentials = future.result() assert credentials.token == mock.sentinel.access_token assert credentials._refresh_token == mock.sentinel.refresh_token assert credentials.id_token == mock.sentinel.id_token assert webbrowser_mock.open.called expected_auth_response = auth_redirect_url.replace("http", "https") mock_fetch_token.assert_called_with( CLIENT_SECRETS_INFO["web"]["token_uri"], client_secret=CLIENT_SECRETS_INFO["web"]["client_secret"], authorization_response=expected_auth_response, code_verifier="amanaplanacanalpanama", ) @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True) @mock.patch("wsgiref.simple_server.make_server", autospec=True) def test_run_local_server_no_browser( self, make_server_mock, webbrowser_mock, instance, mock_fetch_token ): def assign_last_request_uri(host, port, wsgi_app, **kwargs): wsgi_app.last_request_uri = self.REDIRECT_REQUEST_PATH return mock.Mock() make_server_mock.side_effect = assign_last_request_uri instance.run_local_server(open_browser=False) assert not webbrowser_mock.open.called @pytest.mark.webtest @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True) def test_run_local_server_occupied_port( self, webbrowser_mock, instance, mock_fetch_token, port, socket ): # socket fixture is already bound to http://localhost:port instance.run_local_server with pytest.raises(OSError) as exc: instance.run_local_server(port=port) assert "address already in use" in exc.strerror.lower() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/tests/unit/test_helpers.py0000664000000000017530000000642600000000000022016 0ustar00root00000000000000# Copyright 2017 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import json import os import mock import pytest from google_auth_oauthlib import helpers DATA_DIR = os.path.join(os.path.dirname(__file__), "data") CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json") with open(CLIENT_SECRETS_FILE, "r") as fh: CLIENT_SECRETS_INFO = json.load(fh) def test_session_from_client_config_web(): session, config = helpers.session_from_client_config( CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes ) assert config == CLIENT_SECRETS_INFO assert session.client_id == CLIENT_SECRETS_INFO["web"]["client_id"] assert session.scope == mock.sentinel.scopes def test_session_from_client_config_installed(): info = {"installed": CLIENT_SECRETS_INFO["web"]} session, config = helpers.session_from_client_config( info, scopes=mock.sentinel.scopes ) assert config == info assert session.client_id == info["installed"]["client_id"] assert session.scope == mock.sentinel.scopes def test_session_from_client_config_bad_format(): with pytest.raises(ValueError): helpers.session_from_client_config({}, scopes=[]) def test_session_from_client_config_missing_keys(): with pytest.raises(ValueError): helpers.session_from_client_config({"web": {}}, scopes=[]) def test_session_from_client_secrets_file(): session, config = helpers.session_from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes ) assert config == CLIENT_SECRETS_INFO assert session.client_id == CLIENT_SECRETS_INFO["web"]["client_id"] assert session.scope == mock.sentinel.scopes @pytest.fixture def session(): session, _ = helpers.session_from_client_config( CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes ) yield session def test_credentials_from_session(session): session.token = { "access_token": mock.sentinel.access_token, "refresh_token": mock.sentinel.refresh_token, "id_token": mock.sentinel.id_token, "expires_at": 643969200.0, } credentials = helpers.credentials_from_session(session, CLIENT_SECRETS_INFO["web"]) assert credentials.token == mock.sentinel.access_token assert credentials.expiry == datetime.datetime(1990, 5, 29, 8, 20, 0) assert credentials._refresh_token == mock.sentinel.refresh_token assert credentials.id_token == mock.sentinel.id_token assert credentials._client_id == CLIENT_SECRETS_INFO["web"]["client_id"] assert credentials._client_secret == CLIENT_SECRETS_INFO["web"]["client_secret"] assert credentials._token_uri == CLIENT_SECRETS_INFO["web"]["token_uri"] def test_bad_credentials(session): with pytest.raises(ValueError): helpers.credentials_from_session(session) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/tests/unit/test_interactive.py0000664000000000017530000000271500000000000022666 0ustar00root00000000000000# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock def test_get_user_credentials(): from google_auth_oauthlib import flow from google_auth_oauthlib import interactive as module_under_test mock_flow_instance = mock.create_autospec(flow.InstalledAppFlow, instance=True) with mock.patch( "google_auth_oauthlib.flow.InstalledAppFlow", autospec=True ) as mock_flow: mock_flow.from_client_config.return_value = mock_flow_instance module_under_test.get_user_credentials( ["scopes"], "some-client-id", "shh-secret" ) mock_flow.from_client_config.assert_called_once_with(mock.ANY, scopes=["scopes"]) actual_client_config = mock_flow.from_client_config.call_args[0][0] assert actual_client_config["installed"]["client_id"] == "some-client-id" assert actual_client_config["installed"]["client_secret"] == "shh-secret" mock_flow_instance.run_console.assert_called_once() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603903732.0 google-auth-oauthlib-0.4.2/tests/unit/test_tool.py0000664000000000017530000001312100000000000021317 0ustar00root00000000000000# Copyright 2017 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import io import json import os.path import tempfile import click.testing import google.oauth2.credentials import mock import pytest import google_auth_oauthlib.flow import google_auth_oauthlib.tool.__main__ as cli DATA_DIR = os.path.join(os.path.dirname(__file__), "data") CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json") class TestMain(object): @pytest.fixture def runner(self): return click.testing.CliRunner() @pytest.fixture def dummy_credentials(self): return google.oauth2.credentials.Credentials( token="dummy_access_token", refresh_token="dummy_refresh_token", token_uri="dummy_token_uri", client_id="dummy_client_id", client_secret="dummy_client_secret", scopes=["dummy_scope1", "dummy_scope2"], ) @pytest.fixture def local_server_mock(self, dummy_credentials): run_local_server_patch = mock.patch.object( google_auth_oauthlib.flow.InstalledAppFlow, "run_local_server", autospec=True, ) with run_local_server_patch as flow: flow.return_value = dummy_credentials yield flow @pytest.fixture def console_mock(self, dummy_credentials): run_console_patch = mock.patch.object( google_auth_oauthlib.flow.InstalledAppFlow, "run_console", autospec=True ) with run_console_patch as flow: flow.return_value = dummy_credentials yield flow def test_help(self, runner): result = runner.invoke(cli.main, ["--help"]) assert not result.exception assert "RFC6749" in result.output assert "OAuth 2.0 authorization flow" in result.output assert "not intended for production use" in result.output assert result.exit_code == 0 def test_defaults(self, runner, dummy_credentials, local_server_mock): result = runner.invoke( cli.main, ["--client-secrets", CLIENT_SECRETS_FILE, "--scope", "somescope"] ) local_server_mock.assert_called_with(mock.ANY) assert not result.exception assert result.exit_code == 0 creds_data = json.loads(result.output) creds = google.oauth2.credentials.Credentials(**creds_data) assert creds.token == dummy_credentials.token assert creds.refresh_token == dummy_credentials.refresh_token assert creds.token_uri == dummy_credentials.token_uri assert creds.client_id == dummy_credentials.client_id assert creds.client_secret == dummy_credentials.client_secret assert creds.scopes == dummy_credentials.scopes def test_headless(self, runner, dummy_credentials, console_mock): result = runner.invoke( cli.main, [ "--client-secrets", CLIENT_SECRETS_FILE, "--scope", "somescope", "--headless", ], ) console_mock.assert_called_with(mock.ANY) assert not result.exception assert dummy_credentials.refresh_token in result.output assert result.exit_code == 0 def test_save_new_dir(self, runner, dummy_credentials, local_server_mock): credentials_tmpdir = tempfile.mkdtemp() credentials_path = os.path.join( credentials_tmpdir, "new-directory", "credentials.json" ) result = runner.invoke( cli.main, [ "--client-secrets", CLIENT_SECRETS_FILE, "--scope", "somescope", "--credentials", credentials_path, "--save", ], ) local_server_mock.assert_called_with(mock.ANY) assert not result.exception assert "saved" in result.output assert result.exit_code == 0 with io.open(credentials_path) as f: # pylint: disable=invalid-name creds_data = json.load(f) assert "access_token" not in creds_data creds = google.oauth2.credentials.Credentials(token=None, **creds_data) assert creds.token is None assert creds.refresh_token == dummy_credentials.refresh_token assert creds.token_uri == dummy_credentials.token_uri assert creds.client_id == dummy_credentials.client_id assert creds.client_secret == dummy_credentials.client_secret def test_save_existing_dir(self, runner, local_server_mock): credentials_tmpdir = tempfile.mkdtemp() result = runner.invoke( cli.main, [ "--client-secrets", CLIENT_SECRETS_FILE, "--scope", "somescope", "--credentials", os.path.join(credentials_tmpdir, "credentials.json"), "--save", ], ) local_server_mock.assert_called_with(mock.ANY) assert not result.exception assert "saved" in result.output assert result.exit_code == 0