about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/oauthlib/oauth1
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/oauthlib/oauth1
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/oauthlib/oauth1')
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/__init__.py23
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/__init__.py365
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py8
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py215
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py158
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py244
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py14
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py209
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py163
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py82
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/errors.py76
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/parameters.py133
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/request_validator.py849
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/signature.py852
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/utils.py83
15 files changed, 3474 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/__init__.py
new file mode 100644
index 00000000..9caf12a9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/__init__.py
@@ -0,0 +1,23 @@
+"""
+oauthlib.oauth1
+~~~~~~~~~~~~~~
+
+This module is a wrapper for the most recent implementation of OAuth 1.0 Client
+and Server classes.
+"""
+from .rfc5849 import (
+    SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
+    SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA,
+    SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512,
+    SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY,
+    Client,
+)
+from .rfc5849.endpoints import (
+    AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
+    ResourceEndpoint, SignatureOnlyEndpoint, WebApplicationServer,
+)
+from .rfc5849.errors import (
+    InsecureTransportError, InvalidClientError, InvalidRequestError,
+    InvalidSignatureMethodError, OAuth1Error,
+)
+from .rfc5849.request_validator import RequestValidator
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/__init__.py
new file mode 100644
index 00000000..c559251f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/__init__.py
@@ -0,0 +1,365 @@
+"""
+oauthlib.oauth1.rfc5849
+~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 1.0 RFC 5849 requests.
+
+It supports all three standard signature methods defined in RFC 5849:
+
+- HMAC-SHA1
+- RSA-SHA1
+- PLAINTEXT
+
+It also supports signature methods that are not defined in RFC 5849. These are
+based on the standard ones but replace SHA-1 with the more secure SHA-256:
+
+- HMAC-SHA256
+- RSA-SHA256
+
+"""
+import base64
+import hashlib
+import logging
+import urllib.parse as urlparse
+
+from oauthlib.common import (
+    Request, generate_nonce, generate_timestamp, to_unicode, urlencode,
+)
+
+from . import parameters, signature
+
+log = logging.getLogger(__name__)
+
+# Available signature methods
+#
+# Note: SIGNATURE_HMAC and SIGNATURE_RSA are kept for backward compatibility
+# with previous versions of this library, when it the only HMAC-based and
+# RSA-based signature methods were HMAC-SHA1 and RSA-SHA1. But now that it
+# supports other hashing algorithms besides SHA1, explicitly identifying which
+# hashing algorithm is being used is recommended.
+#
+# Note: if additional values are defined here, don't forget to update the
+# imports in "../__init__.py" so they are available outside this module.
+
+SIGNATURE_HMAC_SHA1 = "HMAC-SHA1"
+SIGNATURE_HMAC_SHA256 = "HMAC-SHA256"
+SIGNATURE_HMAC_SHA512 = "HMAC-SHA512"
+SIGNATURE_HMAC = SIGNATURE_HMAC_SHA1  # deprecated variable for HMAC-SHA1
+
+SIGNATURE_RSA_SHA1 = "RSA-SHA1"
+SIGNATURE_RSA_SHA256 = "RSA-SHA256"
+SIGNATURE_RSA_SHA512 = "RSA-SHA512"
+SIGNATURE_RSA = SIGNATURE_RSA_SHA1  # deprecated variable for RSA-SHA1
+
+SIGNATURE_PLAINTEXT = "PLAINTEXT"
+
+SIGNATURE_METHODS = (
+    SIGNATURE_HMAC_SHA1,
+    SIGNATURE_HMAC_SHA256,
+    SIGNATURE_HMAC_SHA512,
+    SIGNATURE_RSA_SHA1,
+    SIGNATURE_RSA_SHA256,
+    SIGNATURE_RSA_SHA512,
+    SIGNATURE_PLAINTEXT
+)
+
+SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER'
+SIGNATURE_TYPE_QUERY = 'QUERY'
+SIGNATURE_TYPE_BODY = 'BODY'
+
+CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
+
+
+class Client:
+
+    """A client used to sign OAuth 1.0 RFC 5849 requests."""
+    SIGNATURE_METHODS = {
+        SIGNATURE_HMAC_SHA1: signature.sign_hmac_sha1_with_client,
+        SIGNATURE_HMAC_SHA256: signature.sign_hmac_sha256_with_client,
+        SIGNATURE_HMAC_SHA512: signature.sign_hmac_sha512_with_client,
+        SIGNATURE_RSA_SHA1: signature.sign_rsa_sha1_with_client,
+        SIGNATURE_RSA_SHA256: signature.sign_rsa_sha256_with_client,
+        SIGNATURE_RSA_SHA512: signature.sign_rsa_sha512_with_client,
+        SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client
+    }
+
+    @classmethod
+    def register_signature_method(cls, method_name, method_callback):
+        cls.SIGNATURE_METHODS[method_name] = method_callback
+
+    def __init__(self, client_key,
+                 client_secret=None,
+                 resource_owner_key=None,
+                 resource_owner_secret=None,
+                 callback_uri=None,
+                 signature_method=SIGNATURE_HMAC_SHA1,
+                 signature_type=SIGNATURE_TYPE_AUTH_HEADER,
+                 rsa_key=None, verifier=None, realm=None,
+                 encoding='utf-8', decoding=None,
+                 nonce=None, timestamp=None):
+        """Create an OAuth 1 client.
+
+        :param client_key: Client key (consumer key), mandatory.
+        :param resource_owner_key: Resource owner key (oauth token).
+        :param resource_owner_secret: Resource owner secret (oauth token secret).
+        :param callback_uri: Callback used when obtaining request token.
+        :param signature_method: SIGNATURE_HMAC, SIGNATURE_RSA or SIGNATURE_PLAINTEXT.
+        :param signature_type: SIGNATURE_TYPE_AUTH_HEADER (default),
+                               SIGNATURE_TYPE_QUERY or SIGNATURE_TYPE_BODY
+                               depending on where you want to embed the oauth
+                               credentials.
+        :param rsa_key: RSA key used with SIGNATURE_RSA.
+        :param verifier: Verifier used when obtaining an access token.
+        :param realm: Realm (scope) to which access is being requested.
+        :param encoding: If you provide non-unicode input you may use this
+                         to have oauthlib automatically convert.
+        :param decoding: If you wish that the returned uri, headers and body
+                         from sign be encoded back from unicode, then set
+                         decoding to your preferred encoding, i.e. utf-8.
+        :param nonce: Use this nonce instead of generating one. (Mainly for testing)
+        :param timestamp: Use this timestamp instead of using current. (Mainly for testing)
+        """
+        # Convert to unicode using encoding if given, else assume unicode
+        encode = lambda x: to_unicode(x, encoding) if encoding else x
+
+        self.client_key = encode(client_key)
+        self.client_secret = encode(client_secret)
+        self.resource_owner_key = encode(resource_owner_key)
+        self.resource_owner_secret = encode(resource_owner_secret)
+        self.signature_method = encode(signature_method)
+        self.signature_type = encode(signature_type)
+        self.callback_uri = encode(callback_uri)
+        self.rsa_key = encode(rsa_key)
+        self.verifier = encode(verifier)
+        self.realm = encode(realm)
+        self.encoding = encode(encoding)
+        self.decoding = encode(decoding)
+        self.nonce = encode(nonce)
+        self.timestamp = encode(timestamp)
+
+    def __repr__(self):
+        attrs = vars(self).copy()
+        attrs['client_secret'] = '****' if attrs['client_secret'] else None
+        attrs['rsa_key'] = '****' if attrs['rsa_key'] else None
+        attrs[
+            'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None
+        attribute_str = ', '.join('{}={}'.format(k, v) for k, v in attrs.items())
+        return '<{} {}>'.format(self.__class__.__name__, attribute_str)
+
+    def get_oauth_signature(self, request):
+        """Get an OAuth signature to be used in signing a request
+
+        To satisfy `section 3.4.1.2`_ item 2, if the request argument's
+        headers dict attribute contains a Host item, its value will
+        replace any netloc part of the request argument's uri attribute
+        value.
+
+        .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
+        """
+        if self.signature_method == SIGNATURE_PLAINTEXT:
+            # fast-path
+            return signature.sign_plaintext(self.client_secret,
+                                            self.resource_owner_secret)
+
+        uri, headers, body = self._render(request)
+
+        collected_params = signature.collect_parameters(
+            uri_query=urlparse.urlparse(uri).query,
+            body=body,
+            headers=headers)
+        log.debug("Collected params: {}".format(collected_params))
+
+        normalized_params = signature.normalize_parameters(collected_params)
+        normalized_uri = signature.base_string_uri(uri, headers.get('Host', None))
+        log.debug("Normalized params: {}".format(normalized_params))
+        log.debug("Normalized URI: {}".format(normalized_uri))
+
+        base_string = signature.signature_base_string(request.http_method,
+                                                      normalized_uri, normalized_params)
+
+        log.debug("Signing: signature base string: {}".format(base_string))
+
+        if self.signature_method not in self.SIGNATURE_METHODS:
+            raise ValueError('Invalid signature method.')
+
+        sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self)
+
+        log.debug("Signature: {}".format(sig))
+        return sig
+
+    def get_oauth_params(self, request):
+        """Get the basic OAuth parameters to be used in generating a signature.
+        """
+        nonce = (generate_nonce()
+                 if self.nonce is None else self.nonce)
+        timestamp = (generate_timestamp()
+                     if self.timestamp is None else self.timestamp)
+        params = [
+            ('oauth_nonce', nonce),
+            ('oauth_timestamp', timestamp),
+            ('oauth_version', '1.0'),
+            ('oauth_signature_method', self.signature_method),
+            ('oauth_consumer_key', self.client_key),
+        ]
+        if self.resource_owner_key:
+            params.append(('oauth_token', self.resource_owner_key))
+        if self.callback_uri:
+            params.append(('oauth_callback', self.callback_uri))
+        if self.verifier:
+            params.append(('oauth_verifier', self.verifier))
+
+        # providing body hash for requests other than x-www-form-urlencoded
+        # as described in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-4.1.1
+        # 4.1.1. When to include the body hash
+        #    *  [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies
+        #    *  [...] SHOULD include the oauth_body_hash parameter on all other requests.
+        # Note that SHA-1 is vulnerable. The spec acknowledges that in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-6.2
+        # At this time, no further effort has been made to replace SHA-1 for the OAuth Request Body Hash extension.
+        content_type = request.headers.get('Content-Type', None)
+        content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0
+        if request.body is not None and content_type_eligible:
+            params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))
+
+        return params
+
+    def _render(self, request, formencode=False, realm=None):
+        """Render a signed request according to signature type
+
+        Returns a 3-tuple containing the request URI, headers, and body.
+
+        If the formencode argument is True and the body contains parameters, it
+        is escaped and returned as a valid formencoded string.
+        """
+        # TODO what if there are body params on a header-type auth?
+        # TODO what if there are query params on a body-type auth?
+
+        uri, headers, body = request.uri, request.headers, request.body
+
+        # TODO: right now these prepare_* methods are very narrow in scope--they
+        # only affect their little thing. In some cases (for example, with
+        # header auth) it might be advantageous to allow these methods to touch
+        # other parts of the request, like the headers—so the prepare_headers
+        # method could also set the Content-Type header to x-www-form-urlencoded
+        # like the spec requires. This would be a fundamental change though, and
+        # I'm not sure how I feel about it.
+        if self.signature_type == SIGNATURE_TYPE_AUTH_HEADER:
+            headers = parameters.prepare_headers(
+                request.oauth_params, request.headers, realm=realm)
+        elif self.signature_type == SIGNATURE_TYPE_BODY and request.decoded_body is not None:
+            body = parameters.prepare_form_encoded_body(
+                request.oauth_params, request.decoded_body)
+            if formencode:
+                body = urlencode(body)
+            headers['Content-Type'] = 'application/x-www-form-urlencoded'
+        elif self.signature_type == SIGNATURE_TYPE_QUERY:
+            uri = parameters.prepare_request_uri_query(
+                request.oauth_params, request.uri)
+        else:
+            raise ValueError('Unknown signature type specified.')
+
+        return uri, headers, body
+
+    def sign(self, uri, http_method='GET', body=None, headers=None, realm=None):
+        """Sign a request
+
+        Signs an HTTP request with the specified parts.
+
+        Returns a 3-tuple of the signed request's URI, headers, and body.
+        Note that http_method is not returned as it is unaffected by the OAuth
+        signing process. Also worth noting is that duplicate parameters
+        will be included in the signature, regardless of where they are
+        specified (query, body).
+
+        The body argument may be a dict, a list of 2-tuples, or a formencoded
+        string. The Content-Type header must be 'application/x-www-form-urlencoded'
+        if it is present.
+
+        If the body argument is not one of the above, it will be returned
+        verbatim as it is unaffected by the OAuth signing process. Attempting to
+        sign a request with non-formencoded data using the OAuth body signature
+        type is invalid and will raise an exception.
+
+        If the body does contain parameters, it will be returned as a properly-
+        formatted formencoded string.
+
+        Body may not be included if the http_method is either GET or HEAD as
+        this changes the semantic meaning of the request.
+
+        All string data MUST be unicode or be encoded with the same encoding
+        scheme supplied to the Client constructor, default utf-8. This includes
+        strings inside body dicts, for example.
+        """
+        # normalize request data
+        request = Request(uri, http_method, body, headers,
+                          encoding=self.encoding)
+
+        # sanity check
+        content_type = request.headers.get('Content-Type', None)
+        multipart = content_type and content_type.startswith('multipart/')
+        should_have_params = content_type == CONTENT_TYPE_FORM_URLENCODED
+        has_params = request.decoded_body is not None
+        # 3.4.1.3.1.  Parameter Sources
+        # [Parameters are collected from the HTTP request entity-body, but only
+        # if [...]:
+        #    *  The entity-body is single-part.
+        if multipart and has_params:
+            raise ValueError(
+                "Headers indicate a multipart body but body contains parameters.")
+        #    *  The entity-body follows the encoding requirements of the
+        #       "application/x-www-form-urlencoded" content-type as defined by
+        #       [W3C.REC-html40-19980424].
+        elif should_have_params and not has_params:
+            raise ValueError(
+                "Headers indicate a formencoded body but body was not decodable.")
+        #    *  The HTTP request entity-header includes the "Content-Type"
+        #       header field set to "application/x-www-form-urlencoded".
+        elif not should_have_params and has_params:
+            raise ValueError(
+                "Body contains parameters but Content-Type header was {} "
+                "instead of {}".format(content_type or "not set",
+                                        CONTENT_TYPE_FORM_URLENCODED))
+
+        # 3.5.2.  Form-Encoded Body
+        # Protocol parameters can be transmitted in the HTTP request entity-
+        # body, but only if the following REQUIRED conditions are met:
+        # o  The entity-body is single-part.
+        # o  The entity-body follows the encoding requirements of the
+        #    "application/x-www-form-urlencoded" content-type as defined by
+        #    [W3C.REC-html40-19980424].
+        # o  The HTTP request entity-header includes the "Content-Type" header
+        #    field set to "application/x-www-form-urlencoded".
+        elif self.signature_type == SIGNATURE_TYPE_BODY and not (
+                should_have_params and has_params and not multipart):
+            raise ValueError(
+                'Body signatures may only be used with form-urlencoded content')
+
+        # We amend https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
+        # with the clause that parameters from body should only be included
+        # in non GET or HEAD requests. Extracting the request body parameters
+        # and including them in the signature base string would give semantic
+        # meaning to the body, which it should not have according to the
+        # HTTP 1.1 spec.
+        elif http_method.upper() in ('GET', 'HEAD') and has_params:
+            raise ValueError('GET/HEAD requests should not include body.')
+
+        # generate the basic OAuth parameters
+        request.oauth_params = self.get_oauth_params(request)
+
+        # generate the signature
+        request.oauth_params.append(
+            ('oauth_signature', self.get_oauth_signature(request)))
+
+        # render the signed request and return it
+        uri, headers, body = self._render(request, formencode=True,
+                                          realm=(realm or self.realm))
+
+        if self.decoding:
+            log.debug('Encoding URI, headers and body to %s.', self.decoding)
+            uri = uri.encode(self.decoding)
+            body = body.encode(self.decoding) if body else body
+            new_headers = {}
+            for k, v in headers.items():
+                new_headers[k.encode(self.decoding)] = v.encode(self.decoding)
+            headers = new_headers
+        return uri, headers, body
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py
new file mode 100644
index 00000000..9f30389f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py
@@ -0,0 +1,8 @@
+from .access_token import AccessTokenEndpoint
+from .authorization import AuthorizationEndpoint
+from .base import BaseEndpoint
+from .request_token import RequestTokenEndpoint
+from .resource import ResourceEndpoint
+from .signature_only import SignatureOnlyEndpoint
+
+from .pre_configured import WebApplicationServer  # isort:skip
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py
new file mode 100644
index 00000000..13665db0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.access_token
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the access token provider logic of
+OAuth 1.0 RFC 5849. It validates the correctness of access token requests,
+creates and persists tokens as well as create the proper response to be
+returned to the client.
+"""
+import logging
+
+from oauthlib.common import urlencode
+
+from .. import errors
+from .base import BaseEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class AccessTokenEndpoint(BaseEndpoint):
+
+    """An endpoint responsible for providing OAuth 1 access tokens.
+
+    Typical use is to instantiate with a request validator and invoke the
+    ``create_access_token_response`` from a view function. The tuple returned
+    has all information necessary (body, status, headers) to quickly form
+    and return a proper response. See :doc:`/oauth1/validator` for details on which
+    validator methods to implement for this endpoint.
+    """
+
+    def create_access_token(self, request, credentials):
+        """Create and save a new access token.
+
+        Similar to OAuth 2, indication of granted scopes will be included as a
+        space separated list in ``oauth_authorized_realms``.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The token as an urlencoded string.
+        """
+        request.realms = self.request_validator.get_realms(
+            request.resource_owner_key, request)
+        token = {
+            'oauth_token': self.token_generator(),
+            'oauth_token_secret': self.token_generator(),
+            # Backport the authorized scopes indication used in OAuth2
+            'oauth_authorized_realms': ' '.join(request.realms)
+        }
+        token.update(credentials)
+        self.request_validator.save_access_token(token, request)
+        return urlencode(token.items())
+
+    def create_access_token_response(self, uri, http_method='GET', body=None,
+                                     headers=None, credentials=None):
+        """Create an access token response, with a new request token if valid.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :param credentials: A list of extra credentials to include in the token.
+        :returns: A tuple of 3 elements.
+                  1. A dict of headers to set on the response.
+                  2. The response body as a string.
+                  3. The response status code as an integer.
+
+        An example of a valid request::
+
+            >>> from your_validator import your_validator
+            >>> from oauthlib.oauth1 import AccessTokenEndpoint
+            >>> endpoint = AccessTokenEndpoint(your_validator)
+            >>> h, b, s = endpoint.create_access_token_response(
+            ...     'https://your.provider/access_token?foo=bar',
+            ...     headers={
+            ...         'Authorization': 'OAuth oauth_token=234lsdkf....'
+            ...     },
+            ...     credentials={
+            ...         'my_specific': 'argument',
+            ...     })
+            >>> h
+            {'Content-Type': 'application/x-www-form-urlencoded'}
+            >>> b
+            'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_authorized_realms=movies+pics&my_specific=argument'
+            >>> s
+            200
+
+        An response to invalid request would have a different body and status::
+
+            >>> b
+            'error=invalid_request&description=missing+resource+owner+key'
+            >>> s
+            400
+
+        The same goes for an an unauthorized request:
+
+            >>> b
+            ''
+            >>> s
+            401
+        """
+        resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+        try:
+            request = self._create_request(uri, http_method, body, headers)
+            valid, processed_request = self.validate_access_token_request(
+                request)
+            if valid:
+                token = self.create_access_token(request, credentials or {})
+                self.request_validator.invalidate_request_token(
+                    request.client_key,
+                    request.resource_owner_key,
+                    request)
+                return resp_headers, token, 200
+            else:
+                return {}, None, 401
+        except errors.OAuth1Error as e:
+            return resp_headers, e.urlencoded, e.status_code
+
+    def validate_access_token_request(self, request):
+        """Validate an access token request.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :raises: OAuth1Error if the request is invalid.
+        :returns: A tuple of 2 elements.
+                  1. The validation result (True or False).
+                  2. The request object.
+        """
+        self._check_transport_security(request)
+        self._check_mandatory_parameters(request)
+
+        if not request.resource_owner_key:
+            raise errors.InvalidRequestError(
+                description='Missing resource owner.')
+
+        if not self.request_validator.check_request_token(
+                request.resource_owner_key):
+            raise errors.InvalidRequestError(
+                description='Invalid resource owner key format.')
+
+        if not request.verifier:
+            raise errors.InvalidRequestError(
+                description='Missing verifier.')
+
+        if not self.request_validator.check_verifier(request.verifier):
+            raise errors.InvalidRequestError(
+                description='Invalid verifier format.')
+
+        if not self.request_validator.validate_timestamp_and_nonce(
+                request.client_key, request.timestamp, request.nonce, request,
+                request_token=request.resource_owner_key):
+            return False, request
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid client credentials.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy client is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable client enumeration
+        valid_client = self.request_validator.validate_client_key(
+            request.client_key, request)
+        if not valid_client:
+            request.client_key = self.request_validator.dummy_client
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid or expired token.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy token is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable resource owner enumeration
+        valid_resource_owner = self.request_validator.validate_request_token(
+            request.client_key, request.resource_owner_key, request)
+        if not valid_resource_owner:
+            request.resource_owner_key = self.request_validator.dummy_request_token
+
+        # The server MUST verify (Section 3.2) the validity of the request,
+        # ensure that the resource owner has authorized the provisioning of
+        # token credentials to the client, and ensure that the temporary
+        # credentials have not expired or been used before.  The server MUST
+        # also verify the verification code received from the client.
+        # .. _`Section 3.2`: https://tools.ietf.org/html/rfc5849#section-3.2
+        #
+        # Note that early exit would enable resource owner authorization
+        # verifier enumertion.
+        valid_verifier = self.request_validator.validate_verifier(
+            request.client_key,
+            request.resource_owner_key,
+            request.verifier,
+            request)
+
+        valid_signature = self._check_signature(request, is_token_request=True)
+
+        # log the results to the validator_log
+        # this lets us handle internal reporting and analysis
+        request.validator_log['client'] = valid_client
+        request.validator_log['resource_owner'] = valid_resource_owner
+        request.validator_log['verifier'] = valid_verifier
+        request.validator_log['signature'] = valid_signature
+
+        # We delay checking validity until the very end, using dummy values for
+        # calculations and fetching secrets/keys to ensure the flow of every
+        # request remains almost identical regardless of whether valid values
+        # have been supplied. This ensures near constant time execution and
+        # prevents malicious users from guessing sensitive information
+        v = all((valid_client, valid_resource_owner, valid_verifier,
+                 valid_signature))
+        if not v:
+            log.info("[Failure] request verification failed.")
+            log.info("Valid client:, %s", valid_client)
+            log.info("Valid token:, %s", valid_resource_owner)
+            log.info("Valid verifier:, %s", valid_verifier)
+            log.info("Valid signature:, %s", valid_signature)
+        return v, request
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py
new file mode 100644
index 00000000..00d9576b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.authorization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 1.0 RFC 5849 requests.
+"""
+from urllib.parse import urlencode
+
+from oauthlib.common import add_params_to_uri
+
+from .. import errors
+from .base import BaseEndpoint
+
+
+class AuthorizationEndpoint(BaseEndpoint):
+
+    """An endpoint responsible for letting authenticated users authorize access
+    to their protected resources to a client.
+
+    Typical use would be to have two views, one for displaying the authorization
+    form and one to process said form on submission.
+
+    The first view will want to utilize ``get_realms_and_credentials`` to fetch
+    requested realms and useful client credentials, such as name and
+    description, to be used when creating the authorization form.
+
+    During form processing you can use ``create_authorization_response`` to
+    validate the request, create a verifier as well as prepare the final
+    redirection URI used to send the user back to the client.
+
+    See :doc:`/oauth1/validator` for details on which validator methods to implement
+    for this endpoint.
+    """
+
+    def create_verifier(self, request, credentials):
+        """Create and save a new request token.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param credentials: A dict of extra token credentials.
+        :returns: The verifier as a dict.
+        """
+        verifier = {
+            'oauth_token': request.resource_owner_key,
+            'oauth_verifier': self.token_generator(),
+        }
+        verifier.update(credentials)
+        self.request_validator.save_verifier(
+            request.resource_owner_key, verifier, request)
+        return verifier
+
+    def create_authorization_response(self, uri, http_method='GET', body=None,
+                                      headers=None, realms=None, credentials=None):
+        """Create an authorization response, with a new request token if valid.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :param credentials: A list of credentials to include in the verifier.
+        :returns: A tuple of 3 elements.
+                  1. A dict of headers to set on the response.
+                  2. The response body as a string.
+                  3. The response status code as an integer.
+
+        If the callback URI tied to the current token is "oob", a response with
+        a 200 status code will be returned. In this case, it may be desirable to
+        modify the response to better display the verifier to the client.
+
+        An example of an authorization request::
+
+            >>> from your_validator import your_validator
+            >>> from oauthlib.oauth1 import AuthorizationEndpoint
+            >>> endpoint = AuthorizationEndpoint(your_validator)
+            >>> h, b, s = endpoint.create_authorization_response(
+            ...     'https://your.provider/authorize?oauth_token=...',
+            ...     credentials={
+            ...         'extra': 'argument',
+            ...     })
+            >>> h
+            {'Location': 'https://the.client/callback?oauth_verifier=...&extra=argument'}
+            >>> b
+            None
+            >>> s
+            302
+
+        An example of a request with an "oob" callback::
+
+            >>> from your_validator import your_validator
+            >>> from oauthlib.oauth1 import AuthorizationEndpoint
+            >>> endpoint = AuthorizationEndpoint(your_validator)
+            >>> h, b, s = endpoint.create_authorization_response(
+            ...     'https://your.provider/authorize?foo=bar',
+            ...     credentials={
+            ...         'extra': 'argument',
+            ...     })
+            >>> h
+            {'Content-Type': 'application/x-www-form-urlencoded'}
+            >>> b
+            'oauth_verifier=...&extra=argument'
+            >>> s
+            200
+        """
+        request = self._create_request(uri, http_method=http_method, body=body,
+                                       headers=headers)
+
+        if not request.resource_owner_key:
+            raise errors.InvalidRequestError(
+                'Missing mandatory parameter oauth_token.')
+        if not self.request_validator.verify_request_token(
+                request.resource_owner_key, request):
+            raise errors.InvalidClientError()
+
+        request.realms = realms
+        if (request.realms and not self.request_validator.verify_realms(
+                request.resource_owner_key, request.realms, request)):
+            raise errors.InvalidRequestError(
+                description=('User granted access to realms outside of '
+                             'what the client may request.'))
+
+        verifier = self.create_verifier(request, credentials or {})
+        redirect_uri = self.request_validator.get_redirect_uri(
+            request.resource_owner_key, request)
+        if redirect_uri == 'oob':
+            response_headers = {
+                'Content-Type': 'application/x-www-form-urlencoded'}
+            response_body = urlencode(verifier)
+            return response_headers, response_body, 200
+        else:
+            populated_redirect = add_params_to_uri(
+                redirect_uri, verifier.items())
+            return {'Location': populated_redirect}, None, 302
+
+    def get_realms_and_credentials(self, uri, http_method='GET', body=None,
+                                   headers=None):
+        """Fetch realms and credentials for the presented request token.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :returns: A tuple of 2 elements.
+                  1. A list of request realms.
+                  2. A dict of credentials which may be useful in creating the
+                  authorization form.
+        """
+        request = self._create_request(uri, http_method=http_method, body=body,
+                                       headers=headers)
+
+        if not self.request_validator.verify_request_token(
+                request.resource_owner_key, request):
+            raise errors.InvalidClientError()
+
+        realms = self.request_validator.get_realms(
+            request.resource_owner_key, request)
+        return realms, {'resource_owner_key': request.resource_owner_key}
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py
new file mode 100644
index 00000000..7831be7c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.base
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 1.0 RFC 5849 requests.
+"""
+import time
+
+from oauthlib.common import CaseInsensitiveDict, Request, generate_token
+
+from .. import (
+    CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
+    SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA_SHA1,
+    SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, SIGNATURE_TYPE_AUTH_HEADER,
+    SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, errors, signature, utils,
+)
+
+
+class BaseEndpoint:
+
+    def __init__(self, request_validator, token_generator=None):
+        self.request_validator = request_validator
+        self.token_generator = token_generator or generate_token
+
+    def _get_signature_type_and_params(self, request):
+        """Extracts parameters from query, headers and body. Signature type
+        is set to the source in which parameters were found.
+        """
+        # Per RFC5849, only the Authorization header may contain the 'realm'
+        # optional parameter.
+        header_params = signature.collect_parameters(headers=request.headers,
+                                                     exclude_oauth_signature=False, with_realm=True)
+        body_params = signature.collect_parameters(body=request.body,
+                                                   exclude_oauth_signature=False)
+        query_params = signature.collect_parameters(uri_query=request.uri_query,
+                                                    exclude_oauth_signature=False)
+
+        params = []
+        params.extend(header_params)
+        params.extend(body_params)
+        params.extend(query_params)
+        signature_types_with_oauth_params = list(filter(lambda s: s[2], (
+            (SIGNATURE_TYPE_AUTH_HEADER, params,
+                utils.filter_oauth_params(header_params)),
+            (SIGNATURE_TYPE_BODY, params,
+                utils.filter_oauth_params(body_params)),
+            (SIGNATURE_TYPE_QUERY, params,
+                utils.filter_oauth_params(query_params))
+        )))
+
+        if len(signature_types_with_oauth_params) > 1:
+            found_types = [s[0] for s in signature_types_with_oauth_params]
+            raise errors.InvalidRequestError(
+                description=('oauth_ params must come from only 1 signature'
+                             'type but were found in %s',
+                             ', '.join(found_types)))
+
+        try:
+            signature_type, params, oauth_params = signature_types_with_oauth_params[
+                0]
+        except IndexError:
+            raise errors.InvalidRequestError(
+                description='Missing mandatory OAuth parameters.')
+
+        return signature_type, params, oauth_params
+
+    def _create_request(self, uri, http_method, body, headers):
+        # Only include body data from x-www-form-urlencoded requests
+        headers = CaseInsensitiveDict(headers or {})
+        if ("Content-Type" in headers and
+                CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]):
+            request = Request(uri, http_method, body, headers)
+        else:
+            request = Request(uri, http_method, '', headers)
+
+        signature_type, params, oauth_params = (
+            self._get_signature_type_and_params(request))
+
+        # The server SHOULD return a 400 (Bad Request) status code when
+        # receiving a request with duplicated protocol parameters.
+        if len(dict(oauth_params)) != len(oauth_params):
+            raise errors.InvalidRequestError(
+                description='Duplicate OAuth1 entries.')
+
+        oauth_params = dict(oauth_params)
+        request.signature = oauth_params.get('oauth_signature')
+        request.client_key = oauth_params.get('oauth_consumer_key')
+        request.resource_owner_key = oauth_params.get('oauth_token')
+        request.nonce = oauth_params.get('oauth_nonce')
+        request.timestamp = oauth_params.get('oauth_timestamp')
+        request.redirect_uri = oauth_params.get('oauth_callback')
+        request.verifier = oauth_params.get('oauth_verifier')
+        request.signature_method = oauth_params.get('oauth_signature_method')
+        request.realm = dict(params).get('realm')
+        request.oauth_params = oauth_params
+
+        # Parameters to Client depend on signature method which may vary
+        # for each request. Note that HMAC-SHA1 and PLAINTEXT share parameters
+        request.params = [(k, v) for k, v in params if k != "oauth_signature"]
+
+        if 'realm' in request.headers.get('Authorization', ''):
+            request.params = [(k, v)
+                              for k, v in request.params if k != "realm"]
+
+        return request
+
+    def _check_transport_security(self, request):
+        # TODO: move into oauthlib.common from oauth2.utils
+        if (self.request_validator.enforce_ssl and
+                not request.uri.lower().startswith("https://")):
+            raise errors.InsecureTransportError()
+
+    def _check_mandatory_parameters(self, request):
+        # The server SHOULD return a 400 (Bad Request) status code when
+        # receiving a request with missing parameters.
+        if not all((request.signature, request.client_key,
+                    request.nonce, request.timestamp,
+                    request.signature_method)):
+            raise errors.InvalidRequestError(
+                description='Missing mandatory OAuth parameters.')
+
+        # OAuth does not mandate a particular signature method, as each
+        # implementation can have its own unique requirements.  Servers are
+        # free to implement and document their own custom methods.
+        # Recommending any particular method is beyond the scope of this
+        # specification.  Implementers should review the Security
+        # Considerations section (`Section 4`_) before deciding on which
+        # method to support.
+        # .. _`Section 4`: https://tools.ietf.org/html/rfc5849#section-4
+        if (not request.signature_method in
+                self.request_validator.allowed_signature_methods):
+            raise errors.InvalidSignatureMethodError(
+                description="Invalid signature, {} not in {!r}.".format(
+                    request.signature_method,
+                    self.request_validator.allowed_signature_methods))
+
+        # Servers receiving an authenticated request MUST validate it by:
+        #   If the "oauth_version" parameter is present, ensuring its value is
+        #   "1.0".
+        if ('oauth_version' in request.oauth_params and
+                request.oauth_params['oauth_version'] != '1.0'):
+            raise errors.InvalidRequestError(
+                description='Invalid OAuth version.')
+
+        # The timestamp value MUST be a positive integer. Unless otherwise
+        # specified by the server's documentation, the timestamp is expressed
+        # in the number of seconds since January 1, 1970 00:00:00 GMT.
+        if len(request.timestamp) != 10:
+            raise errors.InvalidRequestError(
+                description='Invalid timestamp size')
+
+        try:
+            ts = int(request.timestamp)
+
+        except ValueError:
+            raise errors.InvalidRequestError(
+                description='Timestamp must be an integer.')
+
+        else:
+            # To avoid the need to retain an infinite number of nonce values for
+            # future checks, servers MAY choose to restrict the time period after
+            # which a request with an old timestamp is rejected.
+            if abs(time.time() - ts) > self.request_validator.timestamp_lifetime:
+                raise errors.InvalidRequestError(
+                    description=('Timestamp given is invalid, differ from '
+                                 'allowed by over %s seconds.' % (
+                                     self.request_validator.timestamp_lifetime)))
+
+        # Provider specific validation of parameters, used to enforce
+        # restrictions such as character set and length.
+        if not self.request_validator.check_client_key(request.client_key):
+            raise errors.InvalidRequestError(
+                description='Invalid client key format.')
+
+        if not self.request_validator.check_nonce(request.nonce):
+            raise errors.InvalidRequestError(
+                description='Invalid nonce format.')
+
+    def _check_signature(self, request, is_token_request=False):
+        # ---- RSA Signature verification ----
+        if request.signature_method == SIGNATURE_RSA_SHA1 or \
+           request.signature_method == SIGNATURE_RSA_SHA256 or \
+           request.signature_method == SIGNATURE_RSA_SHA512:
+            # RSA-based signature method
+
+            # The server verifies the signature per `[RFC3447] section 8.2.2`_
+            # .. _`[RFC3447] section 8.2.2`: https://tools.ietf.org/html/rfc3447#section-8.2.1
+
+            rsa_key = self.request_validator.get_rsa_key(
+                request.client_key, request)
+
+            if request.signature_method == SIGNATURE_RSA_SHA1:
+                valid_signature = signature.verify_rsa_sha1(request, rsa_key)
+            elif request.signature_method == SIGNATURE_RSA_SHA256:
+                valid_signature = signature.verify_rsa_sha256(request, rsa_key)
+            elif request.signature_method == SIGNATURE_RSA_SHA512:
+                valid_signature = signature.verify_rsa_sha512(request, rsa_key)
+            else:
+                valid_signature = False
+
+        # ---- HMAC or Plaintext Signature verification ----
+        else:
+            # Non-RSA based signature method
+
+            # Servers receiving an authenticated request MUST validate it by:
+            #   Recalculating the request signature independently as described in
+            #   `Section 3.4`_ and comparing it to the value received from the
+            #   client via the "oauth_signature" parameter.
+            # .. _`Section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
+
+            client_secret = self.request_validator.get_client_secret(
+                request.client_key, request)
+
+            resource_owner_secret = None
+            if request.resource_owner_key:
+                if is_token_request:
+                    resource_owner_secret = \
+                        self.request_validator.get_request_token_secret(
+                            request.client_key, request.resource_owner_key,
+                            request)
+                else:
+                    resource_owner_secret = \
+                        self.request_validator.get_access_token_secret(
+                            request.client_key, request.resource_owner_key,
+                            request)
+
+            if request.signature_method == SIGNATURE_HMAC_SHA1:
+                valid_signature = signature.verify_hmac_sha1(
+                    request, client_secret, resource_owner_secret)
+            elif request.signature_method == SIGNATURE_HMAC_SHA256:
+                valid_signature = signature.verify_hmac_sha256(
+                    request, client_secret, resource_owner_secret)
+            elif request.signature_method == SIGNATURE_HMAC_SHA512:
+                valid_signature = signature.verify_hmac_sha512(
+                    request, client_secret, resource_owner_secret)
+            elif request.signature_method == SIGNATURE_PLAINTEXT:
+                valid_signature = signature.verify_plaintext(
+                    request, client_secret, resource_owner_secret)
+            else:
+                valid_signature = False
+
+        return valid_signature
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py
new file mode 100644
index 00000000..23e3cfc8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py
@@ -0,0 +1,14 @@
+from . import (
+    AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
+    ResourceEndpoint,
+)
+
+
+class WebApplicationServer(RequestTokenEndpoint, AuthorizationEndpoint,
+                           AccessTokenEndpoint, ResourceEndpoint):
+
+    def __init__(self, request_validator):
+        RequestTokenEndpoint.__init__(self, request_validator)
+        AuthorizationEndpoint.__init__(self, request_validator)
+        AccessTokenEndpoint.__init__(self, request_validator)
+        ResourceEndpoint.__init__(self, request_validator)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py
new file mode 100644
index 00000000..0323cfb8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.request_token
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the request token provider logic of
+OAuth 1.0 RFC 5849. It validates the correctness of request token requests,
+creates and persists tokens as well as create the proper response to be
+returned to the client.
+"""
+import logging
+
+from oauthlib.common import urlencode
+
+from .. import errors
+from .base import BaseEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class RequestTokenEndpoint(BaseEndpoint):
+
+    """An endpoint responsible for providing OAuth 1 request tokens.
+
+    Typical use is to instantiate with a request validator and invoke the
+    ``create_request_token_response`` from a view function. The tuple returned
+    has all information necessary (body, status, headers) to quickly form
+    and return a proper response. See :doc:`/oauth1/validator` for details on which
+    validator methods to implement for this endpoint.
+    """
+
+    def create_request_token(self, request, credentials):
+        """Create and save a new request token.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param credentials: A dict of extra token credentials.
+        :returns: The token as an urlencoded string.
+        """
+        token = {
+            'oauth_token': self.token_generator(),
+            'oauth_token_secret': self.token_generator(),
+            'oauth_callback_confirmed': 'true'
+        }
+        token.update(credentials)
+        self.request_validator.save_request_token(token, request)
+        return urlencode(token.items())
+
+    def create_request_token_response(self, uri, http_method='GET', body=None,
+                                      headers=None, credentials=None):
+        """Create a request token response, with a new request token if valid.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :param credentials: A list of extra credentials to include in the token.
+        :returns: A tuple of 3 elements.
+                  1. A dict of headers to set on the response.
+                  2. The response body as a string.
+                  3. The response status code as an integer.
+
+        An example of a valid request::
+
+            >>> from your_validator import your_validator
+            >>> from oauthlib.oauth1 import RequestTokenEndpoint
+            >>> endpoint = RequestTokenEndpoint(your_validator)
+            >>> h, b, s = endpoint.create_request_token_response(
+            ...     'https://your.provider/request_token?foo=bar',
+            ...     headers={
+            ...         'Authorization': 'OAuth realm=movies user, oauth_....'
+            ...     },
+            ...     credentials={
+            ...         'my_specific': 'argument',
+            ...     })
+            >>> h
+            {'Content-Type': 'application/x-www-form-urlencoded'}
+            >>> b
+            'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_callback_confirmed=true&my_specific=argument'
+            >>> s
+            200
+
+        An response to invalid request would have a different body and status::
+
+            >>> b
+            'error=invalid_request&description=missing+callback+uri'
+            >>> s
+            400
+
+        The same goes for an an unauthorized request:
+
+            >>> b
+            ''
+            >>> s
+            401
+        """
+        resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+        try:
+            request = self._create_request(uri, http_method, body, headers)
+            valid, processed_request = self.validate_request_token_request(
+                request)
+            if valid:
+                token = self.create_request_token(request, credentials or {})
+                return resp_headers, token, 200
+            else:
+                return {}, None, 401
+        except errors.OAuth1Error as e:
+            return resp_headers, e.urlencoded, e.status_code
+
+    def validate_request_token_request(self, request):
+        """Validate a request token request.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :raises: OAuth1Error if the request is invalid.
+        :returns: A tuple of 2 elements.
+                  1. The validation result (True or False).
+                  2. The request object.
+        """
+        self._check_transport_security(request)
+        self._check_mandatory_parameters(request)
+
+        if request.realm:
+            request.realms = request.realm.split(' ')
+        else:
+            request.realms = self.request_validator.get_default_realms(
+                request.client_key, request)
+        if not self.request_validator.check_realms(request.realms):
+            raise errors.InvalidRequestError(
+                description='Invalid realm {}. Allowed are {!r}.'.format(
+                    request.realms, self.request_validator.realms))
+
+        if not request.redirect_uri:
+            raise errors.InvalidRequestError(
+                description='Missing callback URI.')
+
+        if not self.request_validator.validate_timestamp_and_nonce(
+                request.client_key, request.timestamp, request.nonce, request,
+                request_token=request.resource_owner_key):
+            return False, request
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid client credentials.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy client is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable client enumeration
+        valid_client = self.request_validator.validate_client_key(
+            request.client_key, request)
+        if not valid_client:
+            request.client_key = self.request_validator.dummy_client
+
+        # Note that `realm`_ is only used in authorization headers and how
+        # it should be interpreted is not included in the OAuth spec.
+        # However they could be seen as a scope or realm to which the
+        # client has access and as such every client should be checked
+        # to ensure it is authorized access to that scope or realm.
+        # .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
+        #
+        # Note that early exit would enable client realm access enumeration.
+        #
+        # The require_realm indicates this is the first step in the OAuth
+        # workflow where a client requests access to a specific realm.
+        # This first step (obtaining request token) need not require a realm
+        # and can then be identified by checking the require_resource_owner
+        # flag and absence of realm.
+        #
+        # Clients obtaining an access token will not supply a realm and it will
+        # not be checked. Instead the previously requested realm should be
+        # transferred from the request token to the access token.
+        #
+        # Access to protected resources will always validate the realm but note
+        # that the realm is now tied to the access token and not provided by
+        # the client.
+        valid_realm = self.request_validator.validate_requested_realms(
+            request.client_key, request.realms, request)
+
+        # Callback is normally never required, except for requests for
+        # a Temporary Credential as described in `Section 2.1`_
+        # .._`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1
+        valid_redirect = self.request_validator.validate_redirect_uri(
+            request.client_key, request.redirect_uri, request)
+        if not request.redirect_uri:
+            raise NotImplementedError('Redirect URI must either be provided '
+                                      'or set to a default during validation.')
+
+        valid_signature = self._check_signature(request)
+
+        # log the results to the validator_log
+        # this lets us handle internal reporting and analysis
+        request.validator_log['client'] = valid_client
+        request.validator_log['realm'] = valid_realm
+        request.validator_log['callback'] = valid_redirect
+        request.validator_log['signature'] = valid_signature
+
+        # We delay checking validity until the very end, using dummy values for
+        # calculations and fetching secrets/keys to ensure the flow of every
+        # request remains almost identical regardless of whether valid values
+        # have been supplied. This ensures near constant time execution and
+        # prevents malicious users from guessing sensitive information
+        v = all((valid_client, valid_realm, valid_redirect, valid_signature))
+        if not v:
+            log.info("[Failure] request verification failed.")
+            log.info("Valid client: %s.", valid_client)
+            log.info("Valid realm: %s.", valid_realm)
+            log.info("Valid callback: %s.", valid_redirect)
+            log.info("Valid signature: %s.", valid_signature)
+        return v, request
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py
new file mode 100644
index 00000000..8641152e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.resource
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the resource protection provider logic of
+OAuth 1.0 RFC 5849.
+"""
+import logging
+
+from .. import errors
+from .base import BaseEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class ResourceEndpoint(BaseEndpoint):
+
+    """An endpoint responsible for protecting resources.
+
+    Typical use is to instantiate with a request validator and invoke the
+    ``validate_protected_resource_request`` in a decorator around a view
+    function. If the request is valid, invoke and return the response of the
+    view. If invalid create and return an error response directly from the
+    decorator.
+
+    See :doc:`/oauth1/validator` for details on which validator methods to implement
+    for this endpoint.
+
+    An example decorator::
+
+        from functools import wraps
+        from your_validator import your_validator
+        from oauthlib.oauth1 import ResourceEndpoint
+        endpoint = ResourceEndpoint(your_validator)
+
+        def require_oauth(realms=None):
+            def decorator(f):
+                @wraps(f)
+                def wrapper(request, *args, **kwargs):
+                    v, r = provider.validate_protected_resource_request(
+                            request.url,
+                            http_method=request.method,
+                            body=request.data,
+                            headers=request.headers,
+                            realms=realms or [])
+                    if v:
+                        return f(*args, **kwargs)
+                    else:
+                        return abort(403)
+    """
+
+    def validate_protected_resource_request(self, uri, http_method='GET',
+                                            body=None, headers=None, realms=None):
+        """Create a request token response, with a new request token if valid.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :param realms: A list of realms the resource is protected under.
+                       This will be supplied to the ``validate_realms``
+                       method of the request validator.
+        :returns: A tuple of 2 elements.
+                  1. True if valid, False otherwise.
+                  2. An oauthlib.common.Request object.
+        """
+        try:
+            request = self._create_request(uri, http_method, body, headers)
+        except errors.OAuth1Error:
+            return False, None
+
+        try:
+            self._check_transport_security(request)
+            self._check_mandatory_parameters(request)
+        except errors.OAuth1Error:
+            return False, request
+
+        if not request.resource_owner_key:
+            return False, request
+
+        if not self.request_validator.check_access_token(
+                request.resource_owner_key):
+            return False, request
+
+        if not self.request_validator.validate_timestamp_and_nonce(
+                request.client_key, request.timestamp, request.nonce, request,
+                access_token=request.resource_owner_key):
+            return False, request
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid client credentials.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy client is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable client enumeration
+        valid_client = self.request_validator.validate_client_key(
+            request.client_key, request)
+        if not valid_client:
+            request.client_key = self.request_validator.dummy_client
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid or expired token.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy token is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable resource owner enumeration
+        valid_resource_owner = self.request_validator.validate_access_token(
+            request.client_key, request.resource_owner_key, request)
+        if not valid_resource_owner:
+            request.resource_owner_key = self.request_validator.dummy_access_token
+
+        # Note that `realm`_ is only used in authorization headers and how
+        # it should be interpreted is not included in the OAuth spec.
+        # However they could be seen as a scope or realm to which the
+        # client has access and as such every client should be checked
+        # to ensure it is authorized access to that scope or realm.
+        # .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
+        #
+        # Note that early exit would enable client realm access enumeration.
+        #
+        # The require_realm indicates this is the first step in the OAuth
+        # workflow where a client requests access to a specific realm.
+        # This first step (obtaining request token) need not require a realm
+        # and can then be identified by checking the require_resource_owner
+        # flag and absence of realm.
+        #
+        # Clients obtaining an access token will not supply a realm and it will
+        # not be checked. Instead the previously requested realm should be
+        # transferred from the request token to the access token.
+        #
+        # Access to protected resources will always validate the realm but note
+        # that the realm is now tied to the access token and not provided by
+        # the client.
+        valid_realm = self.request_validator.validate_realms(request.client_key,
+                                                             request.resource_owner_key, request, uri=request.uri,
+                                                             realms=realms)
+
+        valid_signature = self._check_signature(request)
+
+        # log the results to the validator_log
+        # this lets us handle internal reporting and analysis
+        request.validator_log['client'] = valid_client
+        request.validator_log['resource_owner'] = valid_resource_owner
+        request.validator_log['realm'] = valid_realm
+        request.validator_log['signature'] = valid_signature
+
+        # We delay checking validity until the very end, using dummy values for
+        # calculations and fetching secrets/keys to ensure the flow of every
+        # request remains almost identical regardless of whether valid values
+        # have been supplied. This ensures near constant time execution and
+        # prevents malicious users from guessing sensitive information
+        v = all((valid_client, valid_resource_owner, valid_realm,
+                 valid_signature))
+        if not v:
+            log.info("[Failure] request verification failed.")
+            log.info("Valid client: %s", valid_client)
+            log.info("Valid token: %s", valid_resource_owner)
+            log.info("Valid realm: %s", valid_realm)
+            log.info("Valid signature: %s", valid_signature)
+        return v, request
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py
new file mode 100644
index 00000000..d693ccb7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.signature_only
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the signing logic of OAuth 1.0 RFC 5849.
+"""
+
+import logging
+
+from .. import errors
+from .base import BaseEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class SignatureOnlyEndpoint(BaseEndpoint):
+
+    """An endpoint only responsible for verifying an oauth signature."""
+
+    def validate_request(self, uri, http_method='GET',
+                         body=None, headers=None):
+        """Validate a signed OAuth request.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :returns: A tuple of 2 elements.
+                  1. True if valid, False otherwise.
+                  2. An oauthlib.common.Request object.
+        """
+        try:
+            request = self._create_request(uri, http_method, body, headers)
+        except errors.OAuth1Error as err:
+            log.info(
+                'Exception caught while validating request, %s.' % err)
+            return False, None
+
+        try:
+            self._check_transport_security(request)
+            self._check_mandatory_parameters(request)
+        except errors.OAuth1Error as err:
+            log.info(
+                'Exception caught while validating request, %s.' % err)
+            return False, request
+
+        if not self.request_validator.validate_timestamp_and_nonce(
+                request.client_key, request.timestamp, request.nonce, request):
+            log.debug('[Failure] verification failed: timestamp/nonce')
+            return False, request
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid client credentials.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy client is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable client enumeration
+        valid_client = self.request_validator.validate_client_key(
+            request.client_key, request)
+        if not valid_client:
+            request.client_key = self.request_validator.dummy_client
+
+        valid_signature = self._check_signature(request)
+
+        # log the results to the validator_log
+        # this lets us handle internal reporting and analysis
+        request.validator_log['client'] = valid_client
+        request.validator_log['signature'] = valid_signature
+
+        # We delay checking validity until the very end, using dummy values for
+        # calculations and fetching secrets/keys to ensure the flow of every
+        # request remains almost identical regardless of whether valid values
+        # have been supplied. This ensures near constant time execution and
+        # prevents malicious users from guessing sensitive information
+        v = all((valid_client, valid_signature))
+        if not v:
+            log.info("[Failure] request verification failed.")
+            log.info("Valid client: %s", valid_client)
+            log.info("Valid signature: %s", valid_signature)
+        return v, request
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/errors.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/errors.py
new file mode 100644
index 00000000..8774d407
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/errors.py
@@ -0,0 +1,76 @@
+"""
+oauthlib.oauth1.rfc5849.errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Error used both by OAuth 1 clients and provicers to represent the spec
+defined error responses for all four core grant types.
+"""
+from oauthlib.common import add_params_to_uri, urlencode
+
+
+class OAuth1Error(Exception):
+    error = None
+    description = ''
+
+    def __init__(self, description=None, uri=None, status_code=400,
+                 request=None):
+        """
+        description:    A human-readable ASCII [USASCII] text providing
+                        additional information, used to assist the client
+                        developer in understanding the error that occurred.
+                        Values for the "error_description" parameter MUST NOT
+                        include characters outside the set
+                        x20-21 / x23-5B / x5D-7E.
+
+        uri:    A URI identifying a human-readable web page with information
+                about the error, used to provide the client developer with
+                additional information about the error.  Values for the
+                "error_uri" parameter MUST conform to the URI- Reference
+                syntax, and thus MUST NOT include characters outside the set
+                x21 / x23-5B / x5D-7E.
+
+        state:  A CSRF protection value received from the client.
+
+        request:  Oauthlib Request object
+        """
+        self.description = description or self.description
+        message = '({}) {}'.format(self.error, self.description)
+        if request:
+            message += ' ' + repr(request)
+        super().__init__(message)
+
+        self.uri = uri
+        self.status_code = status_code
+
+    def in_uri(self, uri):
+        return add_params_to_uri(uri, self.twotuples)
+
+    @property
+    def twotuples(self):
+        error = [('error', self.error)]
+        if self.description:
+            error.append(('error_description', self.description))
+        if self.uri:
+            error.append(('error_uri', self.uri))
+        return error
+
+    @property
+    def urlencoded(self):
+        return urlencode(self.twotuples)
+
+
+class InsecureTransportError(OAuth1Error):
+    error = 'insecure_transport_protocol'
+    description = 'Only HTTPS connections are permitted.'
+
+
+class InvalidSignatureMethodError(OAuth1Error):
+    error = 'invalid_signature_method'
+
+
+class InvalidRequestError(OAuth1Error):
+    error = 'invalid_request'
+
+
+class InvalidClientError(OAuth1Error):
+    error = 'invalid_client'
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/parameters.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/parameters.py
new file mode 100644
index 00000000..2163772d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/parameters.py
@@ -0,0 +1,133 @@
+"""
+oauthlib.parameters
+~~~~~~~~~~~~~~~~~~~
+
+This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.
+
+.. _`section 3.5`: https://tools.ietf.org/html/rfc5849#section-3.5
+"""
+from urllib.parse import urlparse, urlunparse
+
+from oauthlib.common import extract_params, urlencode
+
+from . import utils
+
+
+# TODO: do we need filter_params now that oauth_params are handled by Request?
+#       We can easily pass in just oauth protocol params.
+@utils.filter_params
+def prepare_headers(oauth_params, headers=None, realm=None):
+    """**Prepare the Authorization header.**
+    Per `section 3.5.1`_ of the spec.
+
+    Protocol parameters can be transmitted using the HTTP "Authorization"
+    header field as defined by `RFC2617`_ with the auth-scheme name set to
+    "OAuth" (case insensitive).
+
+    For example::
+
+        Authorization: OAuth realm="Example",
+            oauth_consumer_key="0685bd9184jfhq22",
+            oauth_token="ad180jjd733klru7",
+            oauth_signature_method="HMAC-SHA1",
+            oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+            oauth_timestamp="137131200",
+            oauth_nonce="4572616e48616d6d65724c61686176",
+            oauth_version="1.0"
+
+
+    .. _`section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
+    .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
+    """
+    headers = headers or {}
+
+    # Protocol parameters SHALL be included in the "Authorization" header
+    # field as follows:
+    authorization_header_parameters_parts = []
+    for oauth_parameter_name, value in oauth_params:
+        # 1.  Parameter names and values are encoded per Parameter Encoding
+        #     (`Section 3.6`_)
+        #
+        # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+        escaped_name = utils.escape(oauth_parameter_name)
+        escaped_value = utils.escape(value)
+
+        # 2.  Each parameter's name is immediately followed by an "=" character
+        #     (ASCII code 61), a """ character (ASCII code 34), the parameter
+        #     value (MAY be empty), and another """ character (ASCII code 34).
+        part = '{}="{}"'.format(escaped_name, escaped_value)
+
+        authorization_header_parameters_parts.append(part)
+
+    # 3.  Parameters are separated by a "," character (ASCII code 44) and
+    #     OPTIONAL linear whitespace per `RFC2617`_.
+    #
+    # .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
+    authorization_header_parameters = ', '.join(
+        authorization_header_parameters_parts)
+
+    # 4.  The OPTIONAL "realm" parameter MAY be added and interpreted per
+    #     `RFC2617 section 1.2`_.
+    #
+    # .. _`RFC2617 section 1.2`: https://tools.ietf.org/html/rfc2617#section-1.2
+    if realm:
+        # NOTE: realm should *not* be escaped
+        authorization_header_parameters = ('realm="%s", ' % realm +
+                                           authorization_header_parameters)
+
+    # the auth-scheme name set to "OAuth" (case insensitive).
+    authorization_header = 'OAuth %s' % authorization_header_parameters
+
+    # contribute the Authorization header to the given headers
+    full_headers = {}
+    full_headers.update(headers)
+    full_headers['Authorization'] = authorization_header
+    return full_headers
+
+
+def _append_params(oauth_params, params):
+    """Append OAuth params to an existing set of parameters.
+
+    Both params and oauth_params is must be lists of 2-tuples.
+
+    Per `section 3.5.2`_ and `3.5.3`_ of the spec.
+
+    .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
+    .. _`3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3
+
+    """
+    merged = list(params)
+    merged.extend(oauth_params)
+    # The request URI / entity-body MAY include other request-specific
+    # parameters, in which case, the protocol parameters SHOULD be appended
+    # following the request-specific parameters, properly separated by an "&"
+    # character (ASCII code 38)
+    merged.sort(key=lambda i: i[0].startswith('oauth_'))
+    return merged
+
+
+def prepare_form_encoded_body(oauth_params, body):
+    """Prepare the Form-Encoded Body.
+
+    Per `section 3.5.2`_ of the spec.
+
+    .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
+
+    """
+    # append OAuth params to the existing body
+    return _append_params(oauth_params, body)
+
+
+def prepare_request_uri_query(oauth_params, uri):
+    """Prepare the Request URI Query.
+
+    Per `section 3.5.3`_ of the spec.
+
+    .. _`section 3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3
+
+    """
+    # append OAuth params to the existing set of query components
+    sch, net, path, par, query, fra = urlparse(uri)
+    query = urlencode(
+        _append_params(oauth_params, extract_params(query) or []))
+    return urlunparse((sch, net, path, par, query, fra))
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/request_validator.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/request_validator.py
new file mode 100644
index 00000000..e937aabf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/request_validator.py
@@ -0,0 +1,849 @@
+"""
+oauthlib.oauth1.rfc5849
+~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 1.0 RFC 5849 requests.
+"""
+from . import SIGNATURE_METHODS, utils
+
+
+class RequestValidator:
+
+    """A validator/datastore interaction base class for OAuth 1 providers.
+
+    OAuth providers should inherit from RequestValidator and implement the
+    methods and properties outlined below. Further details are provided in the
+    documentation for each method and property.
+
+    Methods used to check the format of input parameters. Common tests include
+    length, character set, membership, range or pattern. These tests are
+    referred to as `whitelisting or blacklisting`_. Whitelisting is better
+    but blacklisting can be useful to spot malicious activity.
+    The following have methods a default implementation:
+
+    - check_client_key
+    - check_request_token
+    - check_access_token
+    - check_nonce
+    - check_verifier
+    - check_realms
+
+    The methods above default to whitelist input parameters, checking that they
+    are alphanumerical and between a minimum and maximum length. Rather than
+    overloading the methods a few properties can be used to configure these
+    methods.
+
+    * @safe_characters -> (character set)
+    * @client_key_length -> (min, max)
+    * @request_token_length -> (min, max)
+    * @access_token_length -> (min, max)
+    * @nonce_length -> (min, max)
+    * @verifier_length -> (min, max)
+    * @realms -> [list, of, realms]
+
+    Methods used to validate/invalidate input parameters. These checks usually
+    hit either persistent or temporary storage such as databases or the
+    filesystem. See each methods documentation for detailed usage.
+    The following methods must be implemented:
+
+    - validate_client_key
+    - validate_request_token
+    - validate_access_token
+    - validate_timestamp_and_nonce
+    - validate_redirect_uri
+    - validate_requested_realms
+    - validate_realms
+    - validate_verifier
+    - invalidate_request_token
+
+    Methods used to retrieve sensitive information from storage.
+    The following methods must be implemented:
+
+    - get_client_secret
+    - get_request_token_secret
+    - get_access_token_secret
+    - get_rsa_key
+    - get_realms
+    - get_default_realms
+    - get_redirect_uri
+
+    Methods used to save credentials.
+    The following methods must be implemented:
+
+    - save_request_token
+    - save_verifier
+    - save_access_token
+
+    Methods used to verify input parameters. This methods are used during
+    authorizing request token by user (AuthorizationEndpoint), to check if
+    parameters are valid. During token authorization request is not signed,
+    thus 'validation' methods can not be used. The following methods must be
+    implemented:
+
+    - verify_realms
+    - verify_request_token
+
+    To prevent timing attacks it is necessary to not exit early even if the
+    client key or resource owner key is invalid. Instead dummy values should
+    be used during the remaining verification process. It is very important
+    that the dummy client and token are valid input parameters to the methods
+    get_client_secret, get_rsa_key and get_(access/request)_token_secret and
+    that the running time of those methods when given a dummy value remain
+    equivalent to the running time when given a valid client/resource owner.
+    The following properties must be implemented:
+
+    * @dummy_client
+    * @dummy_request_token
+    * @dummy_access_token
+
+    Example implementations have been provided, note that the database used is
+    a simple dictionary and serves only an illustrative purpose. Use whichever
+    database suits your project and how to access it is entirely up to you.
+    The methods are introduced in an order which should make understanding
+    their use more straightforward and as such it could be worth reading what
+    follows in chronological order.
+
+    .. _`whitelisting or blacklisting`: https://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html
+    """
+
+    def __init__(self):
+        pass
+
+    @property
+    def allowed_signature_methods(self):
+        return SIGNATURE_METHODS
+
+    @property
+    def safe_characters(self):
+        return set(utils.UNICODE_ASCII_CHARACTER_SET)
+
+    @property
+    def client_key_length(self):
+        return 20, 30
+
+    @property
+    def request_token_length(self):
+        return 20, 30
+
+    @property
+    def access_token_length(self):
+        return 20, 30
+
+    @property
+    def timestamp_lifetime(self):
+        return 600
+
+    @property
+    def nonce_length(self):
+        return 20, 30
+
+    @property
+    def verifier_length(self):
+        return 20, 30
+
+    @property
+    def realms(self):
+        return []
+
+    @property
+    def enforce_ssl(self):
+        return True
+
+    def check_client_key(self, client_key):
+        """Check that the client key only contains safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.client_key_length
+        return (set(client_key) <= self.safe_characters and
+                lower <= len(client_key) <= upper)
+
+    def check_request_token(self, request_token):
+        """Checks that the request token contains only safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.request_token_length
+        return (set(request_token) <= self.safe_characters and
+                lower <= len(request_token) <= upper)
+
+    def check_access_token(self, request_token):
+        """Checks that the token contains only safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.access_token_length
+        return (set(request_token) <= self.safe_characters and
+                lower <= len(request_token) <= upper)
+
+    def check_nonce(self, nonce):
+        """Checks that the nonce only contains only safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.nonce_length
+        return (set(nonce) <= self.safe_characters and
+                lower <= len(nonce) <= upper)
+
+    def check_verifier(self, verifier):
+        """Checks that the verifier contains only safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.verifier_length
+        return (set(verifier) <= self.safe_characters and
+                lower <= len(verifier) <= upper)
+
+    def check_realms(self, realms):
+        """Check that the realm is one of a set allowed realms."""
+        return all(r in self.realms for r in realms)
+
+    def _subclass_must_implement(self, fn):
+        """
+        Returns a NotImplementedError for a function that should be implemented.
+        :param fn: name of the function
+        """
+        m = "Missing function implementation in {}: {}".format(type(self), fn)
+        return NotImplementedError(m)
+
+    @property
+    def dummy_client(self):
+        """Dummy client used when an invalid client key is supplied.
+
+        :returns: The dummy client key string.
+
+        The dummy client should be associated with either a client secret,
+        a rsa key or both depending on which signature methods are supported.
+        Providers should make sure that
+
+        get_client_secret(dummy_client)
+        get_rsa_key(dummy_client)
+
+        return a valid secret or key for the dummy client.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement("dummy_client")
+
+    @property
+    def dummy_request_token(self):
+        """Dummy request token used when an invalid token was supplied.
+
+        :returns: The dummy request token string.
+
+        The dummy request token should be associated with a request token
+        secret such that get_request_token_secret(.., dummy_request_token)
+        returns a valid secret.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("dummy_request_token")
+
+    @property
+    def dummy_access_token(self):
+        """Dummy access token used when an invalid token was supplied.
+
+        :returns: The dummy access token string.
+
+        The dummy access token should be associated with an access token
+        secret such that get_access_token_secret(.., dummy_access_token)
+        returns a valid secret.
+
+        This method is used by
+
+        * ResourceEndpoint
+        """
+        raise self._subclass_must_implement("dummy_access_token")
+
+    def get_client_secret(self, client_key, request):
+        """Retrieves the client secret associated with the client key.
+
+        :param client_key: The client/consumer key.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The client secret as a string.
+
+        This method must allow the use of a dummy client_key value.
+        Fetching the secret using the dummy key must take the same amount of
+        time as fetching a secret for a valid client::
+
+            # Unlikely to be near constant time as it uses two database
+            # lookups for a valid client, and only one for an invalid.
+            from your_datastore import ClientSecret
+            if ClientSecret.has(client_key):
+                return ClientSecret.get(client_key)
+            else:
+                return 'dummy'
+
+            # Aim to mimic number of latency inducing operations no matter
+            # whether the client is valid or not.
+            from your_datastore import ClientSecret
+            return ClientSecret.get(client_key, 'dummy')
+
+        Note that the returned key must be in plaintext.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement('get_client_secret')
+
+    def get_request_token_secret(self, client_key, token, request):
+        """Retrieves the shared secret associated with the request token.
+
+        :param client_key: The client/consumer key.
+        :param token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The token secret as a string.
+
+        This method must allow the use of a dummy values and the running time
+        must be roughly equivalent to that of the running time of valid values::
+
+            # Unlikely to be near constant time as it uses two database
+            # lookups for a valid client, and only one for an invalid.
+            from your_datastore import RequestTokenSecret
+            if RequestTokenSecret.has(client_key):
+                return RequestTokenSecret.get((client_key, request_token))
+            else:
+                return 'dummy'
+
+            # Aim to mimic number of latency inducing operations no matter
+            # whether the client is valid or not.
+            from your_datastore import RequestTokenSecret
+            return ClientSecret.get((client_key, request_token), 'dummy')
+
+        Note that the returned key must be in plaintext.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement('get_request_token_secret')
+
+    def get_access_token_secret(self, client_key, token, request):
+        """Retrieves the shared secret associated with the access token.
+
+        :param client_key: The client/consumer key.
+        :param token: The access token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The token secret as a string.
+
+        This method must allow the use of a dummy values and the running time
+        must be roughly equivalent to that of the running time of valid values::
+
+            # Unlikely to be near constant time as it uses two database
+            # lookups for a valid client, and only one for an invalid.
+            from your_datastore import AccessTokenSecret
+            if AccessTokenSecret.has(client_key):
+                return AccessTokenSecret.get((client_key, request_token))
+            else:
+                return 'dummy'
+
+            # Aim to mimic number of latency inducing operations no matter
+            # whether the client is valid or not.
+            from your_datastore import AccessTokenSecret
+            return ClientSecret.get((client_key, request_token), 'dummy')
+
+        Note that the returned key must be in plaintext.
+
+        This method is used by
+
+        * ResourceEndpoint
+        """
+        raise self._subclass_must_implement("get_access_token_secret")
+
+    def get_default_realms(self, client_key, request):
+        """Get the default realms for a client.
+
+        :param client_key: The client/consumer key.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The list of default realms associated with the client.
+
+        The list of default realms will be set during client registration and
+        is outside the scope of OAuthLib.
+
+        This method is used by
+
+        * RequestTokenEndpoint
+        """
+        raise self._subclass_must_implement("get_default_realms")
+
+    def get_realms(self, token, request):
+        """Get realms associated with a request token.
+
+        :param token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The list of realms associated with the request token.
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("get_realms")
+
+    def get_redirect_uri(self, token, request):
+        """Get the redirect URI associated with a request token.
+
+        :param token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The redirect URI associated with the request token.
+
+        It may be desirable to return a custom URI if the redirect is set to "oob".
+        In this case, the user will be redirected to the returned URI and at that
+        endpoint the verifier can be displayed.
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        """
+        raise self._subclass_must_implement("get_redirect_uri")
+
+    def get_rsa_key(self, client_key, request):
+        """Retrieves a previously stored client provided RSA key.
+
+        :param client_key: The client/consumer key.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The rsa public key as a string.
+
+        This method must allow the use of a dummy client_key value. Fetching
+        the rsa key using the dummy key must take the same amount of time
+        as fetching a key for a valid client. The dummy key must also be of
+        the same bit length as client keys.
+
+        Note that the key must be returned in plaintext.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement("get_rsa_key")
+
+    def invalidate_request_token(self, client_key, request_token, request):
+        """Invalidates a used request token.
+
+        :param client_key: The client/consumer key.
+        :param request_token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: None
+
+        Per `Section 2.3`_ of the spec:
+
+        "The server MUST (...) ensure that the temporary
+        credentials have not expired or been used before."
+
+        .. _`Section 2.3`: https://tools.ietf.org/html/rfc5849#section-2.3
+
+        This method should ensure that provided token won't validate anymore.
+        It can be simply removing RequestToken from storage or setting
+        specific flag that makes it invalid (note that such flag should be
+        also validated during request token validation).
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("invalidate_request_token")
+
+    def validate_client_key(self, client_key, request):
+        """Validates that supplied client key is a registered and valid client.
+
+        :param client_key: The client/consumer key.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        Note that if the dummy client is supplied it should validate in same
+        or nearly the same amount of time as a valid one.
+
+        Ensure latency inducing tasks are mimiced even for dummy clients.
+        For example, use::
+
+            from your_datastore import Client
+            try:
+                return Client.exists(client_key, access_token)
+            except DoesNotExist:
+                return False
+
+        Rather than::
+
+            from your_datastore import Client
+            if access_token == self.dummy_access_token:
+                return False
+            else:
+                return Client.exists(client_key, access_token)
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement("validate_client_key")
+
+    def validate_request_token(self, client_key, token, request):
+        """Validates that supplied request token is registered and valid.
+
+        :param client_key: The client/consumer key.
+        :param token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        Note that if the dummy request_token is supplied it should validate in
+        the same nearly the same amount of time as a valid one.
+
+        Ensure latency inducing tasks are mimiced even for dummy clients.
+        For example, use::
+
+            from your_datastore import RequestToken
+            try:
+                return RequestToken.exists(client_key, access_token)
+            except DoesNotExist:
+                return False
+
+        Rather than::
+
+            from your_datastore import RequestToken
+            if access_token == self.dummy_access_token:
+                return False
+            else:
+                return RequestToken.exists(client_key, access_token)
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("validate_request_token")
+
+    def validate_access_token(self, client_key, token, request):
+        """Validates that supplied access token is registered and valid.
+
+        :param client_key: The client/consumer key.
+        :param token: The access token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        Note that if the dummy access token is supplied it should validate in
+        the same or nearly the same amount of time as a valid one.
+
+        Ensure latency inducing tasks are mimiced even for dummy clients.
+        For example, use::
+
+            from your_datastore import AccessToken
+            try:
+                return AccessToken.exists(client_key, access_token)
+            except DoesNotExist:
+                return False
+
+        Rather than::
+
+            from your_datastore import AccessToken
+            if access_token == self.dummy_access_token:
+                return False
+            else:
+                return AccessToken.exists(client_key, access_token)
+
+        This method is used by
+
+        * ResourceEndpoint
+        """
+        raise self._subclass_must_implement("validate_access_token")
+
+    def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
+                                     request, request_token=None, access_token=None):
+        """Validates that the nonce has not been used before.
+
+        :param client_key: The client/consumer key.
+        :param timestamp: The ``oauth_timestamp`` parameter.
+        :param nonce: The ``oauth_nonce`` parameter.
+        :param request_token: Request token string, if any.
+        :param access_token: Access token string, if any.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        Per `Section 3.3`_ of the spec.
+
+        "A nonce is a random string, uniquely generated by the client to allow
+        the server to verify that a request has never been made before and
+        helps prevent replay attacks when requests are made over a non-secure
+        channel.  The nonce value MUST be unique across all requests with the
+        same timestamp, client credentials, and token combinations."
+
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
+
+        One of the first validation checks that will be made is for the validity
+        of the nonce and timestamp, which are associated with a client key and
+        possibly a token. If invalid then immediately fail the request
+        by returning False. If the nonce/timestamp pair has been used before and
+        you may just have detected a replay attack. Therefore it is an essential
+        part of OAuth security that you not allow nonce/timestamp reuse.
+        Note that this validation check is done before checking the validity of
+        the client and token.::
+
+           nonces_and_timestamps_database = [
+              (u'foo', 1234567890, u'rannoMstrInghere', u'bar')
+           ]
+
+           def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
+              request_token=None, access_token=None):
+
+              return ((client_key, timestamp, nonce, request_token or access_token)
+                       not in self.nonces_and_timestamps_database)
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement("validate_timestamp_and_nonce")
+
+    def validate_redirect_uri(self, client_key, redirect_uri, request):
+        """Validates the client supplied redirection URI.
+
+        :param client_key: The client/consumer key.
+        :param redirect_uri: The URI the client which to redirect back to after
+                             authorization is successful.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        It is highly recommended that OAuth providers require their clients
+        to register all redirection URIs prior to using them in requests and
+        register them as absolute URIs. See `CWE-601`_ for more information
+        about open redirection attacks.
+
+        By requiring registration of all redirection URIs it should be
+        straightforward for the provider to verify whether the supplied
+        redirect_uri is valid or not.
+
+        Alternatively per `Section 2.1`_ of the spec:
+
+        "If the client is unable to receive callbacks or a callback URI has
+        been established via other means, the parameter value MUST be set to
+        "oob" (case sensitive), to indicate an out-of-band configuration."
+
+        .. _`CWE-601`: http://cwe.mitre.org/top25/index.html#CWE-601
+        .. _`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1
+
+        This method is used by
+
+        * RequestTokenEndpoint
+        """
+        raise self._subclass_must_implement("validate_redirect_uri")
+
+    def validate_requested_realms(self, client_key, realms, request):
+        """Validates that the client may request access to the realm.
+
+        :param client_key: The client/consumer key.
+        :param realms: The list of realms that client is requesting access to.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        This method is invoked when obtaining a request token and should
+        tie a realm to the request token and after user authorization
+        this realm restriction should transfer to the access token.
+
+        This method is used by
+
+        * RequestTokenEndpoint
+        """
+        raise self._subclass_must_implement("validate_requested_realms")
+
+    def validate_realms(self, client_key, token, request, uri=None,
+                        realms=None):
+        """Validates access to the request realm.
+
+        :param client_key: The client/consumer key.
+        :param token: A request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param uri: The URI the realms is protecting.
+        :param realms: A list of realms that must have been granted to
+                       the access token.
+        :returns: True or False
+
+        How providers choose to use the realm parameter is outside the OAuth
+        specification but it is commonly used to restrict access to a subset
+        of protected resources such as "photos".
+
+        realms is a convenience parameter which can be used to provide
+        a per view method pre-defined list of allowed realms.
+
+        Can be as simple as::
+
+            from your_datastore import RequestToken
+            request_token = RequestToken.get(token, None)
+
+            if not request_token:
+                return False
+            return set(request_token.realms).issuperset(set(realms))
+
+        This method is used by
+
+        * ResourceEndpoint
+        """
+        raise self._subclass_must_implement("validate_realms")
+
+    def validate_verifier(self, client_key, token, verifier, request):
+        """Validates a verification code.
+
+        :param client_key: The client/consumer key.
+        :param token: A request token string.
+        :param verifier: The authorization verifier string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        OAuth providers issue a verification code to clients after the
+        resource owner authorizes access. This code is used by the client to
+        obtain token credentials and the provider must verify that the
+        verifier is valid and associated with the client as well as the
+        resource owner.
+
+        Verifier validation should be done in near constant time
+        (to avoid verifier enumeration). To achieve this we need a
+        constant time string comparison which is provided by OAuthLib
+        in ``oauthlib.common.safe_string_equals``::
+
+            from your_datastore import Verifier
+            correct_verifier = Verifier.get(client_key, request_token)
+            from oauthlib.common import safe_string_equals
+            return safe_string_equals(verifier, correct_verifier)
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("validate_verifier")
+
+    def verify_request_token(self, token, request):
+        """Verify that the given OAuth1 request token is valid.
+
+        :param token: A request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        This method is used only in AuthorizationEndpoint to check whether the
+        oauth_token given in the authorization URL is valid or not.
+        This request is not signed and thus similar ``validate_request_token``
+        method can not be used.
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        """
+        raise self._subclass_must_implement("verify_request_token")
+
+    def verify_realms(self, token, realms, request):
+        """Verify authorized realms to see if they match those given to token.
+
+        :param token: An access token string.
+        :param realms: A list of realms the client attempts to access.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        This prevents the list of authorized realms sent by the client during
+        the authorization step to be altered to include realms outside what
+        was bound with the request token.
+
+        Can be as simple as::
+
+            valid_realms = self.get_realms(token)
+            return all((r in valid_realms for r in realms))
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        """
+        raise self._subclass_must_implement("verify_realms")
+
+    def save_access_token(self, token, request):
+        """Save an OAuth1 access token.
+
+        :param token: A dict with token credentials.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        The token dictionary will at minimum include
+
+        * ``oauth_token`` the access token string.
+        * ``oauth_token_secret`` the token specific secret used in signing.
+        * ``oauth_authorized_realms`` a space separated list of realms.
+
+        Client key can be obtained from ``request.client_key``.
+
+        The list of realms (not joined string) can be obtained from
+        ``request.realm``.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("save_access_token")
+
+    def save_request_token(self, token, request):
+        """Save an OAuth1 request token.
+
+        :param token: A dict with token credentials.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        The token dictionary will at minimum include
+
+        * ``oauth_token`` the request token string.
+        * ``oauth_token_secret`` the token specific secret used in signing.
+        * ``oauth_callback_confirmed`` the string ``true``.
+
+        Client key can be obtained from ``request.client_key``.
+
+        This method is used by
+
+        * RequestTokenEndpoint
+        """
+        raise self._subclass_must_implement("save_request_token")
+
+    def save_verifier(self, token, verifier, request):
+        """Associate an authorization verifier with a request token.
+
+        :param token: A request token string.
+        :param verifier: A dictionary containing the oauth_verifier and
+                        oauth_token
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        We need to associate verifiers with tokens for validation during the
+        access token request.
+
+        Note that unlike save_x_token token here is the ``oauth_token`` token
+        string from the request token saved previously.
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        """
+        raise self._subclass_must_implement("save_verifier")
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/signature.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/signature.py
new file mode 100644
index 00000000..9cb1a517
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/signature.py
@@ -0,0 +1,852 @@
+"""
+This module is an implementation of `section 3.4`_ of RFC 5849.
+
+**Usage**
+
+Steps for signing a request:
+
+1. Collect parameters from the request using ``collect_parameters``.
+2. Normalize those parameters using ``normalize_parameters``.
+3. Create the *base string URI* using ``base_string_uri``.
+4. Create the *signature base string* from the above three components
+   using ``signature_base_string``.
+5. Pass the *signature base string* and the client credentials to one of the
+   sign-with-client functions. The HMAC-based signing functions needs
+   client credentials with secrets. The RSA-based signing functions needs
+   client credentials with an RSA private key.
+
+To verify a request, pass the request and credentials to one of the verify
+functions. The HMAC-based signing functions needs the shared secrets. The
+RSA-based verify functions needs the RSA public key.
+
+**Scope**
+
+All of the functions in this module should be considered internal to OAuthLib,
+since they are not imported into the "oauthlib.oauth1" module. Programs using
+OAuthLib should not use directly invoke any of the functions in this module.
+
+**Deprecated functions**
+
+The "sign_" methods that are not "_with_client" have been deprecated. They may
+be removed in a future release. Since they are all internal functions, this
+should have no impact on properly behaving programs.
+
+.. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
+"""
+
+import binascii
+import hashlib
+import hmac
+import ipaddress
+import logging
+import urllib.parse as urlparse
+import warnings
+
+from oauthlib.common import extract_params, safe_string_equals, urldecode
+
+from . import utils
+
+log = logging.getLogger(__name__)
+
+
+# ==== Common functions ==========================================
+
+def signature_base_string(
+        http_method: str,
+        base_str_uri: str,
+        normalized_encoded_request_parameters: str) -> str:
+    """
+    Construct the signature base string.
+
+    The *signature base string* is the value that is calculated and signed by
+    the client. It is also independently calculated by the server to verify
+    the signature, and therefore must produce the exact same value at both
+    ends or the signature won't verify.
+
+    The rules for calculating the *signature base string* are defined in
+    section 3.4.1.1`_ of RFC 5849.
+
+    .. _`section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
+    """
+
+    # The signature base string is constructed by concatenating together,
+    # in order, the following HTTP request elements:
+
+    # 1.  The HTTP request method in uppercase.  For example: "HEAD",
+    #     "GET", "POST", etc.  If the request uses a custom HTTP method, it
+    #     MUST be encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    base_string = utils.escape(http_method.upper())
+
+    # 2.  An "&" character (ASCII code 38).
+    base_string += '&'
+
+    # 3.  The base string URI from `Section 3.4.1.2`_, after being encoded
+    #     (`Section 3.6`_).
+    #
+    # .. _`Section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    base_string += utils.escape(base_str_uri)
+
+    # 4.  An "&" character (ASCII code 38).
+    base_string += '&'
+
+    # 5.  The request parameters as normalized in `Section 3.4.1.3.2`_, after
+    #     being encoded (`Section 3.6`).
+    #
+    # .. _`Sec 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    base_string += utils.escape(normalized_encoded_request_parameters)
+
+    return base_string
+
+
+def base_string_uri(uri: str, host: str = None) -> str:
+    """
+    Calculates the _base string URI_.
+
+    The *base string URI* is one of the components that make up the
+     *signature base string*.
+
+    The ``host`` is optional. If provided, it is used to override any host and
+    port values in the ``uri``. The value for ``host`` is usually extracted from
+    the "Host" request header from the HTTP request. Its value may be just the
+    hostname, or the hostname followed by a colon and a TCP/IP port number
+    (hostname:port). If a value for the``host`` is provided but it does not
+    contain a port number, the default port number is used (i.e. if the ``uri``
+    contained a port number, it will be discarded).
+
+    The rules for calculating the *base string URI* are defined in
+    section 3.4.1.2`_ of RFC 5849.
+
+    .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
+
+    :param uri: URI
+    :param host: hostname with optional port number, separated by a colon
+    :return: base string URI
+    """
+
+    if not isinstance(uri, str):
+        raise ValueError('uri must be a string.')
+
+    # FIXME: urlparse does not support unicode
+    output = urlparse.urlparse(uri)
+    scheme = output.scheme
+    hostname = output.hostname
+    port = output.port
+    path = output.path
+    params = output.params
+
+    # The scheme, authority, and path of the request resource URI `RFC3986`
+    # are included by constructing an "http" or "https" URI representing
+    # the request resource (without the query or fragment) as follows:
+    #
+    # .. _`RFC3986`: https://tools.ietf.org/html/rfc3986
+
+    if not scheme:
+        raise ValueError('missing scheme')
+
+    # Per `RFC 2616 section 5.1.2`_:
+    #
+    # Note that the absolute path cannot be empty; if none is present in
+    # the original URI, it MUST be given as "/" (the server root).
+    #
+    # .. _`RFC 2616 5.1.2`: https://tools.ietf.org/html/rfc2616#section-5.1.2
+    if not path:
+        path = '/'
+
+    # 1.  The scheme and host MUST be in lowercase.
+    scheme = scheme.lower()
+    # Note: if ``host`` is used, it will be converted to lowercase below
+    if hostname is not None:
+        hostname = hostname.lower()
+
+    # 2.  The host and port values MUST match the content of the HTTP
+    #     request "Host" header field.
+    if host is not None:
+        # NOTE: override value in uri with provided host
+        # Host argument is equal to netloc. It means it's missing scheme.
+        # Add it back, before parsing.
+
+        host = host.lower()
+        host = f"{scheme}://{host}"
+        output = urlparse.urlparse(host)
+        hostname = output.hostname
+        port = output.port
+
+    # 3.  The port MUST be included if it is not the default port for the
+    #     scheme, and MUST be excluded if it is the default.  Specifically,
+    #     the port MUST be excluded when making an HTTP request `RFC2616`_
+    #     to port 80 or when making an HTTPS request `RFC2818`_ to port 443.
+    #     All other non-default port numbers MUST be included.
+    #
+    # .. _`RFC2616`: https://tools.ietf.org/html/rfc2616
+    # .. _`RFC2818`: https://tools.ietf.org/html/rfc2818
+
+    if hostname is None:
+        raise ValueError('missing host')
+
+    # NOTE: Try guessing if we're dealing with IP or hostname
+    try:
+        hostname = ipaddress.ip_address(hostname)
+    except ValueError:
+        pass
+
+    if isinstance(hostname, ipaddress.IPv6Address):
+        hostname = f"[{hostname}]"
+    elif isinstance(hostname, ipaddress.IPv4Address):
+        hostname = f"{hostname}"
+
+    if port is not None and not (0 < port <= 65535):
+        raise ValueError('port out of range')  # 16-bit unsigned ints
+    if (scheme, port) in (('http', 80), ('https', 443)):
+        netloc = hostname  # default port for scheme: exclude port num
+    elif port:
+        netloc = f"{hostname}:{port}"  # use hostname:port
+    else:
+        netloc = hostname
+
+    v = urlparse.urlunparse((scheme, netloc, path, params, '', ''))
+
+    # RFC 5849 does not specify which characters are encoded in the
+    # "base string URI", nor how they are encoded - which is very bad, since
+    # the signatures won't match if there are any differences. Fortunately,
+    # most URIs only use characters that are clearly not encoded (e.g. digits
+    # and A-Z, a-z), so have avoided any differences between implementations.
+    #
+    # The example from its section 3.4.1.2 illustrates that spaces in
+    # the path are percent encoded. But it provides no guidance as to what other
+    # characters (if any) must be encoded (nor how); nor if characters in the
+    # other components are to be encoded or not.
+    #
+    # This implementation **assumes** that **only** the space is percent-encoded
+    # and it is done to the entire value (not just to spaces in the path).
+    #
+    # This code may need to be changed if it is discovered that other characters
+    # are expected to be encoded.
+    #
+    # Note: the "base string URI" returned by this function will be encoded
+    # again before being concatenated into the "signature base string". So any
+    # spaces in the URI will actually appear in the "signature base string"
+    # as "%2520" (the "%20" further encoded according to section 3.6).
+
+    return v.replace(' ', '%20')
+
+
+def collect_parameters(uri_query='', body=None, headers=None,
+                       exclude_oauth_signature=True, with_realm=False):
+    """
+    Gather the request parameters from all the parameter sources.
+
+    This function is used to extract all the parameters, which are then passed
+    to ``normalize_parameters`` to produce one of the components that make up
+    the *signature base string*.
+
+    Parameters starting with `oauth_` will be unescaped.
+
+    Body parameters must be supplied as a dict, a list of 2-tuples, or a
+    form encoded query string.
+
+    Headers must be supplied as a dict.
+
+    The rules where the parameters must be sourced from are defined in
+    `section 3.4.1.3.1`_ of RFC 5849.
+
+    .. _`Sec 3.4.1.3.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
+    """
+    if body is None:
+        body = []
+    headers = headers or {}
+    params = []
+
+    # The parameters from the following sources are collected into a single
+    # list of name/value pairs:
+
+    # *  The query component of the HTTP request URI as defined by
+    #    `RFC3986, Section 3.4`_.  The query component is parsed into a list
+    #    of name/value pairs by treating it as an
+    #    "application/x-www-form-urlencoded" string, separating the names
+    #    and values and decoding them as defined by W3C.REC-html40-19980424
+    #    `W3C-HTML-4.0`_, Section 17.13.4.
+    #
+    # .. _`RFC3986, Sec 3.4`: https://tools.ietf.org/html/rfc3986#section-3.4
+    # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/
+    if uri_query:
+        params.extend(urldecode(uri_query))
+
+    # *  The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if
+    #    present.  The header's content is parsed into a list of name/value
+    #    pairs excluding the "realm" parameter if present.  The parameter
+    #    values are decoded as defined by `Section 3.5.1`_.
+    #
+    # .. _`Section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
+    if headers:
+        headers_lower = {k.lower(): v for k, v in headers.items()}
+        authorization_header = headers_lower.get('authorization')
+        if authorization_header is not None:
+            params.extend([i for i in utils.parse_authorization_header(
+                authorization_header) if with_realm or i[0] != 'realm'])
+
+    # *  The HTTP request entity-body, but only if all of the following
+    #    conditions are met:
+    #     *  The entity-body is single-part.
+    #
+    #     *  The entity-body follows the encoding requirements of the
+    #        "application/x-www-form-urlencoded" content-type as defined by
+    #        W3C.REC-html40-19980424 `W3C-HTML-4.0`_.
+
+    #     *  The HTTP request entity-header includes the "Content-Type"
+    #        header field set to "application/x-www-form-urlencoded".
+    #
+    # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/
+
+    # TODO: enforce header param inclusion conditions
+    bodyparams = extract_params(body) or []
+    params.extend(bodyparams)
+
+    # ensure all oauth params are unescaped
+    unescaped_params = []
+    for k, v in params:
+        if k.startswith('oauth_'):
+            v = utils.unescape(v)
+        unescaped_params.append((k, v))
+
+    # The "oauth_signature" parameter MUST be excluded from the signature
+    # base string if present.
+    if exclude_oauth_signature:
+        unescaped_params = list(filter(lambda i: i[0] != 'oauth_signature',
+                                       unescaped_params))
+
+    return unescaped_params
+
+
+def normalize_parameters(params) -> str:
+    """
+    Calculate the normalized request parameters.
+
+    The *normalized request parameters* is one of the components that make up
+    the *signature base string*.
+
+    The rules for parameter normalization are defined in `section 3.4.1.3.2`_ of
+    RFC 5849.
+
+    .. _`Sec 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
+    """
+
+    # The parameters collected in `Section 3.4.1.3`_ are normalized into a
+    # single string as follows:
+    #
+    # .. _`Section 3.4.1.3`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3
+
+    # 1.  First, the name and value of each parameter are encoded
+    #     (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    key_values = [(utils.escape(k), utils.escape(v)) for k, v in params]
+
+    # 2.  The parameters are sorted by name, using ascending byte value
+    #     ordering.  If two or more parameters share the same name, they
+    #     are sorted by their value.
+    key_values.sort()
+
+    # 3.  The name of each parameter is concatenated to its corresponding
+    #     value using an "=" character (ASCII code 61) as a separator, even
+    #     if the value is empty.
+    parameter_parts = ['{}={}'.format(k, v) for k, v in key_values]
+
+    # 4.  The sorted name/value pairs are concatenated together into a
+    #     single string by using an "&" character (ASCII code 38) as
+    #     separator.
+    return '&'.join(parameter_parts)
+
+
+# ==== Common functions for HMAC-based signature methods =========
+
+def _sign_hmac(hash_algorithm_name: str,
+               sig_base_str: str,
+               client_secret: str,
+               resource_owner_secret: str):
+    """
+    **HMAC-SHA256**
+
+    The "HMAC-SHA256" signature method uses the HMAC-SHA256 signature
+    algorithm as defined in `RFC4634`_::
+
+        digest = HMAC-SHA256 (key, text)
+
+    Per `section 3.4.2`_ of the spec.
+
+    .. _`RFC4634`: https://tools.ietf.org/html/rfc4634
+    .. _`section 3.4.2`: https://tools.ietf.org/html/rfc5849#section-3.4.2
+    """
+
+    # The HMAC-SHA256 function variables are used in following way:
+
+    # text is set to the value of the signature base string from
+    # `Section 3.4.1.1`_.
+    #
+    # .. _`Section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
+    text = sig_base_str
+
+    # key is set to the concatenated values of:
+    # 1.  The client shared-secret, after being encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    key = utils.escape(client_secret or '')
+
+    # 2.  An "&" character (ASCII code 38), which MUST be included
+    #     even when either secret is empty.
+    key += '&'
+
+    # 3.  The token shared-secret, after being encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    key += utils.escape(resource_owner_secret or '')
+
+    # Get the hashing algorithm to use
+
+    m = {
+        'SHA-1': hashlib.sha1,
+        'SHA-256': hashlib.sha256,
+        'SHA-512': hashlib.sha512,
+    }
+    hash_alg = m[hash_algorithm_name]
+
+    # Calculate the signature
+
+    # FIXME: HMAC does not support unicode!
+    key_utf8 = key.encode('utf-8')
+    text_utf8 = text.encode('utf-8')
+    signature = hmac.new(key_utf8, text_utf8, hash_alg)
+
+    # digest  is used to set the value of the "oauth_signature" protocol
+    #         parameter, after the result octet string is base64-encoded
+    #         per `RFC2045, Section 6.8`.
+    #
+    # .. _`RFC2045, Sec 6.8`: https://tools.ietf.org/html/rfc2045#section-6.8
+    return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
+
+
+def _verify_hmac(hash_algorithm_name: str,
+                 request,
+                 client_secret=None,
+                 resource_owner_secret=None):
+    """Verify a HMAC-SHA1 signature.
+
+    Per `section 3.4`_ of the spec.
+
+    .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
+
+    To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
+    attribute MUST be an absolute URI whose netloc part identifies the
+    origin server or gateway on which the resource resides. Any Host
+    item of the request argument's headers dict attribute will be
+    ignored.
+
+    .. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
+
+    """
+    norm_params = normalize_parameters(request.params)
+    bs_uri = base_string_uri(request.uri)
+    sig_base_str = signature_base_string(request.http_method, bs_uri,
+                                         norm_params)
+    signature = _sign_hmac(hash_algorithm_name, sig_base_str,
+                           client_secret, resource_owner_secret)
+    match = safe_string_equals(signature, request.signature)
+    if not match:
+        log.debug('Verify HMAC failed: signature base string: %s', sig_base_str)
+    return match
+
+
+# ==== HMAC-SHA1 =================================================
+
+def sign_hmac_sha1_with_client(sig_base_str, client):
+    return _sign_hmac('SHA-1', sig_base_str,
+                      client.client_secret, client.resource_owner_secret)
+
+
+def verify_hmac_sha1(request, client_secret=None, resource_owner_secret=None):
+    return _verify_hmac('SHA-1', request, client_secret, resource_owner_secret)
+
+
+def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
+    """
+    Deprecated function for calculating a HMAC-SHA1 signature.
+
+    This function has been replaced by invoking ``sign_hmac`` with "SHA-1"
+    as the hash algorithm name.
+
+    This function was invoked by sign_hmac_sha1_with_client and
+    test_signatures.py, but does any application invoke it directly? If not,
+    it can be removed.
+    """
+    warnings.warn('use sign_hmac_sha1_with_client instead of sign_hmac_sha1',
+                  DeprecationWarning)
+
+    # For some unknown reason, the original implementation assumed base_string
+    # could either be bytes or str. The signature base string calculating
+    # function always returned a str, so the new ``sign_rsa`` only expects that.
+
+    base_string = base_string.decode('ascii') \
+        if isinstance(base_string, bytes) else base_string
+
+    return _sign_hmac('SHA-1', base_string,
+                      client_secret, resource_owner_secret)
+
+
+# ==== HMAC-SHA256 ===============================================
+
+def sign_hmac_sha256_with_client(sig_base_str, client):
+    return _sign_hmac('SHA-256', sig_base_str,
+                      client.client_secret, client.resource_owner_secret)
+
+
+def verify_hmac_sha256(request, client_secret=None, resource_owner_secret=None):
+    return _verify_hmac('SHA-256', request,
+                        client_secret, resource_owner_secret)
+
+
+def sign_hmac_sha256(base_string, client_secret, resource_owner_secret):
+    """
+    Deprecated function for calculating a HMAC-SHA256 signature.
+
+    This function has been replaced by invoking ``sign_hmac`` with "SHA-256"
+    as the hash algorithm name.
+
+    This function was invoked by sign_hmac_sha256_with_client and
+    test_signatures.py, but does any application invoke it directly? If not,
+    it can be removed.
+    """
+    warnings.warn(
+        'use sign_hmac_sha256_with_client instead of sign_hmac_sha256',
+        DeprecationWarning)
+
+    # For some unknown reason, the original implementation assumed base_string
+    # could either be bytes or str. The signature base string calculating
+    # function always returned a str, so the new ``sign_rsa`` only expects that.
+
+    base_string = base_string.decode('ascii') \
+        if isinstance(base_string, bytes) else base_string
+
+    return _sign_hmac('SHA-256', base_string,
+                      client_secret, resource_owner_secret)
+
+
+# ==== HMAC-SHA512 ===============================================
+
+def sign_hmac_sha512_with_client(sig_base_str: str,
+                                 client):
+    return _sign_hmac('SHA-512', sig_base_str,
+                      client.client_secret, client.resource_owner_secret)
+
+
+def verify_hmac_sha512(request,
+                       client_secret: str = None,
+                       resource_owner_secret: str = None):
+    return _verify_hmac('SHA-512', request,
+                        client_secret, resource_owner_secret)
+
+
+# ==== Common functions for RSA-based signature methods ==========
+
+_jwt_rsa = {}  # cache of RSA-hash implementations from PyJWT jwt.algorithms
+
+
+def _get_jwt_rsa_algorithm(hash_algorithm_name: str):
+    """
+    Obtains an RSAAlgorithm object that implements RSA with the hash algorithm.
+
+    This method maintains the ``_jwt_rsa`` cache.
+
+    Returns a jwt.algorithm.RSAAlgorithm.
+    """
+    if hash_algorithm_name in _jwt_rsa:
+        # Found in cache: return it
+        return _jwt_rsa[hash_algorithm_name]
+    else:
+        # Not in cache: instantiate a new RSAAlgorithm
+
+        # PyJWT has some nice pycrypto/cryptography abstractions
+        import jwt.algorithms as jwt_algorithms
+        m = {
+            'SHA-1': jwt_algorithms.hashes.SHA1,
+            'SHA-256': jwt_algorithms.hashes.SHA256,
+            'SHA-512': jwt_algorithms.hashes.SHA512,
+        }
+        v = jwt_algorithms.RSAAlgorithm(m[hash_algorithm_name])
+
+        _jwt_rsa[hash_algorithm_name] = v  # populate cache
+
+        return v
+
+
+def _prepare_key_plus(alg, keystr):
+    """
+    Prepare a PEM encoded key (public or private), by invoking the `prepare_key`
+    method on alg with the keystr.
+
+    The keystr should be a string or bytes.  If the keystr is bytes, it is
+    decoded as UTF-8 before being passed to prepare_key. Otherwise, it
+    is passed directly.
+    """
+    if isinstance(keystr, bytes):
+        keystr = keystr.decode('utf-8')
+    return alg.prepare_key(keystr)
+
+
+def _sign_rsa(hash_algorithm_name: str,
+              sig_base_str: str,
+              rsa_private_key: str):
+    """
+    Calculate the signature for an RSA-based signature method.
+
+    The ``alg`` is used to calculate the digest over the signature base string.
+    For the "RSA_SHA1" signature method, the alg must be SHA-1. While OAuth 1.0a
+    only defines the RSA-SHA1 signature method, this function can be used for
+    other non-standard signature methods that only differ from RSA-SHA1 by the
+    digest algorithm.
+
+    Signing for the RSA-SHA1 signature method is defined in
+    `section 3.4.3`_ of RFC 5849.
+
+    The RSASSA-PKCS1-v1_5 signature algorithm used defined by
+    `RFC3447, Section 8.2`_ (also known as PKCS#1), with the `alg` as the
+    hash function for EMSA-PKCS1-v1_5.  To
+    use this method, the client MUST have established client credentials
+    with the server that included its RSA public key (in a manner that is
+    beyond the scope of this specification).
+
+    .. _`section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3
+    .. _`RFC3447, Section 8.2`: https://tools.ietf.org/html/rfc3447#section-8.2
+    """
+
+    # Get the implementation of RSA-hash
+
+    alg = _get_jwt_rsa_algorithm(hash_algorithm_name)
+
+    # Check private key
+
+    if not rsa_private_key:
+        raise ValueError('rsa_private_key required for RSA with ' +
+                         alg.hash_alg.name + ' signature method')
+
+    # Convert the "signature base string" into a sequence of bytes (M)
+    #
+    # The signature base string, by definition, only contain printable US-ASCII
+    # characters. So encoding it as 'ascii' will always work. It will raise a
+    # ``UnicodeError`` if it can't encode the value, which will never happen
+    # if the signature base string was created correctly. Therefore, using
+    # 'ascii' encoding provides an extra level of error checking.
+
+    m = sig_base_str.encode('ascii')
+
+    # Perform signing: S = RSASSA-PKCS1-V1_5-SIGN (K, M)
+
+    key = _prepare_key_plus(alg, rsa_private_key)
+    s = alg.sign(m, key)
+
+    # base64-encoded per RFC2045 section 6.8.
+    #
+    # 1. While b2a_base64 implements base64 defined by RFC 3548. As used here,
+    #    it is the same as base64 defined by RFC 2045.
+    # 2. b2a_base64 includes a "\n" at the end of its result ([:-1] removes it)
+    # 3. b2a_base64 produces a binary string. Use decode to produce a str.
+    #    It should only contain only printable US-ASCII characters.
+
+    return binascii.b2a_base64(s)[:-1].decode('ascii')
+
+
+def _verify_rsa(hash_algorithm_name: str,
+                request,
+                rsa_public_key: str):
+    """
+    Verify a base64 encoded signature for a RSA-based signature method.
+
+    The ``alg`` is used to calculate the digest over the signature base string.
+    For the "RSA_SHA1" signature method, the alg must be SHA-1. While OAuth 1.0a
+    only defines the RSA-SHA1 signature method, this function can be used for
+    other non-standard signature methods that only differ from RSA-SHA1 by the
+    digest algorithm.
+
+    Verification for the RSA-SHA1 signature method is defined in
+    `section 3.4.3`_ of RFC 5849.
+
+    .. _`section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3
+
+        To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
+        attribute MUST be an absolute URI whose netloc part identifies the
+        origin server or gateway on which the resource resides. Any Host
+        item of the request argument's headers dict attribute will be
+        ignored.
+
+        .. _`RFC2616 Sec 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
+    """
+
+    try:
+        # Calculate the *signature base string* of the actual received request
+
+        norm_params = normalize_parameters(request.params)
+        bs_uri = base_string_uri(request.uri)
+        sig_base_str = signature_base_string(
+            request.http_method, bs_uri, norm_params)
+
+        # Obtain the signature that was received in the request
+
+        sig = binascii.a2b_base64(request.signature.encode('ascii'))
+
+        # Get the implementation of RSA-with-hash algorithm to use
+
+        alg = _get_jwt_rsa_algorithm(hash_algorithm_name)
+
+        # Verify the received signature was produced by the private key
+        # corresponding to the `rsa_public_key`, signing exact same
+        # *signature base string*.
+        #
+        #     RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
+
+        key = _prepare_key_plus(alg, rsa_public_key)
+
+        # The signature base string only contain printable US-ASCII characters.
+        # The ``encode`` method with the default "strict" error handling will
+        # raise a ``UnicodeError`` if it can't encode the value. So using
+        # "ascii" will always work.
+
+        verify_ok = alg.verify(sig_base_str.encode('ascii'), key, sig)
+
+        if not verify_ok:
+            log.debug('Verify failed: RSA with ' + alg.hash_alg.name +
+                      ': signature base string=%s' + sig_base_str)
+        return verify_ok
+
+    except UnicodeError:
+        # A properly encoded signature will only contain printable US-ASCII
+        # characters. The ``encode`` method with the default "strict" error
+        # handling will raise a ``UnicodeError`` if it can't decode the value.
+        # So using "ascii" will work with all valid signatures. But an
+        # incorrectly or maliciously produced signature could contain other
+        # bytes.
+        #
+        # This implementation treats that situation as equivalent to the
+        # signature verification having failed.
+        #
+        # Note: simply changing the encode to use 'utf-8' will not remove this
+        # case, since an incorrect or malicious request can contain bytes which
+        # are invalid as UTF-8.
+        return False
+
+
+# ==== RSA-SHA1 ==================================================
+
+def sign_rsa_sha1_with_client(sig_base_str, client):
+    # For some reason, this function originally accepts both str and bytes.
+    # This behaviour is preserved here. But won't be done for the newer
+    # sign_rsa_sha256_with_client and sign_rsa_sha512_with_client functions,
+    # which will only accept strings. The function to calculate a
+    # "signature base string" always produces a string, so it is not clear
+    # why support for bytes would ever be needed.
+    sig_base_str = sig_base_str.decode('ascii')\
+        if isinstance(sig_base_str, bytes) else sig_base_str
+
+    return _sign_rsa('SHA-1', sig_base_str, client.rsa_key)
+
+
+def verify_rsa_sha1(request, rsa_public_key: str):
+    return _verify_rsa('SHA-1', request, rsa_public_key)
+
+
+def sign_rsa_sha1(base_string, rsa_private_key):
+    """
+    Deprecated function for calculating a RSA-SHA1 signature.
+
+    This function has been replaced by invoking ``sign_rsa`` with "SHA-1"
+    as the hash algorithm name.
+
+    This function was invoked by sign_rsa_sha1_with_client and
+    test_signatures.py, but does any application invoke it directly? If not,
+    it can be removed.
+    """
+    warnings.warn('use _sign_rsa("SHA-1", ...) instead of sign_rsa_sha1',
+                  DeprecationWarning)
+
+    if isinstance(base_string, bytes):
+        base_string = base_string.decode('ascii')
+
+    return _sign_rsa('SHA-1', base_string, rsa_private_key)
+
+
+# ==== RSA-SHA256 ================================================
+
+def sign_rsa_sha256_with_client(sig_base_str: str, client):
+    return _sign_rsa('SHA-256', sig_base_str, client.rsa_key)
+
+
+def verify_rsa_sha256(request, rsa_public_key: str):
+    return _verify_rsa('SHA-256', request, rsa_public_key)
+
+
+# ==== RSA-SHA512 ================================================
+
+def sign_rsa_sha512_with_client(sig_base_str: str, client):
+    return _sign_rsa('SHA-512', sig_base_str, client.rsa_key)
+
+
+def verify_rsa_sha512(request, rsa_public_key: str):
+    return _verify_rsa('SHA-512', request, rsa_public_key)
+
+
+# ==== PLAINTEXT =================================================
+
+def sign_plaintext_with_client(_signature_base_string, client):
+    # _signature_base_string is not used because the signature with PLAINTEXT
+    # is just the secret: it isn't a real signature.
+    return sign_plaintext(client.client_secret, client.resource_owner_secret)
+
+
+def sign_plaintext(client_secret, resource_owner_secret):
+    """Sign a request using plaintext.
+
+    Per `section 3.4.4`_ of the spec.
+
+    The "PLAINTEXT" method does not employ a signature algorithm.  It
+    MUST be used with a transport-layer mechanism such as TLS or SSL (or
+    sent over a secure channel with equivalent protections).  It does not
+    utilize the signature base string or the "oauth_timestamp" and
+    "oauth_nonce" parameters.
+
+    .. _`section 3.4.4`: https://tools.ietf.org/html/rfc5849#section-3.4.4
+
+    """
+
+    # The "oauth_signature" protocol parameter is set to the concatenated
+    # value of:
+
+    # 1.  The client shared-secret, after being encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    signature = utils.escape(client_secret or '')
+
+    # 2.  An "&" character (ASCII code 38), which MUST be included even
+    #     when either secret is empty.
+    signature += '&'
+
+    # 3.  The token shared-secret, after being encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    signature += utils.escape(resource_owner_secret or '')
+
+    return signature
+
+
+def verify_plaintext(request, client_secret=None, resource_owner_secret=None):
+    """Verify a PLAINTEXT signature.
+
+    Per `section 3.4`_ of the spec.
+
+    .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
+    """
+    signature = sign_plaintext(client_secret, resource_owner_secret)
+    match = safe_string_equals(signature, request.signature)
+    if not match:
+        log.debug('Verify PLAINTEXT failed')
+    return match
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/utils.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/utils.py
new file mode 100644
index 00000000..8fb8302e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/utils.py
@@ -0,0 +1,83 @@
+"""
+oauthlib.utils
+~~~~~~~~~~~~~~
+
+This module contains utility methods used by various parts of the OAuth
+spec.
+"""
+import urllib.request as urllib2
+
+from oauthlib.common import quote, unquote
+
+UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
+                               'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+                               '0123456789')
+
+
+def filter_params(target):
+    """Decorator which filters params to remove non-oauth_* parameters
+
+    Assumes the decorated method takes a params dict or list of tuples as its
+    first argument.
+    """
+    def wrapper(params, *args, **kwargs):
+        params = filter_oauth_params(params)
+        return target(params, *args, **kwargs)
+
+    wrapper.__doc__ = target.__doc__
+    return wrapper
+
+
+def filter_oauth_params(params):
+    """Removes all non oauth parameters from a dict or a list of params."""
+    is_oauth = lambda kv: kv[0].startswith("oauth_")
+    if isinstance(params, dict):
+        return list(filter(is_oauth, list(params.items())))
+    else:
+        return list(filter(is_oauth, params))
+
+
+def escape(u):
+    """Escape a unicode string in an OAuth-compatible fashion.
+
+    Per `section 3.6`_ of the spec.
+
+    .. _`section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+
+    """
+    if not isinstance(u, str):
+        raise ValueError('Only unicode objects are escapable. ' +
+                         'Got {!r} of type {}.'.format(u, type(u)))
+    # Letters, digits, and the characters '_.-' are already treated as safe
+    # by urllib.quote(). We need to add '~' to fully support rfc5849.
+    return quote(u, safe=b'~')
+
+
+def unescape(u):
+    if not isinstance(u, str):
+        raise ValueError('Only unicode objects are unescapable.')
+    return unquote(u)
+
+
+def parse_keqv_list(l):
+    """A unicode-safe version of urllib2.parse_keqv_list"""
+    # With Python 2.6, parse_http_list handles unicode fine
+    return urllib2.parse_keqv_list(l)
+
+
+def parse_http_list(u):
+    """A unicode-safe version of urllib2.parse_http_list"""
+    # With Python 2.6, parse_http_list handles unicode fine
+    return urllib2.parse_http_list(u)
+
+
+def parse_authorization_header(authorization_header):
+    """Parse an OAuth authorization header into a list of 2-tuples"""
+    auth_scheme = 'OAuth '.lower()
+    if authorization_header[:len(auth_scheme)].lower().startswith(auth_scheme):
+        items = parse_http_list(authorization_header[len(auth_scheme):])
+        try:
+            return list(parse_keqv_list(items).items())
+        except (IndexError, ValueError):
+            pass
+    raise ValueError('Malformed authorization header')