about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py')
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py604
1 files changed, 604 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py
new file mode 100644
index 00000000..d5eb0cc1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py
@@ -0,0 +1,604 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming OAuth 2.0 RFC6749.
+"""
+import base64
+import hashlib
+import re
+import secrets
+import time
+import warnings
+
+from oauthlib.common import generate_token
+from oauthlib.oauth2.rfc6749 import tokens
+from oauthlib.oauth2.rfc6749.errors import (
+    InsecureTransportError, TokenExpiredError,
+)
+from oauthlib.oauth2.rfc6749.parameters import (
+    parse_token_response, prepare_token_request,
+    prepare_token_revocation_request,
+)
+from oauthlib.oauth2.rfc6749.utils import is_secure_transport
+
+AUTH_HEADER = 'auth_header'
+URI_QUERY = 'query'
+BODY = 'body'
+
+FORM_ENC_HEADERS = {
+    'Content-Type': 'application/x-www-form-urlencoded'
+}
+
+
+class Client:
+    """Base OAuth2 client responsible for access token management.
+
+    This class also acts as a generic interface providing methods common to all
+    client types such as ``prepare_authorization_request`` and
+    ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are
+    the recommended way of interacting with clients (as opposed to the abstract
+    prepare uri/body/etc methods). They are recommended over the older set
+    because they are easier to use (more consistent) and add a few additional
+    security checks, such as HTTPS and state checking.
+
+    Some of these methods require further implementation only provided by the
+    specific purpose clients such as
+    :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always
+    seek to use the client class matching the OAuth workflow you need. For
+    Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.
+
+    """
+    refresh_token_key = 'refresh_token'
+
+    def __init__(self, client_id,
+                 default_token_placement=AUTH_HEADER,
+                 token_type='Bearer',
+                 access_token=None,
+                 refresh_token=None,
+                 mac_key=None,
+                 mac_algorithm=None,
+                 token=None,
+                 scope=None,
+                 state=None,
+                 redirect_url=None,
+                 state_generator=generate_token,
+                 code_verifier=None,
+                 code_challenge=None,
+                 code_challenge_method=None,
+                 **kwargs):
+        """Initialize a client with commonly used attributes.
+
+        :param client_id: Client identifier given by the OAuth provider upon
+        registration.
+
+        :param default_token_placement: Tokens can be supplied in the Authorization
+        header (default), the URL query component (``query``) or the request
+        body (``body``).
+
+        :param token_type: OAuth 2 token type. Defaults to Bearer. Change this
+        if you specify the ``access_token`` parameter and know it is of a
+        different token type, such as a MAC, JWT or SAML token. Can
+        also be supplied as ``token_type`` inside the ``token`` dict parameter.
+
+        :param access_token: An access token (string) used to authenticate
+        requests to protected resources. Can also be supplied inside the
+        ``token`` dict parameter.
+
+        :param refresh_token: A refresh token (string) used to refresh expired
+        tokens. Can also be supplied inside the ``token`` dict parameter.
+
+        :param mac_key: Encryption key used with MAC tokens.
+
+        :param mac_algorithm:  Hashing algorithm for MAC tokens.
+
+        :param token: A dict of token attributes such as ``access_token``,
+        ``token_type`` and ``expires_at``.
+
+        :param scope: A list of default scopes to request authorization for.
+
+        :param state: A CSRF protection string used during authorization.
+
+        :param redirect_url: The redirection endpoint on the client side to which
+        the user returns after authorization.
+
+        :param state_generator: A no argument state generation callable. Defaults
+        to :py:meth:`oauthlib.common.generate_token`.
+
+        :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the
+        authorization request to the token request.
+
+        :param code_challenge: PKCE parameter. A challenge derived from the code verifier that is sent in the
+        authorization request, to be verified against later.
+
+        :param code_challenge_method: PKCE parameter. A method that was used to derive code challenge.
+        Defaults to "plain" if not present in the request.
+        """
+
+        self.client_id = client_id
+        self.default_token_placement = default_token_placement
+        self.token_type = token_type
+        self.access_token = access_token
+        self.refresh_token = refresh_token
+        self.mac_key = mac_key
+        self.mac_algorithm = mac_algorithm
+        self.token = token or {}
+        self.scope = scope
+        self.state_generator = state_generator
+        self.state = state
+        self.redirect_url = redirect_url
+        self.code_verifier = code_verifier
+        self.code_challenge = code_challenge
+        self.code_challenge_method = code_challenge_method
+        self.code = None
+        self.expires_in = None
+        self._expires_at = None
+        self.populate_token_attributes(self.token)
+
+    @property
+    def token_types(self):
+        """Supported token types and their respective methods
+
+        Additional tokens can be supported by extending this dictionary.
+
+        The Bearer token spec is stable and safe to use.
+
+        The MAC token spec is not yet stable and support for MAC tokens
+        is experimental and currently matching version 00 of the spec.
+        """
+        return {
+            'Bearer': self._add_bearer_token,
+            'MAC': self._add_mac_token
+        }
+
+    def prepare_request_uri(self, *args, **kwargs):
+        """Abstract method used to create request URIs."""
+        raise NotImplementedError("Must be implemented by inheriting classes.")
+
+    def prepare_request_body(self, *args, **kwargs):
+        """Abstract method used to create request bodies."""
+        raise NotImplementedError("Must be implemented by inheriting classes.")
+
+    def parse_request_uri_response(self, *args, **kwargs):
+        """Abstract method used to parse redirection responses."""
+        raise NotImplementedError("Must be implemented by inheriting classes.")
+
+    def add_token(self, uri, http_method='GET', body=None, headers=None,
+                  token_placement=None, **kwargs):
+        """Add token to the request uri, body or authorization header.
+
+        The access token type provides the client with the information
+        required to successfully utilize the access token to make a protected
+        resource request (along with type-specific attributes).  The client
+        MUST NOT use an access token if it does not understand the token
+        type.
+
+        For example, the "bearer" token type defined in
+        [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access
+        token string in the request:
+
+        .. code-block:: http
+
+            GET /resource/1 HTTP/1.1
+            Host: example.com
+            Authorization: Bearer mF_9.B5f-4.1JqM
+
+        while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is
+        utilized by issuing a MAC key together with the access token which is
+        used to sign certain components of the HTTP requests:
+
+        .. code-block:: http
+
+            GET /resource/1 HTTP/1.1
+            Host: example.com
+            Authorization: MAC id="h480djs93hd8",
+                                nonce="274312:dj83hs9s",
+                                mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
+
+        .. _`I-D.ietf-oauth-v2-bearer`: https://tools.ietf.org/html/rfc6749#section-12.2
+        .. _`I-D.ietf-oauth-v2-http-mac`: https://tools.ietf.org/html/rfc6749#section-12.2
+        """
+        if not is_secure_transport(uri):
+            raise InsecureTransportError()
+
+        token_placement = token_placement or self.default_token_placement
+
+        case_insensitive_token_types = {
+            k.lower(): v for k, v in self.token_types.items()}
+        if not self.token_type.lower() in case_insensitive_token_types:
+            raise ValueError("Unsupported token type: %s" % self.token_type)
+
+        if not (self.access_token or self.token.get('access_token')):
+            raise ValueError("Missing access token.")
+
+        if self._expires_at and self._expires_at < time.time():
+            raise TokenExpiredError()
+
+        return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,
+                                                                     headers, token_placement, **kwargs)
+
+    def prepare_authorization_request(self, authorization_url, state=None,
+                                      redirect_url=None, scope=None, **kwargs):
+        """Prepare the authorization request.
+
+        This is the first step in many OAuth flows in which the user is
+        redirected to a certain authorization URL. This method adds
+        required parameters to the authorization URL.
+
+        :param authorization_url: Provider authorization endpoint URL.
+        :param state: CSRF protection string. Will be automatically created if
+            not provided. The generated state is available via the ``state``
+            attribute. Clients should verify that the state is unchanged and
+            present in the authorization response. This verification is done
+            automatically if using the ``authorization_response`` parameter
+            with ``prepare_token_request``.
+        :param redirect_url: Redirect URL to which the user will be returned
+            after authorization. Must be provided unless previously setup with
+            the provider. If provided then it must also be provided in the
+            token request.
+        :param scope: List of scopes to request. Must be equal to
+            or a subset of the scopes granted when obtaining the refresh
+            token. If none is provided, the ones provided in the constructor are
+            used.
+        :param kwargs: Additional parameters to included in the request.
+        :returns: The prepared request tuple with (url, headers, body).
+        """
+        if not is_secure_transport(authorization_url):
+            raise InsecureTransportError()
+
+        self.state = state or self.state_generator()
+        self.redirect_url = redirect_url or self.redirect_url
+        # do not assign scope to self automatically anymore
+        scope = self.scope if scope is None else scope
+        auth_url = self.prepare_request_uri(
+            authorization_url, redirect_uri=self.redirect_url,
+            scope=scope, state=self.state, **kwargs)
+        return auth_url, FORM_ENC_HEADERS, ''
+
+    def prepare_token_request(self, token_url, authorization_response=None,
+                              redirect_url=None, state=None, body='', **kwargs):
+        """Prepare a token creation request.
+
+        Note that these requests usually require client authentication, either
+        by including client_id or a set of provider specific authentication
+        credentials.
+
+        :param token_url: Provider token creation endpoint URL.
+        :param authorization_response: The full redirection URL string, i.e.
+            the location to which the user was redirected after successful
+            authorization. Used to mine credentials needed to obtain a token
+            in this step, such as authorization code.
+        :param redirect_url: The redirect_url supplied with the authorization
+            request (if there was one).
+        :param state:
+        :param body: Existing request body (URL encoded string) to embed parameters
+                     into. This may contain extra parameters. Default ''.
+        :param kwargs: Additional parameters to included in the request.
+        :returns: The prepared request tuple with (url, headers, body).
+        """
+        if not is_secure_transport(token_url):
+            raise InsecureTransportError()
+
+        state = state or self.state
+        if authorization_response:
+            self.parse_request_uri_response(
+                authorization_response, state=state)
+        self.redirect_url = redirect_url or self.redirect_url
+        body = self.prepare_request_body(body=body,
+                                         redirect_uri=self.redirect_url, **kwargs)
+
+        return token_url, FORM_ENC_HEADERS, body
+
+    def prepare_refresh_token_request(self, token_url, refresh_token=None,
+                                      body='', scope=None, **kwargs):
+        """Prepare an access token refresh request.
+
+        Expired access tokens can be replaced by new access tokens without
+        going through the OAuth dance if the client obtained a refresh token.
+        This refresh token and authentication credentials can be used to
+        obtain a new access token, and possibly a new refresh token.
+
+        :param token_url: Provider token refresh endpoint URL.
+        :param refresh_token: Refresh token string.
+        :param body: Existing request body (URL encoded string) to embed parameters
+            into. This may contain extra parameters. Default ''.
+        :param scope: List of scopes to request. Must be equal to
+            or a subset of the scopes granted when obtaining the refresh
+            token. If none is provided, the ones provided in the constructor are
+            used.
+        :param kwargs: Additional parameters to included in the request.
+        :returns: The prepared request tuple with (url, headers, body).
+        """
+        if not is_secure_transport(token_url):
+            raise InsecureTransportError()
+
+        # do not assign scope to self automatically anymore
+        scope = self.scope if scope is None else scope
+        body = self.prepare_refresh_body(body=body,
+                                         refresh_token=refresh_token, scope=scope, **kwargs)
+        return token_url, FORM_ENC_HEADERS, body
+
+    def prepare_token_revocation_request(self, revocation_url, token,
+                                         token_type_hint="access_token", body='', callback=None, **kwargs):
+        """Prepare a token revocation request.
+
+        :param revocation_url: Provider token revocation endpoint URL.
+        :param token: The access or refresh token to be revoked (string).
+        :param token_type_hint: ``"access_token"`` (default) or
+            ``"refresh_token"``. This is optional and if you wish to not pass it you
+            must provide ``token_type_hint=None``.
+        :param body:
+        :param callback: A jsonp callback such as ``package.callback`` to be invoked
+            upon receiving the response. Not that it should not include a () suffix.
+        :param kwargs: Additional parameters to included in the request.
+        :returns: The prepared request tuple with (url, headers, body).
+
+        Note that JSONP request may use GET requests as the parameters will
+        be added to the request URL query as opposed to the request body.
+
+        An example of a revocation request
+
+        .. code-block:: http
+
+            POST /revoke HTTP/1.1
+            Host: server.example.com
+            Content-Type: application/x-www-form-urlencoded
+            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
+
+            token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
+
+        An example of a jsonp revocation request
+
+        .. code-block:: http
+
+            GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
+            Host: server.example.com
+            Content-Type: application/x-www-form-urlencoded
+            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
+
+        and an error response
+
+        .. code-block:: javascript
+
+            package.myCallback({"error":"unsupported_token_type"});
+
+        Note that these requests usually require client credentials, client_id in
+        the case for public clients and provider specific authentication
+        credentials for confidential clients.
+        """
+        if not is_secure_transport(revocation_url):
+            raise InsecureTransportError()
+
+        return prepare_token_revocation_request(revocation_url, token,
+                                                token_type_hint=token_type_hint, body=body, callback=callback,
+                                                **kwargs)
+
+    def parse_request_body_response(self, body, scope=None, **kwargs):
+        """Parse the JSON response body.
+
+        If the access token request is valid and authorized, the
+        authorization server issues an access token as described in
+        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request
+        failed client authentication or is invalid, the authorization server
+        returns an error response as described in `Section 5.2`_.
+
+        :param body: The response body from the token request.
+        :param scope: Scopes originally requested. If none is provided, the ones
+            provided in the constructor are used.
+        :return: Dictionary of token parameters.
+        :raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error`
+            if response is invalid.
+
+        These response are json encoded and could easily be parsed without
+        the assistance of OAuthLib. However, there are a few subtle issues
+        to be aware of regarding the response which are helpfully addressed
+        through the raising of various errors.
+
+        A successful response should always contain
+
+        **access_token**
+                The access token issued by the authorization server. Often
+                a random string.
+
+        **token_type**
+            The type of the token issued as described in `Section 7.1`_.
+            Commonly ``Bearer``.
+
+        While it is not mandated it is recommended that the provider include
+
+        **expires_in**
+            The lifetime in seconds of the access token.  For
+            example, the value "3600" denotes that the access token will
+            expire in one hour from the time the response was generated.
+            If omitted, the authorization server SHOULD provide the
+            expiration time via other means or document the default value.
+
+         **scope**
+            Providers may supply this in all responses but are required to only
+            if it has changed since the authorization request.
+
+        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
+        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
+        """
+        scope = self.scope if scope is None else scope
+        self.token = parse_token_response(body, scope=scope)
+        self.populate_token_attributes(self.token)
+        return self.token
+
+    def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
+        """Prepare an access token request, using a refresh token.
+
+        If the authorization server issued a refresh token to the client, the
+        client makes a refresh request to the token endpoint by adding the
+        following parameters using the `application/x-www-form-urlencoded`
+        format in the HTTP request entity-body:
+
+        :param refresh_token: REQUIRED.  The refresh token issued to the client.
+        :param scope:  OPTIONAL.  The scope of the access request as described by
+            Section 3.3.  The requested scope MUST NOT include any scope
+            not originally granted by the resource owner, and if omitted is
+            treated as equal to the scope originally granted by the
+            resource owner. Note that if none is provided, the ones provided
+            in the constructor are used if any.
+        """
+        refresh_token = refresh_token or self.refresh_token
+        scope = self.scope if scope is None else scope
+        return prepare_token_request(self.refresh_token_key, body=body, scope=scope,
+                                     refresh_token=refresh_token, **kwargs)
+
+    def _add_bearer_token(self, uri, http_method='GET', body=None,
+                          headers=None, token_placement=None):
+        """Add a bearer token to the request uri, body or authorization header."""
+        if token_placement == AUTH_HEADER:
+            headers = tokens.prepare_bearer_headers(self.access_token, headers)
+
+        elif token_placement == URI_QUERY:
+            uri = tokens.prepare_bearer_uri(self.access_token, uri)
+
+        elif token_placement == BODY:
+            body = tokens.prepare_bearer_body(self.access_token, body)
+
+        else:
+            raise ValueError("Invalid token placement.")
+        return uri, headers, body
+
+    def create_code_verifier(self, length):
+        """Create PKCE **code_verifier** used in computing **code_challenge**. 
+        See `RFC7636 Section 4.1`_
+
+        :param length: REQUIRED. The length of the code_verifier.
+
+        The client first creates a code verifier, "code_verifier", for each
+        OAuth 2.0 [RFC6749] Authorization Request, in the following manner:
+
+        .. code-block:: text
+
+               code_verifier = high-entropy cryptographic random STRING using the
+               unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
+               from Section 2.3 of [RFC3986], with a minimum length of 43 characters
+               and a maximum length of 128 characters.
+
+        .. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1
+        """
+        code_verifier = None
+
+        if not length >= 43:
+            raise ValueError("Length must be greater than or equal to 43")
+
+        if not length <= 128:
+            raise ValueError("Length must be less than or equal to 128")
+
+        allowed_characters = re.compile('^[A-Zaa-z0-9-._~]')
+        code_verifier = secrets.token_urlsafe(length)
+
+        if not re.search(allowed_characters, code_verifier):
+            raise ValueError("code_verifier contains invalid characters")
+
+        self.code_verifier = code_verifier
+
+        return code_verifier
+
+    def create_code_challenge(self, code_verifier, code_challenge_method=None):
+        """Create PKCE **code_challenge** derived from the  **code_verifier**.
+        See `RFC7636 Section 4.2`_
+
+        :param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`.
+        :param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`.
+
+               The client then creates a code challenge derived from the code
+               verifier by using one of the following transformations on the code
+               verifier::
+
+                   plain
+                      code_challenge = code_verifier
+                   S256
+                      code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
+
+               If the client is capable of using `S256`, it MUST use `S256`, as
+               `S256` is Mandatory To Implement (MTI) on the server.  Clients are
+               permitted to use `plain` only if they cannot support `S256` for some
+               technical reason and know via out-of-band configuration that the
+               server supports `plain`.
+
+               The plain transformation is for compatibility with existing
+               deployments and for constrained environments that can't use the S256 transformation.
+
+        .. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2
+        """
+        code_challenge = None
+
+        if code_verifier == None:
+            raise ValueError("Invalid code_verifier")
+
+        if code_challenge_method == None:
+            code_challenge_method = "plain"
+            self.code_challenge_method = code_challenge_method
+            code_challenge = code_verifier
+            self.code_challenge = code_challenge
+
+        if code_challenge_method == "S256":
+            h = hashlib.sha256()
+            h.update(code_verifier.encode(encoding='ascii'))
+            sha256_val = h.digest()
+            code_challenge = bytes.decode(base64.urlsafe_b64encode(sha256_val))
+            # replace '+' with '-', '/' with '_', and remove trailing '='
+            code_challenge = code_challenge.replace("+", "-").replace("/", "_").replace("=", "")
+            self.code_challenge = code_challenge
+
+        return code_challenge
+
+    def _add_mac_token(self, uri, http_method='GET', body=None,
+                       headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs):
+        """Add a MAC token to the request authorization header.
+
+        Warning: MAC token support is experimental as the spec is not yet stable.
+        """
+        if token_placement != AUTH_HEADER:
+            raise ValueError("Invalid token placement.")
+
+        headers = tokens.prepare_mac_header(self.access_token, uri,
+                                            self.mac_key, http_method, headers=headers, body=body, ext=ext,
+                                            hash_algorithm=self.mac_algorithm, **kwargs)
+        return uri, headers, body
+
+    def _populate_attributes(self, response):
+        warnings.warn("Please switch to the public method "
+                      "populate_token_attributes.", DeprecationWarning)
+        return self.populate_token_attributes(response)
+
+    def populate_code_attributes(self, response):
+        """Add attributes from an auth code response to self."""
+
+        if 'code' in response:
+            self.code = response.get('code')
+
+    def populate_token_attributes(self, response):
+        """Add attributes from a token exchange response to self."""
+
+        if 'access_token' in response:
+            self.access_token = response.get('access_token')
+
+        if 'refresh_token' in response:
+            self.refresh_token = response.get('refresh_token')
+
+        if 'token_type' in response:
+            self.token_type = response.get('token_type')
+
+        if 'expires_in' in response:
+            self.expires_in = response.get('expires_in')
+            self._expires_at = time.time() + int(self.expires_in)
+
+        if 'expires_at' in response:
+            try:
+                self._expires_at = int(response.get('expires_at'))
+            except:
+                self._expires_at = None
+
+        if 'mac_key' in response:
+            self.mac_key = response.get('mac_key')
+
+        if 'mac_algorithm' in response:
+            self.mac_algorithm = response.get('mac_algorithm')