diff --git a/script.module.oauthlib/LICENSE b/script.module.oauthlib/LICENSE.txt similarity index 100% rename from script.module.oauthlib/LICENSE rename to script.module.oauthlib/LICENSE.txt diff --git a/script.module.oauthlib/README.rst b/script.module.oauthlib/README.rst deleted file mode 100644 index 7c41a80c9..000000000 --- a/script.module.oauthlib/README.rst +++ /dev/null @@ -1,131 +0,0 @@ -OAuthLib - Python Framework for OAuth1 & OAuth2 -=============================================== - -*A generic, spec-compliant, thorough implementation of the OAuth request-signing -logic for Python 2.7 and 3.4+.* - -.. image:: https://travis-ci.org/oauthlib/oauthlib.svg?branch=master - :target: https://travis-ci.org/oauthlib/oauthlib - :alt: Travis -.. image:: https://coveralls.io/repos/oauthlib/oauthlib/badge.svg?branch=master - :target: https://coveralls.io/r/oauthlib/oauthlib - :alt: Coveralls -.. image:: https://img.shields.io/pypi/pyversions/oauthlib.svg - :target: https://pypi.org/project/oauthlib/ - :alt: Download from PyPI -.. image:: https://img.shields.io/pypi/l/oauthlib.svg - :target: https://pypi.org/project/oauthlib/ - :alt: License -.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib.svg?type=shield - :target: https://app.fossa.io/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib?ref=badge_shield - :alt: FOSSA Status -.. image:: https://img.shields.io/readthedocs/oauthlib.svg - :target: https://oauthlib.readthedocs.io/en/latest/index.html - :alt: Read the Docs -.. image:: https://badges.gitter.im/oauthlib/oauthlib.svg - :target: https://gitter.im/oauthlib/Lobby - :alt: Chat on Gitter - -OAuth often seems complicated and difficult-to-implement. There are several -prominent libraries for handling OAuth requests, but they all suffer from one or -both of the following: - -1. They predate the `OAuth 1.0 spec`_, AKA RFC 5849. -2. They predate the `OAuth 2.0 spec`_, AKA RFC 6749. -3. They assume the usage of a specific HTTP request library. - -.. _`OAuth 1.0 spec`: https://tools.ietf.org/html/rfc5849 -.. _`OAuth 2.0 spec`: https://tools.ietf.org/html/rfc6749 - -OAuthLib is a framework which implements the logic of OAuth1 or OAuth2 without -assuming a specific HTTP request object or web framework. Use it to graft OAuth -client support onto your favorite HTTP library, or provide support onto your -favourite web framework. If you're a maintainer of such a library, write a thin -veneer on top of OAuthLib and get OAuth support for very little effort. - - -Documentation --------------- - -Full documentation is available on `Read the Docs`_. All contributions are very -welcome! The documentation is still quite sparse, please open an issue for what -you'd like to know, or discuss it in our `Gitter community`_, or even better, send a -pull request! - -.. _`Gitter community`: https://gitter.im/oauthlib/Lobby -.. _`Read the Docs`: https://oauthlib.readthedocs.io/en/latest/index.html - -Interested in making OAuth requests? ------------------------------------- - -Then you might be more interested in using `requests`_ which has OAuthLib -powered OAuth support provided by the `requests-oauthlib`_ library. - -.. _`requests`: https://github.com/requests/requests -.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib - -Which web frameworks are supported? ------------------------------------ - -The following packages provide OAuth support using OAuthLib. - -- For Django there is `django-oauth-toolkit`_, which includes `Django REST framework`_ support. -- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_. -- For Pyramid there is `pyramid-oauthlib`_. -- For Bottle there is `bottle-oauthlib`_. - -If you have written an OAuthLib package that supports your favorite framework, -please open a Pull Request, updating the documentation. - -.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit -.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib -.. _`Django REST framework`: http://django-rest-framework.org -.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance -.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib -.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib - -Using OAuthLib? Please get in touch! ------------------------------------- -Patching OAuth support onto an http request framework? Creating an OAuth -provider extension for a web framework? Simply using OAuthLib to Get Things Done -or to learn? - -No matter which we'd love to hear from you in our `Gitter community`_ or if you have -anything in particular you would like to have, change or comment on don't -hesitate for a second to send a pull request or open an issue. We might be quite -busy and therefore slow to reply but we love feedback! - -Chances are you have run into something annoying that you wish there was -documentation for, if you wish to gain eternal fame and glory, and a drink if we -have the pleasure to run into eachother, please send a docs pull request =) - -.. _`Gitter community`: https://gitter.im/oauthlib/Lobby - -License -------- - -OAuthLib is yours to use and abuse according to the terms of the BSD license. -Check the LICENSE file for full details. - -Credits -------- - -OAuthLib has been started and maintained several years by Idan Gazit and other -amazing `AUTHORS`_. Thanks to their wonderful work, the open-source `community`_ -creation has been possible and the project can stay active and reactive to users -requests. - - -.. _`AUTHORS`: https://github.com/oauthlib/oauthlib/blob/master/AUTHORS -.. _`community`: https://github.com/oauthlib/ - -Changelog ---------- - -*OAuthLib is in active development, with the core of both OAuth1 and OAuth2 -completed, for providers as well as clients.* See `supported features`_ for -details. - -.. _`supported features`: https://oauthlib.readthedocs.io/en/latest/feature_matrix.html - -For a full changelog see ``CHANGELOG.rst``. diff --git a/script.module.oauthlib/addon.xml b/script.module.oauthlib/addon.xml index e222010c9..b17f6428a 100644 --- a/script.module.oauthlib/addon.xml +++ b/script.module.oauthlib/addon.xml @@ -1,21 +1,20 @@ - - - - - - - - - all - A generic, spec-compliant, thorough implementation of the OAuth request-signing logic - A generic, spec-compliant, thorough implementation of the OAuth request-signing logic - A generic, spec-compliant, thorough implementation of the OAuth request-signing logic - OAuthLib is yours to use and abuse according to the terms of the BSD license. Check the LICENSE file for full details - https://pypi.python.org/pypi/oauthlib - https://pypi.python.org/pypi/oauthlib - - icon.png - - + + + + + + + + + A generic, spec-compliant, thorough implementation of the OAuth request-signing logic + A generic, spec-compliant, thorough implementation of the OAuth request-signing logic + BSD + all + https://github.com/oauthlib/oauthlib + https://github.com/oauthlib/oauthlib + + resources/icon.png + + diff --git a/script.module.oauthlib/lib/oauthlib/__init__.py b/script.module.oauthlib/lib/oauthlib/__init__.py index 69dd11363..d9a5e38ea 100644 --- a/script.module.oauthlib/lib/oauthlib/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/__init__.py @@ -12,7 +12,7 @@ from logging import NullHandler __author__ = 'The OAuthlib Community' -__version__ = '3.1.0' +__version__ = '3.2.2' logging.getLogger('oauthlib').addHandler(NullHandler()) diff --git a/script.module.oauthlib/lib/oauthlib/common.py b/script.module.oauthlib/lib/oauthlib/common.py index 5aeb01500..fa3298af9 100644 --- a/script.module.oauthlib/lib/oauthlib/common.py +++ b/script.module.oauthlib/lib/oauthlib/common.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.common ~~~~~~~~~~~~~~ @@ -6,34 +5,22 @@ This module provides data structures and utilities common to all implementations of OAuth. """ -from __future__ import absolute_import, unicode_literals - import collections import datetime import logging import re -import sys import time +import urllib.parse as urlparse +from urllib.parse import ( + quote as _quote, unquote as _unquote, urlencode as _urlencode, +) + from . import get_debug try: - from secrets import randbits - from secrets import SystemRandom -except ImportError: - from random import getrandbits as randbits - from random import SystemRandom -try: - from urllib import quote as _quote - from urllib import unquote as _unquote - from urllib import urlencode as _urlencode + from secrets import SystemRandom, randbits except ImportError: - from urllib.parse import quote as _quote - from urllib.parse import unquote as _unquote - from urllib.parse import urlencode as _urlencode -try: - import urlparse -except ImportError: - import urllib.parse as urlparse + from random import SystemRandom, getrandbits as randbits UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -51,17 +38,10 @@ log = logging.getLogger('oauthlib') -PY3 = sys.version_info[0] == 3 - -if PY3: - unicode_type = str -else: - unicode_type = unicode - # 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either) def quote(s, safe=b'/'): - s = s.encode('utf-8') if isinstance(s, unicode_type) else s + s = s.encode('utf-8') if isinstance(s, str) else s s = _quote(s, safe) # PY3 always returns unicode. PY2 may return either, depending on whether # it had to modify the string. @@ -83,7 +63,7 @@ def unquote(s): def urlencode(params): utf8_params = encode_params_utf8(params) urlencoded = _urlencode(utf8_params) - if isinstance(urlencoded, unicode_type): # PY3 returns unicode + if isinstance(urlencoded, str): return urlencoded else: return urlencoded.decode("utf-8") @@ -96,8 +76,8 @@ def encode_params_utf8(params): encoded = [] for k, v in params: encoded.append(( - k.encode('utf-8') if isinstance(k, unicode_type) else k, - v.encode('utf-8') if isinstance(v, unicode_type) else v)) + k.encode('utf-8') if isinstance(k, str) else k, + v.encode('utf-8') if isinstance(v, str) else v)) return encoded @@ -141,22 +121,6 @@ def urldecode(query): if INVALID_HEX_PATTERN.search(query): raise ValueError('Invalid hex encoding in query string.') - # We encode to utf-8 prior to parsing because parse_qsl behaves - # differently on unicode input in python 2 and 3. - # Python 2.7 - # >>> urlparse.parse_qsl(u'%E5%95%A6%E5%95%A6') - # u'\xe5\x95\xa6\xe5\x95\xa6' - # Python 2.7, non unicode input gives the same - # >>> urlparse.parse_qsl('%E5%95%A6%E5%95%A6') - # '\xe5\x95\xa6\xe5\x95\xa6' - # but now we can decode it to unicode - # >>> urlparse.parse_qsl('%E5%95%A6%E5%95%A6').decode('utf-8') - # u'\u5566\u5566' - # Python 3.3 however - # >>> urllib.parse.parse_qsl(u'%E5%95%A6%E5%95%A6') - # u'\u5566\u5566' - query = query.encode( - 'utf-8') if not PY3 and isinstance(query, unicode_type) else query # We want to allow queries such as "c2" whereas urlparse.parse_qsl # with the strict_parsing flag will not. params = urlparse.parse_qsl(query, keep_blank_values=True) @@ -173,7 +137,7 @@ def extract_params(raw): empty list of parameters. Any other input will result in a return value of None. """ - if isinstance(raw, (bytes, unicode_type)): + if isinstance(raw, (bytes, str)): try: params = urldecode(raw) except ValueError: @@ -206,7 +170,7 @@ def generate_nonce(): .. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1 .. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3 """ - return unicode_type(unicode_type(randbits(64)) + generate_timestamp()) + return str(str(randbits(64)) + generate_timestamp()) def generate_timestamp(): @@ -218,7 +182,7 @@ def generate_timestamp(): .. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1 .. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3 """ - return unicode_type(int(time.time())) + return str(int(time.time())) def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET): @@ -234,7 +198,7 @@ def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET): def generate_signed_token(private_pem, request): - import jwt + import pyjwt as jwt now = datetime.datetime.utcnow() @@ -252,7 +216,7 @@ def generate_signed_token(private_pem, request): def verify_signed_token(public_pem, token): - import jwt + import pyjwt as jwt return jwt.decode(token, public_pem, algorithms=['RS256']) @@ -305,11 +269,11 @@ def safe_string_equals(a, b): def to_unicode(data, encoding='UTF-8'): """Convert a number of different types of objects to unicode.""" - if isinstance(data, unicode_type): + if isinstance(data, str): return data if isinstance(data, bytes): - return unicode_type(data, encoding=encoding) + return str(data, encoding=encoding) if hasattr(data, '__iter__'): try: @@ -323,7 +287,7 @@ def to_unicode(data, encoding='UTF-8'): # We support 2.6 which lacks dict comprehensions if hasattr(data, 'items'): data = data.items() - return dict(((to_unicode(k, encoding), to_unicode(v, encoding)) for k, v in data)) + return {to_unicode(k, encoding): to_unicode(v, encoding) for k, v in data} return data @@ -335,7 +299,7 @@ class CaseInsensitiveDict(dict): proxy = {} def __init__(self, data): - self.proxy = dict((k.lower(), k) for k in data) + self.proxy = {k.lower(): k for k in data} for k in data: self[k] = data[k] @@ -344,27 +308,27 @@ def __contains__(self, k): def __delitem__(self, k): key = self.proxy[k.lower()] - super(CaseInsensitiveDict, self).__delitem__(key) + super().__delitem__(key) del self.proxy[k.lower()] def __getitem__(self, k): key = self.proxy[k.lower()] - return super(CaseInsensitiveDict, self).__getitem__(key) + return super().__getitem__(key) def get(self, k, default=None): return self[k] if k in self else default def __setitem__(self, k, v): - super(CaseInsensitiveDict, self).__setitem__(k, v) + super().__setitem__(k, v) self.proxy[k.lower()] = k def update(self, *args, **kwargs): - super(CaseInsensitiveDict, self).update(*args, **kwargs) + super().update(*args, **kwargs) for k in dict(*args, **kwargs): self.proxy[k.lower()] = k -class Request(object): +class Request: """A malleable representation of a signable HTTP request. @@ -444,7 +408,7 @@ def __repr__(self): body = SANITIZE_PATTERN.sub('\1', str(body)) if 'Authorization' in headers: headers['Authorization'] = '' - return '' % ( + return ''.format( self.uri, self.http_method, headers, body) @property diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth1/__init__.py index dc908d4eb..9caf12a90 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth1 ~~~~~~~~~~~~~~ @@ -6,14 +5,19 @@ This module is a wrapper for the most recent implementation of OAuth 1.0 Client and Server classes. """ -from __future__ import absolute_import, unicode_literals - -from .rfc5849 import Client -from .rfc5849 import SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, SIGNATURE_PLAINTEXT -from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY -from .rfc5849 import SIGNATURE_TYPE_BODY +from .rfc5849 import ( + SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, + SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA, + SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, + SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, + Client, +) +from .rfc5849.endpoints import ( + AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint, + ResourceEndpoint, SignatureOnlyEndpoint, WebApplicationServer, +) +from .rfc5849.errors import ( + InsecureTransportError, InvalidClientError, InvalidRequestError, + InvalidSignatureMethodError, OAuth1Error, +) from .rfc5849.request_validator import RequestValidator -from .rfc5849.endpoints import RequestTokenEndpoint, AuthorizationEndpoint -from .rfc5849.endpoints import AccessTokenEndpoint, ResourceEndpoint -from .rfc5849.endpoints import SignatureOnlyEndpoint, WebApplicationServer -from .rfc5849.errors import InsecureTransportError, InvalidClientError, InvalidRequestError, InvalidSignatureMethodError, OAuth1Error diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/__init__.py index 4f462bbc8..c559251fe 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/__init__.py @@ -1,33 +1,68 @@ -# -*- coding: utf-8 -*- """ 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 + """ -from __future__ import absolute_import, unicode_literals import base64 import hashlib import logging -log = logging.getLogger(__name__) +import urllib.parse as urlparse -import sys -try: - import urlparse -except ImportError: - import urllib.parse as urlparse +from oauthlib.common import ( + Request, generate_nonce, generate_timestamp, to_unicode, urlencode, +) -from oauthlib.common import Request, urlencode, generate_nonce -from oauthlib.common import generate_timestamp, to_unicode 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 = SIGNATURE_HMAC_SHA1 -SIGNATURE_RSA = "RSA-SHA1" +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_RSA, SIGNATURE_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' @@ -36,13 +71,16 @@ CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' -class Client(object): +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_RSA: signature.sign_rsa_sha1_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 } @@ -106,8 +144,8 @@ def __repr__(self): attrs['rsa_key'] = '****' if attrs['rsa_key'] else None attrs[ 'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None - attribute_str = ', '.join('%s=%s' % (k, v) for k, v in attrs.items()) - return '<%s %s>' % (self.__class__.__name__, attribute_str) + 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 @@ -130,24 +168,24 @@ def get_oauth_signature(self, request): uri_query=urlparse.urlparse(uri).query, body=body, headers=headers) - log.debug("Collected params: {0}".format(collected_params)) + 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: {0}".format(normalized_params)) - log.debug("Normalized URI: {0}".format(normalized_uri)) + 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: {0}".format(base_string)) + 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: {0}".format(sig)) + log.debug("Signature: {}".format(sig)) return sig def get_oauth_params(self, request): @@ -278,8 +316,8 @@ def sign(self, uri, http_method='GET', body=None, headers=None, realm=None): # 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 {0} " - "instead of {1}".format(content_type or "not set", + "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 diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/__init__.py index b16ccba62..9f30389f2 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/__init__.py @@ -1,9 +1,8 @@ -from __future__ import absolute_import - +from .access_token import AccessTokenEndpoint +from .authorization import AuthorizationEndpoint from .base import BaseEndpoint from .request_token import RequestTokenEndpoint -from .authorization import AuthorizationEndpoint -from .access_token import AccessTokenEndpoint from .resource import ResourceEndpoint from .signature_only import SignatureOnlyEndpoint -from .pre_configured import WebApplicationServer + +from .pre_configured import WebApplicationServer # isort:skip diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/access_token.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/access_token.py index bea827417..13665db08 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/access_token.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/access_token.py @@ -8,8 +8,6 @@ creates and persists tokens as well as create the proper response to be returned to the client. """ -from __future__ import absolute_import, unicode_literals - import logging from oauthlib.common import urlencode diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/authorization.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/authorization.py index b465946f9..00d9576b0 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/authorization.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/authorization.py @@ -6,18 +6,13 @@ This module is an implementation of various logic needed for signing and checking OAuth 1.0 RFC 5849 requests. """ -from __future__ import absolute_import, unicode_literals +from urllib.parse import urlencode -from oauthlib.common import Request, add_params_to_uri +from oauthlib.common import add_params_to_uri from .. import errors from .base import BaseEndpoint -try: - from urllib import urlencode -except ImportError: - from urllib.parse import urlencode - class AuthorizationEndpoint(BaseEndpoint): diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/base.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/base.py index f005256b5..7831be7c5 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/base.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/base.py @@ -6,18 +6,19 @@ This module is an implementation of various logic needed for signing and checking OAuth 1.0 RFC 5849 requests. """ -from __future__ import absolute_import, unicode_literals - import time from oauthlib.common import CaseInsensitiveDict, Request, generate_token -from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, - SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, - SIGNATURE_TYPE_QUERY, errors, signature, utils) +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(object): +class BaseEndpoint: def __init__(self, request_validator, token_generator=None): self.request_validator = request_validator @@ -131,7 +132,7 @@ def _check_mandatory_parameters(self, request): if (not request.signature_method in self.request_validator.allowed_signature_methods): raise errors.InvalidSignatureMethodError( - description="Invalid signature, %s not in %r." % ( + description="Invalid signature, {} not in {!r}.".format( request.signature_method, self.request_validator.allowed_signature_methods)) @@ -179,38 +180,65 @@ def _check_mandatory_parameters(self, request): def _check_signature(self, request, is_token_request=False): # ---- RSA Signature verification ---- - if request.signature_method == SIGNATURE_RSA: + 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) - valid_signature = signature.verify_rsa_sha1(request, rsa_key) + + 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) + 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) + 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) + 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) + 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 = signature.verify_plaintext(request, - client_secret, resource_owner_secret) + valid_signature = False + return valid_signature diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py index f89393a5b..23e3cfc84 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py @@ -1,7 +1,7 @@ -from __future__ import absolute_import, unicode_literals - -from . import (AccessTokenEndpoint, AuthorizationEndpoint, - RequestTokenEndpoint, ResourceEndpoint) +from . import ( + AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint, + ResourceEndpoint, +) class WebApplicationServer(RequestTokenEndpoint, AuthorizationEndpoint, diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/request_token.py index e9ca331d1..0323cfb84 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/request_token.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/request_token.py @@ -8,8 +8,6 @@ creates and persists tokens as well as create the proper response to be returned to the client. """ -from __future__ import absolute_import, unicode_literals - import logging from oauthlib.common import urlencode @@ -129,7 +127,7 @@ def validate_request_token_request(self, request): request.client_key, request) if not self.request_validator.check_realms(request.realms): raise errors.InvalidRequestError( - description='Invalid realm %s. Allowed are %r.' % ( + description='Invalid realm {}. Allowed are {!r}.'.format( request.realms, self.request_validator.realms)) if not request.redirect_uri: @@ -154,7 +152,7 @@ def validate_request_token_request(self, request): request.client_key = self.request_validator.dummy_client # Note that `realm`_ is only used in authorization headers and how - # it should be interepreted is not included in the OAuth spec. + # 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. @@ -166,7 +164,7 @@ def validate_request_token_request(self, request): # 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 abscence of realm. + # 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 diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/resource.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/resource.py index f82e8b1b0..8641152e4 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/resource.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/resource.py @@ -6,8 +6,6 @@ This module is an implementation of the resource protection provider logic of OAuth 1.0 RFC 5849. """ -from __future__ import absolute_import, unicode_literals - import logging from .. import errors @@ -115,7 +113,7 @@ def validate_protected_resource_request(self, uri, http_method='GET', request.resource_owner_key = self.request_validator.dummy_access_token # Note that `realm`_ is only used in authorization headers and how - # it should be interepreted is not included in the OAuth spec. + # 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. @@ -127,7 +125,7 @@ def validate_protected_resource_request(self, uri, http_method='GET', # 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 abscence of realm. + # 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 diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/signature_only.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/signature_only.py index 42977706d..d693ccb7f 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/signature_only.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/endpoints/signature_only.py @@ -6,8 +6,6 @@ This module is an implementation of the signing logic of OAuth 1.0 RFC 5849. """ -from __future__ import absolute_import, unicode_literals - import logging from .. import errors diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/errors.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/errors.py index a5c59bdda..8774d4074 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/errors.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/errors.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ oauthlib.oauth1.rfc5849.errors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,8 +5,6 @@ Error used both by OAuth 1 clients and provicers to represent the spec defined error responses for all four core grant types. """ -from __future__ import unicode_literals - from oauthlib.common import add_params_to_uri, urlencode @@ -37,10 +34,10 @@ def __init__(self, description=None, uri=None, status_code=400, request: Oauthlib Request object """ self.description = description or self.description - message = '(%s) %s' % (self.error, self.description) + message = '({}) {}'.format(self.error, self.description) if request: message += ' ' + repr(request) - super(OAuth1Error, self).__init__(message) + super().__init__(message) self.uri = uri self.status_code = status_code diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/parameters.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/parameters.py index db4400e94..2163772df 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/parameters.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/parameters.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.parameters ~~~~~~~~~~~~~~~~~~~ @@ -7,17 +6,12 @@ .. _`section 3.5`: https://tools.ietf.org/html/rfc5849#section-3.5 """ -from __future__ import absolute_import, unicode_literals +from urllib.parse import urlparse, urlunparse from oauthlib.common import extract_params, urlencode from . import utils -try: - from urlparse import urlparse, urlunparse -except ImportError: # noqa - from urllib.parse import urlparse, urlunparse - # TODO: do we need filter_params now that oauth_params are handled by Request? # We can easily pass in just oauth protocol params. @@ -61,7 +55,7 @@ def prepare_headers(oauth_params, headers=None, realm=None): # 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 = '{0}="{1}"'.format(escaped_name, escaped_value) + part = '{}="{}"'.format(escaped_name, escaped_value) authorization_header_parameters_parts.append(part) diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/request_validator.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/request_validator.py index 330bcbb81..e937aabf4 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/request_validator.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/request_validator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth1.rfc5849 ~~~~~~~~~~~~~~ @@ -6,14 +5,10 @@ This module is an implementation of various logic needed for signing and checking OAuth 1.0 RFC 5849 requests. """ -from __future__ import absolute_import, unicode_literals - -import sys - from . import SIGNATURE_METHODS, utils -class RequestValidator(object): +class RequestValidator: """A validator/datastore interaction base class for OAuth 1 providers. @@ -24,7 +19,7 @@ class RequestValidator(object): 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 usefull to spot malicious activity. + but blacklisting can be useful to spot malicious activity. The following have methods a default implementation: - check_client_key @@ -197,7 +192,7 @@ def check_verifier(self, verifier): 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)) + return all(r in self.realms for r in realms) def _subclass_must_implement(self, fn): """ @@ -448,7 +443,7 @@ def invalidate_request_token(self, client_key, request_token, request): :type request: oauthlib.common.Request :returns: None - Per `Section 2.3`__ of the spec: + Per `Section 2.3`_ of the spec: "The server MUST (...) ensure that the temporary credentials have not expired or been used before." @@ -836,7 +831,7 @@ 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 + :param verifier: A dictionary containing the oauth_verifier and oauth_token :param request: OAuthlib request. :type request: oauthlib.common.Request diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/signature.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/signature.py index a60bee294..8fbf238bd 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/signature.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/signature.py @@ -1,74 +1,70 @@ -# -*- coding: utf-8 -*- """ -oauthlib.oauth1.rfc5849.signature -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This module is an implementation of `section 3.4`_ of RFC 5849. -This module represents a direct implementation of `section 3.4`_ of the spec. - -Terminology: - * Client: software interfacing with an OAuth API - * Server: the API provider - * Resource Owner: the user who is granting authorization to the client +**Usage** Steps for signing a request: -1. Collect parameters from the uri query, auth header, & body -2. Normalize those parameters -3. Normalize the uri -4. Pass the normalized uri, normalized parameters, and http method to - construct the base string -5. Pass the base string and any keys needed to a signing function +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 """ -from __future__ import absolute_import, unicode_literals 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, - unicode_type, urldecode) +from oauthlib.common import extract_params, safe_string_equals, urldecode from . import utils -try: - import urlparse -except ImportError: - import urllib.parse as urlparse - log = logging.getLogger(__name__) -def signature_base_string(http_method, base_str_uri, - normalized_encoded_request_parameters): - """**Construct the signature base string.** - Per `section 3.4.1.1`_ of the spec. - - For example, the HTTP request:: - - POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 - Host: example.com - Content-Type: application/x-www-form-urlencoded - Authorization: OAuth realm="Example", - oauth_consumer_key="9djdj82h48djs9d2", - oauth_token="kkk9d7dh3k39sjv7", - oauth_signature_method="HMAC-SHA1", - oauth_timestamp="137131201", - oauth_nonce="7d8f3e4a", - oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D" +# ==== Common functions ========================================== - c2&a3=2+q +def signature_base_string( + http_method: str, + base_str_uri: str, + normalized_encoded_request_parameters: str) -> str: + """ + Construct the signature base string. - is represented by the following signature base string (line breaks - are for display purposes only):: + 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. - POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q - %26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_ - key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m - ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk - 9d7dh3k39sjv7 + 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 """ @@ -90,7 +86,7 @@ def signature_base_string(http_method, base_str_uri, # (`Section 3.6`_). # # .. _`Section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2 - # .. _`Section 3.4.6`: https://tools.ietf.org/html/rfc5849#section-3.4.6 + # .. _`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). @@ -99,40 +95,48 @@ def signature_base_string(http_method, base_str_uri, # 5. The request parameters as normalized in `Section 3.4.1.3.2`_, after # being encoded (`Section 3.6`). # - # .. _`Section 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2 - # .. _`Section 3.4.6`: https://tools.ietf.org/html/rfc5849#section-3.4.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, host=None): - """**Base String URI** - Per `section 3.4.1.2`_ of RFC 5849. - - For example, the HTTP request:: - - GET /r%20v/X?id=123 HTTP/1.1 - Host: EXAMPLE.COM:80 - - is represented by the base string URI: "http://example.com/r%20v/X". +def base_string_uri(uri: str, host: str = None) -> str: + """ + Calculates the _base string URI_. - In another example, the HTTPS request:: + The *base string URI* is one of the components that make up the + *signature base string*. - GET /?q=1 HTTP/1.1 - Host: www.example.net:8080 + 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). - is represented by the base string URI: "https://www.example.net:8080/". + 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 - The host argument overrides the netloc part of the uri argument. + :param uri: URI + :param host: hostname with optional port number, separated by a colon + :return: base string URI """ - if not isinstance(uri, unicode_type): - raise ValueError('uri must be a unicode object.') + + if not isinstance(uri, str): + raise ValueError('uri must be a string.') # FIXME: urlparse does not support unicode - scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri) + 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 @@ -140,26 +144,36 @@ def base_string_uri(uri, host=None): # # .. _`RFC3986`: https://tools.ietf.org/html/rfc3986 - if not scheme or not netloc: - raise ValueError('uri must include a scheme and netloc') + 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 section 5.1.2`: https://tools.ietf.org/html/rfc2616#section-5.1.2 + # .. _`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() - netloc = netloc.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: - netloc = host.lower() + # 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, @@ -169,14 +183,29 @@ def base_string_uri(uri, host=None): # # .. _`RFC2616`: https://tools.ietf.org/html/rfc2616 # .. _`RFC2818`: https://tools.ietf.org/html/rfc2818 - default_ports = ( - ('http', '80'), - ('https', '443'), - ) - if ':' in netloc: - host, port = netloc.split(':', 1) - if (scheme, port) in default_ports: - netloc = host + + 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, '', '')) @@ -205,77 +234,29 @@ def base_string_uri(uri, host=None): return v.replace(' ', '%20') -# ** Request Parameters ** -# -# Per `section 3.4.1.3`_ of the spec. -# -# In order to guarantee a consistent and reproducible representation of -# the request parameters, the parameters are collected and decoded to -# their original decoded form. They are then sorted and encoded in a -# particular manner that is often different from their original -# encoding scheme, and concatenated into a single string. -# -# .. _`section 3.4.1.3`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3 - -def collect_parameters(uri_query='', body=[], headers=None, +def collect_parameters(uri_query='', body=None, headers=None, exclude_oauth_signature=True, with_realm=False): - """**Parameter Sources** + """ + 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 - formencoded query string. + form encoded query string. Headers must be supplied as a dict. - Per `section 3.4.1.3.1`_ of the spec. - - For example, the HTTP request:: - - POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 - Host: example.com - Content-Type: application/x-www-form-urlencoded - Authorization: OAuth realm="Example", - oauth_consumer_key="9djdj82h48djs9d2", - oauth_token="kkk9d7dh3k39sjv7", - oauth_signature_method="HMAC-SHA1", - oauth_timestamp="137131201", - oauth_nonce="7d8f3e4a", - oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D" - - c2&a3=2+q - - contains the following (fully decoded) parameters used in the - signature base sting:: - - +------------------------+------------------+ - | Name | Value | - +------------------------+------------------+ - | b5 | =%3D | - | a3 | a | - | c@ | | - | a2 | r b | - | oauth_consumer_key | 9djdj82h48djs9d2 | - | oauth_token | kkk9d7dh3k39sjv7 | - | oauth_signature_method | HMAC-SHA1 | - | oauth_timestamp | 137131201 | - | oauth_nonce | 7d8f3e4a | - | c2 | | - | a3 | 2 q | - +------------------------+------------------+ - - Note that the value of "b5" is "=%3D" and not "==". Both "c@" and - "c2" have empty values. While the encoding rules specified in this - specification for the purpose of constructing the signature base - string exclude the use of a "+" character (ASCII code 43) to - represent an encoded space character (ASCII code 32), this practice - is widely used in "application/x-www-form-urlencoded" encoded values, - and MUST be properly decoded, as demonstrated by one of the "a3" - parameter instances (the "a3" parameter is used twice in this - request). - - .. _`section 3.4.1.3.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 + 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 = [] @@ -286,11 +267,11 @@ def collect_parameters(uri_query='', body=[], headers=None, # `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`_, Section 17.13.4. + # and values and decoding them as defined by W3C.REC-html40-19980424 + # `W3C-HTML-4.0`_, Section 17.13.4. # - # .. _`RFC3986, Section 3.4`: https://tools.ietf.org/html/rfc3986#section-3.4 - # .. _`W3C.REC-html40-19980424`: https://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 + # .. _`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)) @@ -301,7 +282,7 @@ def collect_parameters(uri_query='', body=[], headers=None, # # .. _`Section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1 if headers: - headers_lower = dict((k.lower(), v) for k, v in headers.items()) + 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( @@ -313,12 +294,12 @@ def collect_parameters(uri_query='', body=[], headers=None, # # * The entity-body follows the encoding requirements of the # "application/x-www-form-urlencoded" content-type as defined by - # `W3C.REC-html40-19980424`_. + # 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.REC-html40-19980424`: https://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 + # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/ # TODO: enforce header param inclusion conditions bodyparams = extract_params(body) or [] @@ -340,75 +321,17 @@ def collect_parameters(uri_query='', body=[], headers=None, return unescaped_params -def normalize_parameters(params): - """**Parameters Normalization** - Per `section 3.4.1.3.2`_ of the spec. - - For example, the list of parameters from the previous section would - be normalized as follows: - - Encoded:: - - +------------------------+------------------+ - | Name | Value | - +------------------------+------------------+ - | b5 | %3D%253D | - | a3 | a | - | c%40 | | - | a2 | r%20b | - | oauth_consumer_key | 9djdj82h48djs9d2 | - | oauth_token | kkk9d7dh3k39sjv7 | - | oauth_signature_method | HMAC-SHA1 | - | oauth_timestamp | 137131201 | - | oauth_nonce | 7d8f3e4a | - | c2 | | - | a3 | 2%20q | - +------------------------+------------------+ - - Sorted:: - - +------------------------+------------------+ - | Name | Value | - +------------------------+------------------+ - | a2 | r%20b | - | a3 | 2%20q | - | a3 | a | - | b5 | %3D%253D | - | c%40 | | - | c2 | | - | oauth_consumer_key | 9djdj82h48djs9d2 | - | oauth_nonce | 7d8f3e4a | - | oauth_signature_method | HMAC-SHA1 | - | oauth_timestamp | 137131201 | - | oauth_token | kkk9d7dh3k39sjv7 | - +------------------------+------------------+ - - Concatenated Pairs:: - - +-------------------------------------+ - | Name=Value | - +-------------------------------------+ - | a2=r%20b | - | a3=2%20q | - | a3=a | - | b5=%3D%253D | - | c%40= | - | c2= | - | oauth_consumer_key=9djdj82h48djs9d2 | - | oauth_nonce=7d8f3e4a | - | oauth_signature_method=HMAC-SHA1 | - | oauth_timestamp=137131201 | - | oauth_token=kkk9d7dh3k39sjv7 | - +-------------------------------------+ - - and concatenated together into a single string (line breaks are for - display purposes only):: - - a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj - dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1 - &oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7 - - .. _`section 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2 +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 @@ -430,7 +353,7 @@ def normalize_parameters(params): # 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 = ['{0}={1}'.format(k, v) for k, v in key_values] + 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 @@ -438,34 +361,33 @@ def normalize_parameters(params): return '&'.join(parameter_parts) -def sign_hmac_sha1_with_client(base_string, client): - return sign_hmac_sha1(base_string, - client.client_secret, - client.resource_owner_secret - ) +# ==== 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** -def sign_hmac_sha1(base_string, client_secret, resource_owner_secret): - """**HMAC-SHA1** - - The "HMAC-SHA1" signature method uses the HMAC-SHA1 signature - algorithm as defined in `RFC2104`_:: + The "HMAC-SHA256" signature method uses the HMAC-SHA256 signature + algorithm as defined in `RFC4634`_:: - digest = HMAC-SHA1 (key, text) + digest = HMAC-SHA256 (key, text) Per `section 3.4.2`_ of the spec. - .. _`RFC2104`: https://tools.ietf.org/html/rfc2104 + .. _`RFC4634`: https://tools.ietf.org/html/rfc4634 .. _`section 3.4.2`: https://tools.ietf.org/html/rfc5849#section-3.4.2 """ - # The HMAC-SHA1 function variables are used in following way: + # 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 = base_string + text = sig_base_str # key is set to the concatenated values of: # 1. The client shared-secret, after being encoded (`Section 3.6`_). @@ -482,251 +404,438 @@ def sign_hmac_sha1(base_string, client_secret, resource_owner_secret): # .. _`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, hashlib.sha1) + 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, Section 6.8`: https://tools.ietf.org/html/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 sign_hmac_sha256_with_client(base_string, client): - return sign_hmac_sha256(base_string, - client.client_secret, - client.resource_owner_secret - ) +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): - """**HMAC-SHA256** + """ + Deprecated function for calculating a HMAC-SHA256 signature. - The "HMAC-SHA256" signature method uses the HMAC-SHA256 signature - algorithm as defined in `RFC4634`_:: + This function has been replaced by invoking ``sign_hmac`` with "SHA-256" + as the hash algorithm name. - digest = HMAC-SHA256 (key, text) + 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) - Per `section 3.4.2`_ of the spec. + # 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. - .. _`RFC4634`: https://tools.ietf.org/html/rfc4634 - .. _`section 3.4.2`: https://tools.ietf.org/html/rfc5849#section-3.4.2 + 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. - # The HMAC-SHA256 function variables are used in following way: + This method maintains the ``_jwt_rsa`` cache. - # 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 = base_string + 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 - # 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 '') + # PyJWT has some nice pycrypto/cryptography abstractions + import pyjwt.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]) - # 2. An "&" character (ASCII code 38), which MUST be included - # even when either secret is empty. - key += '&' + _jwt_rsa[hash_algorithm_name] = v # populate cache - # 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 '') + return v - # 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, hashlib.sha256) - # 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, Section 6.8`: https://tools.ietf.org/html/rfc2045#section-6.8 - return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8') +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. -_jwtrs1 = None + 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) -#jwt has some nice pycrypto/cryptography abstractions -def _jwt_rs1_signing_algorithm(): - global _jwtrs1 - if _jwtrs1 is None: - import jwt.algorithms as jwtalgo - _jwtrs1 = jwtalgo.RSAAlgorithm(jwtalgo.hashes.SHA1) - return _jwtrs1 -def sign_rsa_sha1(base_string, rsa_private_key): - """**RSA-SHA1** +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. - Per `section 3.4.3`_ of the spec. + Signing for the RSA-SHA1 signature method is defined in + `section 3.4.3`_ of RFC 5849. - The "RSA-SHA1" signature method uses the RSASSA-PKCS1-v1_5 signature - algorithm as defined in `RFC3447, Section 8.2`_ (also known as - PKCS#1), using SHA-1 as the hash function for EMSA-PKCS1-v1_5. To + 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 - """ - if isinstance(base_string, unicode_type): - base_string = base_string.encode('utf-8') - # TODO: finish RSA documentation - alg = _jwt_rs1_signing_algorithm() + + # 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(base_string, key) - return binascii.b2a_base64(s)[:-1].decode('utf-8') + 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. -def sign_rsa_sha1_with_client(base_string, client): - if not client.rsa_key: - raise ValueError('rsa_key is required when using RSA signature method.') - return sign_rsa_sha1(base_string, client.rsa_key) + return binascii.b2a_base64(s)[:-1].decode('ascii') -def sign_plaintext(client_secret, resource_owner_secret): - """Sign a request using plaintext. +def _verify_rsa(hash_algorithm_name: str, + request, + rsa_public_key: str): + """ + Verify a base64 encoded signature for a RSA-based signature method. - Per `section 3.4.4`_ of the spec. + 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. - 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. + Verification for the RSA-SHA1 signature method is defined in + `section 3.4.3`_ of RFC 5849. - .. _`section 3.4.4`: https://tools.ietf.org/html/rfc5849#section-3.4.4 + .. _`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 """ - # The "oauth_signature" protocol parameter is set to the concatenated - # value of: + try: + # Calculate the *signature base string* of the actual received request - # 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 '') + 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) - # 2. An "&" character (ASCII code 38), which MUST be included even - # when either secret is empty. - signature += '&' + # Obtain the signature that was received in the request - # 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 '') + sig = binascii.a2b_base64(request.signature.encode('ascii')) - return signature + # Get the implementation of RSA-with-hash algorithm to use + alg = _get_jwt_rsa_algorithm(hash_algorithm_name) -def sign_plaintext_with_client(base_string, client): - return sign_plaintext(client.client_secret, client.resource_owner_secret) + # 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) -def verify_hmac_sha1(request, client_secret=None, - resource_owner_secret=None): - """Verify a HMAC-SHA1 signature. + # 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. - Per `section 3.4`_ of the spec. + verify_ok = alg.verify(sig_base_str.encode('ascii'), key, sig) - .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4 + if not verify_ok: + log.debug('Verify failed: RSA with ' + alg.hash_alg.name + + ': signature base string=%s' + sig_base_str) + return verify_ok - 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. + 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 - .. _`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_sha1(sig_base_str, client_secret, - resource_owner_secret) - match = safe_string_equals(signature, request.signature) - if not match: - log.debug('Verify HMAC-SHA1 failed: signature base string: %s', - sig_base_str) - return match +# ==== 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 -def verify_hmac_sha256(request, client_secret=None, - resource_owner_secret=None): - """Verify a HMAC-SHA256 signature. + return _sign_rsa('SHA-1', sig_base_str, client.rsa_key) - Per `section 3.4`_ of the spec. - .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4 +def verify_rsa_sha1(request, rsa_public_key: str): + return _verify_rsa('SHA-1', request, rsa_public_key) - 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 +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. """ - 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_sha256(sig_base_str, client_secret, - resource_owner_secret) - match = safe_string_equals(signature, request.signature) - if not match: - log.debug('Verify HMAC-SHA256 failed: signature base string: %s', - sig_base_str) - return match + warnings.warn('use _sign_rsa("SHA-1", ...) instead of sign_rsa_sha1', + DeprecationWarning) + if isinstance(base_string, bytes): + base_string = base_string.decode('ascii') -def _prepare_key_plus(alg, keystr): - if isinstance(keystr, bytes): - keystr = keystr.decode('utf-8') - return alg.prepare_key(keystr) + return _sign_rsa('SHA-1', base_string, rsa_private_key) -def verify_rsa_sha1(request, rsa_public_key): - """Verify a RSASSA-PKCS #1 v1.5 base64 encoded signature. - Per `section 3.4.3`_ of the spec. +# ==== RSA-SHA256 ================================================ - Note this method requires the jwt and cryptography libraries. +def sign_rsa_sha256_with_client(sig_base_str: str, client): + return _sign_rsa('SHA-256', sig_base_str, client.rsa_key) - .. _`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. +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 - .. _`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).encode('utf-8') - sig = binascii.a2b_base64(request.signature.encode('utf-8')) - alg = _jwt_rs1_signing_algorithm() - key = _prepare_key_plus(alg, rsa_public_key) + # 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 '') - verify_ok = alg.verify(sig_base_str, key, sig) - if not verify_ok: - log.debug('Verify RSA-SHA1 failed: signature base string: %s', - sig_base_str) - return verify_ok + return signature def verify_plaintext(request, client_secret=None, resource_owner_secret=None): diff --git a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/utils.py b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/utils.py index 735f21d65..8fb8302e3 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/utils.py +++ b/script.module.oauthlib/lib/oauthlib/oauth1/rfc5849/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.utils ~~~~~~~~~~~~~~ @@ -6,15 +5,9 @@ This module contains utility methods used by various parts of the OAuth spec. """ -from __future__ import absolute_import, unicode_literals - -from oauthlib.common import quote, unicode_type, unquote - -try: - import urllib2 -except ImportError: - import urllib.request as urllib2 +import urllib.request as urllib2 +from oauthlib.common import quote, unquote UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -52,16 +45,16 @@ def escape(u): .. _`section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6 """ - if not isinstance(u, unicode_type): + if not isinstance(u, str): raise ValueError('Only unicode objects are escapable. ' + - 'Got %r of type %s.' % (u, type(u))) + '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, unicode_type): + if not isinstance(u, str): raise ValueError('Only unicode objects are unescapable.') return unquote(u) diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth2/__init__.py index 3f4375569..deefb1af7 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2 ~~~~~~~~~~~~~~ @@ -6,31 +5,32 @@ This module is a wrapper for the most recent implementation of OAuth 2.0 Client and Server classes. """ -from __future__ import absolute_import, unicode_literals - -from .rfc6749.clients import Client -from .rfc6749.clients import WebApplicationClient -from .rfc6749.clients import MobileApplicationClient -from .rfc6749.clients import LegacyApplicationClient -from .rfc6749.clients import BackendApplicationClient -from .rfc6749.clients import ServiceApplicationClient -from .rfc6749.endpoints import AuthorizationEndpoint -from .rfc6749.endpoints import IntrospectEndpoint -from .rfc6749.endpoints import MetadataEndpoint -from .rfc6749.endpoints import TokenEndpoint -from .rfc6749.endpoints import ResourceEndpoint -from .rfc6749.endpoints import RevocationEndpoint -from .rfc6749.endpoints import Server -from .rfc6749.endpoints import WebApplicationServer -from .rfc6749.endpoints import MobileApplicationServer -from .rfc6749.endpoints import LegacyApplicationServer -from .rfc6749.endpoints import BackendApplicationServer -from .rfc6749.errors import AccessDeniedError, OAuth2Error, FatalClientError, InsecureTransportError, InvalidClientError, InvalidClientIdError, InvalidGrantError, InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError, InvalidScopeError, MismatchingRedirectURIError, MismatchingStateError, MissingClientIdError, MissingCodeError, MissingRedirectURIError, MissingResponseTypeError, MissingTokenError, MissingTokenTypeError, ServerError, TemporarilyUnavailableError, TokenExpiredError, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError -from .rfc6749.grant_types import AuthorizationCodeGrant -from .rfc6749.grant_types import ImplicitGrant -from .rfc6749.grant_types import ResourceOwnerPasswordCredentialsGrant -from .rfc6749.grant_types import ClientCredentialsGrant -from .rfc6749.grant_types import RefreshTokenGrant +from .rfc6749.clients import ( + BackendApplicationClient, Client, LegacyApplicationClient, + MobileApplicationClient, ServiceApplicationClient, WebApplicationClient, +) +from .rfc6749.endpoints import ( + AuthorizationEndpoint, BackendApplicationServer, IntrospectEndpoint, + LegacyApplicationServer, MetadataEndpoint, MobileApplicationServer, + ResourceEndpoint, RevocationEndpoint, Server, TokenEndpoint, + WebApplicationServer, +) +from .rfc6749.errors import ( + AccessDeniedError, FatalClientError, InsecureTransportError, + InvalidClientError, InvalidClientIdError, InvalidGrantError, + InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError, + InvalidScopeError, MismatchingRedirectURIError, MismatchingStateError, + MissingClientIdError, MissingCodeError, MissingRedirectURIError, + MissingResponseTypeError, MissingTokenError, MissingTokenTypeError, + OAuth2Error, ServerError, TemporarilyUnavailableError, TokenExpiredError, + UnauthorizedClientError, UnsupportedGrantTypeError, + UnsupportedResponseTypeError, UnsupportedTokenTypeError, +) +from .rfc6749.grant_types import ( + AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant, + RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant, +) from .rfc6749.request_validator import RequestValidator from .rfc6749.tokens import BearerToken, OAuth2Token from .rfc6749.utils import is_secure_transport +from .rfc8628.clients import DeviceClient diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/__init__.py index 1a4128c5e..4b75a8a19 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,15 +5,12 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - import functools import logging -from .endpoints.base import BaseEndpoint -from .endpoints.base import catch_errors_and_unavailability -from .errors import TemporarilyUnavailableError, ServerError -from .errors import FatalClientError, OAuth2Error - +from .endpoints.base import BaseEndpoint, catch_errors_and_unavailability +from .errors import ( + FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError, +) log = logging.getLogger(__name__) diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/__init__.py index 17d002368..8fc6c955a 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/__init__.py @@ -6,11 +6,9 @@ This module is an implementation of various logic needed for consuming OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - -from .base import Client, AUTH_HEADER, URI_QUERY, BODY -from .web_application import WebApplicationClient -from .mobile_application import MobileApplicationClient -from .legacy_application import LegacyApplicationClient from .backend_application import BackendApplicationClient +from .base import AUTH_HEADER, BODY, URI_QUERY, Client +from .legacy_application import LegacyApplicationClient +from .mobile_application import MobileApplicationClient from .service_application import ServiceApplicationClient +from .web_application import WebApplicationClient diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/backend_application.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/backend_application.py index 573781409..e11e8fae3 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/backend_application.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/backend_application.py @@ -6,9 +6,7 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - -from ..parameters import parse_token_response, prepare_token_request +from ..parameters import prepare_token_request from .base import Client @@ -41,7 +39,7 @@ def prepare_request_body(self, body='', scope=None, format per `Appendix B`_ in the HTTP request entity-body: :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/base.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/base.py index 9b05ad5da..d5eb0cc15 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/base.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/base.py @@ -6,18 +6,22 @@ This module is an implementation of various logic needed for consuming OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - +import base64 +import hashlib +import re +import secrets import time import warnings from oauthlib.common import generate_token from oauthlib.oauth2.rfc6749 import tokens -from oauthlib.oauth2.rfc6749.errors import (InsecureTransportError, - TokenExpiredError) -from oauthlib.oauth2.rfc6749.parameters import (parse_token_response, - prepare_token_request, - prepare_token_revocation_request) +from oauthlib.oauth2.rfc6749.errors import ( + InsecureTransportError, TokenExpiredError, +) +from oauthlib.oauth2.rfc6749.parameters import ( + parse_token_response, prepare_token_request, + prepare_token_revocation_request, +) from oauthlib.oauth2.rfc6749.utils import is_secure_transport AUTH_HEADER = 'auth_header' @@ -29,7 +33,7 @@ } -class Client(object): +class Client: """Base OAuth2 client responsible for access token management. This class also acts as a generic interface providing methods common to all @@ -61,6 +65,9 @@ def __init__(self, client_id, state=None, redirect_url=None, state_generator=generate_token, + code_verifier=None, + code_challenge=None, + code_challenge_method=None, **kwargs): """Initialize a client with commonly used attributes. @@ -99,6 +106,15 @@ def __init__(self, client_id, :param state_generator: A no argument state generation callable. Defaults to :py:meth:`oauthlib.common.generate_token`. + + :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the + authorization request to the token request. + + :param code_challenge: PKCE parameter. A challenge derived from the code verifier that is sent in the + authorization request, to be verified against later. + + :param code_challenge_method: PKCE parameter. A method that was used to derive code challenge. + Defaults to "plain" if not present in the request. """ self.client_id = client_id @@ -113,6 +129,9 @@ def __init__(self, client_id, self.state_generator = state_generator self.state = state self.redirect_url = redirect_url + self.code_verifier = code_verifier + self.code_challenge = code_challenge + self.code_challenge_method = code_challenge_method self.code = None self.expires_in = None self._expires_at = None @@ -186,8 +205,8 @@ def add_token(self, uri, http_method='GET', body=None, headers=None, token_placement = token_placement or self.default_token_placement - case_insensitive_token_types = dict( - (k.lower(), v) for k, v in self.token_types.items()) + case_insensitive_token_types = { + k.lower(): v for k, v in self.token_types.items()} if not self.token_type.lower() in case_insensitive_token_types: raise ValueError("Unsupported token type: %s" % self.token_type) @@ -209,23 +228,21 @@ def prepare_authorization_request(self, authorization_url, state=None, required parameters to the authorization URL. :param authorization_url: Provider authorization endpoint URL. - :param state: CSRF protection string. Will be automatically created if - not provided. The generated state is available via the ``state`` - attribute. Clients should verify that the state is unchanged and - present in the authorization response. This verification is done - automatically if using the ``authorization_response`` parameter - with ``prepare_token_request``. - + not provided. The generated state is available via the ``state`` + attribute. Clients should verify that the state is unchanged and + present in the authorization response. This verification is done + automatically if using the ``authorization_response`` parameter + with ``prepare_token_request``. :param redirect_url: Redirect URL to which the user will be returned - after authorization. Must be provided unless previously setup with - the provider. If provided then it must also be provided in the - token request. - - :param scope: - + after authorization. Must be provided unless previously setup with + the provider. If provided then it must also be provided in the + token request. + :param scope: List of scopes to request. Must be equal to + or a subset of the scopes granted when obtaining the refresh + token. If none is provided, the ones provided in the constructor are + used. :param kwargs: Additional parameters to included in the request. - :returns: The prepared request tuple with (url, headers, body). """ if not is_secure_transport(authorization_url): @@ -233,10 +250,11 @@ def prepare_authorization_request(self, authorization_url, state=None, self.state = state or self.state_generator() self.redirect_url = redirect_url or self.redirect_url - self.scope = scope or self.scope + # do not assign scope to self automatically anymore + scope = self.scope if scope is None else scope auth_url = self.prepare_request_uri( authorization_url, redirect_uri=self.redirect_url, - scope=self.scope, state=self.state, **kwargs) + scope=scope, state=self.state, **kwargs) return auth_url, FORM_ENC_HEADERS, '' def prepare_token_request(self, token_url, authorization_response=None, @@ -248,22 +266,16 @@ def prepare_token_request(self, token_url, authorization_response=None, credentials. :param token_url: Provider token creation endpoint URL. - :param authorization_response: The full redirection URL string, i.e. - the location to which the user was redirected after successfull - authorization. Used to mine credentials needed to obtain a token - in this step, such as authorization code. - + the location to which the user was redirected after successful + authorization. Used to mine credentials needed to obtain a token + in this step, such as authorization code. :param redirect_url: The redirect_url supplied with the authorization - request (if there was one). - + request (if there was one). :param state: - :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. - + into. This may contain extra parameters. Default ''. :param kwargs: Additional parameters to included in the request. - :returns: The prepared request tuple with (url, headers, body). """ if not is_secure_transport(token_url): @@ -289,26 +301,23 @@ def prepare_refresh_token_request(self, token_url, refresh_token=None, obtain a new access token, and possibly a new refresh token. :param token_url: Provider token refresh endpoint URL. - :param refresh_token: Refresh token string. - :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. - + into. This may contain extra parameters. Default ''. :param scope: List of scopes to request. Must be equal to - or a subset of the scopes granted when obtaining the refresh - token. - + or a subset of the scopes granted when obtaining the refresh + token. If none is provided, the ones provided in the constructor are + used. :param kwargs: Additional parameters to included in the request. - :returns: The prepared request tuple with (url, headers, body). """ if not is_secure_transport(token_url): raise InsecureTransportError() - self.scope = scope or self.scope + # do not assign scope to self automatically anymore + scope = self.scope if scope is None else scope body = self.prepare_refresh_body(body=body, - refresh_token=refresh_token, scope=self.scope, **kwargs) + refresh_token=refresh_token, scope=scope, **kwargs) return token_url, FORM_ENC_HEADERS, body def prepare_token_revocation_request(self, revocation_url, token, @@ -316,20 +325,14 @@ def prepare_token_revocation_request(self, revocation_url, token, """Prepare a token revocation request. :param revocation_url: Provider token revocation endpoint URL. - :param token: The access or refresh token to be revoked (string). - :param token_type_hint: ``"access_token"`` (default) or - ``"refresh_token"``. This is optional and if you wish to not pass it you - must provide ``token_type_hint=None``. - + ``"refresh_token"``. This is optional and if you wish to not pass it you + must provide ``token_type_hint=None``. :param body: - :param callback: A jsonp callback such as ``package.callback`` to be invoked - upon receiving the response. Not that it should not include a () suffix. - + upon receiving the response. Not that it should not include a () suffix. :param kwargs: Additional parameters to included in the request. - :returns: The prepared request tuple with (url, headers, body). Note that JSONP request may use GET requests as the parameters will @@ -337,7 +340,7 @@ def prepare_token_revocation_request(self, revocation_url, token, An example of a revocation request - .. code-block: http + .. code-block:: http POST /revoke HTTP/1.1 Host: server.example.com @@ -348,7 +351,7 @@ def prepare_token_revocation_request(self, revocation_url, token, An example of a jsonp revocation request - .. code-block: http + .. code-block:: http GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1 Host: server.example.com @@ -357,9 +360,9 @@ def prepare_token_revocation_request(self, revocation_url, token, and an error response - .. code-block: http + .. code-block:: javascript - package.myCallback({"error":"unsupported_token_type"}); + package.myCallback({"error":"unsupported_token_type"}); Note that these requests usually require client credentials, client_id in the case for public clients and provider specific authentication @@ -382,9 +385,11 @@ def parse_request_body_response(self, body, scope=None, **kwargs): returns an error response as described in `Section 5.2`_. :param body: The response body from the token request. - :param scope: Scopes originally requested. + :param scope: Scopes originally requested. If none is provided, the ones + provided in the constructor are used. :return: Dictionary of token parameters. - :raises: Warning if scope has changed. OAuth2Error if response is invalid. + :raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error` + if response is invalid. These response are json encoded and could easily be parsed without the assistance of OAuthLib. However, there are a few subtle issues @@ -410,7 +415,7 @@ def parse_request_body_response(self, body, scope=None, **kwargs): If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value. - **scope** + **scope** Providers may supply this in all responses but are required to only if it has changed since the authorization request. @@ -418,6 +423,7 @@ def parse_request_body_response(self, body, scope=None, **kwargs): .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2 .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1 """ + scope = self.scope if scope is None else scope self.token = parse_token_response(body, scope=scope) self.populate_token_attributes(self.token) return self.token @@ -427,21 +433,19 @@ def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs If the authorization server issued a refresh token to the client, the client makes a refresh request to the token endpoint by adding the - following parameters using the "application/x-www-form-urlencoded" + following parameters using the `application/x-www-form-urlencoded` format in the HTTP request entity-body: - grant_type - REQUIRED. Value MUST be set to "refresh_token". - refresh_token - REQUIRED. The refresh token issued to the client. - scope - OPTIONAL. The scope of the access request as described by - Section 3.3. The requested scope MUST NOT include any scope - not originally granted by the resource owner, and if omitted is - treated as equal to the scope originally granted by the - resource owner. + :param refresh_token: REQUIRED. The refresh token issued to the client. + :param scope: OPTIONAL. The scope of the access request as described by + Section 3.3. The requested scope MUST NOT include any scope + not originally granted by the resource owner, and if omitted is + treated as equal to the scope originally granted by the + resource owner. Note that if none is provided, the ones provided + in the constructor are used if any. """ refresh_token = refresh_token or self.refresh_token + scope = self.scope if scope is None else scope return prepare_token_request(self.refresh_token_key, body=body, scope=scope, refresh_token=refresh_token, **kwargs) @@ -461,6 +465,91 @@ def _add_bearer_token(self, uri, http_method='GET', body=None, raise ValueError("Invalid token placement.") return uri, headers, body + def create_code_verifier(self, length): + """Create PKCE **code_verifier** used in computing **code_challenge**. + See `RFC7636 Section 4.1`_ + + :param length: REQUIRED. The length of the code_verifier. + + The client first creates a code verifier, "code_verifier", for each + OAuth 2.0 [RFC6749] Authorization Request, in the following manner: + + .. code-block:: text + + code_verifier = high-entropy cryptographic random STRING using the + unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" + from Section 2.3 of [RFC3986], with a minimum length of 43 characters + and a maximum length of 128 characters. + + .. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1 + """ + code_verifier = None + + if not length >= 43: + raise ValueError("Length must be greater than or equal to 43") + + if not length <= 128: + raise ValueError("Length must be less than or equal to 128") + + allowed_characters = re.compile('^[A-Zaa-z0-9-._~]') + code_verifier = secrets.token_urlsafe(length) + + if not re.search(allowed_characters, code_verifier): + raise ValueError("code_verifier contains invalid characters") + + self.code_verifier = code_verifier + + return code_verifier + + def create_code_challenge(self, code_verifier, code_challenge_method=None): + """Create PKCE **code_challenge** derived from the **code_verifier**. + See `RFC7636 Section 4.2`_ + + :param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`. + :param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`. + + The client then creates a code challenge derived from the code + verifier by using one of the following transformations on the code + verifier:: + + plain + code_challenge = code_verifier + S256 + code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) + + If the client is capable of using `S256`, it MUST use `S256`, as + `S256` is Mandatory To Implement (MTI) on the server. Clients are + permitted to use `plain` only if they cannot support `S256` for some + technical reason and know via out-of-band configuration that the + server supports `plain`. + + The plain transformation is for compatibility with existing + deployments and for constrained environments that can't use the S256 transformation. + + .. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2 + """ + code_challenge = None + + if code_verifier == None: + raise ValueError("Invalid code_verifier") + + if code_challenge_method == None: + code_challenge_method = "plain" + self.code_challenge_method = code_challenge_method + code_challenge = code_verifier + self.code_challenge = code_challenge + + if code_challenge_method == "S256": + h = hashlib.sha256() + h.update(code_verifier.encode(encoding='ascii')) + sha256_val = h.digest() + code_challenge = bytes.decode(base64.urlsafe_b64encode(sha256_val)) + # replace '+' with '-', '/' with '_', and remove trailing '=' + code_challenge = code_challenge.replace("+", "-").replace("/", "_").replace("=", "") + self.code_challenge = code_challenge + + return code_challenge + def _add_mac_token(self, uri, http_method='GET', body=None, headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs): """Add a MAC token to the request authorization header. @@ -503,7 +592,10 @@ def populate_token_attributes(self, response): self._expires_at = time.time() + int(self.expires_in) if 'expires_at' in response: - self._expires_at = int(response.get('expires_at')) + try: + self._expires_at = int(response.get('expires_at')) + except: + self._expires_at = None if 'mac_key' in response: self.mac_key = response.get('mac_key') diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py index ca218e45c..9920981d2 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py @@ -6,9 +6,7 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - -from ..parameters import parse_token_response, prepare_token_request +from ..parameters import prepare_token_request from .base import Client @@ -38,7 +36,7 @@ class LegacyApplicationClient(Client): grant_type = 'password' def __init__(self, client_id, **kwargs): - super(LegacyApplicationClient, self).__init__(client_id, **kwargs) + super().__init__(client_id, **kwargs) def prepare_request_body(self, username, password, body='', scope=None, include_client_id=False, **kwargs): @@ -51,7 +49,7 @@ def prepare_request_body(self, username, password, body='', scope=None, :param username: The resource owner username. :param password: The resource owner password. :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. :param include_client_id: `True` to send the `client_id` in the @@ -81,5 +79,6 @@ def prepare_request_body(self, username, password, body='', scope=None, """ kwargs['client_id'] = self.client_id kwargs['include_client_id'] = include_client_id + scope = self.scope if scope is None else scope return prepare_token_request(self.grant_type, body=body, username=username, password=password, scope=scope, **kwargs) diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py index 11c6c51c9..b10b41ced 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py @@ -6,8 +6,6 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - from ..parameters import parse_implicit_response, prepare_grant_uri from .base import Client @@ -57,7 +55,7 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None, using the "application/x-www-form-urlencoded" format, per `Appendix B`_: :param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI - and it should have been registerd with the OAuth + and it should have been registered with the OAuth provider prior to use. As described in `Section 3.1.2`_. :param scope: OPTIONAL. The scope of the access request as described by @@ -93,6 +91,7 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None, .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12 """ + scope = self.scope if scope is None else scope return prepare_grant_uri(uri, self.client_id, self.response_type, redirect_uri=redirect_uri, state=state, scope=scope, **kwargs) @@ -169,6 +168,7 @@ def parse_request_uri_response(self, uri, state=None, scope=None): .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1 .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 """ + scope = self.scope if scope is None else scope self.token = parse_implicit_response(uri, state=state, scope=scope) self.populate_token_attributes(self.token) return self.token diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/service_application.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/service_application.py index ea946cec7..b4ef4b797 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -6,13 +6,11 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - import time from oauthlib.common import to_unicode -from ..parameters import parse_token_response, prepare_token_request +from ..parameters import prepare_token_request from .base import Client @@ -33,7 +31,7 @@ class ServiceApplicationClient(Client): def __init__(self, client_id, private_key=None, subject=None, issuer=None, audience=None, **kwargs): - """Initalize a JWT client with defaults for implicit use later. + """Initialize a JWT client with defaults for implicit use later. :param client_id: Client identifier given by the OAuth provider upon registration. @@ -57,7 +55,7 @@ def __init__(self, client_id, private_key=None, subject=None, issuer=None, state and token. See ``Client.__init__.__doc__`` for details. """ - super(ServiceApplicationClient, self).__init__(client_id, **kwargs) + super().__init__(client_id, **kwargs) self.private_key = private_key self.subject = subject self.issuer = issuer @@ -101,7 +99,7 @@ def prepare_request_body(self, :param extra_claims: A dict of additional claims to include in the JWT. :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param scope: The scope of the access request. @@ -151,7 +149,7 @@ def prepare_request_body(self, .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1 """ - import jwt + import pyjwt as jwt key = private_key or self.private_key if not key: @@ -183,6 +181,7 @@ def prepare_request_body(self, kwargs['client_id'] = self.client_id kwargs['include_client_id'] = include_client_id + scope = self.scope if scope is None else scope return prepare_token_request(self.grant_type, body=body, assertion=assertion, diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/web_application.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/web_application.py index 0cd39ce51..50890fbf8 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/web_application.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/clients/web_application.py @@ -6,13 +6,12 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - import warnings -from ..parameters import (parse_authorization_code_response, - parse_token_response, prepare_grant_uri, - prepare_token_request) +from ..parameters import ( + parse_authorization_code_response, prepare_grant_uri, + prepare_token_request, +) from .base import Client @@ -38,11 +37,11 @@ class WebApplicationClient(Client): grant_type = 'authorization_code' def __init__(self, client_id, code=None, **kwargs): - super(WebApplicationClient, self).__init__(client_id, **kwargs) + super().__init__(client_id, **kwargs) self.code = code def prepare_request_uri(self, uri, redirect_uri=None, scope=None, - state=None, **kwargs): + state=None, code_challenge=None, code_challenge_method='plain', **kwargs): """Prepare the authorization code request URI The client constructs the request URI by adding the following @@ -50,7 +49,7 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None, using the "application/x-www-form-urlencoded" format, per `Appendix B`_: :param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI - and it should have been registerd with the OAuth + and it should have been registered with the OAuth provider prior to use. As described in `Section 3.1.2`_. :param scope: OPTIONAL. The scope of the access request as described by @@ -63,6 +62,13 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None, to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in `Section 10.12`_. + :param code_challenge: OPTIONAL. PKCE parameter. REQUIRED if PKCE is enforced. + A challenge derived from the code_verifier that is sent in the + authorization request, to be verified against later. + + :param code_challenge_method: OPTIONAL. PKCE parameter. A method that was used to derive code challenge. + Defaults to "plain" if not present in the request. + :param kwargs: Extra arguments to include in the request URI. In addition to supplied parameters, OAuthLib will append the ``client_id`` @@ -77,6 +83,10 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None, 'https://example.com?client_id=your_id&response_type=code&redirect_uri=https%3A%2F%2Fa.b%2Fcallback' >>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures']) 'https://example.com?client_id=your_id&response_type=code&scope=profile+pictures' + >>> client.prepare_request_uri('https://example.com', code_challenge='kjasBS523KdkAILD2k78NdcJSk2k3KHG6') + 'https://example.com?client_id=your_id&response_type=code&code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6' + >>> client.prepare_request_uri('https://example.com', code_challenge_method='S256') + 'https://example.com?client_id=your_id&response_type=code&code_challenge_method=S256' >>> client.prepare_request_uri('https://example.com', foo='bar') 'https://example.com?client_id=your_id&response_type=code&foo=bar' @@ -86,11 +96,13 @@ def prepare_request_uri(self, uri, redirect_uri=None, scope=None, .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12 """ + scope = self.scope if scope is None else scope return prepare_grant_uri(uri, self.client_id, 'code', - redirect_uri=redirect_uri, scope=scope, state=state, **kwargs) + redirect_uri=redirect_uri, scope=scope, state=state, code_challenge=code_challenge, + code_challenge_method=code_challenge_method, **kwargs) def prepare_request_body(self, code=None, redirect_uri=None, body='', - include_client_id=True, **kwargs): + include_client_id=True, code_verifier=None, **kwargs): """Prepare the access token request body. The client makes a request to the token endpoint by adding the @@ -105,7 +117,7 @@ def prepare_request_body(self, code=None, redirect_uri=None, body='', values MUST be identical. :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param include_client_id: `True` (default) to send the `client_id` in the body of the upstream request. This is required @@ -113,6 +125,9 @@ def prepare_request_body(self, code=None, redirect_uri=None, body='', authorization server as described in `Section 3.2.1`_. :type include_client_id: Boolean + :param code_verifier: OPTIONAL. A cryptographically random string that is used to correlate the + authorization request to the token request. + :param kwargs: Extra parameters to include in the token request. In addition OAuthLib will add the ``grant_type`` parameter set to @@ -127,6 +142,8 @@ def prepare_request_body(self, code=None, redirect_uri=None, body='', >>> client = WebApplicationClient('your_id') >>> client.prepare_request_body(code='sh35ksdf09sf') 'grant_type=authorization_code&code=sh35ksdf09sf' + >>> client.prepare_request_body(code_verifier='KB46DCKJ873NCGXK5GD682NHDKK34GR') + 'grant_type=authorization_code&code_verifier=KB46DCKJ873NCGXK5GD682NHDKK34GR' >>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar') 'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar' @@ -154,7 +171,7 @@ def prepare_request_body(self, code=None, redirect_uri=None, body='', kwargs['client_id'] = self.client_id kwargs['include_client_id'] = include_client_id return prepare_token_request(self.grant_type, code=code, body=body, - redirect_uri=redirect_uri, **kwargs) + redirect_uri=redirect_uri, code_verifier=code_verifier, **kwargs) def parse_request_uri_response(self, uri, state=None): """Parse the URI query for code and state. diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/__init__.py index 51e173df3..1695b41b6 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,16 +5,13 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - from .authorization import AuthorizationEndpoint from .introspect import IntrospectEndpoint from .metadata import MetadataEndpoint -from .token import TokenEndpoint +from .pre_configured import ( + BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer, + Server, WebApplicationServer, +) from .resource import ResourceEndpoint from .revocation import RevocationEndpoint -from .pre_configured import Server -from .pre_configured import WebApplicationServer -from .pre_configured import MobileApplicationServer -from .pre_configured import LegacyApplicationServer -from .pre_configured import BackendApplicationServer +from .token import TokenEndpoint diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/authorization.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/authorization.py index 92cde3465..71967865d 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/authorization.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/authorization.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,8 +5,6 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - import logging from oauthlib.common import Request diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/base.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/base.py index e39232f4a..3f239917c 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,21 +5,18 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - import functools import logging -from ..errors import (FatalClientError, OAuth2Error, ServerError, - TemporarilyUnavailableError, InvalidRequestError, - InvalidClientError, UnsupportedTokenTypeError) - -from oauthlib.common import CaseInsensitiveDict, urldecode +from ..errors import ( + FatalClientError, InvalidClientError, InvalidRequestError, OAuth2Error, + ServerError, TemporarilyUnavailableError, UnsupportedTokenTypeError, +) log = logging.getLogger(__name__) -class BaseEndpoint(object): +class BaseEndpoint: def __init__(self): self._available = True diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py index 4accbdc10..3cc61e662 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.endpoint.introspect ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -7,14 +6,12 @@ .. _`Token Introspection`: https://tools.ietf.org/html/rfc7662 """ -from __future__ import absolute_import, unicode_literals - import json import logging from oauthlib.common import Request -from ..errors import OAuth2Error, UnsupportedTokenTypeError +from ..errors import OAuth2Error from .base import BaseEndpoint, catch_errors_and_unavailability log = logging.getLogger(__name__) @@ -89,9 +86,9 @@ def validate_introspect_request(self, request): an HTTP POST request with parameters sent as "application/x-www-form-urlencoded". - token REQUIRED. The string value of the token. + * token REQUIRED. The string value of the token. + * token_type_hint OPTIONAL. - token_type_hint OPTIONAL. A hint about the type of the token submitted for introspection. The protected resource MAY pass this parameter to help the authorization server optimize the token lookup. If the @@ -99,11 +96,9 @@ def validate_introspect_request(self, request): extend its search across all of its supported token types. An authorization server MAY ignore this parameter, particularly if it is able to detect the token type automatically. - * access_token: An Access Token as defined in [`RFC6749`], - `section 1.4`_ - * refresh_token: A Refresh Token as defined in [`RFC6749`], - `section 1.5`_ + * access_token: An Access Token as defined in [`RFC6749`], `section 1.4`_ + * refresh_token: A Refresh Token as defined in [`RFC6749`], `section 1.5`_ The introspection endpoint MAY accept other OPTIONAL parameters to provide further context to the query. For diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py index 936e87886..a2820f28a 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.endpoint.metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -7,20 +6,16 @@ .. _`OAuth 2.0 Authorization Server Metadata`: https://tools.ietf.org/html/rfc8414 """ -from __future__ import absolute_import, unicode_literals - import copy import json import logging -from ....common import unicode_type -from .base import BaseEndpoint, catch_errors_and_unavailability +from .. import grant_types, utils from .authorization import AuthorizationEndpoint +from .base import BaseEndpoint, catch_errors_and_unavailability from .introspect import IntrospectEndpoint -from .token import TokenEndpoint from .revocation import RevocationEndpoint -from .. import grant_types - +from .token import TokenEndpoint log = logging.getLogger(__name__) @@ -59,7 +54,8 @@ def create_metadata_response(self, uri, http_method='GET', body=None, """Create metadata response """ headers = { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', } return headers, json.dumps(self.claims), 200 @@ -72,7 +68,7 @@ def validate_metadata(self, array, key, is_required=False, is_list=False, is_url raise ValueError("key {} is a mandatory metadata.".format(key)) elif is_issuer: - if not array[key].startswith("https"): + if not utils.is_secure_transport(array[key]): raise ValueError("key {}: {} must be an HTTPS URL".format(key, array[key])) if "?" in array[key] or "&" in array[key] or "#" in array[key]: raise ValueError("key {}: {} must not contain query or fragment components".format(key, array[key])) @@ -85,7 +81,7 @@ def validate_metadata(self, array, key, is_required=False, is_list=False, is_url if not isinstance(array[key], list): raise ValueError("key {}: {} must be an Array".format(key, array[key])) for elem in array[key]: - if not isinstance(elem, unicode_type): + if not isinstance(elem, str): raise ValueError("array {}: {} must contains only string (not {})".format(key, array[key], elem)) def validate_metadata_token(self, claims, endpoint): @@ -166,10 +162,10 @@ def validate_metadata_server(self): response_types_supported REQUIRED. - * Other OPTIONAL fields: - jwks_uri - registration_endpoint - response_modes_supported + Other OPTIONAL fields: + jwks_uri, + registration_endpoint, + response_modes_supported grant_types_supported OPTIONAL. JSON array containing a list of the OAuth 2.0 grant diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py index e2cc9db71..d64a16639 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.endpoints.pre_configured ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,13 +5,10 @@ This module is an implementation of various endpoints needed for providing OAuth 2.0 RFC6749 servers. """ -from __future__ import absolute_import, unicode_literals - -from ..grant_types import (AuthorizationCodeGrant, - ClientCredentialsGrant, - ImplicitGrant, - RefreshTokenGrant, - ResourceOwnerPasswordCredentialsGrant) +from ..grant_types import ( + AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant, + RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant, +) from ..tokens import BearerToken from .authorization import AuthorizationEndpoint from .introspect import IntrospectEndpoint @@ -42,34 +38,34 @@ def __init__(self, request_validator, token_expires_in=None, :param kwargs: Extra parameters to pass to authorization-, token-, resource-, and revocation-endpoint constructors. """ - auth_grant = AuthorizationCodeGrant(request_validator) - implicit_grant = ImplicitGrant(request_validator) - password_grant = ResourceOwnerPasswordCredentialsGrant( + self.auth_grant = AuthorizationCodeGrant(request_validator) + self.implicit_grant = ImplicitGrant(request_validator) + self.password_grant = ResourceOwnerPasswordCredentialsGrant( request_validator) - credentials_grant = ClientCredentialsGrant(request_validator) - refresh_grant = RefreshTokenGrant(request_validator) + self.credentials_grant = ClientCredentialsGrant(request_validator) + self.refresh_grant = RefreshTokenGrant(request_validator) - bearer = BearerToken(request_validator, token_generator, + self.bearer = BearerToken(request_validator, token_generator, token_expires_in, refresh_token_generator) AuthorizationEndpoint.__init__(self, default_response_type='code', response_types={ - 'code': auth_grant, - 'token': implicit_grant, - 'none': auth_grant + 'code': self.auth_grant, + 'token': self.implicit_grant, + 'none': self.auth_grant }, - default_token_type=bearer) + default_token_type=self.bearer) TokenEndpoint.__init__(self, default_grant_type='authorization_code', grant_types={ - 'authorization_code': auth_grant, - 'password': password_grant, - 'client_credentials': credentials_grant, - 'refresh_token': refresh_grant, + 'authorization_code': self.auth_grant, + 'password': self.password_grant, + 'client_credentials': self.credentials_grant, + 'refresh_token': self.refresh_grant, }, - default_token_type=bearer) + default_token_type=self.bearer) ResourceEndpoint.__init__(self, default_token='Bearer', - token_types={'Bearer': bearer}) + token_types={'Bearer': self.bearer}) RevocationEndpoint.__init__(self, request_validator) IntrospectEndpoint.__init__(self, request_validator) @@ -94,21 +90,21 @@ def __init__(self, request_validator, token_generator=None, :param kwargs: Extra parameters to pass to authorization-, token-, resource-, and revocation-endpoint constructors. """ - auth_grant = AuthorizationCodeGrant(request_validator) - refresh_grant = RefreshTokenGrant(request_validator) - bearer = BearerToken(request_validator, token_generator, + self.auth_grant = AuthorizationCodeGrant(request_validator) + self.refresh_grant = RefreshTokenGrant(request_validator) + self.bearer = BearerToken(request_validator, token_generator, token_expires_in, refresh_token_generator) AuthorizationEndpoint.__init__(self, default_response_type='code', - response_types={'code': auth_grant}, - default_token_type=bearer) + response_types={'code': self.auth_grant}, + default_token_type=self.bearer) TokenEndpoint.__init__(self, default_grant_type='authorization_code', grant_types={ - 'authorization_code': auth_grant, - 'refresh_token': refresh_grant, + 'authorization_code': self.auth_grant, + 'refresh_token': self.refresh_grant, }, - default_token_type=bearer) + default_token_type=self.bearer) ResourceEndpoint.__init__(self, default_token='Bearer', - token_types={'Bearer': bearer}) + token_types={'Bearer': self.bearer}) RevocationEndpoint.__init__(self, request_validator) IntrospectEndpoint.__init__(self, request_validator) @@ -133,15 +129,15 @@ def __init__(self, request_validator, token_generator=None, :param kwargs: Extra parameters to pass to authorization-, token-, resource-, and revocation-endpoint constructors. """ - implicit_grant = ImplicitGrant(request_validator) - bearer = BearerToken(request_validator, token_generator, + self.implicit_grant = ImplicitGrant(request_validator) + self.bearer = BearerToken(request_validator, token_generator, token_expires_in, refresh_token_generator) AuthorizationEndpoint.__init__(self, default_response_type='token', response_types={ - 'token': implicit_grant}, - default_token_type=bearer) + 'token': self.implicit_grant}, + default_token_type=self.bearer) ResourceEndpoint.__init__(self, default_token='Bearer', - token_types={'Bearer': bearer}) + token_types={'Bearer': self.bearer}) RevocationEndpoint.__init__(self, request_validator, supported_token_types=['access_token']) IntrospectEndpoint.__init__(self, request_validator, @@ -168,19 +164,19 @@ def __init__(self, request_validator, token_generator=None, :param kwargs: Extra parameters to pass to authorization-, token-, resource-, and revocation-endpoint constructors. """ - password_grant = ResourceOwnerPasswordCredentialsGrant( + self.password_grant = ResourceOwnerPasswordCredentialsGrant( request_validator) - refresh_grant = RefreshTokenGrant(request_validator) - bearer = BearerToken(request_validator, token_generator, + self.refresh_grant = RefreshTokenGrant(request_validator) + self.bearer = BearerToken(request_validator, token_generator, token_expires_in, refresh_token_generator) TokenEndpoint.__init__(self, default_grant_type='password', grant_types={ - 'password': password_grant, - 'refresh_token': refresh_grant, + 'password': self.password_grant, + 'refresh_token': self.refresh_grant, }, - default_token_type=bearer) + default_token_type=self.bearer) ResourceEndpoint.__init__(self, default_token='Bearer', - token_types={'Bearer': bearer}) + token_types={'Bearer': self.bearer}) RevocationEndpoint.__init__(self, request_validator) IntrospectEndpoint.__init__(self, request_validator) @@ -205,15 +201,15 @@ def __init__(self, request_validator, token_generator=None, :param kwargs: Extra parameters to pass to authorization-, token-, resource-, and revocation-endpoint constructors. """ - credentials_grant = ClientCredentialsGrant(request_validator) - bearer = BearerToken(request_validator, token_generator, + self.credentials_grant = ClientCredentialsGrant(request_validator) + self.bearer = BearerToken(request_validator, token_generator, token_expires_in, refresh_token_generator) TokenEndpoint.__init__(self, default_grant_type='client_credentials', grant_types={ - 'client_credentials': credentials_grant}, - default_token_type=bearer) + 'client_credentials': self.credentials_grant}, + default_token_type=self.bearer) ResourceEndpoint.__init__(self, default_token='Bearer', - token_types={'Bearer': bearer}) + token_types={'Bearer': self.bearer}) RevocationEndpoint.__init__(self, request_validator, supported_token_types=['access_token']) IntrospectEndpoint.__init__(self, request_validator, diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/resource.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/resource.py index f19c60c36..f7562255d 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/resource.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/resource.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,8 +5,6 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - import logging from oauthlib.common import Request diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py index 1fabd0382..596d0860f 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.endpoint.revocation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -7,13 +6,11 @@ .. _`Token Revocation`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11 """ -from __future__ import absolute_import, unicode_literals - import logging from oauthlib.common import Request -from ..errors import OAuth2Error, UnsupportedTokenTypeError +from ..errors import OAuth2Error from .base import BaseEndpoint, catch_errors_and_unavailability log = logging.getLogger(__name__) @@ -45,7 +42,7 @@ def create_revocation_response(self, uri, http_method='POST', body=None, The authorization server responds with HTTP status code 200 if the - token has been revoked sucessfully or if the client submitted an + token has been revoked successfully or if the client submitted an invalid token. Note: invalid tokens do not cause an error response since the client @@ -73,7 +70,7 @@ def create_revocation_response(self, uri, http_method='POST', body=None, log.debug('Client error during validation of %r. %r.', request, e) response_body = e.json if self.enable_jsonp and request.callback: - response_body = '%s(%s);' % (request.callback, response_body) + response_body = '{}({});'.format(request.callback, response_body) resp_headers.update(e.headers) return resp_headers, response_body, e.status_code @@ -98,7 +95,7 @@ def validate_revocation_request(self, request): submitted for revocation. Clients MAY pass this parameter in order to help the authorization server to optimize the token lookup. If the server is unable to locate the token using the given hint, it MUST - extend its search accross all of its supported token types. An + extend its search across all of its supported token types. An authorization server MAY ignore this parameter, particularly if it is able to detect the token type automatically. This specification defines two such values: diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/token.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/token.py index bc87e9bd3..ab9e0918b 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/token.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/endpoints/token.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,8 +5,6 @@ This module is an implementation of various logic needed for consuming and providing OAuth 2.0 RFC6749. """ -from __future__ import absolute_import, unicode_literals - import logging from oauthlib.common import Request @@ -39,7 +36,6 @@ class TokenEndpoint(BaseEndpoint): https://example.com/path?query=component # OK https://example.com/path?query=component#fragment # Not OK - Since requests to the authorization endpoint result in user Since requests to the token endpoint result in the transmission of clear-text credentials (in the HTTP request and response), the authorization server MUST require the use of TLS as described in diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/errors.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/errors.py index d2a14026a..da24feab7 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/errors.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/errors.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ oauthlib.oauth2.rfc6749.errors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,8 +5,6 @@ Error used both by OAuth 2 clients and providers to represent the spec defined error responses for all four core grant types. """ -from __future__ import unicode_literals - import json from oauthlib.common import add_params_to_uri, urlencode @@ -45,10 +42,10 @@ def __init__(self, description=None, uri=None, state=None, if description is not None: self.description = description - message = '(%s) %s' % (self.error, self.description) + message = '({}) {}'.format(self.error, self.description) if request: message += ' ' + repr(request) - super(OAuth2Error, self).__init__(message) + super().__init__(message) self.uri = uri self.state = state @@ -106,15 +103,12 @@ def headers(self): value "Bearer". This scheme MUST be followed by one or more auth-param values. """ - authvalues = [ - "Bearer", - 'error="{}"'.format(self.error) - ] + authvalues = ['error="{}"'.format(self.error)] if self.description: authvalues.append('error_description="{}"'.format(self.description)) if self.uri: authvalues.append('error_uri="{}"'.format(self.uri)) - return {"WWW-Authenticate": ", ".join(authvalues)} + return {"WWW-Authenticate": "Bearer " + ", ".join(authvalues)} return {} @@ -389,7 +383,7 @@ class CustomOAuth2Error(OAuth2Error): """ def __init__(self, error, *args, **kwargs): self.error = error - super(CustomOAuth2Error, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def raise_from_error(error, params=None): diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/__init__.py index 2ec8e4faa..eb88cfc2e 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/__init__.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import unicode_literals, absolute_import - from .authorization_code import AuthorizationCodeGrant -from .implicit import ImplicitGrant -from .resource_owner_password_credentials import ResourceOwnerPasswordCredentialsGrant from .client_credentials import ClientCredentialsGrant +from .implicit import ImplicitGrant from .refresh_token import RefreshTokenGrant +from .resource_owner_password_credentials import ( + ResourceOwnerPasswordCredentialsGrant, +) diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index 9b84c4c41..858855a17 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import base64 import hashlib import json @@ -275,6 +272,8 @@ def create_authorization_response(self, request, token_handler): grant = self.create_authorization_code(request) for modifier in self._code_modifiers: grant = modifier(grant, token_handler, request) + if 'access_token' in grant: + self.request_validator.save_token(grant, request) log.debug('Saving grant %r for %r.', grant, request) self.request_validator.save_authorization_code( request.client_id, grant, request) @@ -313,6 +312,7 @@ def create_token_response(self, request, token_handler): self.request_validator.save_token(token, request) self.request_validator.invalidate_authorization_code( request.client_id, request.code, request) + headers.update(self._create_cors_headers(request)) return headers, json.dumps(token), 200 def validate_authorization_request(self, request): diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/base.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/base.py index f0772e287..ca343a119 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/base.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/base.py @@ -1,23 +1,21 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import logging from itertools import chain from oauthlib.common import add_params_to_uri -from oauthlib.uri_validate import is_absolute_uri from oauthlib.oauth2.rfc6749 import errors, utils +from oauthlib.uri_validate import is_absolute_uri from ..request_validator import RequestValidator +from ..utils import is_secure_transport log = logging.getLogger(__name__) -class ValidatorsContainer(object): +class ValidatorsContainer: """ Container object for holding custom validator callables to be invoked as part of the grant type `validate_authorization_request()` or @@ -74,7 +72,7 @@ def all_post(self): return chain(self.post_auth, self.post_token) -class GrantTypeBase(object): +class GrantTypeBase: error_uri = None request_validator = None default_response_mode = 'fragment' @@ -251,3 +249,20 @@ def _handle_redirects(self, request): raise errors.MissingRedirectURIError(request=request) if not is_absolute_uri(request.redirect_uri): raise errors.InvalidRedirectURIError(request=request) + + def _create_cors_headers(self, request): + """If CORS is allowed, create the appropriate headers.""" + if 'origin' not in request.headers: + return {} + + origin = request.headers['origin'] + if not is_secure_transport(origin): + log.debug('Origin "%s" is not HTTPS, CORS not allowed.', origin) + return {} + elif not self.request_validator.is_origin_allowed( + request.client_id, origin, request): + log.debug('Invalid origin "%s", CORS not allowed.', origin) + return {} + else: + log.debug('Valid origin "%s", injecting CORS headers.', origin) + return {'Access-Control-Allow-Origin': origin} diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py index 7e508577a..e7b461897 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py @@ -1,15 +1,11 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import json import logging from .. import errors -from ..request_validator import RequestValidator from .base import GrantTypeBase log = logging.getLogger(__name__) @@ -119,8 +115,8 @@ def validate_token_request(self, request): # Ensure client is authorized use of this grant type self.validate_grant_type(request) - log.debug('Authorizing access to user %r.', request.user) request.client_id = request.client_id or request.client.client_id + log.debug('Authorizing access to client %r.', request.client_id) self.validate_scopes(request) for validator in self.custom_validators.post_token: diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/implicit.py index 48bae7a5b..6110b6f33 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/implicit.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/implicit.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import logging from oauthlib import common diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py index fc61d65cd..ce33df0e7 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py @@ -1,15 +1,11 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import json import logging from .. import errors, utils -from ..request_validator import RequestValidator from .base import GrantTypeBase log = logging.getLogger(__name__) @@ -25,7 +21,7 @@ class RefreshTokenGrant(GrantTypeBase): def __init__(self, request_validator=None, issue_new_refresh_tokens=True, **kwargs): - super(RefreshTokenGrant, self).__init__( + super().__init__( request_validator, issue_new_refresh_tokens=issue_new_refresh_tokens, **kwargs) @@ -67,12 +63,13 @@ def create_token_response(self, request, token_handler): refresh_token=self.issue_new_refresh_tokens) for modifier in self._token_modifiers: - token = modifier(token) + token = modifier(token, token_handler, request) self.request_validator.save_token(token, request) log.debug('Issuing new token to client id %r (%r), %r.', request.client_id, request.client, token) + headers.update(self._create_cors_headers(request)) return headers, json.dumps(token), 200 def validate_token_request(self, request): @@ -126,7 +123,7 @@ def validate_token_request(self, request): if request.scope: request.scopes = utils.scope_to_list(request.scope) - if (not all((s in original_scopes for s in request.scopes)) + if (not all(s in original_scopes for s in request.scopes) and not self.request_validator.is_within_original_scope( request.scopes, request.refresh_token, request)): log.debug('Refresh token %s lack requested scopes, %r.', diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py index 5929afb4c..4b0de5bf6 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py @@ -1,15 +1,11 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import json import logging from .. import errors -from ..request_validator import RequestValidator from .base import GrantTypeBase log = logging.getLogger(__name__) diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/parameters.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/parameters.py index 14d4c0d87..8f6ce2c7f 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/parameters.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/parameters.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -7,29 +6,24 @@ .. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4 """ -from __future__ import absolute_import, unicode_literals - import json import os import time +import urllib.parse as urlparse -from oauthlib.common import add_params_to_qs, add_params_to_uri, unicode_type +from oauthlib.common import add_params_to_qs, add_params_to_uri from oauthlib.signals import scope_changed -from .errors import (InsecureTransportError, MismatchingStateError, - MissingCodeError, MissingTokenError, - MissingTokenTypeError, raise_from_error) +from .errors import ( + InsecureTransportError, MismatchingStateError, MissingCodeError, + MissingTokenError, MissingTokenTypeError, raise_from_error, +) from .tokens import OAuth2Token from .utils import is_secure_transport, list_to_scope, scope_to_list -try: - import urlparse -except ImportError: - import urllib.parse as urlparse - def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, - scope=None, state=None, **kwargs): + scope=None, state=None, code_challenge=None, code_challenge_method='plain', **kwargs): """Prepare the authorization grant request URI. The client constructs the request URI by adding the following @@ -51,6 +45,11 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in `Section 10.12`_. + :param code_challenge: PKCE parameter. A challenge derived from the + code_verifier that is sent in the authorization + request, to be verified against later. + :param code_challenge_method: PKCE parameter. A method that was used to derive the + code_challenge. Defaults to "plain" if not present in the request. :param kwargs: Extra arguments to embed in the grant/authorization URL. An example of an authorization code grant authorization URL: @@ -58,6 +57,7 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, .. code-block:: http GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz + &code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6&code_challenge_method=S256 &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com @@ -79,15 +79,18 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, params.append(('scope', list_to_scope(scope))) if state: params.append(('state', state)) + if code_challenge is not None: + params.append(('code_challenge', code_challenge)) + params.append(('code_challenge_method', code_challenge_method)) for k in kwargs: if kwargs[k]: - params.append((unicode_type(k), kwargs[k])) + params.append((str(k), kwargs[k])) return add_params_to_uri(uri, params) -def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs): +def prepare_token_request(grant_type, body='', include_client_id=True, code_verifier=None, **kwargs): """Prepare the access token request. The client makes a request to the token endpoint by adding the @@ -122,6 +125,9 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs) authorization request as described in `Section 4.1.1`_, and their values MUST be identical. * + :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the + authorization request to the token request. + :param kwargs: Extra arguments to embed in the request body. Parameters marked with a `*` above are not explicit arguments in the @@ -146,18 +152,22 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs) client_id = kwargs.pop('client_id', None) if include_client_id: if client_id is not None: - params.append((unicode_type('client_id'), client_id)) + params.append(('client_id', client_id)) + + # use code_verifier if code_challenge was passed in the authorization request + if code_verifier is not None: + params.append(('code_verifier', code_verifier)) # the kwargs iteration below only supports including boolean truth (truthy) # values, but some servers may require an empty string for `client_secret` client_secret = kwargs.pop('client_secret', None) if client_secret is not None: - params.append((unicode_type('client_secret'), client_secret)) + params.append(('client_secret', client_secret)) # this handles: `code`, `redirect_uri`, and other undocumented params for k in kwargs: if kwargs[k]: - params.append((unicode_type(k), kwargs[k])) + params.append((str(k), kwargs[k])) return add_params_to_qs(body, params) @@ -167,7 +177,7 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token", """Prepare a token revocation request. The client constructs the request by including the following parameters - using the "application/x-www-form-urlencoded" format in the HTTP request + using the ``application/x-www-form-urlencoded`` format in the HTTP request entity-body: :param token: REQUIRED. The token that the client wants to get revoked. @@ -209,7 +219,7 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token", for k in kwargs: if kwargs[k]: - params.append((unicode_type(k), kwargs[k])) + params.append((str(k), kwargs[k])) headers = {'Content-Type': 'application/x-www-form-urlencoded'} @@ -433,7 +443,7 @@ def parse_token_response(body, scope=None): def validate_token_parameters(params): - """Ensures token precence, token type, expiration and scope in params.""" + """Ensures token presence, token type, expiration and scope in params.""" if 'error' in params: raise_from_error(params.get('error'), params) diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/request_validator.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/request_validator.py index 86509b62e..3910c0b91 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/request_validator.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/request_validator.py @@ -1,16 +1,13 @@ -# -*- coding: utf-8 -*- """ oauthlib.oauth2.rfc6749.request_validator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import logging log = logging.getLogger(__name__) -class RequestValidator(object): +class RequestValidator: def client_authentication_required(self, request, *args, **kwargs): """Determine if client authentication is required for current request. @@ -51,6 +48,17 @@ def authenticate_client(self, request, *args, **kwargs): Headers may be accesses through request.headers and parameters found in both body and query can be obtained by direct attribute access, i.e. request.client_id for client_id in the URL query. + + The authentication process is required to contain the identification of + the client (i.e. search the database based on the client_id). In case the + client doesn't exist based on the received client_id, this method has to + return False and the HTTP response created by the library will contain + 'invalid_client' message. + + After the client identification succeeds, this method needs to set the + client on the request, i.e. request.client = client. A client object's + class must contain the 'client_id' attribute and the 'client_id' must have + a value. :param request: OAuthlib request. :type request: oauthlib.common.Request @@ -183,6 +191,7 @@ def introspect_token(self, token, token_type_hint, request, *args, **kwargs): claims associated, or `None` in case the token is unknown. Below the list of registered claims you should be interested in: + - scope : space-separated list of scopes - client_id : client identifier - username : human-readable identifier for the resource owner @@ -196,10 +205,10 @@ def introspect_token(self, token, token_type_hint, request, *args, **kwargs): - jti : string identifier for the token Note that most of them are coming directly from JWT RFC. More details - can be found in `Introspect Claims`_ or `_JWT Claims`_. + can be found in `Introspect Claims`_ or `JWT Claims`_. The implementation can use *token_type_hint* to improve lookup - efficency, but must fallback to other types to be compliant with RFC. + efficiency, but must fallback to other types to be compliant with RFC. The dict of claims is added to request.token after this method. @@ -435,6 +444,7 @@ def validate_code(self, client_id, code, client, request, *args, **kwargs): - request.user - request.scopes - request.claims (if given) + OBS! The request.user attribute should be set to the resource owner associated with this authorization code. Similarly request.scopes must also be set. @@ -443,6 +453,7 @@ def validate_code(self, client_id, code, client, request, *args, **kwargs): If PKCE is enabled (see 'is_pkce_required' and 'save_authorization_code') you MUST set the following based on the information stored: + - request.code_challenge - request.code_challenge_method @@ -553,7 +564,7 @@ def validate_user(self, username, password, client, request, *args, **kwargs): OBS! The validation should also set the user attribute of the request to a valid resource owner, i.e. request.user = username or similar. If not set you will be unable to associate a token with a user in the - persistance method used (commonly, save_bearer_token). + persistence method used (commonly, save_bearer_token). :param username: Unicode username. :param password: Unicode password. @@ -641,3 +652,29 @@ def get_code_challenge_method(self, code, request): """ raise NotImplementedError('Subclasses must implement this method.') + + def is_origin_allowed(self, client_id, origin, request, *args, **kwargs): + """Indicate if the given origin is allowed to access the token endpoint + via Cross-Origin Resource Sharing (CORS). CORS is used by browser-based + clients, such as Single-Page Applications, to perform the Authorization + Code Grant. + + (Note: If performing Authorization Code Grant via a public client such + as a browser, you should use PKCE as well.) + + If this method returns true, the appropriate CORS headers will be added + to the response. By default this method always returns False, meaning + CORS is disabled. + + :param client_id: Unicode client identifier. + :param redirect_uri: Unicode origin. + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :rtype: bool + + Method is used by: + - Authorization Code Grant + - Refresh Token Grant + + """ + return False diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/tokens.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/tokens.py index 3587af430..0757d07ea 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/tokens.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/tokens.py @@ -7,28 +7,22 @@ - Bearer https://tools.ietf.org/html/rfc6750 - MAC https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 """ -from __future__ import absolute_import, unicode_literals - import hashlib import hmac -from binascii import b2a_base64 import warnings +from binascii import b2a_base64 +from urllib.parse import urlparse from oauthlib import common -from oauthlib.common import add_params_to_qs, add_params_to_uri, unicode_type +from oauthlib.common import add_params_to_qs, add_params_to_uri from . import utils -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse - class OAuth2Token(dict): def __init__(self, params, old_scope=None): - super(OAuth2Token, self).__init__(params) + super().__init__(params) self._new_scope = None if 'scope' in params and params['scope']: self._new_scope = set(utils.scope_to_list(params['scope'])) @@ -121,7 +115,7 @@ def prepare_mac_header(token, uri, key, http_method, raise ValueError('unknown hash algorithm') if draft == 0: - nonce = nonce or '{0}:{1}'.format(utils.generate_age(issue_time), + nonce = nonce or '{}:{}'.format(utils.generate_age(issue_time), common.generate_nonce()) else: ts = common.generate_timestamp() @@ -158,7 +152,7 @@ def prepare_mac_header(token, uri, key, http_method, base_string = '\n'.join(base) + '\n' # hmac struggles with unicode strings - http://bugs.python.org/issue5285 - if isinstance(key, unicode_type): + if isinstance(key, str): key = key.encode('utf-8') sign = hmac.new(key, base_string.encode('utf-8'), h) sign = b2a_base64(sign.digest())[:-1].decode('utf-8') @@ -262,7 +256,8 @@ def get_token_from_header(request): return token -class TokenBase(object): +class TokenBase: + __slots__ = () def __call__(self, request, refresh_token=False): raise NotImplementedError('Subclasses must implement this method.') diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/utils.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/utils.py index f67019d32..7dc27b3df 100644 --- a/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/utils.py +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc6749/utils.py @@ -1,33 +1,22 @@ -# -*- coding: utf-8 -*- """ oauthlib.utils ~~~~~~~~~~~~~~ This module contains utility methods used by various parts of the OAuth 2 spec. """ -from __future__ import absolute_import, unicode_literals - import datetime import os +from urllib.parse import quote, urlparse -from oauthlib.common import unicode_type, urldecode - -try: - from urllib import quote -except ImportError: - from urllib.parse import quote -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse +from oauthlib.common import urldecode def list_to_scope(scope): """Convert a list of scopes to a space separated string.""" - if isinstance(scope, unicode_type) or scope is None: + if isinstance(scope, str) or scope is None: return scope elif isinstance(scope, (set, tuple, list)): - return " ".join([unicode_type(s) for s in scope]) + return " ".join([str(s) for s in scope]) else: raise ValueError("Invalid scope (%s), must be string, tuple, set, or list." % scope) @@ -35,7 +24,7 @@ def list_to_scope(scope): def scope_to_list(scope): """Convert a space separated string to a list of scopes.""" if isinstance(scope, (tuple, list, set)): - return [unicode_type(s) for s in scope] + return [str(s) for s in scope] elif scope is None: return None else: @@ -74,7 +63,7 @@ def escape(u): TODO: verify whether this can in fact be used for OAuth 2 """ - if not isinstance(u, unicode_type): + if not isinstance(u, str): raise ValueError('Only unicode objects are escapable.') return quote(u.encode('utf-8'), safe=b'~') @@ -84,7 +73,7 @@ def generate_age(issue_time): td = datetime.datetime.now() - issue_time age = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 6 - return unicode_type(age) + return str(age) def is_secure_transport(uri): diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/__init__.py new file mode 100644 index 000000000..531929dcc --- /dev/null +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/__init__.py @@ -0,0 +1,10 @@ +""" +oauthlib.oauth2.rfc8628 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 Device Authorization RFC8628. +""" +import logging + +log = logging.getLogger(__name__) diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/clients/__init__.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/clients/__init__.py new file mode 100644 index 000000000..130b52e38 --- /dev/null +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/clients/__init__.py @@ -0,0 +1,8 @@ +""" +oauthlib.oauth2.rfc8628 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming OAuth 2.0 Device Authorization RFC8628. +""" +from .device import DeviceClient diff --git a/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/clients/device.py b/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/clients/device.py new file mode 100644 index 000000000..b9ba2150a --- /dev/null +++ b/script.module.oauthlib/lib/oauthlib/oauth2/rfc8628/clients/device.py @@ -0,0 +1,95 @@ +""" +oauthlib.oauth2.rfc8628 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 Device Authorization RFC8628. +""" +from oauthlib.common import add_params_to_uri +from oauthlib.oauth2 import BackendApplicationClient, Client +from oauthlib.oauth2.rfc6749.errors import InsecureTransportError +from oauthlib.oauth2.rfc6749.parameters import prepare_token_request +from oauthlib.oauth2.rfc6749.utils import is_secure_transport, list_to_scope + + +class DeviceClient(Client): + + """A public client utilizing the device authorization workflow. + + The client can request an access token using a device code and + a public client id associated with the device code as defined + in RFC8628. + + The device authorization grant type can be used to obtain both + access tokens and refresh tokens and is intended to be used in + a scenario where the device being authorized does not have a + user interface that is suitable for performing authentication. + """ + + grant_type = 'urn:ietf:params:oauth:grant-type:device_code' + + def __init__(self, client_id, **kwargs): + super().__init__(client_id, **kwargs) + self.client_secret = kwargs.get('client_secret') + + def prepare_request_uri(self, uri, scope=None, **kwargs): + if not is_secure_transport(uri): + raise InsecureTransportError() + + scope = self.scope if scope is None else scope + params = [(('client_id', self.client_id)), (('grant_type', self.grant_type))] + + if self.client_secret is not None: + params.append(('client_secret', self.client_secret)) + + if scope: + params.append(('scope', list_to_scope(scope))) + + for k in kwargs: + if kwargs[k]: + params.append((str(k), kwargs[k])) + + return add_params_to_uri(uri, params) + + def prepare_request_body(self, device_code, body='', scope=None, + include_client_id=False, **kwargs): + """Add device_code to request body + + The client makes a request to the token endpoint by adding the + device_code as a parameter using the + "application/x-www-form-urlencoded" format to the HTTP request + body. + + :param body: Existing request body (URL encoded string) to embed parameters + into. This may contain extra parameters. Default ''. + :param scope: The scope of the access request as described by + `Section 3.3`_. + + :param include_client_id: `True` to send the `client_id` in the + body of the upstream request. This is required + if the client is not authenticating with the + authorization server as described in + `Section 3.2.1`_. False otherwise (default). + :type include_client_id: Boolean + + :param kwargs: Extra credentials to include in the token request. + + The prepared body will include all provided device_code as well as + the ``grant_type`` parameter set to + ``urn:ietf:params:oauth:grant-type:device_code``:: + + >>> from oauthlib.oauth2 import DeviceClient + >>> client = DeviceClient('your_id', 'your_code') + >>> client.prepare_request_body(scope=['hello', 'world']) + 'grant_type=urn:ietf:params:oauth:grant-type:device_code&scope=hello+world' + + .. _`Section 3.2.1`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1 + .. _`Section 3.3`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3 + .. _`Section 3.4`: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4 + """ + + kwargs['client_id'] = self.client_id + kwargs['include_client_id'] = include_client_id + scope = self.scope if scope is None else scope + return prepare_token_request(self.grant_type, body=body, device_code=device_code, + scope=scope, **kwargs) diff --git a/script.module.oauthlib/lib/oauthlib/openid/__init__.py b/script.module.oauthlib/lib/oauthlib/openid/__init__.py index 8157c2972..e31743747 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/openid/__init__.py @@ -1,11 +1,7 @@ -# -*- coding: utf-8 -*- """ oauthlib.openid ~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - -from .connect.core.endpoints import Server -from .connect.core.endpoints import UserInfoEndpoint +from .connect.core.endpoints import Server, UserInfoEndpoint from .connect.core.request_validator import RequestValidator diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/__init__.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/__init__.py index 528841f4e..7017ff4f3 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.oopenid.core ~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,7 +5,5 @@ This module is an implementation of various logic needed for consuming and providing OpenID Connect """ -from __future__ import absolute_import, unicode_literals - from .pre_configured import Server from .userinfo import UserInfoEndpoint diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/pre_configured.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/pre_configured.py index fde2739e8..8ce8bee67 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/pre_configured.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/pre_configured.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ oauthlib.openid.connect.core.endpoints.pre_configured ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,32 +5,21 @@ This module is an implementation of various endpoints needed for providing OpenID Connect servers. """ -from __future__ import absolute_import, unicode_literals - from oauthlib.oauth2.rfc6749.endpoints import ( - AuthorizationEndpoint, - IntrospectEndpoint, - ResourceEndpoint, - RevocationEndpoint, - TokenEndpoint + AuthorizationEndpoint, IntrospectEndpoint, ResourceEndpoint, + RevocationEndpoint, TokenEndpoint, ) from oauthlib.oauth2.rfc6749.grant_types import ( AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant, - ImplicitGrant as OAuth2ImplicitGrant, - ClientCredentialsGrant, - RefreshTokenGrant, - ResourceOwnerPasswordCredentialsGrant + ClientCredentialsGrant, ImplicitGrant as OAuth2ImplicitGrant, + RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant, ) from oauthlib.oauth2.rfc6749.tokens import BearerToken -from ..grant_types import ( - AuthorizationCodeGrant, - ImplicitGrant, - HybridGrant, -) + +from ..grant_types import AuthorizationCodeGrant, HybridGrant, ImplicitGrant from ..grant_types.dispatchers import ( - AuthorizationCodeGrantDispatcher, + AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher, ImplicitTokenGrantDispatcher, - AuthorizationTokenGrantDispatcher ) from ..tokens import JWTToken from .userinfo import UserInfoEndpoint @@ -58,52 +46,52 @@ def __init__(self, request_validator, token_expires_in=None, :param kwargs: Extra parameters to pass to authorization-, token-, resource-, and revocation-endpoint constructors. """ - auth_grant = OAuth2AuthorizationCodeGrant(request_validator) - implicit_grant = OAuth2ImplicitGrant(request_validator) - password_grant = ResourceOwnerPasswordCredentialsGrant( + self.auth_grant = OAuth2AuthorizationCodeGrant(request_validator) + self.implicit_grant = OAuth2ImplicitGrant(request_validator) + self.password_grant = ResourceOwnerPasswordCredentialsGrant( request_validator) - credentials_grant = ClientCredentialsGrant(request_validator) - refresh_grant = RefreshTokenGrant(request_validator) - openid_connect_auth = AuthorizationCodeGrant(request_validator) - openid_connect_implicit = ImplicitGrant(request_validator) - openid_connect_hybrid = HybridGrant(request_validator) + self.credentials_grant = ClientCredentialsGrant(request_validator) + self.refresh_grant = RefreshTokenGrant(request_validator) + self.openid_connect_auth = AuthorizationCodeGrant(request_validator) + self.openid_connect_implicit = ImplicitGrant(request_validator) + self.openid_connect_hybrid = HybridGrant(request_validator) - bearer = BearerToken(request_validator, token_generator, + self.bearer = BearerToken(request_validator, token_generator, token_expires_in, refresh_token_generator) - jwt = JWTToken(request_validator, token_generator, + self.jwt = JWTToken(request_validator, token_generator, token_expires_in, refresh_token_generator) - auth_grant_choice = AuthorizationCodeGrantDispatcher(default_grant=auth_grant, oidc_grant=openid_connect_auth) - implicit_grant_choice = ImplicitTokenGrantDispatcher(default_grant=implicit_grant, oidc_grant=openid_connect_implicit) + self.auth_grant_choice = AuthorizationCodeGrantDispatcher(default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth) + self.implicit_grant_choice = ImplicitTokenGrantDispatcher(default_grant=self.implicit_grant, oidc_grant=self.openid_connect_implicit) # See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations for valid combinations # internally our AuthorizationEndpoint will ensure they can appear in any order for any valid combination AuthorizationEndpoint.__init__(self, default_response_type='code', response_types={ - 'code': auth_grant_choice, - 'token': implicit_grant_choice, - 'id_token': openid_connect_implicit, - 'id_token token': openid_connect_implicit, - 'code token': openid_connect_hybrid, - 'code id_token': openid_connect_hybrid, - 'code id_token token': openid_connect_hybrid, - 'none': auth_grant + 'code': self.auth_grant_choice, + 'token': self.implicit_grant_choice, + 'id_token': self.openid_connect_implicit, + 'id_token token': self.openid_connect_implicit, + 'code token': self.openid_connect_hybrid, + 'code id_token': self.openid_connect_hybrid, + 'code id_token token': self.openid_connect_hybrid, + 'none': self.auth_grant }, - default_token_type=bearer) + default_token_type=self.bearer) - token_grant_choice = AuthorizationTokenGrantDispatcher(request_validator, default_grant=auth_grant, oidc_grant=openid_connect_auth) + self.token_grant_choice = AuthorizationTokenGrantDispatcher(request_validator, default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth) TokenEndpoint.__init__(self, default_grant_type='authorization_code', grant_types={ - 'authorization_code': token_grant_choice, - 'password': password_grant, - 'client_credentials': credentials_grant, - 'refresh_token': refresh_grant, + 'authorization_code': self.token_grant_choice, + 'password': self.password_grant, + 'client_credentials': self.credentials_grant, + 'refresh_token': self.refresh_grant, }, - default_token_type=bearer) + default_token_type=self.bearer) ResourceEndpoint.__init__(self, default_token='Bearer', - token_types={'Bearer': bearer, 'JWT': jwt}) + token_types={'Bearer': self.bearer, 'JWT': self.jwt}) RevocationEndpoint.__init__(self, request_validator) IntrospectEndpoint.__init__(self, request_validator) UserInfoEndpoint.__init__(self, request_validator) diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/userinfo.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/userinfo.py index 7a39f76b8..7aa2bbe97 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/userinfo.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/endpoints/userinfo.py @@ -4,18 +4,15 @@ This module is an implementation of userinfo endpoint. """ -from __future__ import absolute_import, unicode_literals - import json import logging from oauthlib.common import Request -from oauthlib.common import unicode_type -from oauthlib.oauth2.rfc6749.endpoints.base import BaseEndpoint -from oauthlib.oauth2.rfc6749.endpoints.base import catch_errors_and_unavailability -from oauthlib.oauth2.rfc6749.tokens import BearerToken from oauthlib.oauth2.rfc6749 import errors - +from oauthlib.oauth2.rfc6749.endpoints.base import ( + BaseEndpoint, catch_errors_and_unavailability, +) +from oauthlib.oauth2.rfc6749.tokens import BearerToken log = logging.getLogger(__name__) @@ -55,7 +52,7 @@ def create_userinfo_response(self, uri, http_method='GET', body=None, headers=No log.error('Userinfo MUST have "sub" for %r.', request) raise errors.ServerError(status_code=500) body = json.dumps(claims) - elif isinstance(claims, unicode_type): + elif isinstance(claims, str): resp_headers = { 'Content-Type': 'application/jwt' } @@ -72,7 +69,7 @@ def validate_userinfo_request(self, request): 5.3.1. UserInfo Request The Client sends the UserInfo Request using either HTTP GET or HTTP POST. The Access Token obtained from an OpenID Connect Authentication - Request MUST be sent as a Bearer Token, per Section 2 of OAuth 2.0 + Request MUST be sent as a Bearer Token, per `Section 2`_ of OAuth 2.0 Bearer Token Usage [RFC6750]. It is RECOMMENDED that the request use the HTTP GET method and the @@ -80,21 +77,28 @@ def validate_userinfo_request(self, request): The following is a non-normative example of a UserInfo Request: - GET /userinfo HTTP/1.1 - Host: server.example.com - Authorization: Bearer SlAV32hkKG + .. code-block:: http + + GET /userinfo HTTP/1.1 + Host: server.example.com + Authorization: Bearer SlAV32hkKG 5.3.3. UserInfo Error Response When an error condition occurs, the UserInfo Endpoint returns an Error - Response as defined in Section 3 of OAuth 2.0 Bearer Token Usage + Response as defined in `Section 3`_ of OAuth 2.0 Bearer Token Usage [RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User Agent using the appropriate HTTP status code.) The following is a non-normative example of a UserInfo Error Response: - HTTP/1.1 401 Unauthorized - WWW-Authenticate: Bearer error="invalid_token", + .. code-block:: http + + HTTP/1.1 401 Unauthorized + WWW-Authenticate: Bearer error="invalid_token", error_description="The Access Token expired" + + .. _`Section 2`: https://datatracker.ietf.org/doc/html/rfc6750#section-2 + .. _`Section 3`: https://datatracker.ietf.org/doc/html/rfc6750#section-3 """ if not self.bearer.validate_request(request): raise errors.InvalidTokenError() diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/exceptions.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/exceptions.py index 8b08d21f3..099b84e2d 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/exceptions.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/exceptions.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ oauthlib.oauth2.rfc6749.errors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -6,8 +5,6 @@ Error used both by OAuth 2 clients and providers to represent the spec defined error responses for all four core grant types. """ -from __future__ import unicode_literals - from oauthlib.oauth2.rfc6749.errors import FatalClientError, OAuth2Error diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/__init__.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/__init__.py index 63f30ac94..8dad5f607 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/__init__.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/__init__.py @@ -1,17 +1,13 @@ -# -*- coding: utf-8 -*- """ oauthlib.openid.connect.core.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import unicode_literals, absolute_import - from .authorization_code import AuthorizationCodeGrant -from .implicit import ImplicitGrant from .base import GrantTypeBase -from .hybrid import HybridGrant -from .exceptions import OIDCNoPrompt from .dispatchers import ( - AuthorizationCodeGrantDispatcher, + AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher, ImplicitTokenGrantDispatcher, - AuthorizationTokenGrantDispatcher ) +from .hybrid import HybridGrant +from .implicit import ImplicitGrant +from .refresh_token import RefreshTokenGrant diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/authorization_code.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/authorization_code.py index becfcfabb..6b2dcc3bd 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/authorization_code.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/authorization_code.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- """ oauthlib.openid.connect.core.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import logging -from oauthlib.oauth2.rfc6749.grant_types.authorization_code import AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant +from oauthlib.oauth2.rfc6749.grant_types.authorization_code import ( + AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant, +) from .base import GrantTypeBase @@ -41,4 +40,4 @@ def add_id_token(self, token, token_handler, request): request.redirect_uri, request ) - return super(AuthorizationCodeGrant, self).add_id_token(token, token_handler, request, nonce=nonce) + return super().add_id_token(token, token_handler, request, nonce=nonce) diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/base.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/base.py index 32a21b62d..33411dad7 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/base.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/base.py @@ -1,17 +1,17 @@ -from .exceptions import OIDCNoPrompt - import base64 import hashlib import logging import time from json import loads -from oauthlib.oauth2.rfc6749.errors import ConsentRequired, InvalidRequestError, LoginRequired +from oauthlib.oauth2.rfc6749.errors import ( + ConsentRequired, InvalidRequestError, LoginRequired, +) log = logging.getLogger(__name__) -class GrantTypeBase(object): +class GrantTypeBase: # Just proxy the majority of method calls through to the # proxy_target grant type handler, which will usually be either @@ -20,7 +20,7 @@ def __getattr__(self, attr): return getattr(self.proxy_target, attr) def __setattr__(self, attr, value): - proxied_attrs = set(('refresh_token', 'response_types')) + proxied_attrs = {'refresh_token', 'response_types'} if attr in proxied_attrs: setattr(self.proxy_target, attr, value) else: @@ -31,13 +31,7 @@ def validate_authorization_request(self, request): :returns: (list of scopes, dict of request info) """ - # If request.prompt is 'none' then no login/authorization form should - # be presented to the user. Instead, a silent login/authorization - # should be performed. - if request.prompt == 'none': - raise OIDCNoPrompt() - else: - return self.proxy_target.validate_authorization_request(request) + return self.proxy_target.validate_authorization_request(request) def _inflate_claims(self, request): # this may be called multiple times in a single request so make sure we only de-serialize the claims once diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/dispatchers.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/dispatchers.py index be8e2f3cb..5aa7d4698 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/dispatchers.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/dispatchers.py @@ -1,16 +1,19 @@ import logging + log = logging.getLogger(__name__) -class Dispatcher(object): +class Dispatcher: default_grant = None oidc_grant = None class AuthorizationCodeGrantDispatcher(Dispatcher): """ - This is an adapter class that will route simple Authorization Code requests, those that have response_type=code and a scope - including 'openid' to either the default_grant or the oidc_grant based on the scopes requested. + This is an adapter class that will route simple Authorization Code + requests, those that have `response_type=code` and a scope including + `openid` to either the `default_grant` or the `oidc_grant` based on + the scopes requested. """ def __init__(self, default_grant=None, oidc_grant=None): self.default_grant = default_grant @@ -26,16 +29,20 @@ def _handler_for_request(self, request): return handler def create_authorization_response(self, request, token_handler): + """Read scope and route to the designated handler.""" return self._handler_for_request(request).create_authorization_response(request, token_handler) def validate_authorization_request(self, request): + """Read scope and route to the designated handler.""" return self._handler_for_request(request).validate_authorization_request(request) class ImplicitTokenGrantDispatcher(Dispatcher): """ - This is an adapter class that will route simple Authorization Code requests, those that have response_type=code and a scope - including 'openid' to either the default_grant or the oidc_grant based on the scopes requested. + This is an adapter class that will route simple Authorization + requests, those that have `id_token` in `response_type` and a scope + including `openid` to either the `default_grant` or the `oidc_grant` + based on the scopes requested. """ def __init__(self, default_grant=None, oidc_grant=None): self.default_grant = default_grant @@ -51,9 +58,11 @@ def _handler_for_request(self, request): return handler def create_authorization_response(self, request, token_handler): + """Read scope and route to the designated handler.""" return self._handler_for_request(request).create_authorization_response(request, token_handler) def validate_authorization_request(self, request): + """Read scope and route to the designated handler.""" return self._handler_for_request(request).validate_authorization_request(request) @@ -75,7 +84,7 @@ def _handler_for_request(self, request): code = parameters.get('code', None) redirect_uri = parameters.get('redirect_uri', None) - # If code is not pressent fallback to `default_grant` wich will + # If code is not present fallback to `default_grant` which will # raise an error for the missing `code` in `create_token_response` step. if code: scopes = self.request_validator.get_authorization_code_scopes(client_id, code, redirect_uri, request) @@ -87,5 +96,6 @@ def _handler_for_request(self, request): return handler def create_token_response(self, request, token_handler): + """Read scope and route to the designated handler.""" handler = self._handler_for_request(request) return handler.create_token_response(request, token_handler) diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/exceptions.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/exceptions.py deleted file mode 100644 index 809f1b3be..000000000 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/exceptions.py +++ /dev/null @@ -1,32 +0,0 @@ -class OIDCNoPrompt(Exception): - """Exception used to inform users that no explicit authorization is needed. - - Normally users authorize requests after validation of the request is done. - Then post-authorization validation is again made and a response containing - an auth code or token is created. However, when OIDC clients request - no prompting of user authorization the final response is created directly. - - Example (without the shortcut for no prompt) - - scopes, req_info = endpoint.validate_authorization_request(url, ...) - authorization_view = create_fancy_auth_form(scopes, req_info) - return authorization_view - - Example (with the no prompt shortcut) - try: - scopes, req_info = endpoint.validate_authorization_request(url, ...) - authorization_view = create_fancy_auth_form(scopes, req_info) - return authorization_view - except OIDCNoPrompt: - # Note: Location will be set for you - headers, body, status = endpoint.create_authorization_response(url, ...) - redirect_view = create_redirect(headers, body, status) - return redirect_view - """ - - def __init__(self): - msg = ("OIDC request for no user interaction received. Do not ask user " - "for authorization, it should been done using silent " - "authentication through create_authorization_response. " - "See OIDCNoPrompt.__doc__ for more details.") - super(OIDCNoPrompt, self).__init__(msg) diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/hybrid.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/hybrid.py index 685fa08ab..7cb0758b8 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/hybrid.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/hybrid.py @@ -1,17 +1,16 @@ -# -*- coding: utf-8 -*- """ oauthlib.openid.connect.core.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import logging -from oauthlib.oauth2.rfc6749.grant_types.authorization_code import AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant from oauthlib.oauth2.rfc6749.errors import InvalidRequestError +from oauthlib.oauth2.rfc6749.grant_types.authorization_code import ( + AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant, +) -from .base import GrantTypeBase from ..request_validator import RequestValidator +from .base import GrantTypeBase log = logging.getLogger(__name__) @@ -36,10 +35,13 @@ def __init__(self, request_validator=None, **kwargs): self.register_code_modifier(self.add_id_token) self.register_token_modifier(self.add_id_token) + def add_id_token(self, token, token_handler, request): + return super().add_id_token(token, token_handler, request, nonce=request.nonce) + def openid_authorization_validator(self, request): """Additional validation when following the Authorization Code flow. """ - request_info = super(HybridGrant, self).openid_authorization_validator(request) + request_info = super().openid_authorization_validator(request) if not request_info: # returns immediately if OAuth2.0 return request_info diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/implicit.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/implicit.py index c2dbc2784..a4fe6049b 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/implicit.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/implicit.py @@ -1,16 +1,15 @@ -# -*- coding: utf-8 -*- """ oauthlib.openid.connect.core.grant_types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import logging -from .base import GrantTypeBase - -from oauthlib.oauth2.rfc6749.grant_types.implicit import ImplicitGrant as OAuth2ImplicitGrant from oauthlib.oauth2.rfc6749.errors import InvalidRequestError +from oauthlib.oauth2.rfc6749.grant_types.implicit import ( + ImplicitGrant as OAuth2ImplicitGrant, +) + +from .base import GrantTypeBase log = logging.getLogger(__name__) @@ -29,12 +28,12 @@ def __init__(self, request_validator=None, **kwargs): def add_id_token(self, token, token_handler, request): if 'state' not in token and request.state: token['state'] = request.state - return super(ImplicitGrant, self).add_id_token(token, token_handler, request, nonce=request.nonce) + return super().add_id_token(token, token_handler, request, nonce=request.nonce) def openid_authorization_validator(self, request): """Additional validation when following the implicit flow. """ - request_info = super(ImplicitGrant, self).openid_authorization_validator(request) + request_info = super().openid_authorization_validator(request) if not request_info: # returns immediately if OAuth2.0 return request_info diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/refresh_token.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/refresh_token.py new file mode 100644 index 000000000..43e4499c5 --- /dev/null +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/grant_types/refresh_token.py @@ -0,0 +1,34 @@ +""" +oauthlib.openid.connect.core.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +import logging + +from oauthlib.oauth2.rfc6749.grant_types.refresh_token import ( + RefreshTokenGrant as OAuth2RefreshTokenGrant, +) + +from .base import GrantTypeBase + +log = logging.getLogger(__name__) + + +class RefreshTokenGrant(GrantTypeBase): + + def __init__(self, request_validator=None, **kwargs): + self.proxy_target = OAuth2RefreshTokenGrant( + request_validator=request_validator, **kwargs) + self.register_token_modifier(self.add_id_token) + + def add_id_token(self, token, token_handler, request): + """ + Construct an initial version of id_token, and let the + request_validator sign or encrypt it. + + The authorization_code version of this method is used to + retrieve the nonce accordingly to the code storage. + """ + if not self.request_validator.refresh_id_token(request): + return token + + return super().add_id_token(token, token_handler, request) diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/request_validator.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/request_validator.py index e853d39c2..47c4cd940 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/request_validator.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/request_validator.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- """ oauthlib.openid.connect.core.request_validator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ -from __future__ import absolute_import, unicode_literals - import logging -from oauthlib.oauth2.rfc6749.request_validator import RequestValidator as OAuth2RequestValidator +from oauthlib.oauth2.rfc6749.request_validator import ( + RequestValidator as OAuth2RequestValidator, +) log = logging.getLogger(__name__) @@ -307,3 +306,15 @@ def get_userinfo_claims(self, request): Method is used by: UserInfoEndpoint """ + + def refresh_id_token(self, request): + """Whether the id token should be refreshed. Default, True + + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :rtype: True or False + + Method is used by: + RefreshTokenGrant + """ + return True diff --git a/script.module.oauthlib/lib/oauthlib/openid/connect/core/tokens.py b/script.module.oauthlib/lib/oauthlib/openid/connect/core/tokens.py index b67cdf2a9..936ab52e3 100644 --- a/script.module.oauthlib/lib/oauthlib/openid/connect/core/tokens.py +++ b/script.module.oauthlib/lib/oauthlib/openid/connect/core/tokens.py @@ -4,10 +4,9 @@ This module contains methods for adding JWT tokens to requests. """ -from __future__ import absolute_import, unicode_literals - - -from oauthlib.oauth2.rfc6749.tokens import TokenBase, random_token_generator +from oauthlib.oauth2.rfc6749.tokens import ( + TokenBase, get_token_from_header, random_token_generator, +) class JWTToken(TokenBase): @@ -38,17 +37,12 @@ def create_token(self, request, refresh_token=False): return self.request_validator.get_jwt_bearer_token(None, None, request) def validate_request(self, request): - token = None - if 'Authorization' in request.headers: - token = request.headers.get('Authorization')[7:] - else: - token = request.access_token + token = get_token_from_header(request) return self.request_validator.validate_jwt_bearer_token( token, request.scopes, request) def estimate_type(self, request): - token = request.headers.get('Authorization', '')[7:] - if token.startswith('ey') and token.count('.') in (2, 4): + token = get_token_from_header(request) + if token and token.startswith('ey') and token.count('.') in (2, 4): return 10 - else: - return 0 + return 0 diff --git a/script.module.oauthlib/lib/oauthlib/signals.py b/script.module.oauthlib/lib/oauthlib/signals.py index 22d47a415..8fd347a5c 100644 --- a/script.module.oauthlib/lib/oauthlib/signals.py +++ b/script.module.oauthlib/lib/oauthlib/signals.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Implements signals based on blinker if available, otherwise falls silently back to a noop. Shamelessly stolen from flask.signals: @@ -9,11 +8,11 @@ from blinker import Namespace signals_available = True except ImportError: # noqa - class Namespace(object): + class Namespace: def signal(self, name, doc=None): return _FakeSignal(name, doc) - class _FakeSignal(object): + class _FakeSignal: """If blinker is unavailable, create a fake class with the same interface that allows sending of signals but will fail with an error on anything else. Instead of doing anything on send, it diff --git a/script.module.oauthlib/lib/oauthlib/uri_validate.py b/script.module.oauthlib/lib/oauthlib/uri_validate.py index ce8ea4029..a6fe0fb23 100644 --- a/script.module.oauthlib/lib/oauthlib/uri_validate.py +++ b/script.module.oauthlib/lib/oauthlib/uri_validate.py @@ -8,8 +8,6 @@ Thanks Mark Nottingham for this code - https://gist.github.com/138549 """ -from __future__ import unicode_literals - import re # basics @@ -67,32 +65,8 @@ IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s" % locals( ) -# h16 = 1*4HEXDIG -h16 = r"(?: %(HEXDIG)s ){1,4}" % locals() - -# ls32 = ( h16 ":" h16 ) / IPv4address -ls32 = r"(?: (?: %(h16)s : %(h16)s ) | %(IPv4address)s )" % locals() - -# IPv6address = 6( h16 ":" ) ls32 -# / "::" 5( h16 ":" ) ls32 -# / [ h16 ] "::" 4( h16 ":" ) ls32 -# / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 -# / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 -# / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 -# / [ *4( h16 ":" ) h16 ] "::" ls32 -# / [ *5( h16 ":" ) h16 ] "::" h16 -# / [ *6( h16 ":" ) h16 ] "::" -IPv6address = r"""(?: (?: %(h16)s : ){6} %(ls32)s | - :: (?: %(h16)s : ){5} %(ls32)s | - %(h16)s :: (?: %(h16)s : ){4} %(ls32)s | - (?: %(h16)s : ) %(h16)s :: (?: %(h16)s : ){3} %(ls32)s | - (?: %(h16)s : ){2} %(h16)s :: (?: %(h16)s : ){2} %(ls32)s | - (?: %(h16)s : ){3} %(h16)s :: %(h16)s : %(ls32)s | - (?: %(h16)s : ){4} %(h16)s :: %(ls32)s | - (?: %(h16)s : ){5} %(h16)s :: %(h16)s | - (?: %(h16)s : ){6} %(h16)s :: - ) -""" % locals() +# IPv6address +IPv6address = r"([A-Fa-f0-9:]+[:$])[A-Fa-f0-9]{1,4}" # IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals() diff --git a/script.module.oauthlib/icon.png b/script.module.oauthlib/resources/icon.png similarity index 100% rename from script.module.oauthlib/icon.png rename to script.module.oauthlib/resources/icon.png