././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5312295
google-auth-oauthlib-0.4.2/ 0002755 0000000 0001753 00000000000 00000000000 014612 5 ustar 00root 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/LICENSE 0000664 0000000 0001753 00000026132 00000000000 015623 0 ustar 00root 0000000 0000000 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.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/MANIFEST.in 0000664 0000000 0001753 00000001522 00000000000 016350 0 ustar 00root 0000000 0000000 # -*- 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 ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5312295
google-auth-oauthlib-0.4.2/PKG-INFO 0000644 0000000 0001753 00000005062 00000000000 015710 0 ustar 00root 0000000 0000000 Metadata-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
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/README.rst 0000664 0000000 0001753 00000002361 00000000000 016303 0 ustar 00root 0000000 0000000 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
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5312295
google-auth-oauthlib-0.4.2/google_auth_oauthlib/ 0002755 0000000 0001753 00000000000 00000000000 020776 5 ustar 00root 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib/__init__.py 0000664 0000000 0001753 00000001516 00000000000 023112 0 ustar 00root 0000000 0000000 # 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"]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib/flow.py 0000664 0000000 0001753 00000046233 00000000000 022327 0 ustar 00root 0000000 0000000 # 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")]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib/helpers.py 0000664 0000000 0001753 00000011737 00000000000 023023 0 ustar 00root 0000000 0000000 # 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
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib/interactive.py 0000664 0000000 0001753 00000010017 00000000000 023664 0 ustar 00root 0000000 0000000 # 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()
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5312295
google-auth-oauthlib-0.4.2/google_auth_oauthlib/tool/ 0002755 0000000 0001753 00000000000 00000000000 021753 5 ustar 00root 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib/tool/__init__.py 0000664 0000000 0001753 00000000000 00000000000 024052 0 ustar 00root 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib/tool/__main__.py 0000664 0000000 0001753 00000010241 00000000000 024043 0 ustar 00root 0000000 0000000 # 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
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5312295
google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/ 0002755 0000000 0001753 00000000000 00000000000 022470 5 ustar 00root 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903875.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/PKG-INFO 0000644 0000000 0001753 00000005062 00000000000 023566 0 ustar 00root 0000000 0000000 Metadata-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
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903875.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/SOURCES.txt 0000644 0000000 0001753 00000001227 00000000000 024354 0 ustar 00root 0000000 0000000 LICENSE
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 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903875.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/dependency_links.txt 0000644 0000000 0001753 00000000001 00000000000 026534 0 ustar 00root 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903875.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/entry_points.txt 0000644 0000000 0001753 00000000131 00000000000 025757 0 ustar 00root 0000000 0000000 [console_scripts]
google-oauthlib-tool = google_auth_oauthlib.tool.__main__:main [tool]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903875.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/requires.txt 0000644 0000000 0001753 00000000063 00000000000 025065 0 ustar 00root 0000000 0000000 google-auth
requests-oauthlib>=0.7.0
[tool]
click
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903875.0
google-auth-oauthlib-0.4.2/google_auth_oauthlib.egg-info/top_level.txt 0000644 0000000 0001753 00000000025 00000000000 025215 0 ustar 00root 0000000 0000000 google_auth_oauthlib
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5312295
google-auth-oauthlib-0.4.2/setup.cfg 0000664 0000000 0001753 00000000103 00000000000 016425 0 ustar 00root 0000000 0000000 [bdist_wheel]
universal = 1
[egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/setup.py 0000664 0000000 0001753 00000004217 00000000000 016330 0 ustar 00root 0000000 0000000 # 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",
],
)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5272295
google-auth-oauthlib-0.4.2/tests/ 0002755 0000000 0001753 00000000000 00000000000 015754 5 ustar 00root 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5312295
google-auth-oauthlib-0.4.2/tests/unit/ 0002755 0000000 0001753 00000000000 00000000000 016733 5 ustar 00root 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1603903875.5312295
google-auth-oauthlib-0.4.2/tests/unit/data/ 0002755 0000000 0001753 00000000000 00000000000 017644 5 ustar 00root 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/tests/unit/data/client_secrets.json 0000664 0000000 0001753 00000000672 00000000000 023552 0 ustar 00root 0000000 0000000 {
"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"
]
}
}
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/tests/unit/test_flow.py 0000664 0000000 0001753 00000035325 00000000000 021323 0 ustar 00root 0000000 0000000 # 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()
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/tests/unit/test_helpers.py 0000664 0000000 0001753 00000006426 00000000000 022016 0 ustar 00root 0000000 0000000 # 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)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/tests/unit/test_interactive.py 0000664 0000000 0001753 00000002715 00000000000 022666 0 ustar 00root 0000000 0000000 # 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()
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1603903732.0
google-auth-oauthlib-0.4.2/tests/unit/test_tool.py 0000664 0000000 0001753 00000013121 00000000000 021317 0 ustar 00root 0000000 0000000 # 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