diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/google_auth_oauthlib')
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 |