aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/google_auth_oauthlib
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/google_auth_oauthlib')
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/__init__.py23
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py507
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/helpers.py151
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/interactive.py172
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__main__.py124
6 files changed, 977 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/__init__.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/__init__.py
new file mode 100644
index 00000000..1905f9a4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/__init__.py
@@ -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"]
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py
new file mode 100644
index 00000000..e564ca43
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py
@@ -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")]
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/helpers.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/helpers.py
new file mode 100644
index 00000000..25462f4c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/helpers.py
@@ -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
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/interactive.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/interactive.py
new file mode 100644
index 00000000..b1ed990e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/interactive.py
@@ -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)
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__init__.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__main__.py b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__main__.py
new file mode 100644
index 00000000..db679a18
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/google_auth_oauthlib/tool/__main__.py
@@ -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