feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://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
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.

View File

@@ -0,0 +1,23 @@
# 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 <https://oauthlib.readthedocs.io/>`__
integration with `google-auth <https://google-auth.readthedocs.io/>`__.
"""
from .interactive import get_user_credentials
__all__ = ["get_user_credentials"]

View File

@@ -0,0 +1,565 @@
# 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. See
`Using OAuth 2.0 to Access Google APIs`_ for an overview of OAuth 2.0
authorization scenarios Google APIs support.
Here's an example of using :class:`InstalledAppFlow`::
from google_auth_oauthlib.flow import InstalledAppFlow
# Create the flow using the client secrets file from the Google API
# Console.
flow = InstalledAppFlow.from_client_secrets_file(
'client_secrets.json',
scopes=['profile', 'email'])
flow.run_local_server()
# You can use flow.credentials, or you can just get a requests session
# using flow.authorized_session.
session = flow.authorized_session()
profile_info = session.get(
'https://www.googleapis.com/userinfo/v2/me').json()
print(profile_info)
# {'name': '...', 'email': '...', ...}
.. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/
.. _OAuth 2.0 Authorization Flow:
https://tools.ietf.org/html/rfc6749#section-1.2
.. _Using OAuth 2.0 to Access Google APIs:
https://developers.google.com/identity/protocols/oauth2
"""
from base64 import urlsafe_b64encode
import hashlib
import json
import logging
import warnings
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
import google_auth_oauthlib.helpers
_LOGGER = logging.getLogger(__name__)
_OOB_REDIRECT_URIS = [
"urn:ietf:wg:oauth:2.0:oob",
"urn:ietf:wg:oauth:2.0:oob:auto",
"oob",
]
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/oauth2/web-server
#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://github.com/googleapis/google-api-python-client/blob
/main/docs/client-secrets.md
"""
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://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md
"""
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):
if value in _OOB_REDIRECT_URIS:
warnings.warn(
"'{}' is an OOB redirect URI. The OAuth out-of-band (OOB) flow is deprecated. "
"New clients will be unable to use this flow starting on Feb 28, 2022. "
"This flow will be deprecated for all clients on Oct 3, 2022. "
"Migrate to an alternative flow. "
"See https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html?m=1#disallowed-oob".format(
value
),
DeprecationWarning,
)
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 uses a 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 this isn't the only way to accomplish the installed
application flow, just one of the most common. 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://github.com/googleapis/google-api-python-client/blob/main/docs/oauth-installed.md
"""
_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.
.. deprecated:: 0.5.0
Use :meth:`run_local_server` instead.
The OAuth out-of-band (OOB) flow is deprecated. New clients will be unable to
use this flow starting on Feb 28, 2022. This flow will be deprecated
for all clients on Oct 3, 2022. Migrate to an alternative flow.
See https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html?m=1#disallowed-oob"
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")
warnings.warn(
"New clients will be unable to use `InstalledAppFlow.run_console` "
"starting on Feb 28, 2022. All clients will be unable to use this method starting on Oct 3, 2022. "
"Use `InstalledAppFlow.run_local_server` instead. For details on the OOB flow deprecation, "
"see https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html?m=1#disallowed-oob",
DeprecationWarning,
)
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",
bind_addr=None,
port=8080,
authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE,
success_message=_DEFAULT_WEB_SUCCESS_MESSAGE,
open_browser=True,
redirect_uri_trailing_slash=True,
timeout_seconds=None,
**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.
bind_addr (str): Optionally provide an ip address for the redirect
server to listen on when it is not the same as host
(e.g. in a container). Default value is None,
which means that the redirect server will listen
on the ip address specified in the host parameter.
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.
redirect_uri_trailing_slash (bool): whether or not to add trailing
slash when constructing the redirect_uri. Default value is True.
timeout_seconds (int): It will raise an error after the timeout timing
if there are no credentials response. The value is in seconds.
When set to None there is no timeout.
Default value is None.
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(
bind_addr or host, port, wsgi_app, handler_class=_WSGIRequestHandler
)
redirect_uri_format = (
"http://{}:{}/" if redirect_uri_trailing_slash else "http://{}:{}"
)
self.redirect_uri = redirect_uri_format.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.timeout = timeout_seconds
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; charset=utf-8")])
self.last_request_uri = wsgiref.util.request_uri(environ)
return [self._success_message.encode("utf-8")]

View File

@@ -0,0 +1,150 @@
# 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
from google.auth import external_account_authorized_user
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://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md
"""
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://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md
"""
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?"
)
if "3pi" in client_config:
credentials = external_account_authorized_user.Credentials(
token=session.token["access_token"],
refresh_token=session.token.get("refresh_token"),
token_url=client_config.get("token_uri"),
client_id=client_config.get("client_id"),
client_secret=client_config.get("client_secret"),
token_info_url=client_config.get("token_info_url"),
scopes=session.scope,
)
else:
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

View File

@@ -0,0 +1,173 @@
# 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 contextlib
import socket
import google_auth_oauthlib.flow
LOCALHOST = "localhost"
DEFAULT_PORTS_TO_TRY = 100
def is_port_open(port):
"""Check if a port is open on localhost.
Based on StackOverflow answer: https://stackoverflow.com/a/43238489/101923
Parameters
----------
port : int
A port to check on localhost.
Returns
-------
is_open : bool
True if a socket can be opened at the requested port.
"""
with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
try:
sock.bind((LOCALHOST, port))
sock.listen(1)
except socket.error:
is_open = False
else:
is_open = True
return is_open
def find_open_port(start=8080, stop=None):
"""Find an open port between ``start`` and ``stop``.
Parameters
----------
start : Optional[int]
Beginning of range of ports to try. Defaults to 8080.
stop : Optional[int]
End of range of ports to try (not including exactly equals ``stop``).
This function tries 100 possible ports if no ``stop`` is specified.
Returns
-------
Optional[int]
``None`` if no open port is found, otherwise an integer indicating an
open port.
"""
if not stop:
stop = start + DEFAULT_PORTS_TO_TRY
for port in range(start, stop):
if is_port_open(port):
return port
# No open ports found.
return None
def get_user_credentials(
scopes, client_id, client_secret, minimum_port=8080, maximum_port=None
):
"""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
<https://console.developers.google.com/apis/credentials>`_. Learn more
with the `Authenticating as an end user
<https://cloud.google.com/docs/authentication/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
<https://developers.google.com/identity/protocols/googlescopes>`_.
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
<https://console.developers.google.com/apis/credentials>`_.
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
<https://console.developers.google.com/apis/credentials>`_.
minimum_port (int):
Beginning of range of ports to try for redirect URI HTTP server.
Defaults to 8080.
maximum_port (Optional[int]):
End of range of ports to try (not including exactly equals ``stop``).
This function tries 100 possible ports if no ``stop`` is specified.
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
)
port = find_open_port(start=minimum_port, stop=maximum_port)
if not port:
raise ConnectionError("Could not find open port.")
return app_flow.run_local_server(host=LOCALHOST, port=port)