Update dashboard, memory, root +2 more (+3 ~5)
This commit is contained in:
@@ -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"]
|
||||
507
venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py
Normal file
507
venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py
Normal file
@@ -0,0 +1,507 @@
|
||||
# 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/latest/
|
||||
.. _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
|
||||
|
||||
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__)
|
||||
|
||||
|
||||
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=True,
|
||||
):
|
||||
"""
|
||||
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):
|
||||
"""The OAuth 2.0 redirect URI. Pass-through to
|
||||
``self.oauth2session.redirect_uri``."""
|
||||
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 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
|
||||
"""
|
||||
|
||||
_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_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,
|
||||
token_audience=None,
|
||||
browser=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 | None): The message to display to tell
|
||||
the user to navigate to the authorization URL. If None or empty,
|
||||
don't display anything.
|
||||
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.
|
||||
token_audience (str): Passed along with the request for an access
|
||||
token. Determines the endpoints with which the token can be
|
||||
used. Optional.
|
||||
browser (str): specify which browser to open for authentication. If not
|
||||
specified this defaults to default 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(
|
||||
bind_addr or host, port, wsgi_app, handler_class=_WSGIRequestHandler
|
||||
)
|
||||
|
||||
try:
|
||||
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:
|
||||
# if browser is None it defaults to default browser
|
||||
webbrowser.get(browser).open(auth_url, new=1, autoraise=True)
|
||||
|
||||
if authorization_prompt_message:
|
||||
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, audience=token_audience
|
||||
)
|
||||
finally:
|
||||
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")]
|
||||
@@ -0,0 +1,151 @@
|
||||
# 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/latest/
|
||||
"""
|
||||
|
||||
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,
|
||||
granted_scopes=session.token.get("scope"),
|
||||
)
|
||||
credentials.expiry = datetime.datetime.utcfromtimestamp(session.token["expires_at"])
|
||||
return credentials
|
||||
@@ -0,0 +1,172 @@
|
||||
# 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,
|
||||
"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)
|
||||
@@ -0,0 +1,124 @@
|
||||
# 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="<client_secret_json_file>",
|
||||
required=True,
|
||||
help="Path to OAuth2 client secret JSON file.",
|
||||
)
|
||||
@click.option(
|
||||
"--scope",
|
||||
multiple=True,
|
||||
metavar="<oauth2 scope>",
|
||||
required=True,
|
||||
help="API scopes to authorize access for.",
|
||||
)
|
||||
@click.option(
|
||||
"--save",
|
||||
is_flag=True,
|
||||
metavar="<save_mode>",
|
||||
show_default=True,
|
||||
default=False,
|
||||
help="Save the credentials to file.",
|
||||
)
|
||||
@click.option(
|
||||
"--credentials",
|
||||
metavar="<oauth2_credentials>",
|
||||
show_default=True,
|
||||
default=os.path.join(click.get_app_dir(APP_NAME), DEFAULT_CREDENTIALS_FILENAME),
|
||||
help="Path to store OAuth2 credentials.",
|
||||
)
|
||||
def main(client_secrets, scope, save, credentials):
|
||||
"""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 or samples.
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
creds = flow.run_local_server()
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user