summaryrefslogtreecommitdiff
path: root/game/python-extra/oauthlib/oauth1/rfc5849
diff options
context:
space:
mode:
Diffstat (limited to 'game/python-extra/oauthlib/oauth1/rfc5849')
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/__init__.py365
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/endpoints/__init__.py8
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/endpoints/access_token.py215
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/endpoints/authorization.py158
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/endpoints/base.py244
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py14
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/endpoints/request_token.py209
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/endpoints/resource.py163
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/endpoints/signature_only.py82
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/errors.py76
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/parameters.py133
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/request_validator.py849
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/signature.py852
-rw-r--r--game/python-extra/oauthlib/oauth1/rfc5849/utils.py83
14 files changed, 3451 insertions, 0 deletions
diff --git a/game/python-extra/oauthlib/oauth1/rfc5849/__init__.py b/game/python-extra/oauthlib/oauth1/rfc5849/__init__.py
new file mode 100644
index 0000000..c559251
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/__init__.py b/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/__init__.py
new file mode 100644
index 0000000..9f30389
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/access_token.py b/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/access_token.py
new file mode 100644
index 0000000..13665db
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/authorization.py b/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/authorization.py
new file mode 100644
index 0000000..00d9576
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/base.py b/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/base.py
new file mode 100644
index 0000000..7831be7
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py b/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py
new file mode 100644
index 0000000..23e3cfc
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/request_token.py
new file mode 100644
index 0000000..0323cfb
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/resource.py b/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/resource.py
new file mode 100644
index 0000000..8641152
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/signature_only.py b/game/python-extra/oauthlib/oauth1/rfc5849/endpoints/signature_only.py
new file mode 100644
index 0000000..d693ccb
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/errors.py b/game/python-extra/oauthlib/oauth1/rfc5849/errors.py
new file mode 100644
index 0000000..8774d40
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/parameters.py b/game/python-extra/oauthlib/oauth1/rfc5849/parameters.py
new file mode 100644
index 0000000..2163772
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/request_validator.py b/game/python-extra/oauthlib/oauth1/rfc5849/request_validator.py
new file mode 100644
index 0000000..e937aab
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/signature.py b/game/python-extra/oauthlib/oauth1/rfc5849/signature.py
new file mode 100644
index 0000000..9cb1a51
--- /dev/null
+++ b/game/python-extra/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/game/python-extra/oauthlib/oauth1/rfc5849/utils.py b/game/python-extra/oauthlib/oauth1/rfc5849/utils.py
new file mode 100644
index 0000000..8fb8302
--- /dev/null
+++ b/game/python-extra/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')