mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 21:21:15 -07:00
Update oauthlib-3.1.1
This commit is contained in:
parent
e58aa40099
commit
d76838a607
64 changed files with 4329 additions and 1421 deletions
|
@ -5,21 +5,30 @@
|
||||||
A generic, spec-compliant, thorough implementation of the OAuth
|
A generic, spec-compliant, thorough implementation of the OAuth
|
||||||
request-signing logic.
|
request-signing logic.
|
||||||
|
|
||||||
:copyright: (c) 2011 by Idan Gazit.
|
:copyright: (c) 2019 by The OAuthlib Community
|
||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = 'Idan Gazit <idan@gazit.me>'
|
|
||||||
__version__ = '1.1.1'
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
try: # Python 2.7+
|
|
||||||
from logging import NullHandler
|
from logging import NullHandler
|
||||||
except ImportError:
|
|
||||||
class NullHandler(logging.Handler):
|
|
||||||
|
|
||||||
def emit(self, record):
|
__author__ = 'The OAuthlib Community'
|
||||||
pass
|
__version__ = '3.1.1'
|
||||||
|
|
||||||
logging.getLogger('oauthlib').addHandler(NullHandler())
|
logging.getLogger('oauthlib').addHandler(NullHandler())
|
||||||
|
|
||||||
|
_DEBUG = False
|
||||||
|
|
||||||
|
def set_debug(debug_val):
|
||||||
|
"""Set value of debug flag
|
||||||
|
|
||||||
|
:param debug_val: Value to set. Must be a bool value.
|
||||||
|
"""
|
||||||
|
global _DEBUG
|
||||||
|
_DEBUG = debug_val
|
||||||
|
|
||||||
|
def get_debug():
|
||||||
|
"""Get debug mode value.
|
||||||
|
|
||||||
|
:return: `True` if debug mode is on, `False` otherwise
|
||||||
|
"""
|
||||||
|
return _DEBUG
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.common
|
oauthlib.common
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
@ -6,28 +5,24 @@ oauthlib.common
|
||||||
This module provides data structures and utilities common
|
This module provides data structures and utilities common
|
||||||
to all implementations of OAuth.
|
to all implementations of OAuth.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import time
|
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:
|
try:
|
||||||
from urllib import quote as _quote
|
from secrets import randbits
|
||||||
from urllib import unquote as _unquote
|
from secrets import SystemRandom
|
||||||
from urllib import urlencode as _urlencode
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urllib.parse import quote as _quote
|
from random import getrandbits as randbits
|
||||||
from urllib.parse import unquote as _unquote
|
from random import SystemRandom
|
||||||
from urllib.parse import urlencode as _urlencode
|
|
||||||
try:
|
|
||||||
import urlparse
|
|
||||||
except ImportError:
|
|
||||||
import urllib.parse as urlparse
|
|
||||||
|
|
||||||
UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
|
UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
|
@ -45,23 +40,14 @@ always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
|
|
||||||
log = logging.getLogger('oauthlib')
|
log = logging.getLogger('oauthlib')
|
||||||
|
|
||||||
PY3 = sys.version_info[0] == 3
|
|
||||||
|
|
||||||
if PY3:
|
|
||||||
unicode_type = str
|
|
||||||
bytes_type = bytes
|
|
||||||
else:
|
|
||||||
unicode_type = unicode
|
|
||||||
bytes_type = str
|
|
||||||
|
|
||||||
|
|
||||||
# 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either)
|
# 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either)
|
||||||
def quote(s, safe=b'/'):
|
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)
|
s = _quote(s, safe)
|
||||||
# PY3 always returns unicode. PY2 may return either, depending on whether
|
# PY3 always returns unicode. PY2 may return either, depending on whether
|
||||||
# it had to modify the string.
|
# it had to modify the string.
|
||||||
if isinstance(s, bytes_type):
|
if isinstance(s, bytes):
|
||||||
s = s.decode('utf-8')
|
s = s.decode('utf-8')
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -71,7 +57,7 @@ def unquote(s):
|
||||||
# PY3 always returns unicode. PY2 seems to always return what you give it,
|
# PY3 always returns unicode. PY2 seems to always return what you give it,
|
||||||
# which differs from quote's behavior. Just to be safe, make sure it is
|
# which differs from quote's behavior. Just to be safe, make sure it is
|
||||||
# unicode before we return.
|
# unicode before we return.
|
||||||
if isinstance(s, bytes_type):
|
if isinstance(s, bytes):
|
||||||
s = s.decode('utf-8')
|
s = s.decode('utf-8')
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -79,7 +65,7 @@ def unquote(s):
|
||||||
def urlencode(params):
|
def urlencode(params):
|
||||||
utf8_params = encode_params_utf8(params)
|
utf8_params = encode_params_utf8(params)
|
||||||
urlencoded = _urlencode(utf8_params)
|
urlencoded = _urlencode(utf8_params)
|
||||||
if isinstance(urlencoded, unicode_type): # PY3 returns unicode
|
if isinstance(urlencoded, str):
|
||||||
return urlencoded
|
return urlencoded
|
||||||
else:
|
else:
|
||||||
return urlencoded.decode("utf-8")
|
return urlencoded.decode("utf-8")
|
||||||
|
@ -92,8 +78,8 @@ def encode_params_utf8(params):
|
||||||
encoded = []
|
encoded = []
|
||||||
for k, v in params:
|
for k, v in params:
|
||||||
encoded.append((
|
encoded.append((
|
||||||
k.encode('utf-8') if isinstance(k, unicode_type) else k,
|
k.encode('utf-8') if isinstance(k, str) else k,
|
||||||
v.encode('utf-8') if isinstance(v, unicode_type) else v))
|
v.encode('utf-8') if isinstance(v, str) else v))
|
||||||
return encoded
|
return encoded
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,12 +90,12 @@ def decode_params_utf8(params):
|
||||||
decoded = []
|
decoded = []
|
||||||
for k, v in params:
|
for k, v in params:
|
||||||
decoded.append((
|
decoded.append((
|
||||||
k.decode('utf-8') if isinstance(k, bytes_type) else k,
|
k.decode('utf-8') if isinstance(k, bytes) else k,
|
||||||
v.decode('utf-8') if isinstance(v, bytes_type) else v))
|
v.decode('utf-8') if isinstance(v, bytes) else v))
|
||||||
return decoded
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
urlencoded = set(always_safe) | set('=&;%+~,*@!()/?')
|
urlencoded = set(always_safe) | set('=&;:%+~,*@!()/?\'$')
|
||||||
|
|
||||||
|
|
||||||
def urldecode(query):
|
def urldecode(query):
|
||||||
|
@ -137,22 +123,6 @@ def urldecode(query):
|
||||||
if INVALID_HEX_PATTERN.search(query):
|
if INVALID_HEX_PATTERN.search(query):
|
||||||
raise ValueError('Invalid hex encoding in query string.')
|
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
|
# We want to allow queries such as "c2" whereas urlparse.parse_qsl
|
||||||
# with the strict_parsing flag will not.
|
# with the strict_parsing flag will not.
|
||||||
params = urlparse.parse_qsl(query, keep_blank_values=True)
|
params = urlparse.parse_qsl(query, keep_blank_values=True)
|
||||||
|
@ -169,7 +139,7 @@ def extract_params(raw):
|
||||||
empty list of parameters. Any other input will result in a return
|
empty list of parameters. Any other input will result in a return
|
||||||
value of None.
|
value of None.
|
||||||
"""
|
"""
|
||||||
if isinstance(raw, bytes_type) or isinstance(raw, unicode_type):
|
if isinstance(raw, (bytes, str)):
|
||||||
try:
|
try:
|
||||||
params = urldecode(raw)
|
params = urldecode(raw)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -199,10 +169,10 @@ def generate_nonce():
|
||||||
A random 64-bit number is appended to the epoch timestamp for both
|
A random 64-bit number is appended to the epoch timestamp for both
|
||||||
randomness and to decrease the likelihood of collisions.
|
randomness and to decrease the likelihood of collisions.
|
||||||
|
|
||||||
.. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
|
.. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
|
||||||
.. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
|
.. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
|
||||||
"""
|
"""
|
||||||
return unicode_type(unicode_type(random.getrandbits(64)) + generate_timestamp())
|
return str(str(randbits(64)) + generate_timestamp())
|
||||||
|
|
||||||
|
|
||||||
def generate_timestamp():
|
def generate_timestamp():
|
||||||
|
@ -211,10 +181,10 @@ def generate_timestamp():
|
||||||
Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
|
Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
|
||||||
Per `section 3.2.1`_ of the MAC Access Authentication spec.
|
Per `section 3.2.1`_ of the MAC Access Authentication spec.
|
||||||
|
|
||||||
.. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
|
.. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
|
||||||
.. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
|
.. _`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):
|
def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
|
||||||
|
@ -225,7 +195,7 @@ def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
|
||||||
and entropy when generating the random characters is important. Which is
|
and entropy when generating the random characters is important. Which is
|
||||||
why SystemRandom is used instead of the default random.choice method.
|
why SystemRandom is used instead of the default random.choice method.
|
||||||
"""
|
"""
|
||||||
rand = random.SystemRandom()
|
rand = SystemRandom()
|
||||||
return ''.join(rand.choice(chars) for x in range(length))
|
return ''.join(rand.choice(chars) for x in range(length))
|
||||||
|
|
||||||
|
|
||||||
|
@ -257,7 +227,7 @@ def generate_client_id(length=30, chars=CLIENT_ID_CHARACTER_SET):
|
||||||
"""Generates an OAuth client_id
|
"""Generates an OAuth client_id
|
||||||
|
|
||||||
OAuth 2 specify the format of client_id in
|
OAuth 2 specify the format of client_id in
|
||||||
http://tools.ietf.org/html/rfc6749#appendix-A.
|
https://tools.ietf.org/html/rfc6749#appendix-A.
|
||||||
"""
|
"""
|
||||||
return generate_token(length, chars)
|
return generate_token(length, chars)
|
||||||
|
|
||||||
|
@ -301,11 +271,11 @@ def safe_string_equals(a, b):
|
||||||
|
|
||||||
def to_unicode(data, encoding='UTF-8'):
|
def to_unicode(data, encoding='UTF-8'):
|
||||||
"""Convert a number of different types of objects to unicode."""
|
"""Convert a number of different types of objects to unicode."""
|
||||||
if isinstance(data, unicode_type):
|
if isinstance(data, str):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
if isinstance(data, bytes_type):
|
if isinstance(data, bytes):
|
||||||
return unicode_type(data, encoding=encoding)
|
return str(data, encoding=encoding)
|
||||||
|
|
||||||
if hasattr(data, '__iter__'):
|
if hasattr(data, '__iter__'):
|
||||||
try:
|
try:
|
||||||
|
@ -319,7 +289,7 @@ def to_unicode(data, encoding='UTF-8'):
|
||||||
# We support 2.6 which lacks dict comprehensions
|
# We support 2.6 which lacks dict comprehensions
|
||||||
if hasattr(data, 'items'):
|
if hasattr(data, 'items'):
|
||||||
data = 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
|
return data
|
||||||
|
|
||||||
|
@ -331,7 +301,7 @@ class CaseInsensitiveDict(dict):
|
||||||
proxy = {}
|
proxy = {}
|
||||||
|
|
||||||
def __init__(self, data):
|
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:
|
for k in data:
|
||||||
self[k] = data[k]
|
self[k] = data[k]
|
||||||
|
|
||||||
|
@ -340,22 +310,27 @@ class CaseInsensitiveDict(dict):
|
||||||
|
|
||||||
def __delitem__(self, k):
|
def __delitem__(self, k):
|
||||||
key = self.proxy[k.lower()]
|
key = self.proxy[k.lower()]
|
||||||
super(CaseInsensitiveDict, self).__delitem__(key)
|
super().__delitem__(key)
|
||||||
del self.proxy[k.lower()]
|
del self.proxy[k.lower()]
|
||||||
|
|
||||||
def __getitem__(self, k):
|
def __getitem__(self, k):
|
||||||
key = self.proxy[k.lower()]
|
key = self.proxy[k.lower()]
|
||||||
return super(CaseInsensitiveDict, self).__getitem__(key)
|
return super().__getitem__(key)
|
||||||
|
|
||||||
def get(self, k, default=None):
|
def get(self, k, default=None):
|
||||||
return self[k] if k in self else default
|
return self[k] if k in self else default
|
||||||
|
|
||||||
def __setitem__(self, k, v):
|
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().update(*args, **kwargs)
|
||||||
|
for k in dict(*args, **kwargs):
|
||||||
self.proxy[k.lower()] = k
|
self.proxy[k.lower()] = k
|
||||||
|
|
||||||
|
|
||||||
class Request(object):
|
class Request:
|
||||||
|
|
||||||
"""A malleable representation of a signable HTTP request.
|
"""A malleable representation of a signable HTTP request.
|
||||||
|
|
||||||
|
@ -389,6 +364,9 @@ class Request(object):
|
||||||
"client_id": None,
|
"client_id": None,
|
||||||
"client_secret": None,
|
"client_secret": None,
|
||||||
"code": None,
|
"code": None,
|
||||||
|
"code_challenge": None,
|
||||||
|
"code_challenge_method": None,
|
||||||
|
"code_verifier": None,
|
||||||
"extra_credentials": None,
|
"extra_credentials": None,
|
||||||
"grant_type": None,
|
"grant_type": None,
|
||||||
"redirect_uri": None,
|
"redirect_uri": None,
|
||||||
|
@ -401,10 +379,21 @@ class Request(object):
|
||||||
"token": None,
|
"token": None,
|
||||||
"user": None,
|
"user": None,
|
||||||
"token_type_hint": None,
|
"token_type_hint": None,
|
||||||
|
|
||||||
|
# OpenID Connect
|
||||||
|
"response_mode": None,
|
||||||
|
"nonce": None,
|
||||||
|
"display": None,
|
||||||
|
"prompt": None,
|
||||||
|
"claims": None,
|
||||||
|
"max_age": None,
|
||||||
|
"ui_locales": None,
|
||||||
|
"id_token_hint": None,
|
||||||
|
"login_hint": None,
|
||||||
|
"acr_values": None
|
||||||
}
|
}
|
||||||
self._params.update(dict(urldecode(self.uri_query)))
|
self._params.update(dict(urldecode(self.uri_query)))
|
||||||
self._params.update(dict(self.decoded_body or []))
|
self._params.update(dict(self.decoded_body or []))
|
||||||
self._params.update(self.headers)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name in self._params:
|
if name in self._params:
|
||||||
|
@ -413,13 +402,15 @@ class Request(object):
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
if not get_debug():
|
||||||
|
return "<oauthlib.Request SANITIZED>"
|
||||||
body = self.body
|
body = self.body
|
||||||
headers = self.headers.copy()
|
headers = self.headers.copy()
|
||||||
if body:
|
if body:
|
||||||
body = SANITIZE_PATTERN.sub('\1<SANITIZED>', body)
|
body = SANITIZE_PATTERN.sub('\1<SANITIZED>', str(body))
|
||||||
if 'Authorization' in headers:
|
if 'Authorization' in headers:
|
||||||
headers['Authorization'] = '<SANITIZED>'
|
headers['Authorization'] = '<SANITIZED>'
|
||||||
return '<oauthlib.Request url="%s", http_method="%s", headers="%s", body="%s">' % (
|
return '<oauthlib.Request url="{}", http_method="{}", headers="{}", body="{}">'.format(
|
||||||
self.uri, self.http_method, headers, body)
|
self.uri, self.http_method, headers, body)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth1
|
oauthlib.oauth1
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
@ -6,14 +5,24 @@ oauthlib.oauth1
|
||||||
This module is a wrapper for the most recent implementation of OAuth 1.0 Client
|
This module is a wrapper for the most recent implementation of OAuth 1.0 Client
|
||||||
and Server classes.
|
and Server classes.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from .rfc5849 import Client
|
from .rfc5849 import Client
|
||||||
from .rfc5849 import SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_PLAINTEXT
|
from .rfc5849 import (SIGNATURE_HMAC,
|
||||||
|
SIGNATURE_HMAC_SHA1,
|
||||||
|
SIGNATURE_HMAC_SHA256,
|
||||||
|
SIGNATURE_HMAC_SHA512,
|
||||||
|
SIGNATURE_RSA,
|
||||||
|
SIGNATURE_RSA_SHA1,
|
||||||
|
SIGNATURE_RSA_SHA256,
|
||||||
|
SIGNATURE_RSA_SHA512,
|
||||||
|
SIGNATURE_PLAINTEXT)
|
||||||
from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY
|
from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY
|
||||||
from .rfc5849 import SIGNATURE_TYPE_BODY
|
from .rfc5849 import SIGNATURE_TYPE_BODY
|
||||||
from .rfc5849.request_validator import RequestValidator
|
from .rfc5849.request_validator import RequestValidator
|
||||||
from .rfc5849.endpoints import RequestTokenEndpoint, AuthorizationEndpoint
|
from .rfc5849.endpoints import RequestTokenEndpoint, AuthorizationEndpoint
|
||||||
from .rfc5849.endpoints import AccessTokenEndpoint, ResourceEndpoint
|
from .rfc5849.endpoints import AccessTokenEndpoint, ResourceEndpoint
|
||||||
from .rfc5849.endpoints import SignatureOnlyEndpoint, WebApplicationServer
|
from .rfc5849.endpoints import SignatureOnlyEndpoint, WebApplicationServer
|
||||||
from .rfc5849.errors import *
|
from .rfc5849.errors import (InsecureTransportError,
|
||||||
|
InvalidClientError,
|
||||||
|
InvalidRequestError,
|
||||||
|
InvalidSignatureMethodError,
|
||||||
|
OAuth1Error)
|
||||||
|
|
|
@ -1,36 +1,68 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth1.rfc5849
|
oauthlib.oauth1.rfc5849
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for signing and checking OAuth 1.0 RFC 5849 requests.
|
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 base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
try:
|
|
||||||
import urlparse
|
|
||||||
except ImportError:
|
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
|
|
||||||
if sys.version_info[0] == 3:
|
from oauthlib.common import (
|
||||||
bytes_type = bytes
|
Request, generate_nonce, generate_timestamp, to_unicode, urlencode,
|
||||||
else:
|
)
|
||||||
bytes_type = str
|
|
||||||
|
|
||||||
from oauthlib.common import Request, urlencode, generate_nonce
|
|
||||||
from oauthlib.common import generate_timestamp, to_unicode
|
|
||||||
from . import parameters, signature
|
from . import parameters, signature
|
||||||
|
|
||||||
SIGNATURE_HMAC = "HMAC-SHA1"
|
log = logging.getLogger(__name__)
|
||||||
SIGNATURE_RSA = "RSA-SHA1"
|
|
||||||
|
# Available signature methods
|
||||||
|
#
|
||||||
|
# Note: SIGNATURE_HMAC and SIGNATURE_RSA are kept for backward compatibility
|
||||||
|
# with previous versions of this library, when it the only HMAC-based and
|
||||||
|
# RSA-based signature methods were HMAC-SHA1 and RSA-SHA1. But now that it
|
||||||
|
# supports other hashing algorithms besides SHA1, explicitly identifying which
|
||||||
|
# hashing algorithm is being used is recommended.
|
||||||
|
#
|
||||||
|
# Note: if additional values are defined here, don't forget to update the
|
||||||
|
# imports in "../__init__.py" so they are available outside this module.
|
||||||
|
|
||||||
|
SIGNATURE_HMAC_SHA1 = "HMAC-SHA1"
|
||||||
|
SIGNATURE_HMAC_SHA256 = "HMAC-SHA256"
|
||||||
|
SIGNATURE_HMAC_SHA512 = "HMAC-SHA512"
|
||||||
|
SIGNATURE_HMAC = SIGNATURE_HMAC_SHA1 # deprecated variable for HMAC-SHA1
|
||||||
|
|
||||||
|
SIGNATURE_RSA_SHA1 = "RSA-SHA1"
|
||||||
|
SIGNATURE_RSA_SHA256 = "RSA-SHA256"
|
||||||
|
SIGNATURE_RSA_SHA512 = "RSA-SHA512"
|
||||||
|
SIGNATURE_RSA = SIGNATURE_RSA_SHA1 # deprecated variable for RSA-SHA1
|
||||||
|
|
||||||
SIGNATURE_PLAINTEXT = "PLAINTEXT"
|
SIGNATURE_PLAINTEXT = "PLAINTEXT"
|
||||||
SIGNATURE_METHODS = (SIGNATURE_HMAC, 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_AUTH_HEADER = 'AUTH_HEADER'
|
||||||
SIGNATURE_TYPE_QUERY = 'QUERY'
|
SIGNATURE_TYPE_QUERY = 'QUERY'
|
||||||
|
@ -39,12 +71,16 @@ SIGNATURE_TYPE_BODY = 'BODY'
|
||||||
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
|
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."""
|
"""A client used to sign OAuth 1.0 RFC 5849 requests."""
|
||||||
SIGNATURE_METHODS = {
|
SIGNATURE_METHODS = {
|
||||||
SIGNATURE_HMAC: signature.sign_hmac_sha1_with_client,
|
SIGNATURE_HMAC_SHA1: signature.sign_hmac_sha1_with_client,
|
||||||
SIGNATURE_RSA: signature.sign_rsa_sha1_with_client,
|
SIGNATURE_HMAC_SHA256: signature.sign_hmac_sha256_with_client,
|
||||||
|
SIGNATURE_HMAC_SHA512: signature.sign_hmac_sha512_with_client,
|
||||||
|
SIGNATURE_RSA_SHA1: signature.sign_rsa_sha1_with_client,
|
||||||
|
SIGNATURE_RSA_SHA256: signature.sign_rsa_sha256_with_client,
|
||||||
|
SIGNATURE_RSA_SHA512: signature.sign_rsa_sha512_with_client,
|
||||||
SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client
|
SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +93,7 @@ class Client(object):
|
||||||
resource_owner_key=None,
|
resource_owner_key=None,
|
||||||
resource_owner_secret=None,
|
resource_owner_secret=None,
|
||||||
callback_uri=None,
|
callback_uri=None,
|
||||||
signature_method=SIGNATURE_HMAC,
|
signature_method=SIGNATURE_HMAC_SHA1,
|
||||||
signature_type=SIGNATURE_TYPE_AUTH_HEADER,
|
signature_type=SIGNATURE_TYPE_AUTH_HEADER,
|
||||||
rsa_key=None, verifier=None, realm=None,
|
rsa_key=None, verifier=None, realm=None,
|
||||||
encoding='utf-8', decoding=None,
|
encoding='utf-8', decoding=None,
|
||||||
|
@ -105,10 +141,11 @@ class Client(object):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
attrs = vars(self).copy()
|
attrs = vars(self).copy()
|
||||||
attrs['client_secret'] = '****' if attrs['client_secret'] else None
|
attrs['client_secret'] = '****' if attrs['client_secret'] else None
|
||||||
|
attrs['rsa_key'] = '****' if attrs['rsa_key'] else None
|
||||||
attrs[
|
attrs[
|
||||||
'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None
|
'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None
|
||||||
attribute_str = ', '.join('%s=%s' % (k, v) for k, v in attrs.items())
|
attribute_str = ', '.join('{}={}'.format(k, v) for k, v in attrs.items())
|
||||||
return '<%s %s>' % (self.__class__.__name__, attribute_str)
|
return '<{} {}>'.format(self.__class__.__name__, attribute_str)
|
||||||
|
|
||||||
def get_oauth_signature(self, request):
|
def get_oauth_signature(self, request):
|
||||||
"""Get an OAuth signature to be used in signing a request
|
"""Get an OAuth signature to be used in signing a request
|
||||||
|
@ -118,7 +155,7 @@ class Client(object):
|
||||||
replace any netloc part of the request argument's uri attribute
|
replace any netloc part of the request argument's uri attribute
|
||||||
value.
|
value.
|
||||||
|
|
||||||
.. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2
|
.. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
|
||||||
"""
|
"""
|
||||||
if self.signature_method == SIGNATURE_PLAINTEXT:
|
if self.signature_method == SIGNATURE_PLAINTEXT:
|
||||||
# fast-path
|
# fast-path
|
||||||
|
@ -131,25 +168,24 @@ class Client(object):
|
||||||
uri_query=urlparse.urlparse(uri).query,
|
uri_query=urlparse.urlparse(uri).query,
|
||||||
body=body,
|
body=body,
|
||||||
headers=headers)
|
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_params = signature.normalize_parameters(collected_params)
|
||||||
normalized_uri = signature.normalize_base_string_uri(uri,
|
normalized_uri = signature.base_string_uri(uri, headers.get('Host', None))
|
||||||
headers.get('Host', None))
|
log.debug("Normalized params: {}".format(normalized_params))
|
||||||
log.debug("Normalized params: {0}".format(normalized_params))
|
log.debug("Normalized URI: {}".format(normalized_uri))
|
||||||
log.debug("Normalized URI: {0}".format(normalized_uri))
|
|
||||||
|
|
||||||
base_string = signature.construct_base_string(request.http_method,
|
base_string = signature.signature_base_string(request.http_method,
|
||||||
normalized_uri, normalized_params)
|
normalized_uri, normalized_params)
|
||||||
|
|
||||||
log.debug("Base signing string: {0}".format(base_string))
|
log.debug("Signing: signature base string: {}".format(base_string))
|
||||||
|
|
||||||
if self.signature_method not in self.SIGNATURE_METHODS:
|
if self.signature_method not in self.SIGNATURE_METHODS:
|
||||||
raise ValueError('Invalid signature method.')
|
raise ValueError('Invalid signature method.')
|
||||||
|
|
||||||
sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self)
|
sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self)
|
||||||
|
|
||||||
log.debug("Signature: {0}".format(sig))
|
log.debug("Signature: {}".format(sig))
|
||||||
return sig
|
return sig
|
||||||
|
|
||||||
def get_oauth_params(self, request):
|
def get_oauth_params(self, request):
|
||||||
|
@ -174,10 +210,12 @@ class Client(object):
|
||||||
params.append(('oauth_verifier', self.verifier))
|
params.append(('oauth_verifier', self.verifier))
|
||||||
|
|
||||||
# providing body hash for requests other than x-www-form-urlencoded
|
# providing body hash for requests other than x-www-form-urlencoded
|
||||||
# as described in http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
|
# as described in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-4.1.1
|
||||||
# 4.1.1. When to include the body hash
|
# 4.1.1. When to include the body hash
|
||||||
# * [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies
|
# * [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies
|
||||||
# * [...] SHOULD include the oauth_body_hash parameter on all other requests.
|
# * [...] SHOULD include the oauth_body_hash parameter on all other requests.
|
||||||
|
# Note that SHA-1 is vulnerable. The spec acknowledges that in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-6.2
|
||||||
|
# At this time, no further effort has been made to replace SHA-1 for the OAuth Request Body Hash extension.
|
||||||
content_type = request.headers.get('Content-Type', None)
|
content_type = request.headers.get('Content-Type', None)
|
||||||
content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0
|
content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0
|
||||||
if request.body is not None and content_type_eligible:
|
if request.body is not None and content_type_eligible:
|
||||||
|
@ -278,8 +316,8 @@ class Client(object):
|
||||||
# header field set to "application/x-www-form-urlencoded".
|
# header field set to "application/x-www-form-urlencoded".
|
||||||
elif not should_have_params and has_params:
|
elif not should_have_params and has_params:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Body contains parameters but Content-Type header was {0} "
|
"Body contains parameters but Content-Type header was {} "
|
||||||
"instead of {1}".format(content_type or "not set",
|
"instead of {}".format(content_type or "not set",
|
||||||
CONTENT_TYPE_FORM_URLENCODED))
|
CONTENT_TYPE_FORM_URLENCODED))
|
||||||
|
|
||||||
# 3.5.2. Form-Encoded Body
|
# 3.5.2. Form-Encoded Body
|
||||||
|
@ -296,7 +334,7 @@ class Client(object):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Body signatures may only be used with form-urlencoded content')
|
'Body signatures may only be used with form-urlencoded content')
|
||||||
|
|
||||||
# We amend http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
|
# We amend https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
|
||||||
# with the clause that parameters from body should only be included
|
# with the clause that parameters from body should only be included
|
||||||
# in non GET or HEAD requests. Extracting the request body parameters
|
# in non GET or HEAD requests. Extracting the request body parameters
|
||||||
# and including them in the signature base string would give semantic
|
# and including them in the signature base string would give semantic
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
from __future__ import absolute_import
|
from .access_token import AccessTokenEndpoint
|
||||||
|
from .authorization import AuthorizationEndpoint
|
||||||
from .base import BaseEndpoint
|
from .base import BaseEndpoint
|
||||||
from .request_token import RequestTokenEndpoint
|
from .request_token import RequestTokenEndpoint
|
||||||
from .authorization import AuthorizationEndpoint
|
|
||||||
from .access_token import AccessTokenEndpoint
|
|
||||||
from .resource import ResourceEndpoint
|
from .resource import ResourceEndpoint
|
||||||
from .signature_only import SignatureOnlyEndpoint
|
from .signature_only import SignatureOnlyEndpoint
|
||||||
from .pre_configured import WebApplicationServer
|
|
||||||
|
from .pre_configured import WebApplicationServer # isort:skip
|
||||||
|
|
|
@ -8,14 +8,12 @@ OAuth 1.0 RFC 5849. It validates the correctness of access token requests,
|
||||||
creates and persists tokens as well as create the proper response to be
|
creates and persists tokens as well as create the proper response to be
|
||||||
returned to the client.
|
returned to the client.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oauthlib.common import urlencode
|
from oauthlib.common import urlencode
|
||||||
|
|
||||||
from .base import BaseEndpoint
|
|
||||||
from .. import errors
|
from .. import errors
|
||||||
|
from .base import BaseEndpoint
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -37,7 +35,8 @@ class AccessTokenEndpoint(BaseEndpoint):
|
||||||
Similar to OAuth 2, indication of granted scopes will be included as a
|
Similar to OAuth 2, indication of granted scopes will be included as a
|
||||||
space separated list in ``oauth_authorized_realms``.
|
space separated list in ``oauth_authorized_realms``.
|
||||||
|
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: The token as an urlencoded string.
|
:returns: The token as an urlencoded string.
|
||||||
"""
|
"""
|
||||||
request.realms = self.request_validator.get_realms(
|
request.realms = self.request_validator.get_realms(
|
||||||
|
@ -120,7 +119,8 @@ class AccessTokenEndpoint(BaseEndpoint):
|
||||||
def validate_access_token_request(self, request):
|
def validate_access_token_request(self, request):
|
||||||
"""Validate an access token request.
|
"""Validate an access token request.
|
||||||
|
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:raises: OAuth1Error if the request is invalid.
|
:raises: OAuth1Error if the request is invalid.
|
||||||
:returns: A tuple of 2 elements.
|
:returns: A tuple of 2 elements.
|
||||||
1. The validation result (True or False).
|
1. The validation result (True or False).
|
||||||
|
@ -180,7 +180,7 @@ class AccessTokenEndpoint(BaseEndpoint):
|
||||||
# token credentials to the client, and ensure that the temporary
|
# token credentials to the client, and ensure that the temporary
|
||||||
# credentials have not expired or been used before. The server MUST
|
# credentials have not expired or been used before. The server MUST
|
||||||
# also verify the verification code received from the client.
|
# also verify the verification code received from the client.
|
||||||
# .. _`Section 3.2`: http://tools.ietf.org/html/rfc5849#section-3.2
|
# .. _`Section 3.2`: https://tools.ietf.org/html/rfc5849#section-3.2
|
||||||
#
|
#
|
||||||
# Note that early exit would enable resource owner authorization
|
# Note that early exit would enable resource owner authorization
|
||||||
# verifier enumertion.
|
# verifier enumertion.
|
||||||
|
|
|
@ -6,17 +6,13 @@ oauthlib.oauth1.rfc5849.endpoints.authorization
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for signing and checking OAuth 1.0 RFC 5849 requests.
|
for signing and checking OAuth 1.0 RFC 5849 requests.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from oauthlib.common import Request, add_params_to_uri
|
|
||||||
|
|
||||||
from .base import BaseEndpoint
|
|
||||||
from .. import errors
|
|
||||||
try:
|
|
||||||
from urllib import urlencode
|
|
||||||
except ImportError:
|
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
from oauthlib.common import add_params_to_uri
|
||||||
|
|
||||||
|
from .. import errors
|
||||||
|
from .base import BaseEndpoint
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationEndpoint(BaseEndpoint):
|
class AuthorizationEndpoint(BaseEndpoint):
|
||||||
|
|
||||||
|
@ -41,7 +37,8 @@ class AuthorizationEndpoint(BaseEndpoint):
|
||||||
def create_verifier(self, request, credentials):
|
def create_verifier(self, request, credentials):
|
||||||
"""Create and save a new request token.
|
"""Create and save a new request token.
|
||||||
|
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:param credentials: A dict of extra token credentials.
|
:param credentials: A dict of extra token credentials.
|
||||||
:returns: The verifier as a dict.
|
:returns: The verifier as a dict.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,21 +6,20 @@ oauthlib.oauth1.rfc5849.endpoints.base
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for signing and checking OAuth 1.0 RFC 5849 requests.
|
for signing and checking OAuth 1.0 RFC 5849 requests.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oauthlib.common import Request, generate_token
|
from oauthlib.common import CaseInsensitiveDict, Request, generate_token
|
||||||
|
|
||||||
from .. import signature, utils, errors
|
from .. import (
|
||||||
from .. import CONTENT_TYPE_FORM_URLENCODED
|
CONTENT_TYPE_FORM_URLENCODED,
|
||||||
from .. import SIGNATURE_HMAC, SIGNATURE_RSA
|
SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_HMAC_SHA512,
|
||||||
from .. import SIGNATURE_TYPE_AUTH_HEADER
|
SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512,
|
||||||
from .. import SIGNATURE_TYPE_QUERY
|
SIGNATURE_PLAINTEXT,
|
||||||
from .. import SIGNATURE_TYPE_BODY
|
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):
|
def __init__(self, request_validator, token_generator=None):
|
||||||
self.request_validator = request_validator
|
self.request_validator = request_validator
|
||||||
|
@ -70,7 +69,7 @@ class BaseEndpoint(object):
|
||||||
|
|
||||||
def _create_request(self, uri, http_method, body, headers):
|
def _create_request(self, uri, http_method, body, headers):
|
||||||
# Only include body data from x-www-form-urlencoded requests
|
# Only include body data from x-www-form-urlencoded requests
|
||||||
headers = headers or {}
|
headers = CaseInsensitiveDict(headers or {})
|
||||||
if ("Content-Type" in headers and
|
if ("Content-Type" in headers and
|
||||||
CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]):
|
CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]):
|
||||||
request = Request(uri, http_method, body, headers)
|
request = Request(uri, http_method, body, headers)
|
||||||
|
@ -130,11 +129,11 @@ class BaseEndpoint(object):
|
||||||
# specification. Implementers should review the Security
|
# specification. Implementers should review the Security
|
||||||
# Considerations section (`Section 4`_) before deciding on which
|
# Considerations section (`Section 4`_) before deciding on which
|
||||||
# method to support.
|
# method to support.
|
||||||
# .. _`Section 4`: http://tools.ietf.org/html/rfc5849#section-4
|
# .. _`Section 4`: https://tools.ietf.org/html/rfc5849#section-4
|
||||||
if (not request.signature_method in
|
if (not request.signature_method in
|
||||||
self.request_validator.allowed_signature_methods):
|
self.request_validator.allowed_signature_methods):
|
||||||
raise errors.InvalidSignatureMethodError(
|
raise errors.InvalidSignatureMethodError(
|
||||||
description="Invalid signature, %s not in %r." % (
|
description="Invalid signature, {} not in {!r}.".format(
|
||||||
request.signature_method,
|
request.signature_method,
|
||||||
self.request_validator.allowed_signature_methods))
|
self.request_validator.allowed_signature_methods))
|
||||||
|
|
||||||
|
@ -182,35 +181,65 @@ class BaseEndpoint(object):
|
||||||
|
|
||||||
def _check_signature(self, request, is_token_request=False):
|
def _check_signature(self, request, is_token_request=False):
|
||||||
# ---- RSA Signature verification ----
|
# ---- 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`_
|
# The server verifies the signature per `[RFC3447] section 8.2.2`_
|
||||||
# .. _`[RFC3447] section 8.2.2`: http://tools.ietf.org/html/rfc3447#section-8.2.1
|
# .. _`[RFC3447] section 8.2.2`: https://tools.ietf.org/html/rfc3447#section-8.2.1
|
||||||
|
|
||||||
rsa_key = self.request_validator.get_rsa_key(
|
rsa_key = self.request_validator.get_rsa_key(
|
||||||
request.client_key, request)
|
request.client_key, request)
|
||||||
|
|
||||||
|
if request.signature_method == SIGNATURE_RSA_SHA1:
|
||||||
valid_signature = signature.verify_rsa_sha1(request, rsa_key)
|
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 ----
|
# ---- HMAC or Plaintext Signature verification ----
|
||||||
else:
|
else:
|
||||||
|
# Non-RSA based signature method
|
||||||
|
|
||||||
# Servers receiving an authenticated request MUST validate it by:
|
# Servers receiving an authenticated request MUST validate it by:
|
||||||
# Recalculating the request signature independently as described in
|
# Recalculating the request signature independently as described in
|
||||||
# `Section 3.4`_ and comparing it to the value received from the
|
# `Section 3.4`_ and comparing it to the value received from the
|
||||||
# client via the "oauth_signature" parameter.
|
# client via the "oauth_signature" parameter.
|
||||||
# .. _`Section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
|
# .. _`Section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
|
||||||
|
|
||||||
client_secret = self.request_validator.get_client_secret(
|
client_secret = self.request_validator.get_client_secret(
|
||||||
request.client_key, request)
|
request.client_key, request)
|
||||||
|
|
||||||
resource_owner_secret = None
|
resource_owner_secret = None
|
||||||
if request.resource_owner_key:
|
if request.resource_owner_key:
|
||||||
if is_token_request:
|
if is_token_request:
|
||||||
resource_owner_secret = self.request_validator.get_request_token_secret(
|
resource_owner_secret = \
|
||||||
request.client_key, request.resource_owner_key, request)
|
self.request_validator.get_request_token_secret(
|
||||||
|
request.client_key, request.resource_owner_key,
|
||||||
|
request)
|
||||||
else:
|
else:
|
||||||
resource_owner_secret = self.request_validator.get_access_token_secret(
|
resource_owner_secret = \
|
||||||
request.client_key, request.resource_owner_key, request)
|
self.request_validator.get_access_token_secret(
|
||||||
|
request.client_key, request.resource_owner_key,
|
||||||
|
request)
|
||||||
|
|
||||||
if request.signature_method == SIGNATURE_HMAC:
|
if request.signature_method == SIGNATURE_HMAC_SHA1:
|
||||||
valid_signature = signature.verify_hmac_sha1(request,
|
valid_signature = signature.verify_hmac_sha1(
|
||||||
client_secret, resource_owner_secret)
|
request, client_secret, resource_owner_secret)
|
||||||
|
elif request.signature_method == SIGNATURE_HMAC_SHA256:
|
||||||
|
valid_signature = signature.verify_hmac_sha256(
|
||||||
|
request, client_secret, resource_owner_secret)
|
||||||
|
elif request.signature_method == SIGNATURE_HMAC_SHA512:
|
||||||
|
valid_signature = signature.verify_hmac_sha512(
|
||||||
|
request, client_secret, resource_owner_secret)
|
||||||
|
elif request.signature_method == SIGNATURE_PLAINTEXT:
|
||||||
|
valid_signature = signature.verify_plaintext(
|
||||||
|
request, client_secret, resource_owner_secret)
|
||||||
else:
|
else:
|
||||||
valid_signature = signature.verify_plaintext(request,
|
valid_signature = False
|
||||||
client_secret, resource_owner_secret)
|
|
||||||
return valid_signature
|
return valid_signature
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import absolute_import, unicode_literals
|
from . import (
|
||||||
|
AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
|
||||||
from . import RequestTokenEndpoint, AuthorizationEndpoint
|
ResourceEndpoint,
|
||||||
from . import AccessTokenEndpoint, ResourceEndpoint
|
)
|
||||||
|
|
||||||
|
|
||||||
class WebApplicationServer(RequestTokenEndpoint, AuthorizationEndpoint,
|
class WebApplicationServer(RequestTokenEndpoint, AuthorizationEndpoint,
|
||||||
|
|
|
@ -8,14 +8,12 @@ OAuth 1.0 RFC 5849. It validates the correctness of request token requests,
|
||||||
creates and persists tokens as well as create the proper response to be
|
creates and persists tokens as well as create the proper response to be
|
||||||
returned to the client.
|
returned to the client.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oauthlib.common import urlencode
|
from oauthlib.common import urlencode
|
||||||
|
|
||||||
from .base import BaseEndpoint
|
|
||||||
from .. import errors
|
from .. import errors
|
||||||
|
from .base import BaseEndpoint
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -34,7 +32,8 @@ class RequestTokenEndpoint(BaseEndpoint):
|
||||||
def create_request_token(self, request, credentials):
|
def create_request_token(self, request, credentials):
|
||||||
"""Create and save a new request token.
|
"""Create and save a new request token.
|
||||||
|
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:param credentials: A dict of extra token credentials.
|
:param credentials: A dict of extra token credentials.
|
||||||
:returns: The token as an urlencoded string.
|
:returns: The token as an urlencoded string.
|
||||||
"""
|
"""
|
||||||
|
@ -111,7 +110,8 @@ class RequestTokenEndpoint(BaseEndpoint):
|
||||||
def validate_request_token_request(self, request):
|
def validate_request_token_request(self, request):
|
||||||
"""Validate a request token request.
|
"""Validate a request token request.
|
||||||
|
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:raises: OAuth1Error if the request is invalid.
|
:raises: OAuth1Error if the request is invalid.
|
||||||
:returns: A tuple of 2 elements.
|
:returns: A tuple of 2 elements.
|
||||||
1. The validation result (True or False).
|
1. The validation result (True or False).
|
||||||
|
@ -127,7 +127,7 @@ class RequestTokenEndpoint(BaseEndpoint):
|
||||||
request.client_key, request)
|
request.client_key, request)
|
||||||
if not self.request_validator.check_realms(request.realms):
|
if not self.request_validator.check_realms(request.realms):
|
||||||
raise errors.InvalidRequestError(
|
raise errors.InvalidRequestError(
|
||||||
description='Invalid realm %s. Allowed are %r.' % (
|
description='Invalid realm {}. Allowed are {!r}.'.format(
|
||||||
request.realms, self.request_validator.realms))
|
request.realms, self.request_validator.realms))
|
||||||
|
|
||||||
if not request.redirect_uri:
|
if not request.redirect_uri:
|
||||||
|
@ -156,7 +156,7 @@ class RequestTokenEndpoint(BaseEndpoint):
|
||||||
# However they could be seen as a scope or realm to which the
|
# However they could be seen as a scope or realm to which the
|
||||||
# client has access and as such every client should be checked
|
# client has access and as such every client should be checked
|
||||||
# to ensure it is authorized access to that scope or realm.
|
# to ensure it is authorized access to that scope or realm.
|
||||||
# .. _`realm`: http://tools.ietf.org/html/rfc2617#section-1.2
|
# .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
|
||||||
#
|
#
|
||||||
# Note that early exit would enable client realm access enumeration.
|
# Note that early exit would enable client realm access enumeration.
|
||||||
#
|
#
|
||||||
|
@ -178,7 +178,7 @@ class RequestTokenEndpoint(BaseEndpoint):
|
||||||
|
|
||||||
# Callback is normally never required, except for requests for
|
# Callback is normally never required, except for requests for
|
||||||
# a Temporary Credential as described in `Section 2.1`_
|
# a Temporary Credential as described in `Section 2.1`_
|
||||||
# .._`Section 2.1`: http://tools.ietf.org/html/rfc5849#section-2.1
|
# .._`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1
|
||||||
valid_redirect = self.request_validator.validate_redirect_uri(
|
valid_redirect = self.request_validator.validate_redirect_uri(
|
||||||
request.client_key, request.redirect_uri, request)
|
request.client_key, request.redirect_uri, request)
|
||||||
if not request.redirect_uri:
|
if not request.redirect_uri:
|
||||||
|
|
|
@ -6,12 +6,10 @@ oauthlib.oauth1.rfc5849.endpoints.resource
|
||||||
This module is an implementation of the resource protection provider logic of
|
This module is an implementation of the resource protection provider logic of
|
||||||
OAuth 1.0 RFC 5849.
|
OAuth 1.0 RFC 5849.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .base import BaseEndpoint
|
|
||||||
from .. import errors
|
from .. import errors
|
||||||
|
from .base import BaseEndpoint
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -119,7 +117,7 @@ class ResourceEndpoint(BaseEndpoint):
|
||||||
# However they could be seen as a scope or realm to which the
|
# However they could be seen as a scope or realm to which the
|
||||||
# client has access and as such every client should be checked
|
# client has access and as such every client should be checked
|
||||||
# to ensure it is authorized access to that scope or realm.
|
# to ensure it is authorized access to that scope or realm.
|
||||||
# .. _`realm`: http://tools.ietf.org/html/rfc2617#section-1.2
|
# .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
|
||||||
#
|
#
|
||||||
# Note that early exit would enable client realm access enumeration.
|
# Note that early exit would enable client realm access enumeration.
|
||||||
#
|
#
|
||||||
|
|
|
@ -6,12 +6,10 @@ oauthlib.oauth1.rfc5849.endpoints.signature_only
|
||||||
This module is an implementation of the signing logic of OAuth 1.0 RFC 5849.
|
This module is an implementation of the signing logic of OAuth 1.0 RFC 5849.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .base import BaseEndpoint
|
|
||||||
from .. import errors
|
from .. import errors
|
||||||
|
from .base import BaseEndpoint
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -34,17 +32,22 @@ class SignatureOnlyEndpoint(BaseEndpoint):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
request = self._create_request(uri, http_method, body, headers)
|
request = self._create_request(uri, http_method, body, headers)
|
||||||
except errors.OAuth1Error:
|
except errors.OAuth1Error as err:
|
||||||
|
log.info(
|
||||||
|
'Exception caught while validating request, %s.' % err)
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._check_transport_security(request)
|
self._check_transport_security(request)
|
||||||
self._check_mandatory_parameters(request)
|
self._check_mandatory_parameters(request)
|
||||||
except errors.OAuth1Error:
|
except errors.OAuth1Error as err:
|
||||||
|
log.info(
|
||||||
|
'Exception caught while validating request, %s.' % err)
|
||||||
return False, request
|
return False, request
|
||||||
|
|
||||||
if not self.request_validator.validate_timestamp_and_nonce(
|
if not self.request_validator.validate_timestamp_and_nonce(
|
||||||
request.client_key, request.timestamp, request.nonce, request):
|
request.client_key, request.timestamp, request.nonce, request):
|
||||||
|
log.debug('[Failure] verification failed: timestamp/nonce')
|
||||||
return False, request
|
return False, request
|
||||||
|
|
||||||
# The server SHOULD return a 401 (Unauthorized) status code when
|
# The server SHOULD return a 401 (Unauthorized) status code when
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth1.rfc5849.errors
|
oauthlib.oauth1.rfc5849.errors
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -6,9 +5,7 @@ oauthlib.oauth1.rfc5849.errors
|
||||||
Error used both by OAuth 1 clients and provicers to represent the spec
|
Error used both by OAuth 1 clients and provicers to represent the spec
|
||||||
defined error responses for all four core grant types.
|
defined error responses for all four core grant types.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from oauthlib.common import add_params_to_uri, urlencode
|
||||||
|
|
||||||
from oauthlib.common import urlencode, add_params_to_uri
|
|
||||||
|
|
||||||
|
|
||||||
class OAuth1Error(Exception):
|
class OAuth1Error(Exception):
|
||||||
|
@ -37,10 +34,10 @@ class OAuth1Error(Exception):
|
||||||
request: Oauthlib Request object
|
request: Oauthlib Request object
|
||||||
"""
|
"""
|
||||||
self.description = description or self.description
|
self.description = description or self.description
|
||||||
message = '(%s) %s' % (self.error, self.description)
|
message = '({}) {}'.format(self.error, self.description)
|
||||||
if request:
|
if request:
|
||||||
message += ' ' + repr(request)
|
message += ' ' + repr(request)
|
||||||
super(OAuth1Error, self).__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
self.uri = uri
|
self.uri = uri
|
||||||
self.status_code = status_code
|
self.status_code = status_code
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.parameters
|
oauthlib.parameters
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.
|
This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.
|
||||||
|
|
||||||
.. _`section 3.5`: http://tools.ietf.org/html/rfc5849#section-3.5
|
.. _`section 3.5`: https://tools.ietf.org/html/rfc5849#section-3.5
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
try:
|
|
||||||
from urlparse import urlparse, urlunparse
|
|
||||||
except ImportError:
|
|
||||||
from urllib.parse import urlparse, urlunparse
|
from urllib.parse import urlparse, urlunparse
|
||||||
from . import utils
|
|
||||||
from oauthlib.common import extract_params, urlencode
|
from oauthlib.common import extract_params, urlencode
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
# TODO: do we need filter_params now that oauth_params are handled by Request?
|
# TODO: do we need filter_params now that oauth_params are handled by Request?
|
||||||
# We can easily pass in just oauth protocol params.
|
# We can easily pass in just oauth protocol params.
|
||||||
|
@ -40,8 +36,8 @@ def prepare_headers(oauth_params, headers=None, realm=None):
|
||||||
oauth_version="1.0"
|
oauth_version="1.0"
|
||||||
|
|
||||||
|
|
||||||
.. _`section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1
|
.. _`section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
|
||||||
.. _`RFC2617`: http://tools.ietf.org/html/rfc2617
|
.. _`RFC2617`: https://tools.ietf.org/html/rfc2617
|
||||||
"""
|
"""
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
|
|
||||||
|
@ -52,28 +48,28 @@ def prepare_headers(oauth_params, headers=None, realm=None):
|
||||||
# 1. Parameter names and values are encoded per Parameter Encoding
|
# 1. Parameter names and values are encoded per Parameter Encoding
|
||||||
# (`Section 3.6`_)
|
# (`Section 3.6`_)
|
||||||
#
|
#
|
||||||
# .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
|
# .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
|
||||||
escaped_name = utils.escape(oauth_parameter_name)
|
escaped_name = utils.escape(oauth_parameter_name)
|
||||||
escaped_value = utils.escape(value)
|
escaped_value = utils.escape(value)
|
||||||
|
|
||||||
# 2. Each parameter's name is immediately followed by an "=" character
|
# 2. Each parameter's name is immediately followed by an "=" character
|
||||||
# (ASCII code 61), a """ character (ASCII code 34), the parameter
|
# (ASCII code 61), a """ character (ASCII code 34), the parameter
|
||||||
# value (MAY be empty), and another """ character (ASCII code 34).
|
# 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)
|
authorization_header_parameters_parts.append(part)
|
||||||
|
|
||||||
# 3. Parameters are separated by a "," character (ASCII code 44) and
|
# 3. Parameters are separated by a "," character (ASCII code 44) and
|
||||||
# OPTIONAL linear whitespace per `RFC2617`_.
|
# OPTIONAL linear whitespace per `RFC2617`_.
|
||||||
#
|
#
|
||||||
# .. _`RFC2617`: http://tools.ietf.org/html/rfc2617
|
# .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
|
||||||
authorization_header_parameters = ', '.join(
|
authorization_header_parameters = ', '.join(
|
||||||
authorization_header_parameters_parts)
|
authorization_header_parameters_parts)
|
||||||
|
|
||||||
# 4. The OPTIONAL "realm" parameter MAY be added and interpreted per
|
# 4. The OPTIONAL "realm" parameter MAY be added and interpreted per
|
||||||
# `RFC2617 section 1.2`_.
|
# `RFC2617 section 1.2`_.
|
||||||
#
|
#
|
||||||
# .. _`RFC2617 section 1.2`: http://tools.ietf.org/html/rfc2617#section-1.2
|
# .. _`RFC2617 section 1.2`: https://tools.ietf.org/html/rfc2617#section-1.2
|
||||||
if realm:
|
if realm:
|
||||||
# NOTE: realm should *not* be escaped
|
# NOTE: realm should *not* be escaped
|
||||||
authorization_header_parameters = ('realm="%s", ' % realm +
|
authorization_header_parameters = ('realm="%s", ' % realm +
|
||||||
|
@ -96,8 +92,8 @@ def _append_params(oauth_params, params):
|
||||||
|
|
||||||
Per `section 3.5.2`_ and `3.5.3`_ of the spec.
|
Per `section 3.5.2`_ and `3.5.3`_ of the spec.
|
||||||
|
|
||||||
.. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2
|
.. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
|
||||||
.. _`3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3
|
.. _`3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3
|
||||||
|
|
||||||
"""
|
"""
|
||||||
merged = list(params)
|
merged = list(params)
|
||||||
|
@ -115,7 +111,7 @@ def prepare_form_encoded_body(oauth_params, body):
|
||||||
|
|
||||||
Per `section 3.5.2`_ of the spec.
|
Per `section 3.5.2`_ of the spec.
|
||||||
|
|
||||||
.. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2
|
.. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# append OAuth params to the existing body
|
# append OAuth params to the existing body
|
||||||
|
@ -127,7 +123,7 @@ def prepare_request_uri_query(oauth_params, uri):
|
||||||
|
|
||||||
Per `section 3.5.3`_ of the spec.
|
Per `section 3.5.3`_ of the spec.
|
||||||
|
|
||||||
.. _`section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3
|
.. _`section 3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# append OAuth params to the existing set of query components
|
# append OAuth params to the existing set of query components
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth1.rfc5849
|
oauthlib.oauth1.rfc5849
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
@ -6,12 +5,10 @@ oauthlib.oauth1.rfc5849
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for signing and checking OAuth 1.0 RFC 5849 requests.
|
for signing and checking OAuth 1.0 RFC 5849 requests.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from . import SIGNATURE_METHODS, utils
|
from . import SIGNATURE_METHODS, utils
|
||||||
|
|
||||||
|
|
||||||
class RequestValidator(object):
|
class RequestValidator:
|
||||||
|
|
||||||
"""A validator/datastore interaction base class for OAuth 1 providers.
|
"""A validator/datastore interaction base class for OAuth 1 providers.
|
||||||
|
|
||||||
|
@ -107,7 +104,7 @@ class RequestValidator(object):
|
||||||
their use more straightforward and as such it could be worth reading what
|
their use more straightforward and as such it could be worth reading what
|
||||||
follows in chronological order.
|
follows in chronological order.
|
||||||
|
|
||||||
.. _`whitelisting or blacklisting`: http://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html
|
.. _`whitelisting or blacklisting`: https://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -195,7 +192,15 @@ class RequestValidator(object):
|
||||||
|
|
||||||
def check_realms(self, realms):
|
def check_realms(self, realms):
|
||||||
"""Check that the realm is one of a set allowed 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):
|
||||||
|
"""
|
||||||
|
Returns a NotImplementedError for a function that should be implemented.
|
||||||
|
:param fn: name of the function
|
||||||
|
"""
|
||||||
|
m = "Missing function implementation in {}: {}".format(type(self), fn)
|
||||||
|
return NotImplementedError(m)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dummy_client(self):
|
def dummy_client(self):
|
||||||
|
@ -219,7 +224,7 @@ class RequestValidator(object):
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
* SignatureOnlyEndpoint
|
* SignatureOnlyEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("dummy_client")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dummy_request_token(self):
|
def dummy_request_token(self):
|
||||||
|
@ -235,7 +240,7 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AccessTokenEndpoint
|
* AccessTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("dummy_request_token")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dummy_access_token(self):
|
def dummy_access_token(self):
|
||||||
|
@ -251,13 +256,14 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("dummy_access_token")
|
||||||
|
|
||||||
def get_client_secret(self, client_key, request):
|
def get_client_secret(self, client_key, request):
|
||||||
"""Retrieves the client secret associated with the client key.
|
"""Retrieves the client secret associated with the client key.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: The client secret as a string.
|
:returns: The client secret as a string.
|
||||||
|
|
||||||
This method must allow the use of a dummy client_key value.
|
This method must allow the use of a dummy client_key value.
|
||||||
|
@ -286,14 +292,15 @@ class RequestValidator(object):
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
* SignatureOnlyEndpoint
|
* SignatureOnlyEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement('get_client_secret')
|
||||||
|
|
||||||
def get_request_token_secret(self, client_key, token, request):
|
def get_request_token_secret(self, client_key, token, request):
|
||||||
"""Retrieves the shared secret associated with the request token.
|
"""Retrieves the shared secret associated with the request token.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param token: The request token string.
|
:param token: The request token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: The token secret as a string.
|
:returns: The token secret as a string.
|
||||||
|
|
||||||
This method must allow the use of a dummy values and the running time
|
This method must allow the use of a dummy values and the running time
|
||||||
|
@ -318,14 +325,15 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AccessTokenEndpoint
|
* AccessTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement('get_request_token_secret')
|
||||||
|
|
||||||
def get_access_token_secret(self, client_key, token, request):
|
def get_access_token_secret(self, client_key, token, request):
|
||||||
"""Retrieves the shared secret associated with the access token.
|
"""Retrieves the shared secret associated with the access token.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param token: The access token string.
|
:param token: The access token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: The token secret as a string.
|
:returns: The token secret as a string.
|
||||||
|
|
||||||
This method must allow the use of a dummy values and the running time
|
This method must allow the use of a dummy values and the running time
|
||||||
|
@ -350,13 +358,14 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("get_access_token_secret")
|
||||||
|
|
||||||
def get_default_realms(self, client_key, request):
|
def get_default_realms(self, client_key, request):
|
||||||
"""Get the default realms for a client.
|
"""Get the default realms for a client.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: The list of default realms associated with the client.
|
:returns: The list of default realms associated with the client.
|
||||||
|
|
||||||
The list of default realms will be set during client registration and
|
The list of default realms will be set during client registration and
|
||||||
|
@ -366,13 +375,14 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* RequestTokenEndpoint
|
* RequestTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("get_default_realms")
|
||||||
|
|
||||||
def get_realms(self, token, request):
|
def get_realms(self, token, request):
|
||||||
"""Get realms associated with a request token.
|
"""Get realms associated with a request token.
|
||||||
|
|
||||||
:param token: The request token string.
|
:param token: The request token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: The list of realms associated with the request token.
|
:returns: The list of realms associated with the request token.
|
||||||
|
|
||||||
This method is used by
|
This method is used by
|
||||||
|
@ -380,13 +390,14 @@ class RequestValidator(object):
|
||||||
* AuthorizationEndpoint
|
* AuthorizationEndpoint
|
||||||
* AccessTokenEndpoint
|
* AccessTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("get_realms")
|
||||||
|
|
||||||
def get_redirect_uri(self, token, request):
|
def get_redirect_uri(self, token, request):
|
||||||
"""Get the redirect URI associated with a request token.
|
"""Get the redirect URI associated with a request token.
|
||||||
|
|
||||||
:param token: The request token string.
|
:param token: The request token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: The redirect URI associated with the request token.
|
:returns: The redirect URI associated with the request token.
|
||||||
|
|
||||||
It may be desirable to return a custom URI if the redirect is set to "oob".
|
It may be desirable to return a custom URI if the redirect is set to "oob".
|
||||||
|
@ -397,13 +408,14 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AuthorizationEndpoint
|
* AuthorizationEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("get_redirect_uri")
|
||||||
|
|
||||||
def get_rsa_key(self, client_key, request):
|
def get_rsa_key(self, client_key, request):
|
||||||
"""Retrieves a previously stored client provided RSA key.
|
"""Retrieves a previously stored client provided RSA key.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: The rsa public key as a string.
|
:returns: The rsa public key as a string.
|
||||||
|
|
||||||
This method must allow the use of a dummy client_key value. Fetching
|
This method must allow the use of a dummy client_key value. Fetching
|
||||||
|
@ -420,14 +432,15 @@ class RequestValidator(object):
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
* SignatureOnlyEndpoint
|
* SignatureOnlyEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("get_rsa_key")
|
||||||
|
|
||||||
def invalidate_request_token(self, client_key, request_token, request):
|
def invalidate_request_token(self, client_key, request_token, request):
|
||||||
"""Invalidates a used request token.
|
"""Invalidates a used request token.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param request_token: The request token string.
|
:param request_token: The request token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: None
|
:returns: None
|
||||||
|
|
||||||
Per `Section 2.3`__ of the spec:
|
Per `Section 2.3`__ of the spec:
|
||||||
|
@ -435,7 +448,7 @@ class RequestValidator(object):
|
||||||
"The server MUST (...) ensure that the temporary
|
"The server MUST (...) ensure that the temporary
|
||||||
credentials have not expired or been used before."
|
credentials have not expired or been used before."
|
||||||
|
|
||||||
.. _`Section 2.3`: http://tools.ietf.org/html/rfc5849#section-2.3
|
.. _`Section 2.3`: https://tools.ietf.org/html/rfc5849#section-2.3
|
||||||
|
|
||||||
This method should ensure that provided token won't validate anymore.
|
This method should ensure that provided token won't validate anymore.
|
||||||
It can be simply removing RequestToken from storage or setting
|
It can be simply removing RequestToken from storage or setting
|
||||||
|
@ -446,13 +459,14 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AccessTokenEndpoint
|
* AccessTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("invalidate_request_token")
|
||||||
|
|
||||||
def validate_client_key(self, client_key, request):
|
def validate_client_key(self, client_key, request):
|
||||||
"""Validates that supplied client key is a registered and valid client.
|
"""Validates that supplied client key is a registered and valid client.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
Note that if the dummy client is supplied it should validate in same
|
Note that if the dummy client is supplied it should validate in same
|
||||||
|
@ -482,14 +496,15 @@ class RequestValidator(object):
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
* SignatureOnlyEndpoint
|
* SignatureOnlyEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("validate_client_key")
|
||||||
|
|
||||||
def validate_request_token(self, client_key, token, request):
|
def validate_request_token(self, client_key, token, request):
|
||||||
"""Validates that supplied request token is registered and valid.
|
"""Validates that supplied request token is registered and valid.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param token: The request token string.
|
:param token: The request token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
Note that if the dummy request_token is supplied it should validate in
|
Note that if the dummy request_token is supplied it should validate in
|
||||||
|
@ -516,14 +531,15 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AccessTokenEndpoint
|
* AccessTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("validate_request_token")
|
||||||
|
|
||||||
def validate_access_token(self, client_key, token, request):
|
def validate_access_token(self, client_key, token, request):
|
||||||
"""Validates that supplied access token is registered and valid.
|
"""Validates that supplied access token is registered and valid.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param token: The access token string.
|
:param token: The access token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
Note that if the dummy access token is supplied it should validate in
|
Note that if the dummy access token is supplied it should validate in
|
||||||
|
@ -550,7 +566,7 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("validate_access_token")
|
||||||
|
|
||||||
def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
|
def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
|
||||||
request, request_token=None, access_token=None):
|
request, request_token=None, access_token=None):
|
||||||
|
@ -561,7 +577,8 @@ class RequestValidator(object):
|
||||||
:param nonce: The ``oauth_nonce`` parameter.
|
:param nonce: The ``oauth_nonce`` parameter.
|
||||||
:param request_token: Request token string, if any.
|
:param request_token: Request token string, if any.
|
||||||
:param access_token: Access token string, if any.
|
:param access_token: Access token string, if any.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
Per `Section 3.3`_ of the spec.
|
Per `Section 3.3`_ of the spec.
|
||||||
|
@ -572,7 +589,7 @@ class RequestValidator(object):
|
||||||
channel. The nonce value MUST be unique across all requests with the
|
channel. The nonce value MUST be unique across all requests with the
|
||||||
same timestamp, client credentials, and token combinations."
|
same timestamp, client credentials, and token combinations."
|
||||||
|
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
|
||||||
|
|
||||||
One of the first validation checks that will be made is for the validity
|
One of the first validation checks that will be made is for the validity
|
||||||
of the nonce and timestamp, which are associated with a client key and
|
of the nonce and timestamp, which are associated with a client key and
|
||||||
|
@ -600,7 +617,7 @@ class RequestValidator(object):
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
* SignatureOnlyEndpoint
|
* SignatureOnlyEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("validate_timestamp_and_nonce")
|
||||||
|
|
||||||
def validate_redirect_uri(self, client_key, redirect_uri, request):
|
def validate_redirect_uri(self, client_key, redirect_uri, request):
|
||||||
"""Validates the client supplied redirection URI.
|
"""Validates the client supplied redirection URI.
|
||||||
|
@ -608,7 +625,8 @@ class RequestValidator(object):
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param redirect_uri: The URI the client which to redirect back to after
|
:param redirect_uri: The URI the client which to redirect back to after
|
||||||
authorization is successful.
|
authorization is successful.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
It is highly recommended that OAuth providers require their clients
|
It is highly recommended that OAuth providers require their clients
|
||||||
|
@ -633,14 +651,15 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* RequestTokenEndpoint
|
* RequestTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("validate_redirect_uri")
|
||||||
|
|
||||||
def validate_requested_realms(self, client_key, realms, request):
|
def validate_requested_realms(self, client_key, realms, request):
|
||||||
"""Validates that the client may request access to the realm.
|
"""Validates that the client may request access to the realm.
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param realms: The list of realms that client is requesting access to.
|
:param realms: The list of realms that client is requesting access to.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
This method is invoked when obtaining a request token and should
|
This method is invoked when obtaining a request token and should
|
||||||
|
@ -651,7 +670,7 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* RequestTokenEndpoint
|
* RequestTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("validate_requested_realms")
|
||||||
|
|
||||||
def validate_realms(self, client_key, token, request, uri=None,
|
def validate_realms(self, client_key, token, request, uri=None,
|
||||||
realms=None):
|
realms=None):
|
||||||
|
@ -659,7 +678,8 @@ class RequestValidator(object):
|
||||||
|
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param token: A request token string.
|
:param token: A request token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:param uri: The URI the realms is protecting.
|
:param uri: The URI the realms is protecting.
|
||||||
:param realms: A list of realms that must have been granted to
|
:param realms: A list of realms that must have been granted to
|
||||||
the access token.
|
the access token.
|
||||||
|
@ -685,7 +705,7 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* ResourceEndpoint
|
* ResourceEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("validate_realms")
|
||||||
|
|
||||||
def validate_verifier(self, client_key, token, verifier, request):
|
def validate_verifier(self, client_key, token, verifier, request):
|
||||||
"""Validates a verification code.
|
"""Validates a verification code.
|
||||||
|
@ -693,7 +713,8 @@ class RequestValidator(object):
|
||||||
:param client_key: The client/consumer key.
|
:param client_key: The client/consumer key.
|
||||||
:param token: A request token string.
|
:param token: A request token string.
|
||||||
:param verifier: The authorization verifier string.
|
:param verifier: The authorization verifier string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
OAuth providers issue a verification code to clients after the
|
OAuth providers issue a verification code to clients after the
|
||||||
|
@ -716,13 +737,14 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AccessTokenEndpoint
|
* AccessTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("validate_verifier")
|
||||||
|
|
||||||
def verify_request_token(self, token, request):
|
def verify_request_token(self, token, request):
|
||||||
"""Verify that the given OAuth1 request token is valid.
|
"""Verify that the given OAuth1 request token is valid.
|
||||||
|
|
||||||
:param token: A request token string.
|
:param token: A request token string.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
This method is used only in AuthorizationEndpoint to check whether the
|
This method is used only in AuthorizationEndpoint to check whether the
|
||||||
|
@ -734,14 +756,15 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AuthorizationEndpoint
|
* AuthorizationEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("verify_request_token")
|
||||||
|
|
||||||
def verify_realms(self, token, realms, request):
|
def verify_realms(self, token, realms, request):
|
||||||
"""Verify authorized realms to see if they match those given to token.
|
"""Verify authorized realms to see if they match those given to token.
|
||||||
|
|
||||||
:param token: An access token string.
|
:param token: An access token string.
|
||||||
:param realms: A list of realms the client attempts to access.
|
:param realms: A list of realms the client attempts to access.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:returns: True or False
|
:returns: True or False
|
||||||
|
|
||||||
This prevents the list of authorized realms sent by the client during
|
This prevents the list of authorized realms sent by the client during
|
||||||
|
@ -757,13 +780,14 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AuthorizationEndpoint
|
* AuthorizationEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("verify_realms")
|
||||||
|
|
||||||
def save_access_token(self, token, request):
|
def save_access_token(self, token, request):
|
||||||
"""Save an OAuth1 access token.
|
"""Save an OAuth1 access token.
|
||||||
|
|
||||||
:param token: A dict with token credentials.
|
:param token: A dict with token credentials.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
The token dictionary will at minimum include
|
The token dictionary will at minimum include
|
||||||
|
|
||||||
|
@ -780,13 +804,14 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AccessTokenEndpoint
|
* AccessTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("save_access_token")
|
||||||
|
|
||||||
def save_request_token(self, token, request):
|
def save_request_token(self, token, request):
|
||||||
"""Save an OAuth1 request token.
|
"""Save an OAuth1 request token.
|
||||||
|
|
||||||
:param token: A dict with token credentials.
|
:param token: A dict with token credentials.
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
The token dictionary will at minimum include
|
The token dictionary will at minimum include
|
||||||
|
|
||||||
|
@ -800,7 +825,7 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* RequestTokenEndpoint
|
* RequestTokenEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("save_request_token")
|
||||||
|
|
||||||
def save_verifier(self, token, verifier, request):
|
def save_verifier(self, token, verifier, request):
|
||||||
"""Associate an authorization verifier with a request token.
|
"""Associate an authorization verifier with a request token.
|
||||||
|
@ -808,7 +833,8 @@ class RequestValidator(object):
|
||||||
:param token: A request token string.
|
: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
|
oauth_token
|
||||||
:param request: An oauthlib.common.Request object.
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
We need to associate verifiers with tokens for validation during the
|
We need to associate verifiers with tokens for validation during the
|
||||||
access token request.
|
access token request.
|
||||||
|
@ -820,4 +846,4 @@ class RequestValidator(object):
|
||||||
|
|
||||||
* AuthorizationEndpoint
|
* AuthorizationEndpoint
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Subclasses must implement this function.")
|
raise self._subclass_must_implement("save_verifier")
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.utils
|
oauthlib.utils
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
@ -6,14 +5,9 @@ oauthlib.utils
|
||||||
This module contains utility methods used by various parts of the OAuth
|
This module contains utility methods used by various parts of the OAuth
|
||||||
spec.
|
spec.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
try:
|
|
||||||
import urllib2
|
|
||||||
except ImportError:
|
|
||||||
import urllib.request as urllib2
|
import urllib.request as urllib2
|
||||||
|
|
||||||
from oauthlib.common import quote, unquote, bytes_type, unicode_type
|
from oauthlib.common import quote, unquote
|
||||||
|
|
||||||
UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
|
UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
|
@ -48,19 +42,19 @@ def escape(u):
|
||||||
|
|
||||||
Per `section 3.6`_ of the spec.
|
Per `section 3.6`_ of the spec.
|
||||||
|
|
||||||
.. _`section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
|
.. _`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. ' +
|
raise ValueError('Only unicode objects are escapable. ' +
|
||||||
'Got %s of type %s.' % (u, type(u)))
|
'Got {!r} of type {}.'.format(u, type(u)))
|
||||||
# Letters, digits, and the characters '_.-' are already treated as safe
|
# Letters, digits, and the characters '_.-' are already treated as safe
|
||||||
# by urllib.quote(). We need to add '~' to fully support rfc5849.
|
# by urllib.quote(). We need to add '~' to fully support rfc5849.
|
||||||
return quote(u, safe=b'~')
|
return quote(u, safe=b'~')
|
||||||
|
|
||||||
|
|
||||||
def unescape(u):
|
def unescape(u):
|
||||||
if not isinstance(u, unicode_type):
|
if not isinstance(u, str):
|
||||||
raise ValueError('Only unicode objects are unescapable.')
|
raise ValueError('Only unicode objects are unescapable.')
|
||||||
return unquote(u)
|
return unquote(u)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2
|
oauthlib.oauth2
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
@ -6,29 +5,31 @@ oauthlib.oauth2
|
||||||
This module is a wrapper for the most recent implementation of OAuth 2.0 Client
|
This module is a wrapper for the most recent implementation of OAuth 2.0 Client
|
||||||
and Server classes.
|
and Server classes.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
from .rfc6749.clients import (
|
||||||
|
BackendApplicationClient, Client, LegacyApplicationClient,
|
||||||
from .rfc6749.clients import Client
|
MobileApplicationClient, ServiceApplicationClient, WebApplicationClient,
|
||||||
from .rfc6749.clients import WebApplicationClient
|
)
|
||||||
from .rfc6749.clients import MobileApplicationClient
|
from .rfc6749.endpoints import (
|
||||||
from .rfc6749.clients import LegacyApplicationClient
|
AuthorizationEndpoint, BackendApplicationServer, IntrospectEndpoint,
|
||||||
from .rfc6749.clients import BackendApplicationClient
|
LegacyApplicationServer, MetadataEndpoint, MobileApplicationServer,
|
||||||
from .rfc6749.clients import ServiceApplicationClient
|
ResourceEndpoint, RevocationEndpoint, Server, TokenEndpoint,
|
||||||
from .rfc6749.endpoints import AuthorizationEndpoint
|
WebApplicationServer,
|
||||||
from .rfc6749.endpoints import TokenEndpoint
|
)
|
||||||
from .rfc6749.endpoints import ResourceEndpoint
|
from .rfc6749.errors import (
|
||||||
from .rfc6749.endpoints import RevocationEndpoint
|
AccessDeniedError, FatalClientError, InsecureTransportError,
|
||||||
from .rfc6749.endpoints import Server
|
InvalidClientError, InvalidClientIdError, InvalidGrantError,
|
||||||
from .rfc6749.endpoints import WebApplicationServer
|
InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError,
|
||||||
from .rfc6749.endpoints import MobileApplicationServer
|
InvalidScopeError, MismatchingRedirectURIError, MismatchingStateError,
|
||||||
from .rfc6749.endpoints import LegacyApplicationServer
|
MissingClientIdError, MissingCodeError, MissingRedirectURIError,
|
||||||
from .rfc6749.endpoints import BackendApplicationServer
|
MissingResponseTypeError, MissingTokenError, MissingTokenTypeError,
|
||||||
from .rfc6749.errors import *
|
OAuth2Error, ServerError, TemporarilyUnavailableError, TokenExpiredError,
|
||||||
from .rfc6749.grant_types import AuthorizationCodeGrant
|
UnauthorizedClientError, UnsupportedGrantTypeError,
|
||||||
from .rfc6749.grant_types import ImplicitGrant
|
UnsupportedResponseTypeError, UnsupportedTokenTypeError,
|
||||||
from .rfc6749.grant_types import ResourceOwnerPasswordCredentialsGrant
|
)
|
||||||
from .rfc6749.grant_types import ClientCredentialsGrant
|
from .rfc6749.grant_types import (
|
||||||
from .rfc6749.grant_types import RefreshTokenGrant
|
AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant,
|
||||||
|
RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
|
||||||
|
)
|
||||||
from .rfc6749.request_validator import RequestValidator
|
from .rfc6749.request_validator import RequestValidator
|
||||||
from .rfc6749.tokens import BearerToken, OAuth2Token
|
from .rfc6749.tokens import BearerToken, OAuth2Token
|
||||||
from .rfc6749.utils import is_secure_transport
|
from .rfc6749.utils import is_secure_transport
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749
|
oauthlib.oauth2.rfc6749
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -6,61 +5,12 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .errors import TemporarilyUnavailableError, ServerError
|
from .endpoints.base import BaseEndpoint, catch_errors_and_unavailability
|
||||||
from .errors import FatalClientError, OAuth2Error
|
from .errors import (
|
||||||
|
FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError,
|
||||||
|
)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseEndpoint(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._available = True
|
|
||||||
self._catch_errors = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self):
|
|
||||||
return self._available
|
|
||||||
|
|
||||||
@available.setter
|
|
||||||
def available(self, available):
|
|
||||||
self._available = available
|
|
||||||
|
|
||||||
@property
|
|
||||||
def catch_errors(self):
|
|
||||||
return self._catch_errors
|
|
||||||
|
|
||||||
@catch_errors.setter
|
|
||||||
def catch_errors(self, catch_errors):
|
|
||||||
self._catch_errors = catch_errors
|
|
||||||
|
|
||||||
|
|
||||||
def catch_errors_and_unavailability(f):
|
|
||||||
@functools.wraps(f)
|
|
||||||
def wrapper(endpoint, uri, *args, **kwargs):
|
|
||||||
if not endpoint.available:
|
|
||||||
e = TemporarilyUnavailableError()
|
|
||||||
log.info('Endpoint unavailable, ignoring request %s.' % uri)
|
|
||||||
return {}, e.json, 503
|
|
||||||
|
|
||||||
if endpoint.catch_errors:
|
|
||||||
try:
|
|
||||||
return f(endpoint, uri, *args, **kwargs)
|
|
||||||
except OAuth2Error:
|
|
||||||
raise
|
|
||||||
except FatalClientError:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
error = ServerError()
|
|
||||||
log.warning(
|
|
||||||
'Exception caught while processing request, %s.' % e)
|
|
||||||
return {}, error.json, 500
|
|
||||||
else:
|
|
||||||
return f(endpoint, uri, *args, **kwargs)
|
|
||||||
return wrapper
|
|
||||||
|
|
|
@ -6,11 +6,9 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming OAuth 2.0 RFC6749.
|
for consuming OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from .base import *
|
|
||||||
from .web_application import WebApplicationClient
|
|
||||||
from .mobile_application import MobileApplicationClient
|
|
||||||
from .legacy_application import LegacyApplicationClient
|
|
||||||
from .backend_application import BackendApplicationClient
|
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 .service_application import ServiceApplicationClient
|
||||||
|
from .web_application import WebApplicationClient
|
||||||
|
|
|
@ -6,11 +6,8 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from .base import Client
|
|
||||||
from ..parameters import prepare_token_request
|
from ..parameters import prepare_token_request
|
||||||
from ..parameters import parse_token_response
|
from .base import Client
|
||||||
|
|
||||||
|
|
||||||
class BackendApplicationClient(Client):
|
class BackendApplicationClient(Client):
|
||||||
|
@ -31,15 +28,28 @@ class BackendApplicationClient(Client):
|
||||||
no additional authorization request is needed.
|
no additional authorization request is needed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def prepare_request_body(self, body='', scope=None, **kwargs):
|
grant_type = 'client_credentials'
|
||||||
|
|
||||||
|
def prepare_request_body(self, body='', scope=None,
|
||||||
|
include_client_id=False, **kwargs):
|
||||||
"""Add the client credentials to the request body.
|
"""Add the client credentials to the request body.
|
||||||
|
|
||||||
The client makes a request to the token endpoint by adding the
|
The client makes a 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 per `Appendix B`_ in the HTTP request entity-body:
|
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 ''.
|
||||||
:param scope: The scope of the access request as described by
|
:param scope: The scope of the access request as described by
|
||||||
`Section 3.3`_.
|
`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.
|
:param kwargs: Extra credentials to include in the token request.
|
||||||
|
|
||||||
The client MUST authenticate with the authorization server as
|
The client MUST authenticate with the authorization server as
|
||||||
|
@ -53,9 +63,12 @@ class BackendApplicationClient(Client):
|
||||||
>>> client.prepare_request_body(scope=['hello', 'world'])
|
>>> client.prepare_request_body(scope=['hello', 'world'])
|
||||||
'grant_type=client_credentials&scope=hello+world'
|
'grant_type=client_credentials&scope=hello+world'
|
||||||
|
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
.. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||||
"""
|
"""
|
||||||
return prepare_token_request('client_credentials', body=body,
|
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,
|
||||||
scope=scope, **kwargs)
|
scope=scope, **kwargs)
|
||||||
|
|
|
@ -6,20 +6,20 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming OAuth 2.0 RFC6749.
|
for consuming OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import warnings
|
||||||
|
|
||||||
from oauthlib.common import generate_token
|
from oauthlib.common import generate_token
|
||||||
from oauthlib.oauth2.rfc6749 import tokens
|
from oauthlib.oauth2.rfc6749 import tokens
|
||||||
from oauthlib.oauth2.rfc6749.parameters import parse_token_response
|
from oauthlib.oauth2.rfc6749.errors import (
|
||||||
from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
|
InsecureTransportError, TokenExpiredError,
|
||||||
from oauthlib.oauth2.rfc6749.parameters import prepare_token_revocation_request
|
)
|
||||||
from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
|
from oauthlib.oauth2.rfc6749.parameters import (
|
||||||
from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
|
parse_token_response, prepare_token_request,
|
||||||
|
prepare_token_revocation_request,
|
||||||
|
)
|
||||||
from oauthlib.oauth2.rfc6749.utils import is_secure_transport
|
from oauthlib.oauth2.rfc6749.utils import is_secure_transport
|
||||||
|
|
||||||
|
|
||||||
AUTH_HEADER = 'auth_header'
|
AUTH_HEADER = 'auth_header'
|
||||||
URI_QUERY = 'query'
|
URI_QUERY = 'query'
|
||||||
BODY = 'body'
|
BODY = 'body'
|
||||||
|
@ -28,8 +28,8 @@ FORM_ENC_HEADERS = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
}
|
}
|
||||||
|
|
||||||
class Client(object):
|
|
||||||
|
|
||||||
|
class Client:
|
||||||
"""Base OAuth2 client responsible for access token management.
|
"""Base OAuth2 client responsible for access token management.
|
||||||
|
|
||||||
This class also acts as a generic interface providing methods common to all
|
This class also acts as a generic interface providing methods common to all
|
||||||
|
@ -47,6 +47,7 @@ class Client(object):
|
||||||
Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.
|
Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
refresh_token_key = 'refresh_token'
|
||||||
|
|
||||||
def __init__(self, client_id,
|
def __init__(self, client_id,
|
||||||
default_token_placement=AUTH_HEADER,
|
default_token_placement=AUTH_HEADER,
|
||||||
|
@ -80,7 +81,7 @@ class Client(object):
|
||||||
``token`` dict parameter.
|
``token`` dict parameter.
|
||||||
|
|
||||||
:param refresh_token: A refresh token (string) used to refresh expired
|
:param refresh_token: A refresh token (string) used to refresh expired
|
||||||
tokens. Can also be supplide inside the ``token`` dict parameter.
|
tokens. Can also be supplied inside the ``token`` dict parameter.
|
||||||
|
|
||||||
:param mac_key: Encryption key used with MAC tokens.
|
:param mac_key: Encryption key used with MAC tokens.
|
||||||
|
|
||||||
|
@ -112,8 +113,10 @@ class Client(object):
|
||||||
self.state_generator = state_generator
|
self.state_generator = state_generator
|
||||||
self.state = state
|
self.state = state
|
||||||
self.redirect_url = redirect_url
|
self.redirect_url = redirect_url
|
||||||
|
self.code = None
|
||||||
|
self.expires_in = None
|
||||||
self._expires_at = None
|
self._expires_at = None
|
||||||
self._populate_attributes(self.token)
|
self.populate_token_attributes(self.token)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def token_types(self):
|
def token_types(self):
|
||||||
|
@ -141,6 +144,7 @@ class Client(object):
|
||||||
|
|
||||||
def parse_request_uri_response(self, *args, **kwargs):
|
def parse_request_uri_response(self, *args, **kwargs):
|
||||||
"""Abstract method used to parse redirection responses."""
|
"""Abstract method used to parse redirection responses."""
|
||||||
|
raise NotImplementedError("Must be implemented by inheriting classes.")
|
||||||
|
|
||||||
def add_token(self, uri, http_method='GET', body=None, headers=None,
|
def add_token(self, uri, http_method='GET', body=None, headers=None,
|
||||||
token_placement=None, **kwargs):
|
token_placement=None, **kwargs):
|
||||||
|
@ -174,20 +178,20 @@ class Client(object):
|
||||||
nonce="274312:dj83hs9s",
|
nonce="274312:dj83hs9s",
|
||||||
mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
|
mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
|
||||||
|
|
||||||
.. _`I-D.ietf-oauth-v2-bearer`: http://tools.ietf.org/html/rfc6749#section-12.2
|
.. _`I-D.ietf-oauth-v2-bearer`: https://tools.ietf.org/html/rfc6749#section-12.2
|
||||||
.. _`I-D.ietf-oauth-v2-http-mac`: http://tools.ietf.org/html/rfc6749#section-12.2
|
.. _`I-D.ietf-oauth-v2-http-mac`: https://tools.ietf.org/html/rfc6749#section-12.2
|
||||||
"""
|
"""
|
||||||
if not is_secure_transport(uri):
|
if not is_secure_transport(uri):
|
||||||
raise InsecureTransportError()
|
raise InsecureTransportError()
|
||||||
|
|
||||||
token_placement = token_placement or self.default_token_placement
|
token_placement = token_placement or self.default_token_placement
|
||||||
|
|
||||||
case_insensitive_token_types = dict(
|
case_insensitive_token_types = {
|
||||||
(k.lower(), v) for k, v in self.token_types.items())
|
k.lower(): v for k, v in self.token_types.items()}
|
||||||
if not self.token_type.lower() in case_insensitive_token_types:
|
if not self.token_type.lower() in case_insensitive_token_types:
|
||||||
raise ValueError("Unsupported token type: %s" % self.token_type)
|
raise ValueError("Unsupported token type: %s" % self.token_type)
|
||||||
|
|
||||||
if not self.access_token:
|
if not (self.access_token or self.token.get('access_token')):
|
||||||
raise ValueError("Missing access token.")
|
raise ValueError("Missing access token.")
|
||||||
|
|
||||||
if self._expires_at and self._expires_at < time.time():
|
if self._expires_at and self._expires_at < time.time():
|
||||||
|
@ -218,6 +222,11 @@ class Client(object):
|
||||||
the provider. If provided then it must also be provided in the
|
the provider. If provided then it must also be provided in the
|
||||||
token request.
|
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.
|
:param kwargs: Additional parameters to included in the request.
|
||||||
|
|
||||||
:returns: The prepared request tuple with (url, headers, body).
|
:returns: The prepared request tuple with (url, headers, body).
|
||||||
|
@ -227,10 +236,11 @@ class Client(object):
|
||||||
|
|
||||||
self.state = state or self.state_generator()
|
self.state = state or self.state_generator()
|
||||||
self.redirect_url = redirect_url or self.redirect_url
|
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(
|
auth_url = self.prepare_request_uri(
|
||||||
authorization_url, redirect_uri=self.redirect_url,
|
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, ''
|
return auth_url, FORM_ENC_HEADERS, ''
|
||||||
|
|
||||||
def prepare_token_request(self, token_url, authorization_response=None,
|
def prepare_token_request(self, token_url, authorization_response=None,
|
||||||
|
@ -251,7 +261,10 @@ class Client(object):
|
||||||
:param redirect_url: The redirect_url supplied with the authorization
|
:param redirect_url: The redirect_url supplied with the authorization
|
||||||
request (if there was one).
|
request (if there was one).
|
||||||
|
|
||||||
:param body: Request body (URL encoded string).
|
:param state:
|
||||||
|
|
||||||
|
:param body: Existing request body (URL encoded string) to embed parameters
|
||||||
|
into. This may contain extra paramters. Default ''.
|
||||||
|
|
||||||
:param kwargs: Additional parameters to included in the request.
|
:param kwargs: Additional parameters to included in the request.
|
||||||
|
|
||||||
|
@ -283,11 +296,13 @@ class Client(object):
|
||||||
|
|
||||||
:param refresh_token: Refresh token string.
|
:param refresh_token: Refresh token string.
|
||||||
|
|
||||||
:param body: Request body (URL encoded string).
|
:param body: Existing request body (URL encoded string) to embed parameters
|
||||||
|
into. This may contain extra paramters. Default ''.
|
||||||
|
|
||||||
:param scope: List of scopes to request. Must be equal to
|
:param scope: List of scopes to request. Must be equal to
|
||||||
or a subset of the scopes granted when obtaining the refresh
|
or a subset of the scopes granted when obtaining the refresh
|
||||||
token.
|
token. If none is provided, the ones provided in the constructor are
|
||||||
|
used.
|
||||||
|
|
||||||
:param kwargs: Additional parameters to included in the request.
|
:param kwargs: Additional parameters to included in the request.
|
||||||
|
|
||||||
|
@ -296,9 +311,10 @@ class Client(object):
|
||||||
if not is_secure_transport(token_url):
|
if not is_secure_transport(token_url):
|
||||||
raise InsecureTransportError()
|
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,
|
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
|
return token_url, FORM_ENC_HEADERS, body
|
||||||
|
|
||||||
def prepare_token_revocation_request(self, revocation_url, token,
|
def prepare_token_revocation_request(self, revocation_url, token,
|
||||||
|
@ -313,6 +329,8 @@ class Client(object):
|
||||||
``"refresh_token"``. This is optional and if you wish to not pass it you
|
``"refresh_token"``. This is optional and if you wish to not pass it you
|
||||||
must provide ``token_type_hint=None``.
|
must provide ``token_type_hint=None``.
|
||||||
|
|
||||||
|
:param body:
|
||||||
|
|
||||||
:param callback: A jsonp callback such as ``package.callback`` to be invoked
|
: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.
|
||||||
|
|
||||||
|
@ -370,7 +388,8 @@ class Client(object):
|
||||||
returns an error response as described in `Section 5.2`_.
|
returns an error response as described in `Section 5.2`_.
|
||||||
|
|
||||||
:param body: The response body from the token request.
|
: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.
|
:return: Dictionary of token parameters.
|
||||||
:raises: Warning if scope has changed. OAuth2Error if response is invalid.
|
:raises: Warning if scope has changed. OAuth2Error if response is invalid.
|
||||||
|
|
||||||
|
@ -402,12 +421,13 @@ class Client(object):
|
||||||
Providers may supply this in all responses but are required to only
|
Providers may supply this in all responses but are required to only
|
||||||
if it has changed since the authorization request.
|
if it has changed since the authorization request.
|
||||||
|
|
||||||
.. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
|
.. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
|
||||||
.. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
|
.. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
|
||||||
.. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
|
.. _`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.token = parse_token_response(body, scope=scope)
|
||||||
self._populate_attributes(self.token)
|
self.populate_token_attributes(self.token)
|
||||||
return self.token
|
return self.token
|
||||||
|
|
||||||
def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
|
def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
|
||||||
|
@ -427,10 +447,12 @@ class Client(object):
|
||||||
Section 3.3. The requested scope MUST NOT include any scope
|
Section 3.3. The requested scope MUST NOT include any scope
|
||||||
not originally granted by the resource owner, and if omitted is
|
not originally granted by the resource owner, and if omitted is
|
||||||
treated as equal to the scope originally granted by the
|
treated as equal to the scope originally granted by the
|
||||||
resource owner.
|
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
|
refresh_token = refresh_token or self.refresh_token
|
||||||
return prepare_token_request('refresh_token', body=body, scope=scope,
|
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)
|
refresh_token=refresh_token, **kwargs)
|
||||||
|
|
||||||
def _add_bearer_token(self, uri, http_method='GET', body=None,
|
def _add_bearer_token(self, uri, http_method='GET', body=None,
|
||||||
|
@ -455,13 +477,27 @@ class Client(object):
|
||||||
|
|
||||||
Warning: MAC token support is experimental as the spec is not yet stable.
|
Warning: MAC token support is experimental as the spec is not yet stable.
|
||||||
"""
|
"""
|
||||||
|
if token_placement != AUTH_HEADER:
|
||||||
|
raise ValueError("Invalid token placement.")
|
||||||
|
|
||||||
headers = tokens.prepare_mac_header(self.access_token, uri,
|
headers = tokens.prepare_mac_header(self.access_token, uri,
|
||||||
self.mac_key, http_method, headers=headers, body=body, ext=ext,
|
self.mac_key, http_method, headers=headers, body=body, ext=ext,
|
||||||
hash_algorithm=self.mac_algorithm, **kwargs)
|
hash_algorithm=self.mac_algorithm, **kwargs)
|
||||||
return uri, headers, body
|
return uri, headers, body
|
||||||
|
|
||||||
def _populate_attributes(self, response):
|
def _populate_attributes(self, response):
|
||||||
"""Add commonly used values such as access_token to self."""
|
warnings.warn("Please switch to the public method "
|
||||||
|
"populate_token_attributes.", DeprecationWarning)
|
||||||
|
return self.populate_token_attributes(response)
|
||||||
|
|
||||||
|
def populate_code_attributes(self, response):
|
||||||
|
"""Add attributes from an auth code response to self."""
|
||||||
|
|
||||||
|
if 'code' in response:
|
||||||
|
self.code = response.get('code')
|
||||||
|
|
||||||
|
def populate_token_attributes(self, response):
|
||||||
|
"""Add attributes from a token exchange response to self."""
|
||||||
|
|
||||||
if 'access_token' in response:
|
if 'access_token' in response:
|
||||||
self.access_token = response.get('access_token')
|
self.access_token = response.get('access_token')
|
||||||
|
@ -479,12 +515,8 @@ class Client(object):
|
||||||
if 'expires_at' in response:
|
if 'expires_at' in response:
|
||||||
self._expires_at = int(response.get('expires_at'))
|
self._expires_at = int(response.get('expires_at'))
|
||||||
|
|
||||||
if 'code' in response:
|
|
||||||
self.code = response.get('code')
|
|
||||||
|
|
||||||
if 'mac_key' in response:
|
if 'mac_key' in response:
|
||||||
self.mac_key = response.get('mac_key')
|
self.mac_key = response.get('mac_key')
|
||||||
|
|
||||||
if 'mac_algorithm' in response:
|
if 'mac_algorithm' in response:
|
||||||
self.mac_algorithm = response.get('mac_algorithm')
|
self.mac_algorithm = response.get('mac_algorithm')
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,8 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from .base import Client
|
|
||||||
from ..parameters import prepare_token_request
|
from ..parameters import prepare_token_request
|
||||||
from ..parameters import parse_token_response
|
from .base import Client
|
||||||
|
|
||||||
|
|
||||||
class LegacyApplicationClient(Client):
|
class LegacyApplicationClient(Client):
|
||||||
|
@ -36,10 +33,13 @@ class LegacyApplicationClient(Client):
|
||||||
MUST discard the credentials once an access token has been obtained.
|
MUST discard the credentials once an access token has been obtained.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, client_id, **kwargs):
|
grant_type = 'password'
|
||||||
super(LegacyApplicationClient, self).__init__(client_id, **kwargs)
|
|
||||||
|
|
||||||
def prepare_request_body(self, username, password, body='', scope=None, **kwargs):
|
def __init__(self, client_id, **kwargs):
|
||||||
|
super().__init__(client_id, **kwargs)
|
||||||
|
|
||||||
|
def prepare_request_body(self, username, password, body='', scope=None,
|
||||||
|
include_client_id=False, **kwargs):
|
||||||
"""Add the resource owner password and username to the request body.
|
"""Add the resource owner password and username to the request body.
|
||||||
|
|
||||||
The client makes a request to the token endpoint by adding the
|
The client makes a request to the token endpoint by adding the
|
||||||
|
@ -48,8 +48,16 @@ class LegacyApplicationClient(Client):
|
||||||
|
|
||||||
:param username: The resource owner username.
|
:param username: The resource owner username.
|
||||||
:param password: The resource owner password.
|
:param password: The resource owner password.
|
||||||
|
:param body: Existing request body (URL encoded string) to embed parameters
|
||||||
|
into. This may contain extra paramters. Default ''.
|
||||||
:param scope: The scope of the access request as described by
|
:param scope: The scope of the access request as described by
|
||||||
`Section 3.3`_.
|
`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.
|
:param kwargs: Extra credentials to include in the token request.
|
||||||
|
|
||||||
If the client type is confidential or the client was issued client
|
If the client type is confidential or the client was issued client
|
||||||
|
@ -65,9 +73,12 @@ class LegacyApplicationClient(Client):
|
||||||
>>> client.prepare_request_body(username='foo', password='bar', scope=['hello', 'world'])
|
>>> client.prepare_request_body(username='foo', password='bar', scope=['hello', 'world'])
|
||||||
'grant_type=password&username=foo&scope=hello+world&password=bar'
|
'grant_type=password&username=foo&scope=hello+world&password=bar'
|
||||||
|
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
.. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||||
"""
|
"""
|
||||||
return prepare_token_request('password', body=body, username=username,
|
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)
|
password=password, scope=scope, **kwargs)
|
||||||
|
|
|
@ -6,11 +6,8 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
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
|
from .base import Client
|
||||||
from ..parameters import prepare_grant_uri
|
|
||||||
from ..parameters import parse_implicit_response
|
|
||||||
|
|
||||||
|
|
||||||
class MobileApplicationClient(Client):
|
class MobileApplicationClient(Client):
|
||||||
|
@ -47,6 +44,8 @@ class MobileApplicationClient(Client):
|
||||||
applications residing on the same device.
|
applications residing on the same device.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
response_type = 'token'
|
||||||
|
|
||||||
def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
|
def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
|
||||||
state=None, **kwargs):
|
state=None, **kwargs):
|
||||||
"""Prepare the implicit grant request URI.
|
"""Prepare the implicit grant request URI.
|
||||||
|
@ -86,13 +85,14 @@ class MobileApplicationClient(Client):
|
||||||
>>> client.prepare_request_uri('https://example.com', foo='bar')
|
>>> client.prepare_request_uri('https://example.com', foo='bar')
|
||||||
'https://example.com?client_id=your_id&response_type=token&foo=bar'
|
'https://example.com?client_id=your_id&response_type=token&foo=bar'
|
||||||
|
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
.. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
|
||||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
.. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
.. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
|
||||||
"""
|
"""
|
||||||
return prepare_grant_uri(uri, self.client_id, 'token',
|
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)
|
redirect_uri=redirect_uri, state=state, scope=scope, **kwargs)
|
||||||
|
|
||||||
def parse_request_uri_response(self, uri, state=None, scope=None):
|
def parse_request_uri_response(self, uri, state=None, scope=None):
|
||||||
|
@ -165,9 +165,10 @@ class MobileApplicationClient(Client):
|
||||||
>>> client.parse_request_body_response(response_body, scope=['other'])
|
>>> client.parse_request_body_response(response_body, scope=['other'])
|
||||||
('Scope has changed from "other" to "hello world".', ['other'], ['hello', 'world'])
|
('Scope has changed from "other" to "hello world".', ['other'], ['hello', 'world'])
|
||||||
|
|
||||||
.. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
|
.. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`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.token = parse_implicit_response(uri, state=state, scope=scope)
|
||||||
self._populate_attributes(self.token)
|
self.populate_token_attributes(self.token)
|
||||||
return self.token
|
return self.token
|
||||||
|
|
|
@ -6,15 +6,12 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oauthlib.common import to_unicode
|
from oauthlib.common import to_unicode
|
||||||
|
|
||||||
from .base import Client
|
|
||||||
from ..parameters import prepare_token_request
|
from ..parameters import prepare_token_request
|
||||||
from ..parameters import parse_token_response
|
from .base import Client
|
||||||
|
|
||||||
|
|
||||||
class ServiceApplicationClient(Client):
|
class ServiceApplicationClient(Client):
|
||||||
|
@ -55,10 +52,10 @@ class ServiceApplicationClient(Client):
|
||||||
``https://provider.com/oauth2/token``.
|
``https://provider.com/oauth2/token``.
|
||||||
|
|
||||||
:param kwargs: Additional arguments to pass to base client, such as
|
:param kwargs: Additional arguments to pass to base client, such as
|
||||||
state and token. See Client.__init__.__doc__ for
|
state and token. See ``Client.__init__.__doc__`` for
|
||||||
details.
|
details.
|
||||||
"""
|
"""
|
||||||
super(ServiceApplicationClient, self).__init__(client_id, **kwargs)
|
super().__init__(client_id, **kwargs)
|
||||||
self.private_key = private_key
|
self.private_key = private_key
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.issuer = issuer
|
self.issuer = issuer
|
||||||
|
@ -74,6 +71,7 @@ class ServiceApplicationClient(Client):
|
||||||
extra_claims=None,
|
extra_claims=None,
|
||||||
body='',
|
body='',
|
||||||
scope=None,
|
scope=None,
|
||||||
|
include_client_id=False,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
"""Create and add a JWT assertion to the request body.
|
"""Create and add a JWT assertion to the request body.
|
||||||
|
|
||||||
|
@ -98,20 +96,32 @@ class ServiceApplicationClient(Client):
|
||||||
:param issued_at: A unix timestamp of when the JWT was created.
|
:param issued_at: A unix timestamp of when the JWT was created.
|
||||||
Defaults to now, i.e. ``time.time()``.
|
Defaults to now, i.e. ``time.time()``.
|
||||||
|
|
||||||
:param not_before: A unix timestamp after which the JWT may be used.
|
|
||||||
Not included unless provided.
|
|
||||||
|
|
||||||
:param jwt_id: A unique JWT token identifier. Not included unless
|
|
||||||
provided.
|
|
||||||
|
|
||||||
:param extra_claims: A dict of additional claims to include in the JWT.
|
: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 ''.
|
||||||
|
|
||||||
:param scope: The scope of the access request.
|
:param scope: The scope of the access request.
|
||||||
|
|
||||||
:param body: Request body (string) with extra parameters.
|
: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 not_before: A unix timestamp after which the JWT may be used.
|
||||||
|
Not included unless provided. *
|
||||||
|
|
||||||
|
:param jwt_id: A unique JWT token identifier. Not included unless
|
||||||
|
provided. *
|
||||||
|
|
||||||
:param kwargs: Extra credentials to include in the token request.
|
:param kwargs: Extra credentials to include in the token request.
|
||||||
|
|
||||||
|
Parameters marked with a `*` above are not explicit arguments in the
|
||||||
|
function signature, but are specially documented arguments for items
|
||||||
|
appearing in the generic `**kwargs` keyworded input.
|
||||||
|
|
||||||
The "scope" parameter may be used, as defined in the Assertion
|
The "scope" parameter may be used, as defined in the Assertion
|
||||||
Framework for OAuth 2.0 Client Authentication and Authorization Grants
|
Framework for OAuth 2.0 Client Authentication and Authorization Grants
|
||||||
[I-D.ietf-oauth-assertions] specification, to indicate the requested
|
[I-D.ietf-oauth-assertions] specification, to indicate the requested
|
||||||
|
@ -137,7 +147,7 @@ class ServiceApplicationClient(Client):
|
||||||
eyJpc3Mi[...omitted for brevity...].
|
eyJpc3Mi[...omitted for brevity...].
|
||||||
J9l-ZhwP[...omitted for brevity...]
|
J9l-ZhwP[...omitted for brevity...]
|
||||||
|
|
||||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
.. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||||
"""
|
"""
|
||||||
import jwt
|
import jwt
|
||||||
|
|
||||||
|
@ -147,8 +157,8 @@ class ServiceApplicationClient(Client):
|
||||||
' token requests.')
|
' token requests.')
|
||||||
claim = {
|
claim = {
|
||||||
'iss': issuer or self.issuer,
|
'iss': issuer or self.issuer,
|
||||||
'aud': audience or self.issuer,
|
'aud': audience or self.audience,
|
||||||
'sub': subject or self.issuer,
|
'sub': subject or self.subject,
|
||||||
'exp': int(expires_at or time.time() + 3600),
|
'exp': int(expires_at or time.time() + 3600),
|
||||||
'iat': int(issued_at or time.time()),
|
'iat': int(issued_at or time.time()),
|
||||||
}
|
}
|
||||||
|
@ -169,6 +179,9 @@ class ServiceApplicationClient(Client):
|
||||||
assertion = jwt.encode(claim, key, 'RS256')
|
assertion = jwt.encode(claim, key, 'RS256')
|
||||||
assertion = to_unicode(assertion)
|
assertion = to_unicode(assertion)
|
||||||
|
|
||||||
|
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,
|
return prepare_token_request(self.grant_type,
|
||||||
body=body,
|
body=body,
|
||||||
assertion=assertion,
|
assertion=assertion,
|
||||||
|
|
|
@ -6,12 +6,13 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
import warnings
|
||||||
|
|
||||||
|
from ..parameters import (
|
||||||
|
parse_authorization_code_response, prepare_grant_uri,
|
||||||
|
prepare_token_request,
|
||||||
|
)
|
||||||
from .base import Client
|
from .base import Client
|
||||||
from ..parameters import prepare_grant_uri, prepare_token_request
|
|
||||||
from ..parameters import parse_authorization_code_response
|
|
||||||
from ..parameters import parse_token_response
|
|
||||||
|
|
||||||
|
|
||||||
class WebApplicationClient(Client):
|
class WebApplicationClient(Client):
|
||||||
|
@ -33,8 +34,10 @@ class WebApplicationClient(Client):
|
||||||
from the authorization server.
|
from the authorization server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
grant_type = 'authorization_code'
|
||||||
|
|
||||||
def __init__(self, client_id, code=None, **kwargs):
|
def __init__(self, client_id, code=None, **kwargs):
|
||||||
super(WebApplicationClient, self).__init__(client_id, **kwargs)
|
super().__init__(client_id, **kwargs)
|
||||||
self.code = code
|
self.code = code
|
||||||
|
|
||||||
def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
|
def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
|
||||||
|
@ -76,26 +79,24 @@ class WebApplicationClient(Client):
|
||||||
>>> client.prepare_request_uri('https://example.com', foo='bar')
|
>>> client.prepare_request_uri('https://example.com', foo='bar')
|
||||||
'https://example.com?client_id=your_id&response_type=code&foo=bar'
|
'https://example.com?client_id=your_id&response_type=code&foo=bar'
|
||||||
|
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
.. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
|
||||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
.. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
.. _`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',
|
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, **kwargs)
|
||||||
|
|
||||||
def prepare_request_body(self, client_id=None, code=None, body='',
|
def prepare_request_body(self, code=None, redirect_uri=None, body='',
|
||||||
redirect_uri=None, **kwargs):
|
include_client_id=True, **kwargs):
|
||||||
"""Prepare the access token request body.
|
"""Prepare the access token request body.
|
||||||
|
|
||||||
The client makes a request to the token endpoint by adding the
|
The client makes a 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:
|
format in the HTTP request entity-body:
|
||||||
|
|
||||||
:param client_id: REQUIRED, if the client is not authenticating with the
|
|
||||||
authorization server as described in `Section 3.2.1`_.
|
|
||||||
|
|
||||||
:param code: REQUIRED. The authorization code received from the
|
:param code: REQUIRED. The authorization code received from the
|
||||||
authorization server.
|
authorization server.
|
||||||
|
|
||||||
|
@ -103,6 +104,15 @@ class WebApplicationClient(Client):
|
||||||
authorization request as described in `Section 4.1.1`_, and their
|
authorization request as described in `Section 4.1.1`_, and their
|
||||||
values MUST be identical.
|
values MUST be identical.
|
||||||
|
|
||||||
|
:param body: Existing request body (URL encoded string) to embed parameters
|
||||||
|
into. This may contain extra paramters. Default ''.
|
||||||
|
|
||||||
|
:param include_client_id: `True` (default) 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`_.
|
||||||
|
:type include_client_id: Boolean
|
||||||
|
|
||||||
:param kwargs: Extra parameters to include in the token request.
|
:param kwargs: Extra parameters to include in the token request.
|
||||||
|
|
||||||
In addition OAuthLib will add the ``grant_type`` parameter set to
|
In addition OAuthLib will add the ``grant_type`` parameter set to
|
||||||
|
@ -120,12 +130,31 @@ class WebApplicationClient(Client):
|
||||||
>>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
|
>>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
|
||||||
'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'
|
'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'
|
||||||
|
|
||||||
.. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
|
`Section 3.2.1` also states:
|
||||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
In the "authorization_code" "grant_type" request to the token
|
||||||
|
endpoint, an unauthenticated client MUST send its "client_id" to
|
||||||
|
prevent itself from inadvertently accepting a code intended for a
|
||||||
|
client with a different "client_id". This protects the client from
|
||||||
|
substitution of the authentication code. (It provides no additional
|
||||||
|
security for the protected resource.)
|
||||||
|
|
||||||
|
.. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||||
|
.. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||||
"""
|
"""
|
||||||
code = code or self.code
|
code = code or self.code
|
||||||
return prepare_token_request('authorization_code', code=code, body=body,
|
if 'client_id' in kwargs:
|
||||||
client_id=self.client_id, redirect_uri=redirect_uri, **kwargs)
|
warnings.warn("`client_id` has been deprecated in favor of "
|
||||||
|
"`include_client_id`, a boolean value which will "
|
||||||
|
"include the already configured `self.client_id`.",
|
||||||
|
DeprecationWarning)
|
||||||
|
if kwargs['client_id'] != self.client_id:
|
||||||
|
raise ValueError("`client_id` was supplied as an argument, but "
|
||||||
|
"it does not match `self.client_id`")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
def parse_request_uri_response(self, uri, state=None):
|
def parse_request_uri_response(self, uri, state=None):
|
||||||
"""Parse the URI query for code and state.
|
"""Parse the URI query for code and state.
|
||||||
|
@ -172,5 +201,5 @@ class WebApplicationClient(Client):
|
||||||
oauthlib.oauth2.rfc6749.errors.MismatchingStateError
|
oauthlib.oauth2.rfc6749.errors.MismatchingStateError
|
||||||
"""
|
"""
|
||||||
response = parse_authorization_code_response(uri, state=state)
|
response = parse_authorization_code_response(uri, state=state)
|
||||||
self._populate_attributes(response)
|
self.populate_code_attributes(response)
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749
|
oauthlib.oauth2.rfc6749
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -6,14 +5,13 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from .authorization import AuthorizationEndpoint
|
from .authorization import AuthorizationEndpoint
|
||||||
from .token import TokenEndpoint
|
from .introspect import IntrospectEndpoint
|
||||||
|
from .metadata import MetadataEndpoint
|
||||||
|
from .pre_configured import (
|
||||||
|
BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer,
|
||||||
|
Server, WebApplicationServer,
|
||||||
|
)
|
||||||
from .resource import ResourceEndpoint
|
from .resource import ResourceEndpoint
|
||||||
from .revocation import RevocationEndpoint
|
from .revocation import RevocationEndpoint
|
||||||
from .pre_configured import Server
|
from .token import TokenEndpoint
|
||||||
from .pre_configured import WebApplicationServer
|
|
||||||
from .pre_configured import MobileApplicationServer
|
|
||||||
from .pre_configured import LegacyApplicationServer
|
|
||||||
from .pre_configured import BackendApplicationServer
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749
|
oauthlib.oauth2.rfc6749
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -6,11 +5,10 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oauthlib.common import Request
|
from oauthlib.common import Request
|
||||||
|
from oauthlib.oauth2.rfc6749 import utils
|
||||||
|
|
||||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||||
|
|
||||||
|
@ -58,7 +56,7 @@ class AuthorizationEndpoint(BaseEndpoint):
|
||||||
|
|
||||||
# Enforced through the design of oauthlib.common.Request
|
# Enforced through the design of oauthlib.common.Request
|
||||||
|
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, default_response_type, default_token_type,
|
def __init__(self, default_response_type, default_token_type,
|
||||||
|
@ -108,7 +106,9 @@ class AuthorizationEndpoint(BaseEndpoint):
|
||||||
"""Extract response_type and route to the designated handler."""
|
"""Extract response_type and route to the designated handler."""
|
||||||
request = Request(
|
request = Request(
|
||||||
uri, http_method=http_method, body=body, headers=headers)
|
uri, http_method=http_method, body=body, headers=headers)
|
||||||
request.scopes = None
|
|
||||||
|
request.scopes = utils.scope_to_list(request.scope)
|
||||||
|
|
||||||
response_type_handler = self.response_types.get(
|
response_type_handler = self.response_types.get(
|
||||||
request.response_type, self.default_response_type_handler)
|
request.response_type, self.default_response_type_handler)
|
||||||
return response_type_handler.validate_authorization_request(request)
|
return response_type_handler.validate_authorization_request(request)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749
|
oauthlib.oauth2.rfc6749
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -6,22 +5,34 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..errors import TemporarilyUnavailableError, ServerError
|
from ..errors import (
|
||||||
from ..errors import FatalClientError, OAuth2Error
|
FatalClientError, InvalidClientError, InvalidRequestError, OAuth2Error,
|
||||||
|
ServerError, TemporarilyUnavailableError, UnsupportedTokenTypeError,
|
||||||
|
)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseEndpoint(object):
|
class BaseEndpoint:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._available = True
|
self._available = True
|
||||||
self._catch_errors = False
|
self._catch_errors = False
|
||||||
|
self._valid_request_methods = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valid_request_methods(self):
|
||||||
|
return self._valid_request_methods
|
||||||
|
|
||||||
|
@valid_request_methods.setter
|
||||||
|
def valid_request_methods(self, valid_request_methods):
|
||||||
|
if valid_request_methods is not None:
|
||||||
|
valid_request_methods = [x.upper() for x in valid_request_methods]
|
||||||
|
self._valid_request_methods = valid_request_methods
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
|
@ -39,6 +50,43 @@ class BaseEndpoint(object):
|
||||||
def catch_errors(self, catch_errors):
|
def catch_errors(self, catch_errors):
|
||||||
self._catch_errors = catch_errors
|
self._catch_errors = catch_errors
|
||||||
|
|
||||||
|
def _raise_on_missing_token(self, request):
|
||||||
|
"""Raise error on missing token."""
|
||||||
|
if not request.token:
|
||||||
|
raise InvalidRequestError(request=request,
|
||||||
|
description='Missing token parameter.')
|
||||||
|
def _raise_on_invalid_client(self, request):
|
||||||
|
"""Raise on failed client authentication."""
|
||||||
|
if self.request_validator.client_authentication_required(request):
|
||||||
|
if not self.request_validator.authenticate_client(request):
|
||||||
|
log.debug('Client authentication failed, %r.', request)
|
||||||
|
raise InvalidClientError(request=request)
|
||||||
|
elif not self.request_validator.authenticate_client_id(request.client_id, request):
|
||||||
|
log.debug('Client authentication failed, %r.', request)
|
||||||
|
raise InvalidClientError(request=request)
|
||||||
|
|
||||||
|
def _raise_on_unsupported_token(self, request):
|
||||||
|
"""Raise on unsupported tokens."""
|
||||||
|
if (request.token_type_hint and
|
||||||
|
request.token_type_hint in self.valid_token_types and
|
||||||
|
request.token_type_hint not in self.supported_token_types):
|
||||||
|
raise UnsupportedTokenTypeError(request=request)
|
||||||
|
|
||||||
|
def _raise_on_bad_method(self, request):
|
||||||
|
if self.valid_request_methods is None:
|
||||||
|
raise ValueError('Configure "valid_request_methods" property first')
|
||||||
|
if request.http_method.upper() not in self.valid_request_methods:
|
||||||
|
raise InvalidRequestError(request=request,
|
||||||
|
description=('Unsupported request method %s' % request.http_method.upper()))
|
||||||
|
|
||||||
|
def _raise_on_bad_post_request(self, request):
|
||||||
|
"""Raise if invalid POST request received
|
||||||
|
"""
|
||||||
|
if request.http_method.upper() == 'POST':
|
||||||
|
query_params = request.uri_query or ""
|
||||||
|
if query_params:
|
||||||
|
raise InvalidRequestError(request=request,
|
||||||
|
description=('URL query parameters are not allowed'))
|
||||||
|
|
||||||
def catch_errors_and_unavailability(f):
|
def catch_errors_and_unavailability(f):
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
|
|
122
lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py
Normal file
122
lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
"""
|
||||||
|
oauthlib.oauth2.rfc6749.endpoint.introspect
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
An implementation of the OAuth 2.0 `Token Introspection`.
|
||||||
|
|
||||||
|
.. _`Token Introspection`: https://tools.ietf.org/html/rfc7662
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oauthlib.common import Request
|
||||||
|
|
||||||
|
from ..errors import OAuth2Error
|
||||||
|
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IntrospectEndpoint(BaseEndpoint):
|
||||||
|
|
||||||
|
"""Introspect token endpoint.
|
||||||
|
|
||||||
|
This endpoint defines a method to query an OAuth 2.0 authorization
|
||||||
|
server to determine the active state of an OAuth 2.0 token and to
|
||||||
|
determine meta-information about this token. OAuth 2.0 deployments
|
||||||
|
can use this method to convey information about the authorization
|
||||||
|
context of the token from the authorization server to the protected
|
||||||
|
resource.
|
||||||
|
|
||||||
|
To prevent the values of access tokens from leaking into
|
||||||
|
server-side logs via query parameters, an authorization server
|
||||||
|
offering token introspection MAY disallow the use of HTTP GET on
|
||||||
|
the introspection endpoint and instead require the HTTP POST method
|
||||||
|
to be used at the introspection endpoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
valid_token_types = ('access_token', 'refresh_token')
|
||||||
|
valid_request_methods = ('POST',)
|
||||||
|
|
||||||
|
def __init__(self, request_validator, supported_token_types=None):
|
||||||
|
BaseEndpoint.__init__(self)
|
||||||
|
self.request_validator = request_validator
|
||||||
|
self.supported_token_types = (
|
||||||
|
supported_token_types or self.valid_token_types)
|
||||||
|
|
||||||
|
@catch_errors_and_unavailability
|
||||||
|
def create_introspect_response(self, uri, http_method='POST', body=None,
|
||||||
|
headers=None):
|
||||||
|
"""Create introspect valid or invalid response
|
||||||
|
|
||||||
|
If the authorization server is unable to determine the state
|
||||||
|
of the token without additional information, it SHOULD return
|
||||||
|
an introspection response indicating the token is not active
|
||||||
|
as described in Section 2.2.
|
||||||
|
"""
|
||||||
|
resp_headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Cache-Control': 'no-store',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
}
|
||||||
|
request = Request(uri, http_method, body, headers)
|
||||||
|
try:
|
||||||
|
self.validate_introspect_request(request)
|
||||||
|
log.debug('Token introspect valid for %r.', request)
|
||||||
|
except OAuth2Error as e:
|
||||||
|
log.debug('Client error during validation of %r. %r.', request, e)
|
||||||
|
resp_headers.update(e.headers)
|
||||||
|
return resp_headers, e.json, e.status_code
|
||||||
|
|
||||||
|
claims = self.request_validator.introspect_token(
|
||||||
|
request.token,
|
||||||
|
request.token_type_hint,
|
||||||
|
request
|
||||||
|
)
|
||||||
|
if claims is None:
|
||||||
|
return resp_headers, json.dumps(dict(active=False)), 200
|
||||||
|
if "active" in claims:
|
||||||
|
claims.pop("active")
|
||||||
|
return resp_headers, json.dumps(dict(active=True, **claims)), 200
|
||||||
|
|
||||||
|
def validate_introspect_request(self, request):
|
||||||
|
"""Ensure the request is valid.
|
||||||
|
|
||||||
|
The protected resource calls the introspection endpoint using
|
||||||
|
an HTTP POST request with parameters sent as
|
||||||
|
"application/x-www-form-urlencoded".
|
||||||
|
|
||||||
|
token REQUIRED. The string value of the token.
|
||||||
|
|
||||||
|
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
|
||||||
|
server is unable to locate the token using the given hint, it MUST
|
||||||
|
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`_
|
||||||
|
|
||||||
|
The introspection endpoint MAY accept other OPTIONAL
|
||||||
|
parameters to provide further context to the query. For
|
||||||
|
instance, an authorization server may desire to know the IP
|
||||||
|
address of the client accessing the protected resource to
|
||||||
|
determine if the correct client is likely to be presenting the
|
||||||
|
token. The definition of this or any other parameters are
|
||||||
|
outside the scope of this specification, to be defined by
|
||||||
|
service documentation or extensions to this specification.
|
||||||
|
|
||||||
|
.. _`section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
|
||||||
|
.. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
|
||||||
|
.. _`RFC6749`: http://tools.ietf.org/html/rfc6749
|
||||||
|
"""
|
||||||
|
self._raise_on_bad_method(request)
|
||||||
|
self._raise_on_bad_post_request(request)
|
||||||
|
self._raise_on_missing_token(request)
|
||||||
|
self._raise_on_invalid_client(request)
|
||||||
|
self._raise_on_unsupported_token(request)
|
237
lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py
Normal file
237
lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
"""
|
||||||
|
oauthlib.oauth2.rfc6749.endpoint.metadata
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
An implementation of the `OAuth 2.0 Authorization Server Metadata`.
|
||||||
|
|
||||||
|
.. _`OAuth 2.0 Authorization Server Metadata`: https://tools.ietf.org/html/rfc8414
|
||||||
|
"""
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .. import grant_types
|
||||||
|
from .authorization import AuthorizationEndpoint
|
||||||
|
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||||
|
from .introspect import IntrospectEndpoint
|
||||||
|
from .revocation import RevocationEndpoint
|
||||||
|
from .token import TokenEndpoint
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataEndpoint(BaseEndpoint):
|
||||||
|
|
||||||
|
"""OAuth2.0 Authorization Server Metadata endpoint.
|
||||||
|
|
||||||
|
This specification generalizes the metadata format defined by
|
||||||
|
`OpenID Connect Discovery 1.0` in a way that is compatible
|
||||||
|
with OpenID Connect Discovery while being applicable to a wider set
|
||||||
|
of OAuth 2.0 use cases. This is intentionally parallel to the way
|
||||||
|
that OAuth 2.0 Dynamic Client Registration Protocol [`RFC7591`_]
|
||||||
|
generalized the dynamic client registration mechanisms defined by
|
||||||
|
OpenID Connect Dynamic Client Registration 1.0
|
||||||
|
in a way that is compatible with it.
|
||||||
|
|
||||||
|
.. _`OpenID Connect Discovery 1.0`: https://openid.net/specs/openid-connect-discovery-1_0.html
|
||||||
|
.. _`RFC7591`: https://tools.ietf.org/html/rfc7591
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, endpoints, claims={}, raise_errors=True):
|
||||||
|
assert isinstance(claims, dict)
|
||||||
|
for endpoint in endpoints:
|
||||||
|
assert isinstance(endpoint, BaseEndpoint)
|
||||||
|
|
||||||
|
BaseEndpoint.__init__(self)
|
||||||
|
self.raise_errors = raise_errors
|
||||||
|
self.endpoints = endpoints
|
||||||
|
self.initial_claims = claims
|
||||||
|
self.claims = self.validate_metadata_server()
|
||||||
|
|
||||||
|
@catch_errors_and_unavailability
|
||||||
|
def create_metadata_response(self, uri, http_method='GET', body=None,
|
||||||
|
headers=None):
|
||||||
|
"""Create metadata response
|
||||||
|
"""
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
return headers, json.dumps(self.claims), 200
|
||||||
|
|
||||||
|
def validate_metadata(self, array, key, is_required=False, is_list=False, is_url=False, is_issuer=False):
|
||||||
|
if not self.raise_errors:
|
||||||
|
return
|
||||||
|
|
||||||
|
if key not in array:
|
||||||
|
if is_required:
|
||||||
|
raise ValueError("key {} is a mandatory metadata.".format(key))
|
||||||
|
|
||||||
|
elif is_issuer:
|
||||||
|
if not array[key].startswith("https"):
|
||||||
|
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]))
|
||||||
|
|
||||||
|
elif is_url:
|
||||||
|
if not array[key].startswith("http"):
|
||||||
|
raise ValueError("key {}: {} must be an URL".format(key, array[key]))
|
||||||
|
|
||||||
|
elif is_list:
|
||||||
|
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, str):
|
||||||
|
raise ValueError("array {}: {} must contains only string (not {})".format(key, array[key], elem))
|
||||||
|
|
||||||
|
def validate_metadata_token(self, claims, endpoint):
|
||||||
|
"""
|
||||||
|
If the token endpoint is used in the grant type, the value of this
|
||||||
|
parameter MUST be the same as the value of the "grant_type"
|
||||||
|
parameter passed to the token endpoint defined in the grant type
|
||||||
|
definition.
|
||||||
|
"""
|
||||||
|
self._grant_types.extend(endpoint._grant_types.keys())
|
||||||
|
claims.setdefault("token_endpoint_auth_methods_supported", ["client_secret_post", "client_secret_basic"])
|
||||||
|
|
||||||
|
self.validate_metadata(claims, "token_endpoint_auth_methods_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "token_endpoint_auth_signing_alg_values_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "token_endpoint", is_required=True, is_url=True)
|
||||||
|
|
||||||
|
def validate_metadata_authorization(self, claims, endpoint):
|
||||||
|
claims.setdefault("response_types_supported",
|
||||||
|
list(filter(lambda x: x != "none", endpoint._response_types.keys())))
|
||||||
|
claims.setdefault("response_modes_supported", ["query", "fragment"])
|
||||||
|
|
||||||
|
# The OAuth2.0 Implicit flow is defined as a "grant type" but it is not
|
||||||
|
# using the "token" endpoint, as such, we have to add it explicitly to
|
||||||
|
# the list of "grant_types_supported" when enabled.
|
||||||
|
if "token" in claims["response_types_supported"]:
|
||||||
|
self._grant_types.append("implicit")
|
||||||
|
|
||||||
|
self.validate_metadata(claims, "response_types_supported", is_required=True, is_list=True)
|
||||||
|
self.validate_metadata(claims, "response_modes_supported", is_list=True)
|
||||||
|
if "code" in claims["response_types_supported"]:
|
||||||
|
code_grant = endpoint._response_types["code"]
|
||||||
|
if not isinstance(code_grant, grant_types.AuthorizationCodeGrant) and hasattr(code_grant, "default_grant"):
|
||||||
|
code_grant = code_grant.default_grant
|
||||||
|
|
||||||
|
claims.setdefault("code_challenge_methods_supported",
|
||||||
|
list(code_grant._code_challenge_methods.keys()))
|
||||||
|
self.validate_metadata(claims, "code_challenge_methods_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "authorization_endpoint", is_required=True, is_url=True)
|
||||||
|
|
||||||
|
def validate_metadata_revocation(self, claims, endpoint):
|
||||||
|
claims.setdefault("revocation_endpoint_auth_methods_supported",
|
||||||
|
["client_secret_post", "client_secret_basic"])
|
||||||
|
|
||||||
|
self.validate_metadata(claims, "revocation_endpoint_auth_methods_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "revocation_endpoint_auth_signing_alg_values_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "revocation_endpoint", is_required=True, is_url=True)
|
||||||
|
|
||||||
|
def validate_metadata_introspection(self, claims, endpoint):
|
||||||
|
claims.setdefault("introspection_endpoint_auth_methods_supported",
|
||||||
|
["client_secret_post", "client_secret_basic"])
|
||||||
|
|
||||||
|
self.validate_metadata(claims, "introspection_endpoint_auth_methods_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "introspection_endpoint_auth_signing_alg_values_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "introspection_endpoint", is_required=True, is_url=True)
|
||||||
|
|
||||||
|
def validate_metadata_server(self):
|
||||||
|
"""
|
||||||
|
Authorization servers can have metadata describing their
|
||||||
|
configuration. The following authorization server metadata values
|
||||||
|
are used by this specification. More details can be found in
|
||||||
|
`RFC8414 section 2`_ :
|
||||||
|
|
||||||
|
issuer
|
||||||
|
REQUIRED
|
||||||
|
|
||||||
|
authorization_endpoint
|
||||||
|
URL of the authorization server's authorization endpoint
|
||||||
|
[`RFC6749#Authorization`_]. This is REQUIRED unless no grant types are supported
|
||||||
|
that use the authorization endpoint.
|
||||||
|
|
||||||
|
token_endpoint
|
||||||
|
URL of the authorization server's token endpoint [`RFC6749#Token`_]. This
|
||||||
|
is REQUIRED unless only the implicit grant type is supported.
|
||||||
|
|
||||||
|
scopes_supported
|
||||||
|
RECOMMENDED.
|
||||||
|
|
||||||
|
response_types_supported
|
||||||
|
REQUIRED.
|
||||||
|
|
||||||
|
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
|
||||||
|
type values that this authorization server supports. The array
|
||||||
|
values used are the same as those used with the "grant_types"
|
||||||
|
parameter defined by "OAuth 2.0 Dynamic Client Registration
|
||||||
|
Protocol" [`RFC7591`_]. If omitted, the default value is
|
||||||
|
"["authorization_code", "implicit"]".
|
||||||
|
|
||||||
|
token_endpoint_auth_methods_supported
|
||||||
|
|
||||||
|
token_endpoint_auth_signing_alg_values_supported
|
||||||
|
|
||||||
|
service_documentation
|
||||||
|
|
||||||
|
ui_locales_supported
|
||||||
|
|
||||||
|
op_policy_uri
|
||||||
|
|
||||||
|
op_tos_uri
|
||||||
|
|
||||||
|
revocation_endpoint
|
||||||
|
|
||||||
|
revocation_endpoint_auth_methods_supported
|
||||||
|
|
||||||
|
revocation_endpoint_auth_signing_alg_values_supported
|
||||||
|
|
||||||
|
introspection_endpoint
|
||||||
|
|
||||||
|
introspection_endpoint_auth_methods_supported
|
||||||
|
|
||||||
|
introspection_endpoint_auth_signing_alg_values_supported
|
||||||
|
|
||||||
|
code_challenge_methods_supported
|
||||||
|
|
||||||
|
Additional authorization server metadata parameters MAY also be used.
|
||||||
|
Some are defined by other specifications, such as OpenID Connect
|
||||||
|
Discovery 1.0 [`OpenID.Discovery`_].
|
||||||
|
|
||||||
|
.. _`RFC8414 section 2`: https://tools.ietf.org/html/rfc8414#section-2
|
||||||
|
.. _`RFC6749#Authorization`: https://tools.ietf.org/html/rfc6749#section-3.1
|
||||||
|
.. _`RFC6749#Token`: https://tools.ietf.org/html/rfc6749#section-3.2
|
||||||
|
.. _`RFC7591`: https://tools.ietf.org/html/rfc7591
|
||||||
|
.. _`OpenID.Discovery`: https://openid.net/specs/openid-connect-discovery-1_0.html
|
||||||
|
"""
|
||||||
|
claims = copy.deepcopy(self.initial_claims)
|
||||||
|
self.validate_metadata(claims, "issuer", is_required=True, is_issuer=True)
|
||||||
|
self.validate_metadata(claims, "jwks_uri", is_url=True)
|
||||||
|
self.validate_metadata(claims, "scopes_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "service_documentation", is_url=True)
|
||||||
|
self.validate_metadata(claims, "ui_locales_supported", is_list=True)
|
||||||
|
self.validate_metadata(claims, "op_policy_uri", is_url=True)
|
||||||
|
self.validate_metadata(claims, "op_tos_uri", is_url=True)
|
||||||
|
|
||||||
|
self._grant_types = []
|
||||||
|
for endpoint in self.endpoints:
|
||||||
|
if isinstance(endpoint, TokenEndpoint):
|
||||||
|
self.validate_metadata_token(claims, endpoint)
|
||||||
|
if isinstance(endpoint, AuthorizationEndpoint):
|
||||||
|
self.validate_metadata_authorization(claims, endpoint)
|
||||||
|
if isinstance(endpoint, RevocationEndpoint):
|
||||||
|
self.validate_metadata_revocation(claims, endpoint)
|
||||||
|
if isinstance(endpoint, IntrospectEndpoint):
|
||||||
|
self.validate_metadata_introspection(claims, endpoint)
|
||||||
|
|
||||||
|
# "grant_types_supported" is a combination of all OAuth2 grant types
|
||||||
|
# allowed in the current provider implementation.
|
||||||
|
claims.setdefault("grant_types_supported", self._grant_types)
|
||||||
|
self.validate_metadata(claims, "grant_types_supported", is_list=True)
|
||||||
|
return claims
|
|
@ -1,28 +1,24 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749
|
oauthlib.oauth2.rfc6749.endpoints.pre_configured
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various endpoints needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for providing OAuth 2.0 RFC6749 servers.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
from ..grant_types import (
|
||||||
|
AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant,
|
||||||
|
RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
|
||||||
|
)
|
||||||
from ..tokens import BearerToken
|
from ..tokens import BearerToken
|
||||||
from ..grant_types import AuthorizationCodeGrant
|
|
||||||
from ..grant_types import ImplicitGrant
|
|
||||||
from ..grant_types import ResourceOwnerPasswordCredentialsGrant
|
|
||||||
from ..grant_types import ClientCredentialsGrant
|
|
||||||
from ..grant_types import RefreshTokenGrant
|
|
||||||
|
|
||||||
from .authorization import AuthorizationEndpoint
|
from .authorization import AuthorizationEndpoint
|
||||||
from .token import TokenEndpoint
|
from .introspect import IntrospectEndpoint
|
||||||
from .resource import ResourceEndpoint
|
from .resource import ResourceEndpoint
|
||||||
from .revocation import RevocationEndpoint
|
from .revocation import RevocationEndpoint
|
||||||
|
from .token import TokenEndpoint
|
||||||
|
|
||||||
|
|
||||||
class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
|
class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
|
||||||
RevocationEndpoint):
|
ResourceEndpoint, RevocationEndpoint):
|
||||||
|
|
||||||
"""An all-in-one endpoint featuring all four major grant types."""
|
"""An all-in-one endpoint featuring all four major grant types."""
|
||||||
|
|
||||||
|
@ -42,35 +38,40 @@ class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
|
||||||
:param kwargs: Extra parameters to pass to authorization-,
|
:param kwargs: Extra parameters to pass to authorization-,
|
||||||
token-, resource-, and revocation-endpoint constructors.
|
token-, resource-, and revocation-endpoint constructors.
|
||||||
"""
|
"""
|
||||||
auth_grant = AuthorizationCodeGrant(request_validator)
|
self.auth_grant = AuthorizationCodeGrant(request_validator)
|
||||||
implicit_grant = ImplicitGrant(request_validator)
|
self.implicit_grant = ImplicitGrant(request_validator)
|
||||||
password_grant = ResourceOwnerPasswordCredentialsGrant(
|
self.password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||||
request_validator)
|
request_validator)
|
||||||
credentials_grant = ClientCredentialsGrant(request_validator)
|
self.credentials_grant = ClientCredentialsGrant(request_validator)
|
||||||
refresh_grant = RefreshTokenGrant(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)
|
token_expires_in, refresh_token_generator)
|
||||||
|
|
||||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||||
response_types={
|
response_types={
|
||||||
'code': auth_grant,
|
'code': self.auth_grant,
|
||||||
'token': implicit_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',
|
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||||
grant_types={
|
grant_types={
|
||||||
'authorization_code': auth_grant,
|
'authorization_code': self.auth_grant,
|
||||||
'password': password_grant,
|
'password': self.password_grant,
|
||||||
'client_credentials': credentials_grant,
|
'client_credentials': self.credentials_grant,
|
||||||
'refresh_token': refresh_grant,
|
'refresh_token': self.refresh_grant,
|
||||||
},
|
},
|
||||||
default_token_type=bearer)
|
default_token_type=self.bearer)
|
||||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||||
token_types={'Bearer': bearer})
|
token_types={'Bearer': self.bearer})
|
||||||
RevocationEndpoint.__init__(self, request_validator)
|
RevocationEndpoint.__init__(self, request_validator)
|
||||||
|
IntrospectEndpoint.__init__(self, request_validator)
|
||||||
|
|
||||||
|
|
||||||
class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
|
class WebApplicationServer(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
|
||||||
RevocationEndpoint):
|
ResourceEndpoint, RevocationEndpoint):
|
||||||
|
|
||||||
"""An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
|
"""An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
|
||||||
|
|
||||||
|
@ -89,26 +90,27 @@ class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoin
|
||||||
:param kwargs: Extra parameters to pass to authorization-,
|
:param kwargs: Extra parameters to pass to authorization-,
|
||||||
token-, resource-, and revocation-endpoint constructors.
|
token-, resource-, and revocation-endpoint constructors.
|
||||||
"""
|
"""
|
||||||
auth_grant = AuthorizationCodeGrant(request_validator)
|
self.auth_grant = AuthorizationCodeGrant(request_validator)
|
||||||
refresh_grant = RefreshTokenGrant(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)
|
token_expires_in, refresh_token_generator)
|
||||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||||
response_types={'code': auth_grant},
|
response_types={'code': self.auth_grant},
|
||||||
default_token_type=bearer)
|
default_token_type=self.bearer)
|
||||||
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||||
grant_types={
|
grant_types={
|
||||||
'authorization_code': auth_grant,
|
'authorization_code': self.auth_grant,
|
||||||
'refresh_token': refresh_grant,
|
'refresh_token': self.refresh_grant,
|
||||||
},
|
},
|
||||||
default_token_type=bearer)
|
default_token_type=self.bearer)
|
||||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||||
token_types={'Bearer': bearer})
|
token_types={'Bearer': self.bearer})
|
||||||
RevocationEndpoint.__init__(self, request_validator)
|
RevocationEndpoint.__init__(self, request_validator)
|
||||||
|
IntrospectEndpoint.__init__(self, request_validator)
|
||||||
|
|
||||||
|
|
||||||
class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint,
|
class MobileApplicationServer(AuthorizationEndpoint, IntrospectEndpoint,
|
||||||
RevocationEndpoint):
|
ResourceEndpoint, RevocationEndpoint):
|
||||||
|
|
||||||
"""An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""
|
"""An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""
|
||||||
|
|
||||||
|
@ -127,21 +129,23 @@ class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint,
|
||||||
:param kwargs: Extra parameters to pass to authorization-,
|
:param kwargs: Extra parameters to pass to authorization-,
|
||||||
token-, resource-, and revocation-endpoint constructors.
|
token-, resource-, and revocation-endpoint constructors.
|
||||||
"""
|
"""
|
||||||
implicit_grant = ImplicitGrant(request_validator)
|
self.implicit_grant = ImplicitGrant(request_validator)
|
||||||
bearer = BearerToken(request_validator, token_generator,
|
self.bearer = BearerToken(request_validator, token_generator,
|
||||||
token_expires_in, refresh_token_generator)
|
token_expires_in, refresh_token_generator)
|
||||||
AuthorizationEndpoint.__init__(self, default_response_type='token',
|
AuthorizationEndpoint.__init__(self, default_response_type='token',
|
||||||
response_types={
|
response_types={
|
||||||
'token': implicit_grant},
|
'token': self.implicit_grant},
|
||||||
default_token_type=bearer)
|
default_token_type=self.bearer)
|
||||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||||
token_types={'Bearer': bearer})
|
token_types={'Bearer': self.bearer})
|
||||||
RevocationEndpoint.__init__(self, request_validator,
|
RevocationEndpoint.__init__(self, request_validator,
|
||||||
supported_token_types=['access_token'])
|
supported_token_types=['access_token'])
|
||||||
|
IntrospectEndpoint.__init__(self, request_validator,
|
||||||
|
supported_token_types=['access_token'])
|
||||||
|
|
||||||
|
|
||||||
class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint,
|
class LegacyApplicationServer(TokenEndpoint, IntrospectEndpoint,
|
||||||
RevocationEndpoint):
|
ResourceEndpoint, RevocationEndpoint):
|
||||||
|
|
||||||
"""An all-in-one endpoint featuring Resource Owner Password Credentials grant and Bearer tokens."""
|
"""An all-in-one endpoint featuring Resource Owner Password Credentials grant and Bearer tokens."""
|
||||||
|
|
||||||
|
@ -160,24 +164,25 @@ class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint,
|
||||||
:param kwargs: Extra parameters to pass to authorization-,
|
:param kwargs: Extra parameters to pass to authorization-,
|
||||||
token-, resource-, and revocation-endpoint constructors.
|
token-, resource-, and revocation-endpoint constructors.
|
||||||
"""
|
"""
|
||||||
password_grant = ResourceOwnerPasswordCredentialsGrant(
|
self.password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||||
request_validator)
|
request_validator)
|
||||||
refresh_grant = RefreshTokenGrant(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)
|
token_expires_in, refresh_token_generator)
|
||||||
TokenEndpoint.__init__(self, default_grant_type='password',
|
TokenEndpoint.__init__(self, default_grant_type='password',
|
||||||
grant_types={
|
grant_types={
|
||||||
'password': password_grant,
|
'password': self.password_grant,
|
||||||
'refresh_token': refresh_grant,
|
'refresh_token': self.refresh_grant,
|
||||||
},
|
},
|
||||||
default_token_type=bearer)
|
default_token_type=self.bearer)
|
||||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||||
token_types={'Bearer': bearer})
|
token_types={'Bearer': self.bearer})
|
||||||
RevocationEndpoint.__init__(self, request_validator)
|
RevocationEndpoint.__init__(self, request_validator)
|
||||||
|
IntrospectEndpoint.__init__(self, request_validator)
|
||||||
|
|
||||||
|
|
||||||
class BackendApplicationServer(TokenEndpoint, ResourceEndpoint,
|
class BackendApplicationServer(TokenEndpoint, IntrospectEndpoint,
|
||||||
RevocationEndpoint):
|
ResourceEndpoint, RevocationEndpoint):
|
||||||
|
|
||||||
"""An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""
|
"""An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""
|
||||||
|
|
||||||
|
@ -196,14 +201,16 @@ class BackendApplicationServer(TokenEndpoint, ResourceEndpoint,
|
||||||
:param kwargs: Extra parameters to pass to authorization-,
|
:param kwargs: Extra parameters to pass to authorization-,
|
||||||
token-, resource-, and revocation-endpoint constructors.
|
token-, resource-, and revocation-endpoint constructors.
|
||||||
"""
|
"""
|
||||||
credentials_grant = ClientCredentialsGrant(request_validator)
|
self.credentials_grant = ClientCredentialsGrant(request_validator)
|
||||||
bearer = BearerToken(request_validator, token_generator,
|
self.bearer = BearerToken(request_validator, token_generator,
|
||||||
token_expires_in, refresh_token_generator)
|
token_expires_in, refresh_token_generator)
|
||||||
TokenEndpoint.__init__(self, default_grant_type='client_credentials',
|
TokenEndpoint.__init__(self, default_grant_type='client_credentials',
|
||||||
grant_types={
|
grant_types={
|
||||||
'client_credentials': credentials_grant},
|
'client_credentials': self.credentials_grant},
|
||||||
default_token_type=bearer)
|
default_token_type=self.bearer)
|
||||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||||
token_types={'Bearer': bearer})
|
token_types={'Bearer': self.bearer})
|
||||||
RevocationEndpoint.__init__(self, request_validator,
|
RevocationEndpoint.__init__(self, request_validator,
|
||||||
supported_token_types=['access_token'])
|
supported_token_types=['access_token'])
|
||||||
|
IntrospectEndpoint.__init__(self, request_validator,
|
||||||
|
supported_token_types=['access_token'])
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749
|
oauthlib.oauth2.rfc6749
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -6,8 +5,6 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oauthlib.common import Request
|
from oauthlib.common import Request
|
||||||
|
@ -83,5 +80,5 @@ class ResourceEndpoint(BaseEndpoint):
|
||||||
to give an estimation based on the request.
|
to give an estimation based on the request.
|
||||||
"""
|
"""
|
||||||
estimates = sorted(((t.estimate_type(request), n)
|
estimates = sorted(((t.estimate_type(request), n)
|
||||||
for n, t in self.tokens.items()))
|
for n, t in self.tokens.items()), reverse=True)
|
||||||
return estimates[0][1] if len(estimates) else None
|
return estimates[0][1] if len(estimates) else None
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.endpoint.revocation
|
oauthlib.oauth2.rfc6749.endpoint.revocation
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
An implementation of the OAuth 2 `Token Revocation`_ spec (draft 11).
|
An implementation of the OAuth 2 `Token Revocation`_ spec (draft 11).
|
||||||
|
|
||||||
.. _`Token Revocation`: http://tools.ietf.org/html/draft-ietf-oauth-revocation-11
|
.. _`Token Revocation`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oauthlib.common import Request
|
from oauthlib.common import Request
|
||||||
|
|
||||||
|
from ..errors import OAuth2Error
|
||||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||||
from ..errors import InvalidClientError, UnsupportedTokenTypeError
|
|
||||||
from ..errors import InvalidRequestError, OAuth2Error
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -29,6 +25,7 @@ class RevocationEndpoint(BaseEndpoint):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
valid_token_types = ('access_token', 'refresh_token')
|
valid_token_types = ('access_token', 'refresh_token')
|
||||||
|
valid_request_methods = ('POST',)
|
||||||
|
|
||||||
def __init__(self, request_validator, supported_token_types=None,
|
def __init__(self, request_validator, supported_token_types=None,
|
||||||
enable_jsonp=False):
|
enable_jsonp=False):
|
||||||
|
@ -59,6 +56,11 @@ class RevocationEndpoint(BaseEndpoint):
|
||||||
An invalid token type hint value is ignored by the authorization server
|
An invalid token type hint value is ignored by the authorization server
|
||||||
and does not influence the revocation response.
|
and does not influence the revocation response.
|
||||||
"""
|
"""
|
||||||
|
resp_headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Cache-Control': 'no-store',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
}
|
||||||
request = Request(
|
request = Request(
|
||||||
uri, http_method=http_method, body=body, headers=headers)
|
uri, http_method=http_method, body=body, headers=headers)
|
||||||
try:
|
try:
|
||||||
|
@ -68,8 +70,9 @@ class RevocationEndpoint(BaseEndpoint):
|
||||||
log.debug('Client error during validation of %r. %r.', request, e)
|
log.debug('Client error during validation of %r. %r.', request, e)
|
||||||
response_body = e.json
|
response_body = e.json
|
||||||
if self.enable_jsonp and request.callback:
|
if self.enable_jsonp and request.callback:
|
||||||
response_body = '%s(%s);' % (request.callback, response_body)
|
response_body = '{}({});'.format(request.callback, response_body)
|
||||||
return {}, response_body, e.status_code
|
resp_headers.update(e.headers)
|
||||||
|
return resp_headers, response_body, e.status_code
|
||||||
|
|
||||||
self.request_validator.revoke_token(request.token,
|
self.request_validator.revoke_token(request.token,
|
||||||
request.token_type_hint, request)
|
request.token_type_hint, request)
|
||||||
|
@ -110,21 +113,14 @@ class RevocationEndpoint(BaseEndpoint):
|
||||||
The client also includes its authentication credentials as described in
|
The client also includes its authentication credentials as described in
|
||||||
`Section 2.3`_. of [`RFC6749`_].
|
`Section 2.3`_. of [`RFC6749`_].
|
||||||
|
|
||||||
.. _`section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
|
.. _`section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4
|
||||||
.. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
|
.. _`section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5
|
||||||
.. _`section 2.3`: http://tools.ietf.org/html/rfc6749#section-2.3
|
.. _`section 2.3`: https://tools.ietf.org/html/rfc6749#section-2.3
|
||||||
.. _`Section 4.1.2`: http://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2
|
.. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2
|
||||||
.. _`RFC6749`: http://tools.ietf.org/html/rfc6749
|
.. _`RFC6749`: https://tools.ietf.org/html/rfc6749
|
||||||
"""
|
"""
|
||||||
if not request.token:
|
self._raise_on_bad_method(request)
|
||||||
raise InvalidRequestError(request=request,
|
self._raise_on_bad_post_request(request)
|
||||||
description='Missing token parameter.')
|
self._raise_on_missing_token(request)
|
||||||
|
self._raise_on_invalid_client(request)
|
||||||
if self.request_validator.client_authentication_required(request):
|
self._raise_on_unsupported_token(request)
|
||||||
if not self.request_validator.authenticate_client(request):
|
|
||||||
raise InvalidClientError(request=request)
|
|
||||||
|
|
||||||
if (request.token_type_hint and
|
|
||||||
request.token_type_hint in self.valid_token_types and
|
|
||||||
request.token_type_hint not in self.supported_token_types):
|
|
||||||
raise UnsupportedTokenTypeError(request=request)
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749
|
oauthlib.oauth2.rfc6749
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -6,15 +5,13 @@ oauthlib.oauth2.rfc6749
|
||||||
This module is an implementation of various logic needed
|
This module is an implementation of various logic needed
|
||||||
for consuming and providing OAuth 2.0 RFC6749.
|
for consuming and providing OAuth 2.0 RFC6749.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oauthlib.common import Request
|
from oauthlib.common import Request
|
||||||
|
from oauthlib.oauth2.rfc6749 import utils
|
||||||
|
|
||||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +36,6 @@ class TokenEndpoint(BaseEndpoint):
|
||||||
https://example.com/path?query=component # OK
|
https://example.com/path?query=component # OK
|
||||||
https://example.com/path?query=component#fragment # Not 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
|
Since requests to the token endpoint result in the transmission of
|
||||||
clear-text credentials (in the HTTP request and response), the
|
clear-text credentials (in the HTTP request and response), the
|
||||||
authorization server MUST require the use of TLS as described in
|
authorization server MUST require the use of TLS as described in
|
||||||
|
@ -59,9 +55,11 @@ class TokenEndpoint(BaseEndpoint):
|
||||||
|
|
||||||
# Delegated to each grant type.
|
# Delegated to each grant type.
|
||||||
|
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
valid_request_methods = ('POST',)
|
||||||
|
|
||||||
def __init__(self, default_grant_type, default_token_type, grant_types):
|
def __init__(self, default_grant_type, default_token_type, grant_types):
|
||||||
BaseEndpoint.__init__(self)
|
BaseEndpoint.__init__(self)
|
||||||
self._grant_types = grant_types
|
self._grant_types = grant_types
|
||||||
|
@ -85,16 +83,37 @@ class TokenEndpoint(BaseEndpoint):
|
||||||
return self._default_token_type
|
return self._default_token_type
|
||||||
|
|
||||||
@catch_errors_and_unavailability
|
@catch_errors_and_unavailability
|
||||||
def create_token_response(self, uri, http_method='GET', body=None,
|
def create_token_response(self, uri, http_method='POST', body=None,
|
||||||
headers=None, credentials=None):
|
headers=None, credentials=None, grant_type_for_scope=None,
|
||||||
|
claims=None):
|
||||||
"""Extract grant_type and route to the designated handler."""
|
"""Extract grant_type and route to the designated handler."""
|
||||||
request = Request(
|
request = Request(
|
||||||
uri, http_method=http_method, body=body, headers=headers)
|
uri, http_method=http_method, body=body, headers=headers)
|
||||||
request.scopes = None
|
self.validate_token_request(request)
|
||||||
|
# 'scope' is an allowed Token Request param in both the "Resource Owner Password Credentials Grant"
|
||||||
|
# and "Client Credentials Grant" flows
|
||||||
|
# https://tools.ietf.org/html/rfc6749#section-4.3.2
|
||||||
|
# https://tools.ietf.org/html/rfc6749#section-4.4.2
|
||||||
|
request.scopes = utils.scope_to_list(request.scope)
|
||||||
|
|
||||||
request.extra_credentials = credentials
|
request.extra_credentials = credentials
|
||||||
|
if grant_type_for_scope:
|
||||||
|
request.grant_type = grant_type_for_scope
|
||||||
|
|
||||||
|
# OpenID Connect claims, if provided. The server using oauthlib might choose
|
||||||
|
# to implement the claims parameter of the Authorization Request. In this case
|
||||||
|
# it should retrieve those claims and pass them via the claims argument here,
|
||||||
|
# as a dict.
|
||||||
|
if claims:
|
||||||
|
request.claims = claims
|
||||||
|
|
||||||
grant_type_handler = self.grant_types.get(request.grant_type,
|
grant_type_handler = self.grant_types.get(request.grant_type,
|
||||||
self.default_grant_type_handler)
|
self.default_grant_type_handler)
|
||||||
log.debug('Dispatching grant_type %s request to %r.',
|
log.debug('Dispatching grant_type %s request to %r.',
|
||||||
request.grant_type, grant_type_handler)
|
request.grant_type, grant_type_handler)
|
||||||
return grant_type_handler.create_token_response(
|
return grant_type_handler.create_token_response(
|
||||||
request, self.default_token_type)
|
request, self.default_token_type)
|
||||||
|
|
||||||
|
def validate_token_request(self, request):
|
||||||
|
self._raise_on_bad_method(request)
|
||||||
|
self._raise_on_bad_post_request(request)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.errors
|
oauthlib.oauth2.rfc6749.errors
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -6,9 +5,9 @@ oauthlib.oauth2.rfc6749.errors
|
||||||
Error used both by OAuth 2 clients and providers to represent the spec
|
Error used both by OAuth 2 clients and providers to represent the spec
|
||||||
defined error responses for all four core grant types.
|
defined error responses for all four core grant types.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
|
||||||
import json
|
import json
|
||||||
from oauthlib.common import urlencode, add_params_to_uri
|
|
||||||
|
from oauthlib.common import add_params_to_uri, urlencode
|
||||||
|
|
||||||
|
|
||||||
class OAuth2Error(Exception):
|
class OAuth2Error(Exception):
|
||||||
|
@ -16,32 +15,37 @@ class OAuth2Error(Exception):
|
||||||
status_code = 400
|
status_code = 400
|
||||||
description = ''
|
description = ''
|
||||||
|
|
||||||
def __init__(self, description=None, uri=None, state=None, status_code=None,
|
def __init__(self, description=None, uri=None, state=None,
|
||||||
request=None):
|
status_code=None, request=None):
|
||||||
"""
|
"""
|
||||||
description: A human-readable ASCII [USASCII] text providing
|
:param description: A human-readable ASCII [USASCII] text providing
|
||||||
additional information, used to assist the client
|
additional information, used to assist the client
|
||||||
developer in understanding the error that occurred.
|
developer in understanding the error that occurred.
|
||||||
Values for the "error_description" parameter MUST NOT
|
Values for the "error_description" parameter
|
||||||
include characters outside the set
|
MUST NOT include characters outside the set
|
||||||
x20-21 / x23-5B / x5D-7E.
|
x20-21 / x23-5B / x5D-7E.
|
||||||
|
|
||||||
uri: A URI identifying a human-readable web page with information
|
:param uri: A URI identifying a human-readable web page with information
|
||||||
about the error, used to provide the client developer with
|
about the error, used to provide the client developer with
|
||||||
additional information about the error. Values for the
|
additional information about the error. Values for the
|
||||||
"error_uri" parameter MUST conform to the URI- Reference
|
"error_uri" parameter MUST conform to the URI- Reference
|
||||||
syntax, and thus MUST NOT include characters outside the set
|
syntax, and thus MUST NOT include characters outside the set
|
||||||
x21 / x23-5B / x5D-7E.
|
x21 / x23-5B / x5D-7E.
|
||||||
|
|
||||||
state: A CSRF protection value received from the client.
|
:param state: A CSRF protection value received from the client.
|
||||||
|
|
||||||
request: Oauthlib Request object
|
:param status_code:
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
"""
|
"""
|
||||||
self.description = description or self.description
|
if description is not None:
|
||||||
message = '(%s) %s' % (self.error, self.description)
|
self.description = description
|
||||||
|
|
||||||
|
message = '({}) {}'.format(self.error, self.description)
|
||||||
if request:
|
if request:
|
||||||
message += ' ' + repr(request)
|
message += ' ' + repr(request)
|
||||||
super(OAuth2Error, self).__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
self.uri = uri
|
self.uri = uri
|
||||||
self.state = state
|
self.state = state
|
||||||
|
@ -54,12 +58,21 @@ class OAuth2Error(Exception):
|
||||||
self.client_id = request.client_id
|
self.client_id = request.client_id
|
||||||
self.scopes = request.scopes
|
self.scopes = request.scopes
|
||||||
self.response_type = request.response_type
|
self.response_type = request.response_type
|
||||||
|
self.response_mode = request.response_mode
|
||||||
self.grant_type = request.grant_type
|
self.grant_type = request.grant_type
|
||||||
if not state:
|
if not state:
|
||||||
self.state = request.state
|
self.state = request.state
|
||||||
|
else:
|
||||||
|
self.redirect_uri = None
|
||||||
|
self.client_id = None
|
||||||
|
self.scopes = None
|
||||||
|
self.response_type = None
|
||||||
|
self.response_mode = None
|
||||||
|
self.grant_type = None
|
||||||
|
|
||||||
def in_uri(self, uri):
|
def in_uri(self, uri):
|
||||||
return add_params_to_uri(uri, self.twotuples)
|
fragment = self.response_mode == "fragment"
|
||||||
|
return add_params_to_uri(uri, self.twotuples, fragment)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def twotuples(self):
|
def twotuples(self):
|
||||||
|
@ -80,6 +93,27 @@ class OAuth2Error(Exception):
|
||||||
def json(self):
|
def json(self):
|
||||||
return json.dumps(dict(self.twotuples))
|
return json.dumps(dict(self.twotuples))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headers(self):
|
||||||
|
if self.status_code == 401:
|
||||||
|
"""
|
||||||
|
https://tools.ietf.org/html/rfc6750#section-3
|
||||||
|
|
||||||
|
All challenges defined by this specification MUST use the auth-scheme
|
||||||
|
value "Bearer". This scheme MUST be followed by one or more
|
||||||
|
auth-param values.
|
||||||
|
"""
|
||||||
|
authvalues = [
|
||||||
|
"Bearer",
|
||||||
|
'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 {}
|
||||||
|
|
||||||
|
|
||||||
class TokenExpiredError(OAuth2Error):
|
class TokenExpiredError(OAuth2Error):
|
||||||
error = 'token_expired'
|
error = 'token_expired'
|
||||||
|
@ -108,8 +142,8 @@ class MissingTokenTypeError(OAuth2Error):
|
||||||
|
|
||||||
|
|
||||||
class FatalClientError(OAuth2Error):
|
class FatalClientError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""Errors during authorization where user should not be redirected back.
|
Errors during authorization where user should not be redirected back.
|
||||||
|
|
||||||
If the request fails due to a missing, invalid, or mismatching
|
If the request fails due to a missing, invalid, or mismatching
|
||||||
redirection URI, or if the client identifier is missing or invalid,
|
redirection URI, or if the client identifier is missing or invalid,
|
||||||
|
@ -123,7 +157,8 @@ class FatalClientError(OAuth2Error):
|
||||||
|
|
||||||
|
|
||||||
class InvalidRequestFatalError(FatalClientError):
|
class InvalidRequestFatalError(FatalClientError):
|
||||||
"""For fatal errors, the request is missing a required parameter, includes
|
"""
|
||||||
|
For fatal errors, the request is missing a required parameter, includes
|
||||||
an invalid parameter value, includes a parameter more than once, or is
|
an invalid parameter value, includes a parameter more than once, or is
|
||||||
otherwise malformed.
|
otherwise malformed.
|
||||||
"""
|
"""
|
||||||
|
@ -151,8 +186,8 @@ class MissingClientIdError(InvalidRequestFatalError):
|
||||||
|
|
||||||
|
|
||||||
class InvalidRequestError(OAuth2Error):
|
class InvalidRequestError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""The request is missing a required parameter, includes an invalid
|
The request is missing a required parameter, includes an invalid
|
||||||
parameter value, includes a parameter more than once, or is
|
parameter value, includes a parameter more than once, or is
|
||||||
otherwise malformed.
|
otherwise malformed.
|
||||||
"""
|
"""
|
||||||
|
@ -163,31 +198,66 @@ class MissingResponseTypeError(InvalidRequestError):
|
||||||
description = 'Missing response_type parameter.'
|
description = 'Missing response_type parameter.'
|
||||||
|
|
||||||
|
|
||||||
class AccessDeniedError(OAuth2Error):
|
class MissingCodeChallengeError(InvalidRequestError):
|
||||||
|
"""
|
||||||
|
If the server requires Proof Key for Code Exchange (PKCE) by OAuth
|
||||||
|
public clients and the client does not send the "code_challenge" in
|
||||||
|
the request, the authorization endpoint MUST return the authorization
|
||||||
|
error response with the "error" value set to "invalid_request". The
|
||||||
|
"error_description" or the response of "error_uri" SHOULD explain the
|
||||||
|
nature of error, e.g., code challenge required.
|
||||||
|
"""
|
||||||
|
description = 'Code challenge required.'
|
||||||
|
|
||||||
"""The resource owner or authorization server denied the request."""
|
|
||||||
|
class MissingCodeVerifierError(InvalidRequestError):
|
||||||
|
"""
|
||||||
|
The request to the token endpoint, when PKCE is enabled, has
|
||||||
|
the parameter `code_verifier` REQUIRED.
|
||||||
|
"""
|
||||||
|
description = 'Code verifier required.'
|
||||||
|
|
||||||
|
|
||||||
|
class AccessDeniedError(OAuth2Error):
|
||||||
|
"""
|
||||||
|
The resource owner or authorization server denied the request.
|
||||||
|
"""
|
||||||
error = 'access_denied'
|
error = 'access_denied'
|
||||||
status_code = 401
|
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedResponseTypeError(OAuth2Error):
|
class UnsupportedResponseTypeError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""The authorization server does not support obtaining an authorization
|
The authorization server does not support obtaining an authorization
|
||||||
code using this method.
|
code using this method.
|
||||||
"""
|
"""
|
||||||
error = 'unsupported_response_type'
|
error = 'unsupported_response_type'
|
||||||
|
|
||||||
|
|
||||||
class InvalidScopeError(OAuth2Error):
|
class UnsupportedCodeChallengeMethodError(InvalidRequestError):
|
||||||
|
"""
|
||||||
|
If the server supporting PKCE does not support the requested
|
||||||
|
transformation, the authorization endpoint MUST return the
|
||||||
|
authorization error response with "error" value set to
|
||||||
|
"invalid_request". The "error_description" or the response of
|
||||||
|
"error_uri" SHOULD explain the nature of error, e.g., transform
|
||||||
|
algorithm not supported.
|
||||||
|
"""
|
||||||
|
description = 'Transform algorithm not supported.'
|
||||||
|
|
||||||
"""The requested scope is invalid, unknown, or malformed."""
|
|
||||||
|
class InvalidScopeError(OAuth2Error):
|
||||||
|
"""
|
||||||
|
The requested scope is invalid, unknown, or malformed, or
|
||||||
|
exceeds the scope granted by the resource owner.
|
||||||
|
|
||||||
|
https://tools.ietf.org/html/rfc6749#section-5.2
|
||||||
|
"""
|
||||||
error = 'invalid_scope'
|
error = 'invalid_scope'
|
||||||
status_code = 401
|
|
||||||
|
|
||||||
|
|
||||||
class ServerError(OAuth2Error):
|
class ServerError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""The authorization server encountered an unexpected condition that
|
The authorization server encountered an unexpected condition that
|
||||||
prevented it from fulfilling the request. (This error code is needed
|
prevented it from fulfilling the request. (This error code is needed
|
||||||
because a 500 Internal Server Error HTTP status code cannot be returned
|
because a 500 Internal Server Error HTTP status code cannot be returned
|
||||||
to the client via a HTTP redirect.)
|
to the client via a HTTP redirect.)
|
||||||
|
@ -196,8 +266,8 @@ class ServerError(OAuth2Error):
|
||||||
|
|
||||||
|
|
||||||
class TemporarilyUnavailableError(OAuth2Error):
|
class TemporarilyUnavailableError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""The authorization server is currently unable to handle the request
|
The authorization server is currently unable to handle the request
|
||||||
due to a temporary overloading or maintenance of the server.
|
due to a temporary overloading or maintenance of the server.
|
||||||
(This error code is needed because a 503 Service Unavailable HTTP
|
(This error code is needed because a 503 Service Unavailable HTTP
|
||||||
status code cannot be returned to the client via a HTTP redirect.)
|
status code cannot be returned to the client via a HTTP redirect.)
|
||||||
|
@ -205,9 +275,9 @@ class TemporarilyUnavailableError(OAuth2Error):
|
||||||
error = 'temporarily_unavailable'
|
error = 'temporarily_unavailable'
|
||||||
|
|
||||||
|
|
||||||
class InvalidClientError(OAuth2Error):
|
class InvalidClientError(FatalClientError):
|
||||||
|
"""
|
||||||
"""Client authentication failed (e.g. unknown client, no client
|
Client authentication failed (e.g. unknown client, no client
|
||||||
authentication included, or unsupported authentication method).
|
authentication included, or unsupported authentication method).
|
||||||
The authorization server MAY return an HTTP 401 (Unauthorized) status
|
The authorization server MAY return an HTTP 401 (Unauthorized) status
|
||||||
code to indicate which HTTP authentication schemes are supported.
|
code to indicate which HTTP authentication schemes are supported.
|
||||||
|
@ -222,42 +292,103 @@ class InvalidClientError(OAuth2Error):
|
||||||
|
|
||||||
|
|
||||||
class InvalidGrantError(OAuth2Error):
|
class InvalidGrantError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""The provided authorization grant (e.g. authorization code, resource
|
The provided authorization grant (e.g. authorization code, resource
|
||||||
owner credentials) or refresh token is invalid, expired, revoked, does
|
owner credentials) or refresh token is invalid, expired, revoked, does
|
||||||
not match the redirection URI used in the authorization request, or was
|
not match the redirection URI used in the authorization request, or was
|
||||||
issued to another client.
|
issued to another client.
|
||||||
|
|
||||||
|
https://tools.ietf.org/html/rfc6749#section-5.2
|
||||||
"""
|
"""
|
||||||
error = 'invalid_grant'
|
error = 'invalid_grant'
|
||||||
status_code = 401
|
status_code = 400
|
||||||
|
|
||||||
|
|
||||||
class UnauthorizedClientError(OAuth2Error):
|
class UnauthorizedClientError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""The authenticated client is not authorized to use this authorization
|
The authenticated client is not authorized to use this authorization
|
||||||
grant type.
|
grant type.
|
||||||
"""
|
"""
|
||||||
error = 'unauthorized_client'
|
error = 'unauthorized_client'
|
||||||
status_code = 401
|
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedGrantTypeError(OAuth2Error):
|
class UnsupportedGrantTypeError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""The authorization grant type is not supported by the authorization
|
The authorization grant type is not supported by the authorization
|
||||||
server.
|
server.
|
||||||
"""
|
"""
|
||||||
error = 'unsupported_grant_type'
|
error = 'unsupported_grant_type'
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedTokenTypeError(OAuth2Error):
|
class UnsupportedTokenTypeError(OAuth2Error):
|
||||||
|
"""
|
||||||
"""The authorization server does not support the revocation of the
|
The authorization server does not support the hint of the
|
||||||
presented token type. I.e. the client tried to revoke an access token
|
presented token type. I.e. the client tried to revoke an access token
|
||||||
on a server not supporting this feature.
|
on a server not supporting this feature.
|
||||||
"""
|
"""
|
||||||
error = 'unsupported_token_type'
|
error = 'unsupported_token_type'
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidTokenError(OAuth2Error):
|
||||||
|
"""
|
||||||
|
The access token provided is expired, revoked, malformed, or
|
||||||
|
invalid for other reasons. The resource SHOULD respond with
|
||||||
|
the HTTP 401 (Unauthorized) status code. The client MAY
|
||||||
|
request a new access token and retry the protected resource
|
||||||
|
request.
|
||||||
|
"""
|
||||||
|
error = 'invalid_token'
|
||||||
|
status_code = 401
|
||||||
|
description = ("The access token provided is expired, revoked, malformed, "
|
||||||
|
"or invalid for other reasons.")
|
||||||
|
|
||||||
|
|
||||||
|
class InsufficientScopeError(OAuth2Error):
|
||||||
|
"""
|
||||||
|
The request requires higher privileges than provided by the
|
||||||
|
access token. The resource server SHOULD respond with the HTTP
|
||||||
|
403 (Forbidden) status code and MAY include the "scope"
|
||||||
|
attribute with the scope necessary to access the protected
|
||||||
|
resource.
|
||||||
|
"""
|
||||||
|
error = 'insufficient_scope'
|
||||||
|
status_code = 403
|
||||||
|
description = ("The request requires higher privileges than provided by "
|
||||||
|
"the access token.")
|
||||||
|
|
||||||
|
|
||||||
|
class ConsentRequired(OAuth2Error):
|
||||||
|
"""
|
||||||
|
The Authorization Server requires End-User consent.
|
||||||
|
|
||||||
|
This error MAY be returned when the prompt parameter value in the
|
||||||
|
Authentication Request is none, but the Authentication Request cannot be
|
||||||
|
completed without displaying a user interface for End-User consent.
|
||||||
|
"""
|
||||||
|
error = 'consent_required'
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequired(OAuth2Error):
|
||||||
|
"""
|
||||||
|
The Authorization Server requires End-User authentication.
|
||||||
|
|
||||||
|
This error MAY be returned when the prompt parameter value in the
|
||||||
|
Authentication Request is none, but the Authentication Request cannot be
|
||||||
|
completed without displaying a user interface for End-User authentication.
|
||||||
|
"""
|
||||||
|
error = 'login_required'
|
||||||
|
|
||||||
|
|
||||||
|
class CustomOAuth2Error(OAuth2Error):
|
||||||
|
"""
|
||||||
|
This error is a placeholder for all custom errors not described by the RFC.
|
||||||
|
Some of the popular OAuth2 providers are using custom errors.
|
||||||
|
"""
|
||||||
|
def __init__(self, error, *args, **kwargs):
|
||||||
|
self.error = error
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def raise_from_error(error, params=None):
|
def raise_from_error(error, params=None):
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
|
@ -269,3 +400,4 @@ def raise_from_error(error, params=None):
|
||||||
for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
|
for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
|
||||||
if cls.error == error:
|
if cls.error == error:
|
||||||
raise cls(**kwargs)
|
raise cls(**kwargs)
|
||||||
|
raise CustomOAuth2Error(error=error, **kwargs)
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.grant_types
|
oauthlib.oauth2.rfc6749.grant_types
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from .authorization_code import AuthorizationCodeGrant
|
from .authorization_code import AuthorizationCodeGrant
|
||||||
from .implicit import ImplicitGrant
|
|
||||||
from .resource_owner_password_credentials import ResourceOwnerPasswordCredentialsGrant
|
|
||||||
from .client_credentials import ClientCredentialsGrant
|
from .client_credentials import ClientCredentialsGrant
|
||||||
|
from .implicit import ImplicitGrant
|
||||||
from .refresh_token import RefreshTokenGrant
|
from .refresh_token import RefreshTokenGrant
|
||||||
|
from .resource_owner_password_credentials import (
|
||||||
|
ResourceOwnerPasswordCredentialsGrant,
|
||||||
|
)
|
||||||
|
|
|
@ -1,23 +1,66 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.grant_types
|
oauthlib.oauth2.rfc6749.grant_types
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals, absolute_import
|
import base64
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oauthlib import common
|
from oauthlib import common
|
||||||
from oauthlib.uri_validate import is_absolute_uri
|
|
||||||
|
|
||||||
from .base import GrantTypeBase
|
|
||||||
from .. import errors
|
from .. import errors
|
||||||
from ..request_validator import RequestValidator
|
from .base import GrantTypeBase
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def code_challenge_method_s256(verifier, challenge):
|
||||||
|
"""
|
||||||
|
If the "code_challenge_method" from `Section 4.3`_ was "S256", the
|
||||||
|
received "code_verifier" is hashed by SHA-256, base64url-encoded, and
|
||||||
|
then compared to the "code_challenge", i.e.:
|
||||||
|
|
||||||
|
BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge
|
||||||
|
|
||||||
|
How to implement a base64url-encoding
|
||||||
|
function without padding, based upon the standard base64-encoding
|
||||||
|
function that uses padding.
|
||||||
|
|
||||||
|
To be concrete, example C# code implementing these functions is shown
|
||||||
|
below. Similar code could be used in other languages.
|
||||||
|
|
||||||
|
static string base64urlencode(byte [] arg)
|
||||||
|
{
|
||||||
|
string s = Convert.ToBase64String(arg); // Regular base64 encoder
|
||||||
|
s = s.Split('=')[0]; // Remove any trailing '='s
|
||||||
|
s = s.Replace('+', '-'); // 62nd char of encoding
|
||||||
|
s = s.Replace('/', '_'); // 63rd char of encoding
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
In python urlsafe_b64encode is already replacing '+' and '/', but preserve
|
||||||
|
the trailing '='. So we have to remove it.
|
||||||
|
|
||||||
|
.. _`Section 4.3`: https://tools.ietf.org/html/rfc7636#section-4.3
|
||||||
|
"""
|
||||||
|
return base64.urlsafe_b64encode(
|
||||||
|
hashlib.sha256(verifier.encode()).digest()
|
||||||
|
).decode().rstrip('=') == challenge
|
||||||
|
|
||||||
|
|
||||||
|
def code_challenge_method_plain(verifier, challenge):
|
||||||
|
"""
|
||||||
|
If the "code_challenge_method" from `Section 4.3`_ was "plain", they are
|
||||||
|
compared directly, i.e.:
|
||||||
|
|
||||||
|
code_verifier == code_challenge.
|
||||||
|
|
||||||
|
.. _`Section 4.3`: https://tools.ietf.org/html/rfc7636#section-4.3
|
||||||
|
"""
|
||||||
|
return verifier == challenge
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationCodeGrant(GrantTypeBase):
|
class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
|
|
||||||
"""`Authorization Code Grant`_
|
"""`Authorization Code Grant`_
|
||||||
|
@ -92,15 +135,35 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
step (C). If valid, the authorization server responds back with
|
step (C). If valid, the authorization server responds back with
|
||||||
an access token and, optionally, a refresh token.
|
an access token and, optionally, a refresh token.
|
||||||
|
|
||||||
.. _`Authorization Code Grant`: http://tools.ietf.org/html/rfc6749#section-4.1
|
OAuth 2.0 public clients utilizing the Authorization Code Grant are
|
||||||
|
susceptible to the authorization code interception attack.
|
||||||
|
|
||||||
|
A technique to mitigate against the threat through the use of Proof Key for Code
|
||||||
|
Exchange (PKCE, pronounced "pixy") is implemented in the current oauthlib
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
.. _`Authorization Code Grant`: https://tools.ietf.org/html/rfc6749#section-4.1
|
||||||
|
.. _`PKCE`: https://tools.ietf.org/html/rfc7636
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, request_validator=None, refresh_token=True):
|
default_response_mode = 'query'
|
||||||
self.request_validator = request_validator or RequestValidator()
|
response_types = ['code']
|
||||||
self.refresh_token = refresh_token
|
|
||||||
|
# This dict below is private because as RFC mention it:
|
||||||
|
# "S256" is Mandatory To Implement (MTI) on the server.
|
||||||
|
#
|
||||||
|
_code_challenge_methods = {
|
||||||
|
'plain': code_challenge_method_plain,
|
||||||
|
'S256': code_challenge_method_s256
|
||||||
|
}
|
||||||
|
|
||||||
def create_authorization_code(self, request):
|
def create_authorization_code(self, request):
|
||||||
"""Generates an authorization grant represented as a dictionary."""
|
"""
|
||||||
|
Generates an authorization grant represented as a dictionary.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
grant = {'code': common.generate_token()}
|
grant = {'code': common.generate_token()}
|
||||||
if hasattr(request, 'state') and request.state:
|
if hasattr(request, 'state') and request.state:
|
||||||
grant['state'] = request.state
|
grant['state'] = request.state
|
||||||
|
@ -115,7 +178,10 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||||
|
|
||||||
response_type
|
response_type
|
||||||
REQUIRED. Value MUST be set to "code".
|
REQUIRED. Value MUST be set to "code" for standard OAuth2
|
||||||
|
authorization flow. For OpenID Connect it must be one of
|
||||||
|
"code token", "code id_token", or "code token id_token" - we
|
||||||
|
essentially test that "code" appears in the response_type.
|
||||||
client_id
|
client_id
|
||||||
REQUIRED. The client identifier as described in `Section 2.2`_.
|
REQUIRED. The client identifier as described in `Section 2.2`_.
|
||||||
redirect_uri
|
redirect_uri
|
||||||
|
@ -134,12 +200,12 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
HTTP redirection response, or by other means available to it via the
|
HTTP redirection response, or by other means available to it via the
|
||||||
user-agent.
|
user-agent.
|
||||||
|
|
||||||
:param request: oauthlib.commong.Request
|
:param request: OAuthlib request.
|
||||||
:param token_handler: A token handler instace, for example of type
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
oauthlib.oauth2.BearerToken.
|
oauthlib.oauth2.BearerToken.
|
||||||
:returns: headers, body, status
|
:returns: headers, body, status
|
||||||
:raises: FatalClientError on invalid redirect URI or client id.
|
:raises: FatalClientError on invalid redirect URI or client id.
|
||||||
ValueError if scopes are not set on the request object.
|
|
||||||
|
|
||||||
A few examples::
|
A few examples::
|
||||||
|
|
||||||
|
@ -150,12 +216,6 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
>>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken
|
>>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken
|
||||||
>>> token = BearerToken(your_validator)
|
>>> token = BearerToken(your_validator)
|
||||||
>>> grant = AuthorizationCodeGrant(your_validator)
|
>>> grant = AuthorizationCodeGrant(your_validator)
|
||||||
>>> grant.create_authorization_response(request, token)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<stdin>", line 1, in <module>
|
|
||||||
File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response
|
|
||||||
raise ValueError('Scopes must be set on post auth.')
|
|
||||||
ValueError: Scopes must be set on post auth.
|
|
||||||
>>> request.scopes = ['authorized', 'in', 'some', 'form']
|
>>> request.scopes = ['authorized', 'in', 'some', 'form']
|
||||||
>>> grant.create_authorization_response(request, token)
|
>>> grant.create_authorization_response(request, token)
|
||||||
(u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400)
|
(u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400)
|
||||||
|
@ -174,18 +234,13 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request
|
File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request
|
||||||
oauthlib.oauth2.rfc6749.errors.InvalidClientIdError
|
oauthlib.oauth2.rfc6749.errors.InvalidClientIdError
|
||||||
|
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
.. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
|
||||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
.. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
.. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# request.scopes is only mandated in post auth and both pre and
|
|
||||||
# post auth use validate_authorization_request
|
|
||||||
if not request.scopes:
|
|
||||||
raise ValueError('Scopes must be set on post auth.')
|
|
||||||
|
|
||||||
self.validate_authorization_request(request)
|
self.validate_authorization_request(request)
|
||||||
log.debug('Pre resource owner authorization validation ok for %r.',
|
log.debug('Pre resource owner authorization validation ok for %r.',
|
||||||
request)
|
request)
|
||||||
|
@ -205,17 +260,23 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
# the authorization server informs the client by adding the following
|
# the authorization server informs the client by adding the following
|
||||||
# parameters to the query component of the redirection URI using the
|
# parameters to the query component of the redirection URI using the
|
||||||
# "application/x-www-form-urlencoded" format, per Appendix B:
|
# "application/x-www-form-urlencoded" format, per Appendix B:
|
||||||
# http://tools.ietf.org/html/rfc6749#appendix-B
|
# https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
except errors.OAuth2Error as e:
|
except errors.OAuth2Error as e:
|
||||||
log.debug('Client error during validation of %r. %r.', request, e)
|
log.debug('Client error during validation of %r. %r.', request, e)
|
||||||
request.redirect_uri = request.redirect_uri or self.error_uri
|
request.redirect_uri = request.redirect_uri or self.error_uri
|
||||||
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples)}, None, 302
|
redirect_uri = common.add_params_to_uri(
|
||||||
|
request.redirect_uri, e.twotuples,
|
||||||
|
fragment=request.response_mode == "fragment")
|
||||||
|
return {'Location': redirect_uri}, None, 302
|
||||||
|
|
||||||
grant = self.create_authorization_code(request)
|
grant = self.create_authorization_code(request)
|
||||||
|
for modifier in self._code_modifiers:
|
||||||
|
grant = modifier(grant, token_handler, request)
|
||||||
log.debug('Saving grant %r for %r.', grant, request)
|
log.debug('Saving grant %r for %r.', grant, request)
|
||||||
self.request_validator.save_authorization_code(
|
self.request_validator.save_authorization_code(
|
||||||
request.client_id, grant, request)
|
request.client_id, grant, request)
|
||||||
return {'Location': common.add_params_to_uri(request.redirect_uri, grant.items())}, None, 302
|
return self.prepare_authorization_response(
|
||||||
|
request, grant, {}, None, 302)
|
||||||
|
|
||||||
def create_token_response(self, request, token_handler):
|
def create_token_response(self, request, token_handler):
|
||||||
"""Validate the authorization code.
|
"""Validate the authorization code.
|
||||||
|
@ -225,20 +286,28 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
MUST deny the request and SHOULD revoke (when possible) all tokens
|
MUST deny the request and SHOULD revoke (when possible) all tokens
|
||||||
previously issued based on that authorization code. The authorization
|
previously issued based on that authorization code. The authorization
|
||||||
code is bound to the client identifier and redirection URI.
|
code is bound to the client identifier and redirection URI.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
headers = {
|
headers = self._get_default_headers()
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Cache-Control': 'no-store',
|
|
||||||
'Pragma': 'no-cache',
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
self.validate_token_request(request)
|
self.validate_token_request(request)
|
||||||
log.debug('Token request validation ok for %r.', request)
|
log.debug('Token request validation ok for %r.', request)
|
||||||
except errors.OAuth2Error as e:
|
except errors.OAuth2Error as e:
|
||||||
log.debug('Client error during validation of %r. %r.', request, e)
|
log.debug('Client error during validation of %r. %r.', request, e)
|
||||||
|
headers.update(e.headers)
|
||||||
return headers, e.json, e.status_code
|
return headers, e.json, e.status_code
|
||||||
|
|
||||||
token = token_handler.create_token(request, refresh_token=self.refresh_token)
|
token = token_handler.create_token(request, refresh_token=self.refresh_token)
|
||||||
|
|
||||||
|
for modifier in self._token_modifiers:
|
||||||
|
token = modifier(token, token_handler, request)
|
||||||
|
|
||||||
|
self.request_validator.save_token(token, request)
|
||||||
self.request_validator.invalidate_authorization_code(
|
self.request_validator.invalidate_authorization_code(
|
||||||
request.client_id, request.code, request)
|
request.client_id, request.code, request)
|
||||||
return headers, json.dumps(token), 200
|
return headers, json.dumps(token), 200
|
||||||
|
@ -255,6 +324,9 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
missing. These must be caught by the provider and handled, how this
|
missing. These must be caught by the provider and handled, how this
|
||||||
is done is outside of the scope of OAuthLib but showing an error
|
is done is outside of the scope of OAuthLib but showing an error
|
||||||
page describing the issue is a good idea.
|
page describing the issue is a good idea.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# First check for fatal errors
|
# First check for fatal errors
|
||||||
|
@ -275,7 +347,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
|
raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
|
||||||
|
|
||||||
# REQUIRED. The client identifier as described in Section 2.2.
|
# REQUIRED. The client identifier as described in Section 2.2.
|
||||||
# http://tools.ietf.org/html/rfc6749#section-2.2
|
# https://tools.ietf.org/html/rfc6749#section-2.2
|
||||||
if not request.client_id:
|
if not request.client_id:
|
||||||
raise errors.MissingClientIdError(request=request)
|
raise errors.MissingClientIdError(request=request)
|
||||||
|
|
||||||
|
@ -283,25 +355,13 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
raise errors.InvalidClientIdError(request=request)
|
raise errors.InvalidClientIdError(request=request)
|
||||||
|
|
||||||
# OPTIONAL. As described in Section 3.1.2.
|
# OPTIONAL. As described in Section 3.1.2.
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.1.2
|
# https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
log.debug('Validating redirection uri %s for client %s.',
|
log.debug('Validating redirection uri %s for client %s.',
|
||||||
request.redirect_uri, request.client_id)
|
request.redirect_uri, request.client_id)
|
||||||
if request.redirect_uri is not None:
|
|
||||||
request.using_default_redirect_uri = False
|
|
||||||
log.debug('Using provided redirect_uri %s', request.redirect_uri)
|
|
||||||
if not is_absolute_uri(request.redirect_uri):
|
|
||||||
raise errors.InvalidRedirectURIError(request=request)
|
|
||||||
|
|
||||||
if not self.request_validator.validate_redirect_uri(
|
# OPTIONAL. As described in Section 3.1.2.
|
||||||
request.client_id, request.redirect_uri, request):
|
# https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
raise errors.MismatchingRedirectURIError(request=request)
|
self._handle_redirects(request)
|
||||||
else:
|
|
||||||
request.redirect_uri = self.request_validator.get_default_redirect_uri(
|
|
||||||
request.client_id, request)
|
|
||||||
request.using_default_redirect_uri = True
|
|
||||||
log.debug('Using default redirect_uri %s.', request.redirect_uri)
|
|
||||||
if not request.redirect_uri:
|
|
||||||
raise errors.MissingRedirectURIError(request=request)
|
|
||||||
|
|
||||||
# Then check for normal errors.
|
# Then check for normal errors.
|
||||||
|
|
||||||
|
@ -310,16 +370,21 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
# the authorization server informs the client by adding the following
|
# the authorization server informs the client by adding the following
|
||||||
# parameters to the query component of the redirection URI using the
|
# parameters to the query component of the redirection URI using the
|
||||||
# "application/x-www-form-urlencoded" format, per Appendix B.
|
# "application/x-www-form-urlencoded" format, per Appendix B.
|
||||||
# http://tools.ietf.org/html/rfc6749#appendix-B
|
# https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
|
|
||||||
# Note that the correct parameters to be added are automatically
|
# Note that the correct parameters to be added are automatically
|
||||||
# populated through the use of specific exceptions.
|
# populated through the use of specific exceptions.
|
||||||
|
|
||||||
|
request_info = {}
|
||||||
|
for validator in self.custom_validators.pre_auth:
|
||||||
|
request_info.update(validator(request))
|
||||||
|
|
||||||
# REQUIRED.
|
# REQUIRED.
|
||||||
if request.response_type is None:
|
if request.response_type is None:
|
||||||
raise errors.MissingResponseTypeError(request=request)
|
raise errors.MissingResponseTypeError(request=request)
|
||||||
# Value MUST be set to "code".
|
# Value MUST be set to "code" or one of the OpenID authorization code including
|
||||||
elif request.response_type != 'code':
|
# response_types "code token", "code id_token", "code token id_token"
|
||||||
|
elif not 'code' in request.response_type and request.response_type != 'none':
|
||||||
raise errors.UnsupportedResponseTypeError(request=request)
|
raise errors.UnsupportedResponseTypeError(request=request)
|
||||||
|
|
||||||
if not self.request_validator.validate_response_type(request.client_id,
|
if not self.request_validator.validate_response_type(request.client_id,
|
||||||
|
@ -330,23 +395,52 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
request.client_id, request.response_type)
|
request.client_id, request.response_type)
|
||||||
raise errors.UnauthorizedClientError(request=request)
|
raise errors.UnauthorizedClientError(request=request)
|
||||||
|
|
||||||
|
# OPTIONAL. Validate PKCE request or reply with "error"/"invalid_request"
|
||||||
|
# https://tools.ietf.org/html/rfc6749#section-4.4.1
|
||||||
|
if self.request_validator.is_pkce_required(request.client_id, request) is True:
|
||||||
|
if request.code_challenge is None:
|
||||||
|
raise errors.MissingCodeChallengeError(request=request)
|
||||||
|
|
||||||
|
if request.code_challenge is not None:
|
||||||
|
request_info["code_challenge"] = request.code_challenge
|
||||||
|
|
||||||
|
# OPTIONAL, defaults to "plain" if not present in the request.
|
||||||
|
if request.code_challenge_method is None:
|
||||||
|
request.code_challenge_method = "plain"
|
||||||
|
|
||||||
|
if request.code_challenge_method not in self._code_challenge_methods:
|
||||||
|
raise errors.UnsupportedCodeChallengeMethodError(request=request)
|
||||||
|
request_info["code_challenge_method"] = request.code_challenge_method
|
||||||
|
|
||||||
# OPTIONAL. The scope of the access request as described by Section 3.3
|
# OPTIONAL. The scope of the access request as described by Section 3.3
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.3
|
# https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
self.validate_scopes(request)
|
self.validate_scopes(request)
|
||||||
|
|
||||||
return request.scopes, {
|
request_info.update({
|
||||||
'client_id': request.client_id,
|
'client_id': request.client_id,
|
||||||
'redirect_uri': request.redirect_uri,
|
'redirect_uri': request.redirect_uri,
|
||||||
'response_type': request.response_type,
|
'response_type': request.response_type,
|
||||||
'state': request.state,
|
'state': request.state,
|
||||||
'request': request,
|
'request': request
|
||||||
}
|
})
|
||||||
|
|
||||||
|
for validator in self.custom_validators.post_auth:
|
||||||
|
request_info.update(validator(request))
|
||||||
|
|
||||||
|
return request.scopes, request_info
|
||||||
|
|
||||||
def validate_token_request(self, request):
|
def validate_token_request(self, request):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
# REQUIRED. Value MUST be set to "authorization_code".
|
# REQUIRED. Value MUST be set to "authorization_code".
|
||||||
if request.grant_type != 'authorization_code':
|
if request.grant_type not in ('authorization_code', 'openid'):
|
||||||
raise errors.UnsupportedGrantTypeError(request=request)
|
raise errors.UnsupportedGrantTypeError(request=request)
|
||||||
|
|
||||||
|
for validator in self.custom_validators.pre_token:
|
||||||
|
validator(request)
|
||||||
|
|
||||||
if request.code is None:
|
if request.code is None:
|
||||||
raise errors.InvalidRequestError(
|
raise errors.InvalidRequestError(
|
||||||
description='Missing code parameter.', request=request)
|
description='Missing code parameter.', request=request)
|
||||||
|
@ -361,14 +455,14 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
# credentials (or assigned other authentication requirements), the
|
# credentials (or assigned other authentication requirements), the
|
||||||
# client MUST authenticate with the authorization server as described
|
# client MUST authenticate with the authorization server as described
|
||||||
# in Section 3.2.1.
|
# in Section 3.2.1.
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.2.1
|
# https://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||||
if not self.request_validator.authenticate_client(request):
|
if not self.request_validator.authenticate_client(request):
|
||||||
log.debug('Client authentication failed, %r.', request)
|
log.debug('Client authentication failed, %r.', request)
|
||||||
raise errors.InvalidClientError(request=request)
|
raise errors.InvalidClientError(request=request)
|
||||||
elif not self.request_validator.authenticate_client_id(request.client_id, request):
|
elif not self.request_validator.authenticate_client_id(request.client_id, request):
|
||||||
# REQUIRED, if the client is not authenticating with the
|
# REQUIRED, if the client is not authenticating with the
|
||||||
# authorization server as described in Section 3.2.1.
|
# authorization server as described in Section 3.2.1.
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.2.1
|
# https://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||||
log.debug('Client authentication failed, %r.', request)
|
log.debug('Client authentication failed, %r.', request)
|
||||||
raise errors.InvalidClientError(request=request)
|
raise errors.InvalidClientError(request=request)
|
||||||
|
|
||||||
|
@ -377,6 +471,8 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
'request.client.client_id attribute '
|
'request.client.client_id attribute '
|
||||||
'in authenticate_client.')
|
'in authenticate_client.')
|
||||||
|
|
||||||
|
request.client_id = request.client_id or request.client.client_id
|
||||||
|
|
||||||
# Ensure client is authorized use of this grant type
|
# Ensure client is authorized use of this grant type
|
||||||
self.validate_grant_type(request)
|
self.validate_grant_type(request)
|
||||||
|
|
||||||
|
@ -388,6 +484,33 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
request.client_id, request.client, request.scopes)
|
request.client_id, request.client, request.scopes)
|
||||||
raise errors.InvalidGrantError(request=request)
|
raise errors.InvalidGrantError(request=request)
|
||||||
|
|
||||||
|
# OPTIONAL. Validate PKCE code_verifier
|
||||||
|
challenge = self.request_validator.get_code_challenge(request.code, request)
|
||||||
|
|
||||||
|
if challenge is not None:
|
||||||
|
if request.code_verifier is None:
|
||||||
|
raise errors.MissingCodeVerifierError(request=request)
|
||||||
|
|
||||||
|
challenge_method = self.request_validator.get_code_challenge_method(request.code, request)
|
||||||
|
if challenge_method is None:
|
||||||
|
raise errors.InvalidGrantError(request=request, description="Challenge method not found")
|
||||||
|
|
||||||
|
if challenge_method not in self._code_challenge_methods:
|
||||||
|
raise errors.ServerError(
|
||||||
|
description="code_challenge_method {} is not supported.".format(challenge_method),
|
||||||
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.validate_code_challenge(challenge,
|
||||||
|
challenge_method,
|
||||||
|
request.code_verifier):
|
||||||
|
log.debug('request provided a invalid code_verifier.')
|
||||||
|
raise errors.InvalidGrantError(request=request)
|
||||||
|
elif self.request_validator.is_pkce_required(request.client_id, request) is True:
|
||||||
|
if request.code_verifier is None:
|
||||||
|
raise errors.MissingCodeVerifierError(request=request)
|
||||||
|
raise errors.InvalidGrantError(request=request, description="Challenge not found")
|
||||||
|
|
||||||
for attr in ('user', 'scopes'):
|
for attr in ('user', 'scopes'):
|
||||||
if getattr(request, attr, None) is None:
|
if getattr(request, attr, None) is None:
|
||||||
log.debug('request.%s was not set on code validation.', attr)
|
log.debug('request.%s was not set on code validation.', attr)
|
||||||
|
@ -395,8 +518,28 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
# REQUIRED, if the "redirect_uri" parameter was included in the
|
# REQUIRED, if the "redirect_uri" parameter was included in the
|
||||||
# authorization request as described in Section 4.1.1, and their
|
# authorization request as described in Section 4.1.1, and their
|
||||||
# values MUST be identical.
|
# values MUST be identical.
|
||||||
|
if request.redirect_uri is None:
|
||||||
|
request.using_default_redirect_uri = True
|
||||||
|
request.redirect_uri = self.request_validator.get_default_redirect_uri(
|
||||||
|
request.client_id, request)
|
||||||
|
log.debug('Using default redirect_uri %s.', request.redirect_uri)
|
||||||
|
if not request.redirect_uri:
|
||||||
|
raise errors.MissingRedirectURIError(request=request)
|
||||||
|
else:
|
||||||
|
request.using_default_redirect_uri = False
|
||||||
|
log.debug('Using provided redirect_uri %s', request.redirect_uri)
|
||||||
|
|
||||||
if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
|
if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
|
||||||
request.redirect_uri, request.client):
|
request.redirect_uri, request.client,
|
||||||
|
request):
|
||||||
log.debug('Redirect_uri (%r) invalid for client %r (%r).',
|
log.debug('Redirect_uri (%r) invalid for client %r (%r).',
|
||||||
request.redirect_uri, request.client_id, request.client)
|
request.redirect_uri, request.client_id, request.client)
|
||||||
raise errors.AccessDeniedError(request=request)
|
raise errors.MismatchingRedirectURIError(request=request)
|
||||||
|
|
||||||
|
for validator in self.custom_validators.post_token:
|
||||||
|
validator(request)
|
||||||
|
|
||||||
|
def validate_code_challenge(self, challenge, challenge_method, verifier):
|
||||||
|
if challenge_method in self._code_challenge_methods:
|
||||||
|
return self._code_challenge_methods[challenge_method](verifier, challenge)
|
||||||
|
raise NotImplementedError('Unknown challenge_method %s' % challenge_method)
|
||||||
|
|
|
@ -1,28 +1,158 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.grant_types
|
oauthlib.oauth2.rfc6749.grant_types
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from oauthlib.common import add_params_to_uri
|
||||||
from oauthlib.oauth2.rfc6749 import errors, utils
|
from oauthlib.oauth2.rfc6749 import errors, utils
|
||||||
|
from oauthlib.uri_validate import is_absolute_uri
|
||||||
|
|
||||||
|
from ..request_validator import RequestValidator
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GrantTypeBase(object):
|
class ValidatorsContainer:
|
||||||
|
"""
|
||||||
|
Container object for holding custom validator callables to be invoked
|
||||||
|
as part of the grant type `validate_authorization_request()` or
|
||||||
|
`validate_authorization_request()` methods on the various grant types.
|
||||||
|
|
||||||
|
Authorization validators must be callables that take a request object and
|
||||||
|
return a dict, which may contain items to be added to the `request_info`
|
||||||
|
returned from the grant_type after validation.
|
||||||
|
|
||||||
|
Token validators must be callables that take a request object and
|
||||||
|
return None.
|
||||||
|
|
||||||
|
Both authorization validators and token validators may raise OAuth2
|
||||||
|
exceptions if validation conditions fail.
|
||||||
|
|
||||||
|
Authorization validators added to `pre_auth` will be run BEFORE
|
||||||
|
the standard validations (but after the critical ones that raise
|
||||||
|
fatal errors) as part of `validate_authorization_request()`
|
||||||
|
|
||||||
|
Authorization validators added to `post_auth` will be run AFTER
|
||||||
|
the standard validations as part of `validate_authorization_request()`
|
||||||
|
|
||||||
|
Token validators added to `pre_token` will be run BEFORE
|
||||||
|
the standard validations as part of `validate_token_request()`
|
||||||
|
|
||||||
|
Token validators added to `post_token` will be run AFTER
|
||||||
|
the standard validations as part of `validate_token_request()`
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
>>> def my_auth_validator(request):
|
||||||
|
... return {'myval': True}
|
||||||
|
>>> auth_code_grant = AuthorizationCodeGrant(request_validator)
|
||||||
|
>>> auth_code_grant.custom_validators.pre_auth.append(my_auth_validator)
|
||||||
|
>>> def my_token_validator(request):
|
||||||
|
... if not request.everything_okay:
|
||||||
|
... raise errors.OAuth2Error("uh-oh")
|
||||||
|
>>> auth_code_grant.custom_validators.post_token.append(my_token_validator)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, post_auth, post_token,
|
||||||
|
pre_auth, pre_token):
|
||||||
|
self.pre_auth = pre_auth
|
||||||
|
self.post_auth = post_auth
|
||||||
|
self.pre_token = pre_token
|
||||||
|
self.post_token = post_token
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_pre(self):
|
||||||
|
return chain(self.pre_auth, self.pre_token)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_post(self):
|
||||||
|
return chain(self.post_auth, self.post_token)
|
||||||
|
|
||||||
|
|
||||||
|
class GrantTypeBase:
|
||||||
error_uri = None
|
error_uri = None
|
||||||
request_validator = None
|
request_validator = None
|
||||||
|
default_response_mode = 'fragment'
|
||||||
|
refresh_token = True
|
||||||
|
response_types = ['code']
|
||||||
|
|
||||||
|
def __init__(self, request_validator=None, **kwargs):
|
||||||
|
self.request_validator = request_validator or RequestValidator()
|
||||||
|
|
||||||
|
# Transforms class variables into instance variables:
|
||||||
|
self.response_types = self.response_types
|
||||||
|
self.refresh_token = self.refresh_token
|
||||||
|
self._setup_custom_validators(kwargs)
|
||||||
|
self._code_modifiers = []
|
||||||
|
self._token_modifiers = []
|
||||||
|
|
||||||
|
for kw, val in kwargs.items():
|
||||||
|
setattr(self, kw, val)
|
||||||
|
|
||||||
|
def _setup_custom_validators(self, kwargs):
|
||||||
|
post_auth = kwargs.get('post_auth', [])
|
||||||
|
post_token = kwargs.get('post_token', [])
|
||||||
|
pre_auth = kwargs.get('pre_auth', [])
|
||||||
|
pre_token = kwargs.get('pre_token', [])
|
||||||
|
if not hasattr(self, 'validate_authorization_request'):
|
||||||
|
if post_auth or pre_auth:
|
||||||
|
msg = ("{} does not support authorization validators. Use "
|
||||||
|
"token validators instead.").format(self.__class__.__name__)
|
||||||
|
raise ValueError(msg)
|
||||||
|
# Using tuples here because they can't be appended to:
|
||||||
|
post_auth, pre_auth = (), ()
|
||||||
|
self.custom_validators = ValidatorsContainer(post_auth, post_token,
|
||||||
|
pre_auth, pre_token)
|
||||||
|
|
||||||
|
def register_response_type(self, response_type):
|
||||||
|
self.response_types.append(response_type)
|
||||||
|
|
||||||
|
def register_code_modifier(self, modifier):
|
||||||
|
self._code_modifiers.append(modifier)
|
||||||
|
|
||||||
|
def register_token_modifier(self, modifier):
|
||||||
|
self._token_modifiers.append(modifier)
|
||||||
|
|
||||||
def create_authorization_response(self, request, token_handler):
|
def create_authorization_response(self, request, token_handler):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
def create_token_response(self, request, token_handler):
|
def create_token_response(self, request, token_handler):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def add_token(self, token, token_handler, request):
|
||||||
|
"""
|
||||||
|
:param token:
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
|
# Only add a hybrid access token on auth step if asked for
|
||||||
|
if not request.response_type in ["token", "code token", "id_token token", "code id_token token"]:
|
||||||
|
return token
|
||||||
|
|
||||||
|
token.update(token_handler.create_token(request, refresh_token=False))
|
||||||
|
return token
|
||||||
|
|
||||||
def validate_grant_type(self, request):
|
def validate_grant_type(self, request):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
client_id = getattr(request, 'client_id', None)
|
client_id = getattr(request, 'client_id', None)
|
||||||
if not self.request_validator.validate_grant_type(client_id,
|
if not self.request_validator.validate_grant_type(client_id,
|
||||||
request.grant_type, request.client, request):
|
request.grant_type, request.client, request):
|
||||||
|
@ -31,6 +161,10 @@ class GrantTypeBase(object):
|
||||||
raise errors.UnauthorizedClientError(request=request)
|
raise errors.UnauthorizedClientError(request=request)
|
||||||
|
|
||||||
def validate_scopes(self, request):
|
def validate_scopes(self, request):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
if not request.scopes:
|
if not request.scopes:
|
||||||
request.scopes = utils.scope_to_list(request.scope) or utils.scope_to_list(
|
request.scopes = utils.scope_to_list(request.scope) or utils.scope_to_list(
|
||||||
self.request_validator.get_default_scopes(request.client_id, request))
|
self.request_validator.get_default_scopes(request.client_id, request))
|
||||||
|
@ -39,3 +173,78 @@ class GrantTypeBase(object):
|
||||||
if not self.request_validator.validate_scopes(request.client_id,
|
if not self.request_validator.validate_scopes(request.client_id,
|
||||||
request.scopes, request.client, request):
|
request.scopes, request.client, request):
|
||||||
raise errors.InvalidScopeError(request=request)
|
raise errors.InvalidScopeError(request=request)
|
||||||
|
|
||||||
|
def prepare_authorization_response(self, request, token, headers, body, status):
|
||||||
|
"""Place token according to response mode.
|
||||||
|
|
||||||
|
Base classes can define a default response mode for their authorization
|
||||||
|
response by overriding the static `default_response_mode` member.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token:
|
||||||
|
:param headers:
|
||||||
|
:param body:
|
||||||
|
:param status:
|
||||||
|
"""
|
||||||
|
request.response_mode = request.response_mode or self.default_response_mode
|
||||||
|
|
||||||
|
if request.response_mode not in ('query', 'fragment'):
|
||||||
|
log.debug('Overriding invalid response mode %s with %s',
|
||||||
|
request.response_mode, self.default_response_mode)
|
||||||
|
request.response_mode = self.default_response_mode
|
||||||
|
|
||||||
|
token_items = token.items()
|
||||||
|
|
||||||
|
if request.response_type == 'none':
|
||||||
|
state = token.get('state', None)
|
||||||
|
if state:
|
||||||
|
token_items = [('state', state)]
|
||||||
|
else:
|
||||||
|
token_items = []
|
||||||
|
|
||||||
|
if request.response_mode == 'query':
|
||||||
|
headers['Location'] = add_params_to_uri(
|
||||||
|
request.redirect_uri, token_items, fragment=False)
|
||||||
|
return headers, body, status
|
||||||
|
|
||||||
|
if request.response_mode == 'fragment':
|
||||||
|
headers['Location'] = add_params_to_uri(
|
||||||
|
request.redirect_uri, token_items, fragment=True)
|
||||||
|
return headers, body, status
|
||||||
|
|
||||||
|
raise NotImplementedError(
|
||||||
|
'Subclasses must set a valid default_response_mode')
|
||||||
|
|
||||||
|
def _get_default_headers(self):
|
||||||
|
"""Create default headers for grant responses."""
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Cache-Control': 'no-store',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _handle_redirects(self, request):
|
||||||
|
if request.redirect_uri is not None:
|
||||||
|
request.using_default_redirect_uri = False
|
||||||
|
log.debug('Using provided redirect_uri %s', request.redirect_uri)
|
||||||
|
if not is_absolute_uri(request.redirect_uri):
|
||||||
|
raise errors.InvalidRedirectURIError(request=request)
|
||||||
|
|
||||||
|
# The authorization server MUST verify that the redirection URI
|
||||||
|
# to which it will redirect the access token matches a
|
||||||
|
# redirection URI registered by the client as described in
|
||||||
|
# Section 3.1.2.
|
||||||
|
# https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
|
if not self.request_validator.validate_redirect_uri(
|
||||||
|
request.client_id, request.redirect_uri, request):
|
||||||
|
raise errors.MismatchingRedirectURIError(request=request)
|
||||||
|
else:
|
||||||
|
request.redirect_uri = self.request_validator.get_default_redirect_uri(
|
||||||
|
request.client_id, request)
|
||||||
|
request.using_default_redirect_uri = True
|
||||||
|
log.debug('Using default redirect_uri %s.', request.redirect_uri)
|
||||||
|
if not request.redirect_uri:
|
||||||
|
raise errors.MissingRedirectURIError(request=request)
|
||||||
|
if not is_absolute_uri(request.redirect_uri):
|
||||||
|
raise errors.InvalidRedirectURIError(request=request)
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.grant_types
|
oauthlib.oauth2.rfc6749.grant_types
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .base import GrantTypeBase
|
|
||||||
from .. import errors
|
from .. import errors
|
||||||
from ..request_validator import RequestValidator
|
from .base import GrantTypeBase
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -47,42 +43,54 @@ class ClientCredentialsGrant(GrantTypeBase):
|
||||||
(B) The authorization server authenticates the client, and if valid,
|
(B) The authorization server authenticates the client, and if valid,
|
||||||
issues an access token.
|
issues an access token.
|
||||||
|
|
||||||
.. _`Client Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.4
|
.. _`Client Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.4
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, request_validator=None):
|
|
||||||
self.request_validator = request_validator or RequestValidator()
|
|
||||||
|
|
||||||
def create_token_response(self, request, token_handler):
|
def create_token_response(self, request, token_handler):
|
||||||
"""Return token or error in JSON format.
|
"""Return token or error in JSON format.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
|
||||||
If the access token request is valid and authorized, the
|
If the access token request is valid and authorized, the
|
||||||
authorization server issues an access token as described in
|
authorization server issues an access token as described in
|
||||||
`Section 5.1`_. A refresh token SHOULD NOT be included. If the request
|
`Section 5.1`_. A refresh token SHOULD NOT be included. If the request
|
||||||
failed client authentication or is invalid, the authorization server
|
failed client authentication or is invalid, the authorization server
|
||||||
returns an error response as described in `Section 5.2`_.
|
returns an error response as described in `Section 5.2`_.
|
||||||
|
|
||||||
.. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
|
.. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
|
||||||
.. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
|
.. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
|
||||||
"""
|
"""
|
||||||
headers = {
|
headers = self._get_default_headers()
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Cache-Control': 'no-store',
|
|
||||||
'Pragma': 'no-cache',
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
log.debug('Validating access token request, %r.', request)
|
log.debug('Validating access token request, %r.', request)
|
||||||
self.validate_token_request(request)
|
self.validate_token_request(request)
|
||||||
except errors.OAuth2Error as e:
|
except errors.OAuth2Error as e:
|
||||||
log.debug('Client error in token request. %s.', e)
|
log.debug('Client error in token request. %s.', e)
|
||||||
|
headers.update(e.headers)
|
||||||
return headers, e.json, e.status_code
|
return headers, e.json, e.status_code
|
||||||
|
|
||||||
token = token_handler.create_token(request, refresh_token=False)
|
token = token_handler.create_token(request, refresh_token=False)
|
||||||
|
|
||||||
|
for modifier in self._token_modifiers:
|
||||||
|
token = modifier(token)
|
||||||
|
|
||||||
|
self.request_validator.save_token(token, request)
|
||||||
|
|
||||||
log.debug('Issuing token to client id %r (%r), %r.',
|
log.debug('Issuing token to client id %r (%r), %r.',
|
||||||
request.client_id, request.client, token)
|
request.client_id, request.client, token)
|
||||||
return headers, json.dumps(token), 200
|
return headers, json.dumps(token), 200
|
||||||
|
|
||||||
def validate_token_request(self, request):
|
def validate_token_request(self, request):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
|
for validator in self.custom_validators.pre_token:
|
||||||
|
validator(request)
|
||||||
|
|
||||||
if not getattr(request, 'grant_type', None):
|
if not getattr(request, 'grant_type', None):
|
||||||
raise errors.InvalidRequestError('Request is missing grant type.',
|
raise errors.InvalidRequestError('Request is missing grant type.',
|
||||||
request=request)
|
request=request)
|
||||||
|
@ -107,6 +115,9 @@ class ClientCredentialsGrant(GrantTypeBase):
|
||||||
# Ensure client is authorized use of this grant type
|
# Ensure client is authorized use of this grant type
|
||||||
self.validate_grant_type(request)
|
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
|
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)
|
self.validate_scopes(request)
|
||||||
|
|
||||||
|
for validator in self.custom_validators.post_token:
|
||||||
|
validator(request)
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.grant_types
|
oauthlib.oauth2.rfc6749.grant_types
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from oauthlib import common
|
from oauthlib import common
|
||||||
from oauthlib.uri_validate import is_absolute_uri
|
|
||||||
|
|
||||||
from .base import GrantTypeBase
|
|
||||||
from .. import errors
|
from .. import errors
|
||||||
from ..request_validator import RequestValidator
|
from .base import GrantTypeBase
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -112,22 +107,29 @@ class ImplicitGrant(GrantTypeBase):
|
||||||
See `Section 10.3`_ and `Section 10.16`_ for important security considerations
|
See `Section 10.3`_ and `Section 10.16`_ for important security considerations
|
||||||
when using the implicit grant.
|
when using the implicit grant.
|
||||||
|
|
||||||
.. _`Implicit Grant`: http://tools.ietf.org/html/rfc6749#section-4.2
|
.. _`Implicit Grant`: https://tools.ietf.org/html/rfc6749#section-4.2
|
||||||
.. _`Section 10.3`: http://tools.ietf.org/html/rfc6749#section-10.3
|
.. _`Section 10.3`: https://tools.ietf.org/html/rfc6749#section-10.3
|
||||||
.. _`Section 10.16`: http://tools.ietf.org/html/rfc6749#section-10.16
|
.. _`Section 10.16`: https://tools.ietf.org/html/rfc6749#section-10.16
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, request_validator=None):
|
response_types = ['token']
|
||||||
self.request_validator = request_validator or RequestValidator()
|
grant_allows_refresh_token = False
|
||||||
|
|
||||||
def create_authorization_response(self, request, token_handler):
|
def create_authorization_response(self, request, token_handler):
|
||||||
"""Create an authorization response.
|
"""Create an authorization response.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
|
||||||
The client constructs the request URI by adding the following
|
The client constructs the request URI by adding the following
|
||||||
parameters to the query component of the authorization endpoint URI
|
parameters to the query component of the authorization endpoint URI
|
||||||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||||
|
|
||||||
response_type
|
response_type
|
||||||
REQUIRED. Value MUST be set to "token".
|
REQUIRED. Value MUST be set to "token" for standard OAuth2 implicit flow
|
||||||
|
or "id_token token" or just "id_token" for OIDC implicit flow
|
||||||
|
|
||||||
client_id
|
client_id
|
||||||
REQUIRED. The client identifier as described in `Section 2.2`_.
|
REQUIRED. The client identifier as described in `Section 2.2`_.
|
||||||
|
@ -152,17 +154,22 @@ class ImplicitGrant(GrantTypeBase):
|
||||||
access token matches a redirection URI registered by the client as
|
access token matches a redirection URI registered by the client as
|
||||||
described in `Section 3.1.2`_.
|
described in `Section 3.1.2`_.
|
||||||
|
|
||||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
.. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
|
||||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
.. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
.. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
"""
|
"""
|
||||||
return self.create_token_response(request, token_handler)
|
return self.create_token_response(request, token_handler)
|
||||||
|
|
||||||
def create_token_response(self, request, token_handler):
|
def create_token_response(self, request, token_handler):
|
||||||
"""Return token or error embedded in the URI fragment.
|
"""Return token or error embedded in the URI fragment.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
|
||||||
If the resource owner grants the access request, the authorization
|
If the resource owner grants the access request, the authorization
|
||||||
server issues an access token and delivers it to the client by adding
|
server issues an access token and delivers it to the client by adding
|
||||||
the following parameters to the fragment component of the redirection
|
the following parameters to the fragment component of the redirection
|
||||||
|
@ -195,16 +202,11 @@ class ImplicitGrant(GrantTypeBase):
|
||||||
|
|
||||||
The authorization server MUST NOT issue a refresh token.
|
The authorization server MUST NOT issue a refresh token.
|
||||||
|
|
||||||
.. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
|
.. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
|
.. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# request.scopes is only mandated in post auth and both pre and
|
|
||||||
# post auth use validate_authorization_request
|
|
||||||
if not request.scopes:
|
|
||||||
raise ValueError('Scopes must be set on post auth.')
|
|
||||||
|
|
||||||
self.validate_token_request(request)
|
self.validate_token_request(request)
|
||||||
|
|
||||||
# If the request fails due to a missing, invalid, or mismatching
|
# If the request fails due to a missing, invalid, or mismatching
|
||||||
|
@ -222,22 +224,47 @@ class ImplicitGrant(GrantTypeBase):
|
||||||
# the authorization server informs the client by adding the following
|
# the authorization server informs the client by adding the following
|
||||||
# parameters to the fragment component of the redirection URI using the
|
# parameters to the fragment component of the redirection URI using the
|
||||||
# "application/x-www-form-urlencoded" format, per Appendix B:
|
# "application/x-www-form-urlencoded" format, per Appendix B:
|
||||||
# http://tools.ietf.org/html/rfc6749#appendix-B
|
# https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
except errors.OAuth2Error as e:
|
except errors.OAuth2Error as e:
|
||||||
log.debug('Client error during validation of %r. %r.', request, e)
|
log.debug('Client error during validation of %r. %r.', request, e)
|
||||||
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
|
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
|
||||||
fragment=True)}, None, 302
|
fragment=True)}, None, 302
|
||||||
|
|
||||||
|
# In OIDC implicit flow it is possible to have a request_type that does not include the access_token!
|
||||||
|
# "id_token token" - return the access token and the id token
|
||||||
|
# "id_token" - don't return the access token
|
||||||
|
if "token" in request.response_type.split():
|
||||||
token = token_handler.create_token(request, refresh_token=False)
|
token = token_handler.create_token(request, refresh_token=False)
|
||||||
return {'Location': common.add_params_to_uri(request.redirect_uri, token.items(),
|
else:
|
||||||
fragment=True)}, None, 302
|
token = {}
|
||||||
|
|
||||||
|
if request.state is not None:
|
||||||
|
token['state'] = request.state
|
||||||
|
|
||||||
|
for modifier in self._token_modifiers:
|
||||||
|
token = modifier(token, token_handler, request)
|
||||||
|
|
||||||
|
# In OIDC implicit flow it is possible to have a request_type that does
|
||||||
|
# not include the access_token! In this case there is no need to save a token.
|
||||||
|
if "token" in request.response_type.split():
|
||||||
|
self.request_validator.save_token(token, request)
|
||||||
|
|
||||||
|
return self.prepare_authorization_response(
|
||||||
|
request, token, {}, None, 302)
|
||||||
|
|
||||||
def validate_authorization_request(self, request):
|
def validate_authorization_request(self, request):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
return self.validate_token_request(request)
|
return self.validate_token_request(request)
|
||||||
|
|
||||||
def validate_token_request(self, request):
|
def validate_token_request(self, request):
|
||||||
"""Check the token request for normal and fatal errors.
|
"""Check the token request for normal and fatal errors.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
This method is very similar to validate_authorization_request in
|
This method is very similar to validate_authorization_request in
|
||||||
the AuthorizationCodeGrant but differ in a few subtle areas.
|
the AuthorizationCodeGrant but differ in a few subtle areas.
|
||||||
|
|
||||||
|
@ -270,7 +297,7 @@ class ImplicitGrant(GrantTypeBase):
|
||||||
raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
|
raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
|
||||||
|
|
||||||
# REQUIRED. The client identifier as described in Section 2.2.
|
# REQUIRED. The client identifier as described in Section 2.2.
|
||||||
# http://tools.ietf.org/html/rfc6749#section-2.2
|
# https://tools.ietf.org/html/rfc6749#section-2.2
|
||||||
if not request.client_id:
|
if not request.client_id:
|
||||||
raise errors.MissingClientIdError(request=request)
|
raise errors.MissingClientIdError(request=request)
|
||||||
|
|
||||||
|
@ -278,39 +305,20 @@ class ImplicitGrant(GrantTypeBase):
|
||||||
raise errors.InvalidClientIdError(request=request)
|
raise errors.InvalidClientIdError(request=request)
|
||||||
|
|
||||||
# OPTIONAL. As described in Section 3.1.2.
|
# OPTIONAL. As described in Section 3.1.2.
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.1.2
|
# https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
if request.redirect_uri is not None:
|
self._handle_redirects(request)
|
||||||
request.using_default_redirect_uri = False
|
|
||||||
log.debug('Using provided redirect_uri %s', request.redirect_uri)
|
|
||||||
if not is_absolute_uri(request.redirect_uri):
|
|
||||||
raise errors.InvalidRedirectURIError(request=request)
|
|
||||||
|
|
||||||
# The authorization server MUST verify that the redirection URI
|
|
||||||
# to which it will redirect the access token matches a
|
|
||||||
# redirection URI registered by the client as described in
|
|
||||||
# Section 3.1.2.
|
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.1.2
|
|
||||||
if not self.request_validator.validate_redirect_uri(
|
|
||||||
request.client_id, request.redirect_uri, request):
|
|
||||||
raise errors.MismatchingRedirectURIError(request=request)
|
|
||||||
else:
|
|
||||||
request.redirect_uri = self.request_validator.get_default_redirect_uri(
|
|
||||||
request.client_id, request)
|
|
||||||
request.using_default_redirect_uri = True
|
|
||||||
log.debug('Using default redirect_uri %s.', request.redirect_uri)
|
|
||||||
if not request.redirect_uri:
|
|
||||||
raise errors.MissingRedirectURIError(request=request)
|
|
||||||
if not is_absolute_uri(request.redirect_uri):
|
|
||||||
raise errors.InvalidRedirectURIError(request=request)
|
|
||||||
|
|
||||||
# Then check for normal errors.
|
# Then check for normal errors.
|
||||||
|
|
||||||
|
request_info = self._run_custom_validators(request,
|
||||||
|
self.custom_validators.all_pre)
|
||||||
|
|
||||||
# If the resource owner denies the access request or if the request
|
# If the resource owner denies the access request or if the request
|
||||||
# fails for reasons other than a missing or invalid redirection URI,
|
# fails for reasons other than a missing or invalid redirection URI,
|
||||||
# the authorization server informs the client by adding the following
|
# the authorization server informs the client by adding the following
|
||||||
# parameters to the fragment component of the redirection URI using the
|
# parameters to the fragment component of the redirection URI using the
|
||||||
# "application/x-www-form-urlencoded" format, per Appendix B.
|
# "application/x-www-form-urlencoded" format, per Appendix B.
|
||||||
# http://tools.ietf.org/html/rfc6749#appendix-B
|
# https://tools.ietf.org/html/rfc6749#appendix-B
|
||||||
|
|
||||||
# Note that the correct parameters to be added are automatically
|
# Note that the correct parameters to be added are automatically
|
||||||
# populated through the use of specific exceptions
|
# populated through the use of specific exceptions
|
||||||
|
@ -318,8 +326,8 @@ class ImplicitGrant(GrantTypeBase):
|
||||||
# REQUIRED.
|
# REQUIRED.
|
||||||
if request.response_type is None:
|
if request.response_type is None:
|
||||||
raise errors.MissingResponseTypeError(request=request)
|
raise errors.MissingResponseTypeError(request=request)
|
||||||
# Value MUST be set to "token".
|
# Value MUST be one of our registered types: "token" by default or if using OIDC "id_token" or "id_token token"
|
||||||
elif request.response_type != 'token':
|
elif not set(request.response_type.split()).issubset(self.response_types):
|
||||||
raise errors.UnsupportedResponseTypeError(request=request)
|
raise errors.UnsupportedResponseTypeError(request=request)
|
||||||
|
|
||||||
log.debug('Validating use of response_type token for client %r (%r).',
|
log.debug('Validating use of response_type token for client %r (%r).',
|
||||||
|
@ -333,13 +341,36 @@ class ImplicitGrant(GrantTypeBase):
|
||||||
raise errors.UnauthorizedClientError(request=request)
|
raise errors.UnauthorizedClientError(request=request)
|
||||||
|
|
||||||
# OPTIONAL. The scope of the access request as described by Section 3.3
|
# OPTIONAL. The scope of the access request as described by Section 3.3
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.3
|
# https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
self.validate_scopes(request)
|
self.validate_scopes(request)
|
||||||
|
|
||||||
return request.scopes, {
|
request_info.update({
|
||||||
'client_id': request.client_id,
|
'client_id': request.client_id,
|
||||||
'redirect_uri': request.redirect_uri,
|
'redirect_uri': request.redirect_uri,
|
||||||
'response_type': request.response_type,
|
'response_type': request.response_type,
|
||||||
'state': request.state,
|
'state': request.state,
|
||||||
'request': request,
|
'request': request,
|
||||||
}
|
})
|
||||||
|
|
||||||
|
request_info = self._run_custom_validators(
|
||||||
|
request,
|
||||||
|
self.custom_validators.all_post,
|
||||||
|
request_info
|
||||||
|
)
|
||||||
|
|
||||||
|
return request.scopes, request_info
|
||||||
|
|
||||||
|
def _run_custom_validators(self,
|
||||||
|
request,
|
||||||
|
validations,
|
||||||
|
request_info=None):
|
||||||
|
# Make a copy so we don't modify the existing request_info dict
|
||||||
|
request_info = {} if request_info is None else request_info.copy()
|
||||||
|
# For implicit grant, auth_validators and token_validators are
|
||||||
|
# basically equivalent since the token is returned from the
|
||||||
|
# authorization endpoint.
|
||||||
|
for validator in validations:
|
||||||
|
result = validator(request)
|
||||||
|
if result is not None:
|
||||||
|
request_info.update(result)
|
||||||
|
return request_info
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.grant_types
|
oauthlib.oauth2.rfc6749.grant_types
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .base import GrantTypeBase
|
|
||||||
from .. import errors, utils
|
from .. import errors, utils
|
||||||
from ..request_validator import RequestValidator
|
from .base import GrantTypeBase
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -19,16 +15,25 @@ class RefreshTokenGrant(GrantTypeBase):
|
||||||
|
|
||||||
"""`Refresh token grant`_
|
"""`Refresh token grant`_
|
||||||
|
|
||||||
.. _`Refresh token grant`: http://tools.ietf.org/html/rfc6749#section-6
|
.. _`Refresh token grant`: https://tools.ietf.org/html/rfc6749#section-6
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, request_validator=None, issue_new_refresh_tokens=True):
|
def __init__(self, request_validator=None,
|
||||||
self.request_validator = request_validator or RequestValidator()
|
issue_new_refresh_tokens=True,
|
||||||
self.issue_new_refresh_tokens = issue_new_refresh_tokens
|
**kwargs):
|
||||||
|
super().__init__(
|
||||||
|
request_validator,
|
||||||
|
issue_new_refresh_tokens=issue_new_refresh_tokens,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
def create_token_response(self, request, token_handler):
|
def create_token_response(self, request, token_handler):
|
||||||
"""Create a new access token from a refresh_token.
|
"""Create a new access token from a refresh_token.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
|
||||||
If valid and authorized, the authorization server issues an access
|
If valid and authorized, the authorization server issues an access
|
||||||
token as described in `Section 5.1`_. If the request failed
|
token as described in `Section 5.1`_. If the request failed
|
||||||
verification or is invalid, the authorization server returns an error
|
verification or is invalid, the authorization server returns an error
|
||||||
|
@ -42,31 +47,42 @@ class RefreshTokenGrant(GrantTypeBase):
|
||||||
identical to that of the refresh token included by the client in the
|
identical to that of the refresh token included by the client in the
|
||||||
request.
|
request.
|
||||||
|
|
||||||
.. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
|
.. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
|
||||||
.. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
|
.. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
|
||||||
"""
|
"""
|
||||||
headers = {
|
headers = self._get_default_headers()
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Cache-Control': 'no-store',
|
|
||||||
'Pragma': 'no-cache',
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
log.debug('Validating refresh token request, %r.', request)
|
log.debug('Validating refresh token request, %r.', request)
|
||||||
self.validate_token_request(request)
|
self.validate_token_request(request)
|
||||||
except errors.OAuth2Error as e:
|
except errors.OAuth2Error as e:
|
||||||
|
log.debug('Client error in token request, %s.', e)
|
||||||
|
headers.update(e.headers)
|
||||||
return headers, e.json, e.status_code
|
return headers, e.json, e.status_code
|
||||||
|
|
||||||
token = token_handler.create_token(request,
|
token = token_handler.create_token(request,
|
||||||
refresh_token=self.issue_new_refresh_tokens)
|
refresh_token=self.issue_new_refresh_tokens)
|
||||||
|
|
||||||
|
for modifier in self._token_modifiers:
|
||||||
|
token = modifier(token)
|
||||||
|
|
||||||
|
self.request_validator.save_token(token, request)
|
||||||
|
|
||||||
log.debug('Issuing new token to client id %r (%r), %r.',
|
log.debug('Issuing new token to client id %r (%r), %r.',
|
||||||
request.client_id, request.client, token)
|
request.client_id, request.client, token)
|
||||||
return headers, json.dumps(token), 200
|
return headers, json.dumps(token), 200
|
||||||
|
|
||||||
def validate_token_request(self, request):
|
def validate_token_request(self, request):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
# REQUIRED. Value MUST be set to "refresh_token".
|
# REQUIRED. Value MUST be set to "refresh_token".
|
||||||
if request.grant_type != 'refresh_token':
|
if request.grant_type != 'refresh_token':
|
||||||
raise errors.UnsupportedGrantTypeError(request=request)
|
raise errors.UnsupportedGrantTypeError(request=request)
|
||||||
|
|
||||||
|
for validator in self.custom_validators.pre_token:
|
||||||
|
validator(request)
|
||||||
|
|
||||||
if request.refresh_token is None:
|
if request.refresh_token is None:
|
||||||
raise errors.InvalidRequestError(
|
raise errors.InvalidRequestError(
|
||||||
description='Missing refresh token parameter.',
|
description='Missing refresh token parameter.',
|
||||||
|
@ -78,7 +94,7 @@ class RefreshTokenGrant(GrantTypeBase):
|
||||||
# the client was issued client credentials (or assigned other
|
# the client was issued client credentials (or assigned other
|
||||||
# authentication requirements), the client MUST authenticate with the
|
# authentication requirements), the client MUST authenticate with the
|
||||||
# authorization server as described in Section 3.2.1.
|
# authorization server as described in Section 3.2.1.
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.2.1
|
# https://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||||
if self.request_validator.client_authentication_required(request):
|
if self.request_validator.client_authentication_required(request):
|
||||||
log.debug('Authenticating client, %r.', request)
|
log.debug('Authenticating client, %r.', request)
|
||||||
if not self.request_validator.authenticate_client(request):
|
if not self.request_validator.authenticate_client(request):
|
||||||
|
@ -106,7 +122,7 @@ class RefreshTokenGrant(GrantTypeBase):
|
||||||
|
|
||||||
if request.scope:
|
if request.scope:
|
||||||
request.scopes = utils.scope_to_list(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(
|
and not self.request_validator.is_within_original_scope(
|
||||||
request.scopes, request.refresh_token, request)):
|
request.scopes, request.refresh_token, request)):
|
||||||
log.debug('Refresh token %s lack requested scopes, %r.',
|
log.debug('Refresh token %s lack requested scopes, %r.',
|
||||||
|
@ -114,3 +130,6 @@ class RefreshTokenGrant(GrantTypeBase):
|
||||||
raise errors.InvalidScopeError(request=request)
|
raise errors.InvalidScopeError(request=request)
|
||||||
else:
|
else:
|
||||||
request.scopes = original_scopes
|
request.scopes = original_scopes
|
||||||
|
|
||||||
|
for validator in self.custom_validators.post_token:
|
||||||
|
validator(request)
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.grant_types
|
oauthlib.oauth2.rfc6749.grant_types
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .base import GrantTypeBase
|
|
||||||
from .. import errors
|
from .. import errors
|
||||||
from ..request_validator import RequestValidator
|
from .base import GrantTypeBase
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -67,34 +63,27 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
|
||||||
the resource owner credentials, and if valid, issues an access
|
the resource owner credentials, and if valid, issues an access
|
||||||
token.
|
token.
|
||||||
|
|
||||||
.. _`Resource Owner Password Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.3
|
.. _`Resource Owner Password Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.3
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, request_validator=None, refresh_token=True):
|
|
||||||
"""
|
|
||||||
If the refresh_token keyword argument is False, do not return
|
|
||||||
a refresh token in the response.
|
|
||||||
"""
|
|
||||||
self.request_validator = request_validator or RequestValidator()
|
|
||||||
self.refresh_token = refresh_token
|
|
||||||
|
|
||||||
def create_token_response(self, request, token_handler):
|
def create_token_response(self, request, token_handler):
|
||||||
"""Return token or error in json format.
|
"""Return token or error in json format.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param token_handler: A token handler instance, for example of type
|
||||||
|
oauthlib.oauth2.BearerToken.
|
||||||
|
|
||||||
If the access token request is valid and authorized, the
|
If the access token request is valid and authorized, the
|
||||||
authorization server issues an access token and optional refresh
|
authorization server issues an access token and optional refresh
|
||||||
token as described in `Section 5.1`_. If the request failed client
|
token as described in `Section 5.1`_. If the request failed client
|
||||||
authentication or is invalid, the authorization server returns an
|
authentication or is invalid, the authorization server returns an
|
||||||
error response as described in `Section 5.2`_.
|
error response as described in `Section 5.2`_.
|
||||||
|
|
||||||
.. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
|
.. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
|
||||||
.. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
|
.. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
|
||||||
"""
|
"""
|
||||||
headers = {
|
headers = self._get_default_headers()
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Cache-Control': 'no-store',
|
|
||||||
'Pragma': 'no-cache',
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
if self.request_validator.client_authentication_required(request):
|
if self.request_validator.client_authentication_required(request):
|
||||||
log.debug('Authenticating client, %r.', request)
|
log.debug('Authenticating client, %r.', request)
|
||||||
|
@ -108,15 +97,25 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
|
||||||
self.validate_token_request(request)
|
self.validate_token_request(request)
|
||||||
except errors.OAuth2Error as e:
|
except errors.OAuth2Error as e:
|
||||||
log.debug('Client error in token request, %s.', e)
|
log.debug('Client error in token request, %s.', e)
|
||||||
|
headers.update(e.headers)
|
||||||
return headers, e.json, e.status_code
|
return headers, e.json, e.status_code
|
||||||
|
|
||||||
token = token_handler.create_token(request, self.refresh_token)
|
token = token_handler.create_token(request, self.refresh_token)
|
||||||
|
|
||||||
|
for modifier in self._token_modifiers:
|
||||||
|
token = modifier(token)
|
||||||
|
|
||||||
|
self.request_validator.save_token(token, request)
|
||||||
|
|
||||||
log.debug('Issuing token %r to client id %r (%r) and username %s.',
|
log.debug('Issuing token %r to client id %r (%r) and username %s.',
|
||||||
token, request.client_id, request.client, request.username)
|
token, request.client_id, request.client, request.username)
|
||||||
return headers, json.dumps(token), 200
|
return headers, json.dumps(token), 200
|
||||||
|
|
||||||
def validate_token_request(self, request):
|
def validate_token_request(self, request):
|
||||||
"""
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
The client makes a request to the token endpoint by adding the
|
The client makes a 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 per Appendix B with a character encoding of UTF-8 in the HTTP
|
format per Appendix B with a character encoding of UTF-8 in the HTTP
|
||||||
|
@ -156,9 +155,12 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
|
||||||
brute force attacks (e.g., using rate-limitation or generating
|
brute force attacks (e.g., using rate-limitation or generating
|
||||||
alerts).
|
alerts).
|
||||||
|
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
|
.. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
|
||||||
"""
|
"""
|
||||||
|
for validator in self.custom_validators.pre_token:
|
||||||
|
validator(request)
|
||||||
|
|
||||||
for param in ('grant_type', 'username', 'password'):
|
for param in ('grant_type', 'username', 'password'):
|
||||||
if not getattr(request, param, None):
|
if not getattr(request, param, None):
|
||||||
raise errors.InvalidRequestError(
|
raise errors.InvalidRequestError(
|
||||||
|
@ -192,3 +194,6 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
|
||||||
if request.client:
|
if request.client:
|
||||||
request.client_id = request.client_id or request.client.client_id
|
request.client_id = request.client_id or request.client.client_id
|
||||||
self.validate_scopes(request)
|
self.validate_scopes(request)
|
||||||
|
|
||||||
|
for validator in self.custom_validators.post_token:
|
||||||
|
validator(request)
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.parameters
|
oauthlib.oauth2.rfc6749.parameters
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
|
This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
|
||||||
|
|
||||||
.. _`Section 4`: http://tools.ietf.org/html/rfc6749#section-4
|
.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
try:
|
|
||||||
import urlparse
|
|
||||||
except ImportError:
|
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type
|
|
||||||
|
from oauthlib.common import add_params_to_qs, add_params_to_uri
|
||||||
from oauthlib.signals import scope_changed
|
from oauthlib.signals import scope_changed
|
||||||
from .errors import raise_from_error, MissingTokenError, MissingTokenTypeError
|
|
||||||
from .errors import MismatchingStateError, MissingCodeError
|
from .errors import (
|
||||||
from .errors import InsecureTransportError
|
InsecureTransportError, MismatchingStateError, MissingCodeError,
|
||||||
|
MissingTokenError, MissingTokenTypeError, raise_from_error,
|
||||||
|
)
|
||||||
from .tokens import OAuth2Token
|
from .tokens import OAuth2Token
|
||||||
from .utils import list_to_scope, scope_to_list, is_secure_transport
|
from .utils import is_secure_transport, list_to_scope, scope_to_list
|
||||||
|
|
||||||
|
|
||||||
def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
||||||
|
@ -34,14 +31,14 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
||||||
using the ``application/x-www-form-urlencoded`` format as defined by
|
using the ``application/x-www-form-urlencoded`` format as defined by
|
||||||
[`W3C.REC-html401-19991224`_]:
|
[`W3C.REC-html401-19991224`_]:
|
||||||
|
|
||||||
|
:param uri:
|
||||||
|
:param client_id: The client identifier as described in `Section 2.2`_.
|
||||||
:param response_type: To indicate which OAuth 2 grant/flow is required,
|
:param response_type: To indicate which OAuth 2 grant/flow is required,
|
||||||
"code" and "token".
|
"code" and "token".
|
||||||
:param client_id: The client identifier as described in `Section 2.2`_.
|
|
||||||
:param redirect_uri: The client provided URI to redirect back to after
|
:param redirect_uri: The client provided URI to redirect back to after
|
||||||
authorization as described in `Section 3.1.2`_.
|
authorization as described in `Section 3.1.2`_.
|
||||||
:param scope: The scope of the access request as described by
|
:param scope: The scope of the access request as described by
|
||||||
`Section 3.3`_.
|
`Section 3.3`_.
|
||||||
|
|
||||||
:param state: An opaque value used by the client to maintain
|
:param state: An opaque value used by the client to maintain
|
||||||
state between the request and callback. The authorization
|
state between the request and callback. The authorization
|
||||||
server includes this value when redirecting the user-agent
|
server includes this value when redirecting the user-agent
|
||||||
|
@ -58,11 +55,11 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
||||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
|
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
|
||||||
Host: server.example.com
|
Host: server.example.com
|
||||||
|
|
||||||
.. _`W3C.REC-html401-19991224`: http://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
|
.. _`W3C.REC-html401-19991224`: https://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
|
||||||
.. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
|
.. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
|
||||||
.. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
|
.. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
|
.. _`section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
|
||||||
"""
|
"""
|
||||||
if not is_secure_transport(uri):
|
if not is_secure_transport(uri):
|
||||||
raise InsecureTransportError()
|
raise InsecureTransportError()
|
||||||
|
@ -79,12 +76,12 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
||||||
|
|
||||||
for k in kwargs:
|
for k in kwargs:
|
||||||
if kwargs[k]:
|
if kwargs[k]:
|
||||||
params.append((unicode_type(k), kwargs[k]))
|
params.append((str(k), kwargs[k]))
|
||||||
|
|
||||||
return add_params_to_uri(uri, params)
|
return add_params_to_uri(uri, params)
|
||||||
|
|
||||||
|
|
||||||
def prepare_token_request(grant_type, body='', **kwargs):
|
def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs):
|
||||||
"""Prepare the access token request.
|
"""Prepare the access token request.
|
||||||
|
|
||||||
The client makes a request to the token endpoint by adding the
|
The client makes a request to the token endpoint by adding the
|
||||||
|
@ -93,14 +90,38 @@ def prepare_token_request(grant_type, body='', **kwargs):
|
||||||
|
|
||||||
:param grant_type: To indicate grant type being used, i.e. "password",
|
:param grant_type: To indicate grant type being used, i.e. "password",
|
||||||
"authorization_code" or "client_credentials".
|
"authorization_code" or "client_credentials".
|
||||||
:param body: Existing request body to embed parameters in.
|
|
||||||
:param code: If using authorization code grant, pass the previously
|
:param body: Existing request body (URL encoded string) to embed parameters
|
||||||
obtained authorization code as the ``code`` argument.
|
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
|
||||||
|
if the client is not authenticating with the
|
||||||
|
authorization server as described in
|
||||||
|
`Section 3.2.1`_.
|
||||||
|
:type include_client_id: Boolean
|
||||||
|
|
||||||
|
:param client_id: Unicode client identifier. Will only appear if
|
||||||
|
`include_client_id` is True. *
|
||||||
|
|
||||||
|
:param client_secret: Unicode client secret. Will only appear if set to a
|
||||||
|
value that is not `None`. Invoking this function with
|
||||||
|
an empty string will send an empty `client_secret`
|
||||||
|
value to the server. *
|
||||||
|
|
||||||
|
:param code: If using authorization_code grant, pass the previously
|
||||||
|
obtained authorization code as the ``code`` argument. *
|
||||||
|
|
||||||
:param redirect_uri: If the "redirect_uri" parameter was included in the
|
:param redirect_uri: If the "redirect_uri" parameter was included in the
|
||||||
authorization request as described in
|
authorization request as described in
|
||||||
`Section 4.1.1`_, and their values MUST be identical.
|
`Section 4.1.1`_, and their values MUST be identical. *
|
||||||
|
|
||||||
:param kwargs: Extra arguments to embed in the request body.
|
:param kwargs: Extra arguments to embed in the request body.
|
||||||
|
|
||||||
|
Parameters marked with a `*` above are not explicit arguments in the
|
||||||
|
function signature, but are specially documented arguments for items
|
||||||
|
appearing in the generic `**kwargs` keyworded input.
|
||||||
|
|
||||||
An example of an authorization code token request body:
|
An example of an authorization code token request body:
|
||||||
|
|
||||||
.. code-block:: http
|
.. code-block:: http
|
||||||
|
@ -108,16 +129,29 @@ def prepare_token_request(grant_type, body='', **kwargs):
|
||||||
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
|
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
|
||||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
|
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
|
||||||
|
|
||||||
.. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
|
.. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
|
||||||
"""
|
"""
|
||||||
params = [('grant_type', grant_type)]
|
params = [('grant_type', grant_type)]
|
||||||
|
|
||||||
if 'scope' in kwargs:
|
if 'scope' in kwargs:
|
||||||
kwargs['scope'] = list_to_scope(kwargs['scope'])
|
kwargs['scope'] = list_to_scope(kwargs['scope'])
|
||||||
|
|
||||||
|
# pull the `client_id` out of the kwargs.
|
||||||
|
client_id = kwargs.pop('client_id', None)
|
||||||
|
if include_client_id:
|
||||||
|
if client_id is not None:
|
||||||
|
params.append(('client_id', client_id))
|
||||||
|
|
||||||
|
# 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(('client_secret', client_secret))
|
||||||
|
|
||||||
|
# this handles: `code`, `redirect_uri`, and other undocumented params
|
||||||
for k in kwargs:
|
for k in kwargs:
|
||||||
if kwargs[k]:
|
if kwargs[k]:
|
||||||
params.append((unicode_type(k), kwargs[k]))
|
params.append((str(k), kwargs[k]))
|
||||||
|
|
||||||
return add_params_to_qs(body, params)
|
return add_params_to_qs(body, params)
|
||||||
|
|
||||||
|
@ -127,18 +161,22 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token",
|
||||||
"""Prepare a token revocation request.
|
"""Prepare a token revocation request.
|
||||||
|
|
||||||
The client constructs the request by including the following parameters
|
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:
|
entity-body:
|
||||||
|
|
||||||
token REQUIRED. The token that the client wants to get revoked.
|
:param token: REQUIRED. The token that the client wants to get revoked.
|
||||||
|
|
||||||
token_type_hint OPTIONAL. A hint about the type of the token submitted
|
:param token_type_hint: OPTIONAL. A hint about the type of the token
|
||||||
for revocation. Clients MAY pass this parameter in order to help the
|
submitted for revocation. Clients MAY pass this
|
||||||
authorization server to optimize the token lookup. If the server is unable
|
parameter in order to help the authorization server
|
||||||
to locate the token using the given hint, it MUST extend its search across
|
to optimize the token lookup. If the server is
|
||||||
all of its supported token types. An authorization server MAY ignore this
|
unable to locate the token using the given hint, it
|
||||||
parameter, particularly if it is able to detect the token type
|
MUST extend its search across all of its supported
|
||||||
automatically. This specification defines two such values:
|
token types. An authorization server MAY ignore
|
||||||
|
this parameter, particularly if it is able to detect
|
||||||
|
the token type automatically.
|
||||||
|
|
||||||
|
This specification defines two values for `token_type_hint`:
|
||||||
|
|
||||||
* access_token: An access token as defined in [RFC6749],
|
* access_token: An access token as defined in [RFC6749],
|
||||||
`Section 1.4`_
|
`Section 1.4`_
|
||||||
|
@ -150,9 +188,9 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token",
|
||||||
specification MAY define other values for this parameter using the
|
specification MAY define other values for this parameter using the
|
||||||
registry defined in `Section 4.1.2`_.
|
registry defined in `Section 4.1.2`_.
|
||||||
|
|
||||||
.. _`Section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
|
.. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4
|
||||||
.. _`Section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
|
.. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5
|
||||||
.. _`Section 4.1.2`: http://tools.ietf.org/html/rfc7009#section-4.1.2
|
.. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not is_secure_transport(url):
|
if not is_secure_transport(url):
|
||||||
|
@ -165,7 +203,7 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token",
|
||||||
|
|
||||||
for k in kwargs:
|
for k in kwargs:
|
||||||
if kwargs[k]:
|
if kwargs[k]:
|
||||||
params.append((unicode_type(k), kwargs[k]))
|
params.append((str(k), kwargs[k]))
|
||||||
|
|
||||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||||
|
|
||||||
|
@ -220,12 +258,15 @@ def parse_authorization_code_response(uri, state=None):
|
||||||
query = urlparse.urlparse(uri).query
|
query = urlparse.urlparse(uri).query
|
||||||
params = dict(urlparse.parse_qsl(query))
|
params = dict(urlparse.parse_qsl(query))
|
||||||
|
|
||||||
if not 'code' in params:
|
|
||||||
raise MissingCodeError("Missing code parameter in response.")
|
|
||||||
|
|
||||||
if state and params.get('state', None) != state:
|
if state and params.get('state', None) != state:
|
||||||
raise MismatchingStateError()
|
raise MismatchingStateError()
|
||||||
|
|
||||||
|
if 'error' in params:
|
||||||
|
raise_from_error(params.get('error'), params)
|
||||||
|
|
||||||
|
if not 'code' in params:
|
||||||
|
raise MissingCodeError("Missing code parameter in response.")
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
@ -261,6 +302,10 @@ def parse_implicit_response(uri, state=None, scope=None):
|
||||||
authorization request. The exact value received from the
|
authorization request. The exact value received from the
|
||||||
client.
|
client.
|
||||||
|
|
||||||
|
:param uri:
|
||||||
|
:param state:
|
||||||
|
:param scope:
|
||||||
|
|
||||||
Similar to the authorization code response, but with a full token provided
|
Similar to the authorization code response, but with a full token provided
|
||||||
in the URL fragment:
|
in the URL fragment:
|
||||||
|
|
||||||
|
@ -276,6 +321,10 @@ def parse_implicit_response(uri, state=None, scope=None):
|
||||||
fragment = urlparse.urlparse(uri).fragment
|
fragment = urlparse.urlparse(uri).fragment
|
||||||
params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
|
params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
|
||||||
|
|
||||||
|
for key in ('expires_in',):
|
||||||
|
if key in params: # cast things to int
|
||||||
|
params[key] = int(params[key])
|
||||||
|
|
||||||
if 'scope' in params:
|
if 'scope' in params:
|
||||||
params['scope'] = scope_to_list(params['scope'])
|
params['scope'] = scope_to_list(params['scope'])
|
||||||
|
|
||||||
|
@ -345,10 +394,10 @@ def parse_token_response(body, scope=None):
|
||||||
"example_parameter":"example_value"
|
"example_parameter":"example_value"
|
||||||
}
|
}
|
||||||
|
|
||||||
.. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
|
.. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
|
||||||
.. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6
|
.. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
|
||||||
.. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
|
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
.. _`RFC4627`: http://tools.ietf.org/html/rfc4627
|
.. _`RFC4627`: https://tools.ietf.org/html/rfc4627
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
params = json.loads(body)
|
params = json.loads(body)
|
||||||
|
@ -356,20 +405,20 @@ def parse_token_response(body, scope=None):
|
||||||
|
|
||||||
# Fall back to URL-encoded string, to support old implementations,
|
# Fall back to URL-encoded string, to support old implementations,
|
||||||
# including (at time of writing) Facebook. See:
|
# including (at time of writing) Facebook. See:
|
||||||
# https://github.com/idan/oauthlib/issues/267
|
# https://github.com/oauthlib/oauthlib/issues/267
|
||||||
|
|
||||||
params = dict(urlparse.parse_qsl(body))
|
params = dict(urlparse.parse_qsl(body))
|
||||||
for key in ('expires_in', 'expires'):
|
for key in ('expires_in',):
|
||||||
if key in params: # cast a couple things to int
|
if key in params: # cast things to int
|
||||||
params[key] = int(params[key])
|
params[key] = int(params[key])
|
||||||
|
|
||||||
if 'scope' in params:
|
if 'scope' in params:
|
||||||
params['scope'] = scope_to_list(params['scope'])
|
params['scope'] = scope_to_list(params['scope'])
|
||||||
|
|
||||||
if 'expires' in params:
|
|
||||||
params['expires_in'] = params.pop('expires')
|
|
||||||
|
|
||||||
if 'expires_in' in params:
|
if 'expires_in' in params:
|
||||||
|
if params['expires_in'] is None:
|
||||||
|
params.pop('expires_in')
|
||||||
|
else:
|
||||||
params['expires_at'] = time.time() + int(params['expires_in'])
|
params['expires_at'] = time.time() + int(params['expires_in'])
|
||||||
|
|
||||||
params = OAuth2Token(params, old_scope=scope)
|
params = OAuth2Token(params, old_scope=scope)
|
||||||
|
@ -378,7 +427,7 @@ def parse_token_response(body, scope=None):
|
||||||
|
|
||||||
|
|
||||||
def validate_token_parameters(params):
|
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:
|
if 'error' in params:
|
||||||
raise_from_error(params.get('error'), params)
|
raise_from_error(params.get('error'), params)
|
||||||
|
|
||||||
|
@ -392,7 +441,7 @@ def validate_token_parameters(params):
|
||||||
# If the issued access token scope is different from the one requested by
|
# If the issued access token scope is different from the one requested by
|
||||||
# the client, the authorization server MUST include the "scope" response
|
# the client, the authorization server MUST include the "scope" response
|
||||||
# parameter to inform the client of the actual scope granted.
|
# parameter to inform the client of the actual scope granted.
|
||||||
# http://tools.ietf.org/html/rfc6749#section-3.3
|
# https://tools.ietf.org/html/rfc6749#section-3.3
|
||||||
if params.scope_changed:
|
if params.scope_changed:
|
||||||
message = 'Scope has changed from "{old}" to "{new}".'.format(
|
message = 'Scope has changed from "{old}" to "{new}".'.format(
|
||||||
old=params.old_scope, new=params.scope,
|
old=params.old_scope, new=params.scope,
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.oauth2.rfc6749.grant_types
|
oauthlib.oauth2.rfc6749.request_validator
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RequestValidator(object):
|
class RequestValidator:
|
||||||
|
|
||||||
def client_authentication_required(self, request, *args, **kwargs):
|
def client_authentication_required(self, request, *args, **kwargs):
|
||||||
"""Determine if client authentication is required for current request.
|
"""Determine if client authentication is required for current request.
|
||||||
|
@ -26,7 +23,8 @@ class RequestValidator(object):
|
||||||
client credentials or whenever Client provided client authentication, see
|
client credentials or whenever Client provided client authentication, see
|
||||||
`Section 6`_
|
`Section 6`_
|
||||||
|
|
||||||
:param request: oauthlib.common.Request
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -34,9 +32,9 @@ class RequestValidator(object):
|
||||||
- Resource Owner Password Credentials Grant
|
- Resource Owner Password Credentials Grant
|
||||||
- Refresh Token Grant
|
- Refresh Token Grant
|
||||||
|
|
||||||
.. _`Section 4.3.2`: http://tools.ietf.org/html/rfc6749#section-4.3.2
|
.. _`Section 4.3.2`: https://tools.ietf.org/html/rfc6749#section-4.3.2
|
||||||
.. _`Section 4.1.3`: http://tools.ietf.org/html/rfc6749#section-4.1.3
|
.. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3
|
||||||
.. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6
|
.. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -51,7 +49,19 @@ class RequestValidator(object):
|
||||||
both body and query can be obtained by direct attribute access, i.e.
|
both body and query can be obtained by direct attribute access, i.e.
|
||||||
request.client_id for client_id in the URL query.
|
request.client_id for client_id in the URL query.
|
||||||
|
|
||||||
:param request: oauthlib.common.Request
|
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
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -60,7 +70,7 @@ class RequestValidator(object):
|
||||||
- Client Credentials Grant
|
- Client Credentials Grant
|
||||||
- Refresh Token Grant
|
- Refresh Token Grant
|
||||||
|
|
||||||
.. _`HTTP Basic Authentication Scheme`: http://tools.ietf.org/html/rfc1945#section-11.1
|
.. _`HTTP Basic Authentication Scheme`: https://tools.ietf.org/html/rfc1945#section-11.1
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
@ -74,7 +84,9 @@ class RequestValidator(object):
|
||||||
to set request.client to the client object associated with the
|
to set request.client to the client object associated with the
|
||||||
given client_id.
|
given client_id.
|
||||||
|
|
||||||
:param request: oauthlib.common.Request
|
:param client_id: Unicode client identifier.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -82,7 +94,7 @@ class RequestValidator(object):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
def confirm_redirect_uri(self, client_id, code, redirect_uri, client,
|
def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
"""Ensure that the authorization process represented by this authorization
|
"""Ensure that the authorization process represented by this authorization
|
||||||
code began with this 'redirect_uri'.
|
code began with this 'redirect_uri'.
|
||||||
|
@ -93,11 +105,12 @@ class RequestValidator(object):
|
||||||
the client's allowed redirect URIs, but against the URI used when the
|
the client's allowed redirect URIs, but against the URI used when the
|
||||||
code was saved.
|
code was saved.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
:param client_id: Unicode client identifier.
|
||||||
:param code: Unicode authorization_code.
|
:param code: Unicode authorization_code.
|
||||||
:param redirect_uri: Unicode absolute URI
|
:param redirect_uri: Unicode absolute URI.
|
||||||
:param client: Client object set by you, see authenticate_client.
|
:param client: Client object set by you, see ``.authenticate_client``.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -108,8 +121,9 @@ class RequestValidator(object):
|
||||||
def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
|
def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
|
||||||
"""Get the default redirect URI for the client.
|
"""Get the default redirect URI for the client.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
:param client_id: Unicode client identifier.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: The default redirect URI for the client
|
:rtype: The default redirect URI for the client
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -121,8 +135,9 @@ class RequestValidator(object):
|
||||||
def get_default_scopes(self, client_id, request, *args, **kwargs):
|
def get_default_scopes(self, client_id, request, *args, **kwargs):
|
||||||
"""Get the default scopes for the client.
|
"""Get the default scopes for the client.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
:param client_id: Unicode client identifier.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: List of default scopes
|
:rtype: List of default scopes
|
||||||
|
|
||||||
Method is used by all core grant types:
|
Method is used by all core grant types:
|
||||||
|
@ -136,8 +151,9 @@ class RequestValidator(object):
|
||||||
def get_original_scopes(self, refresh_token, request, *args, **kwargs):
|
def get_original_scopes(self, refresh_token, request, *args, **kwargs):
|
||||||
"""Get the list of scopes associated with the refresh token.
|
"""Get the list of scopes associated with the refresh token.
|
||||||
|
|
||||||
:param refresh_token: Unicode refresh token
|
:param refresh_token: Unicode refresh token.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: List of scopes.
|
:rtype: List of scopes.
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -156,9 +172,10 @@ class RequestValidator(object):
|
||||||
used in situations where returning all valid scopes from the
|
used in situations where returning all valid scopes from the
|
||||||
get_original_scopes is not practical.
|
get_original_scopes is not practical.
|
||||||
|
|
||||||
:param request_scopes: A list of scopes that were requested by client
|
:param request_scopes: A list of scopes that were requested by client.
|
||||||
:param refresh_token: Unicode refresh_token
|
:param refresh_token: Unicode refresh_token.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -166,12 +183,54 @@ class RequestValidator(object):
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def introspect_token(self, token, token_type_hint, request, *args, **kwargs):
|
||||||
|
"""Introspect an access or refresh token.
|
||||||
|
|
||||||
|
Called once the introspect request is validated. This method should
|
||||||
|
verify the *token* and either return a dictionary with the list of
|
||||||
|
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
|
||||||
|
- token_type : type of the token
|
||||||
|
- exp : integer timestamp indicating when this token will expire
|
||||||
|
- iat : integer timestamp indicating when this token was issued
|
||||||
|
- nbf : integer timestamp indicating when it can be "not-before" used
|
||||||
|
- sub : subject of the token - identifier of the resource owner
|
||||||
|
- aud : list of string identifiers representing the intended audience
|
||||||
|
- iss : string representing issuer of this token
|
||||||
|
- 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`_.
|
||||||
|
|
||||||
|
The implementation can use *token_type_hint* to improve lookup
|
||||||
|
efficency, but must fallback to other types to be compliant with RFC.
|
||||||
|
|
||||||
|
The dict of claims is added to request.token after this method.
|
||||||
|
|
||||||
|
:param token: The token string.
|
||||||
|
:param token_type_hint: access_token or refresh_token.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- Introspect Endpoint (all grants are compatible)
|
||||||
|
|
||||||
|
.. _`Introspect Claims`: https://tools.ietf.org/html/rfc7662#section-2.2
|
||||||
|
.. _`JWT Claims`: https://tools.ietf.org/html/rfc7519#section-4
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
|
def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
|
||||||
"""Invalidate an authorization code after use.
|
"""Invalidate an authorization code after use.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
:param client_id: Unicode client identifier.
|
||||||
:param code: The authorization code grant (request.code).
|
:param code: The authorization code grant (request.code).
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
- Authorization Code Grant
|
- Authorization Code Grant
|
||||||
|
@ -183,7 +242,8 @@ class RequestValidator(object):
|
||||||
|
|
||||||
:param token: The token string.
|
:param token: The token string.
|
||||||
:param token_type_hint: access_token or refresh_token.
|
:param token_type_hint: access_token or refresh_token.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
- Revocation Endpoint
|
- Revocation Endpoint
|
||||||
|
@ -197,7 +257,8 @@ class RequestValidator(object):
|
||||||
or replaced with a new one (rotated). Return True to rotate and
|
or replaced with a new one (rotated). Return True to rotate and
|
||||||
and False for keeping original.
|
and False for keeping original.
|
||||||
|
|
||||||
:param request: oauthlib.common.Request
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -209,30 +270,49 @@ class RequestValidator(object):
|
||||||
"""Persist the authorization_code.
|
"""Persist the authorization_code.
|
||||||
|
|
||||||
The code should at minimum be stored with:
|
The code should at minimum be stored with:
|
||||||
- the client_id (client_id)
|
- the client_id (``client_id``)
|
||||||
- the redirect URI used (request.redirect_uri)
|
- the redirect URI used (``request.redirect_uri``)
|
||||||
- a resource owner / user (request.user)
|
- a resource owner / user (``request.user``)
|
||||||
- the authorized scopes (request.scopes)
|
- the authorized scopes (``request.scopes``)
|
||||||
- the client state, if given (code.get('state'))
|
|
||||||
|
|
||||||
The 'code' argument is actually a dictionary, containing at least a
|
To support PKCE, you MUST associate the code with:
|
||||||
'code' key with the actual authorization code:
|
- Code Challenge (``request.code_challenge``) and
|
||||||
|
- Code Challenge Method (``request.code_challenge_method``)
|
||||||
|
|
||||||
{'code': 'sdf345jsdf0934f'}
|
To support OIDC, you MUST associate the code with:
|
||||||
|
- nonce, if present (``code["nonce"]``)
|
||||||
|
|
||||||
It may also have a 'state' key containing a nonce for the client, if it
|
The ``code`` argument is actually a dictionary, containing at least a
|
||||||
chose to send one. That value should be saved and used in
|
``code`` key with the actual authorization code:
|
||||||
'validate_code'.
|
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
``{'code': 'sdf345jsdf0934f'}``
|
||||||
|
|
||||||
|
It may also have a ``claims`` parameter which, when present, will be a dict
|
||||||
|
deserialized from JSON as described at
|
||||||
|
http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
|
||||||
|
This value should be saved in this method and used again in ``.validate_code``.
|
||||||
|
|
||||||
|
:param client_id: Unicode client identifier.
|
||||||
:param code: A dict of the authorization code grant and, optionally, state.
|
:param code: A dict of the authorization code grant and, optionally, state.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
- Authorization Code Grant
|
- Authorization Code Grant
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def save_token(self, token, request, *args, **kwargs):
|
||||||
|
"""Persist the token with a token type specific method.
|
||||||
|
|
||||||
|
Currently, only save_bearer_token is supported.
|
||||||
|
|
||||||
|
:param token: A (Bearer) token dict.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
|
return self.save_bearer_token(token, request, *args, **kwargs)
|
||||||
|
|
||||||
def save_bearer_token(self, token, request, *args, **kwargs):
|
def save_bearer_token(self, token, request, *args, **kwargs):
|
||||||
"""Persist the Bearer token.
|
"""Persist the Bearer token.
|
||||||
|
|
||||||
|
@ -242,6 +322,7 @@ class RequestValidator(object):
|
||||||
- authorized scopes (request.scopes)
|
- authorized scopes (request.scopes)
|
||||||
- an expiration time
|
- an expiration time
|
||||||
- a refresh token, if issued
|
- a refresh token, if issued
|
||||||
|
- a claims document, if present in request.claims
|
||||||
|
|
||||||
The Bearer token dict may hold a number of items::
|
The Bearer token dict may hold a number of items::
|
||||||
|
|
||||||
|
@ -251,15 +332,27 @@ class RequestValidator(object):
|
||||||
'expires_in': 3600,
|
'expires_in': 3600,
|
||||||
'scope': 'string of space separated authorized scopes',
|
'scope': 'string of space separated authorized scopes',
|
||||||
'refresh_token': '23sdf876234', # if issued
|
'refresh_token': '23sdf876234', # if issued
|
||||||
'state': 'given_by_client', # if supplied by client
|
'state': 'given_by_client', # if supplied by client (implicit ONLY)
|
||||||
}
|
}
|
||||||
|
|
||||||
Note that while "scope" is a string-separated list of authorized scopes,
|
Note that while "scope" is a string-separated list of authorized scopes,
|
||||||
the original list is still available in request.scopes
|
the original list is still available in request.scopes.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
The token dict is passed as a reference so any changes made to the dictionary
|
||||||
:param token: A Bearer token dict
|
will go back to the user. If additional information must return to the client
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
user, and it is only possible to get this information after writing the token
|
||||||
|
to storage, it should be added to the token dictionary. If the token
|
||||||
|
dictionary must be modified but the changes should not go back to the user,
|
||||||
|
a copy of the dictionary must be made before making the changes.
|
||||||
|
|
||||||
|
Also note that if an Authorization Code grant request included a valid claims
|
||||||
|
parameter (for OpenID Connect) then the request.claims property will contain
|
||||||
|
the claims dict, which should be saved for later use when generating the
|
||||||
|
id_token and/or UserInfo response content.
|
||||||
|
|
||||||
|
:param token: A Bearer token dict.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: The default redirect URI for the client
|
:rtype: The default redirect URI for the client
|
||||||
|
|
||||||
Method is used by all core grant types issuing Bearer tokens:
|
Method is used by all core grant types issuing Bearer tokens:
|
||||||
|
@ -275,7 +368,8 @@ class RequestValidator(object):
|
||||||
|
|
||||||
:param token: A string of random characters.
|
:param token: A string of random characters.
|
||||||
:param scopes: A list of scopes associated with the protected resource.
|
:param scopes: A list of scopes associated with the protected resource.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
|
||||||
A key to OAuth 2 security and restricting impact of leaked tokens is
|
A key to OAuth 2 security and restricting impact of leaked tokens is
|
||||||
the short expiration time of tokens, *always ensure the token has not
|
the short expiration time of tokens, *always ensure the token has not
|
||||||
|
@ -309,7 +403,8 @@ class RequestValidator(object):
|
||||||
|
|
||||||
:param token: Unicode Bearer token
|
:param token: Unicode Bearer token
|
||||||
:param scopes: List of scopes (defined by you)
|
:param scopes: List of scopes (defined by you)
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is indirectly used by all core Bearer token issuing grant types:
|
Method is indirectly used by all core Bearer token issuing grant types:
|
||||||
|
@ -327,7 +422,9 @@ class RequestValidator(object):
|
||||||
to set request.client to the client object associated with the
|
to set request.client to the client object associated with the
|
||||||
given client_id.
|
given client_id.
|
||||||
|
|
||||||
:param request: oauthlib.common.Request
|
:param client_id: Unicode client identifier.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -344,16 +441,24 @@ class RequestValidator(object):
|
||||||
with the code in 'save_authorization_code':
|
with the code in 'save_authorization_code':
|
||||||
|
|
||||||
- request.user
|
- request.user
|
||||||
- request.state (if given)
|
|
||||||
- request.scopes
|
- request.scopes
|
||||||
|
- request.claims (if given)
|
||||||
OBS! The request.user attribute should be set to the resource owner
|
OBS! The request.user attribute should be set to the resource owner
|
||||||
associated with this authorization code. Similarly request.scopes
|
associated with this authorization code. Similarly request.scopes
|
||||||
must also be set.
|
must also be set.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
The request.claims property, if it was given, should assigned a dict.
|
||||||
:param code: Unicode authorization code
|
|
||||||
:param client: Client object set by you, see authenticate_client.
|
If PKCE is enabled (see 'is_pkce_required' and 'save_authorization_code')
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
you MUST set the following based on the information stored:
|
||||||
|
- request.code_challenge
|
||||||
|
- request.code_challenge_method
|
||||||
|
|
||||||
|
:param client_id: Unicode client identifier.
|
||||||
|
:param code: Unicode authorization code.
|
||||||
|
:param client: Client object set by you, see ``.authenticate_client``.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -364,10 +469,11 @@ class RequestValidator(object):
|
||||||
def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
|
def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
|
||||||
"""Ensure client is authorized to use the grant_type requested.
|
"""Ensure client is authorized to use the grant_type requested.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
:param client_id: Unicode client identifier.
|
||||||
:param grant_type: Unicode grant type, i.e. authorization_code, password.
|
:param grant_type: Unicode grant type, i.e. authorization_code, password.
|
||||||
:param client: Client object set by you, see authenticate_client.
|
:param client: Client object set by you, see ``.authenticate_client``.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -384,9 +490,10 @@ class RequestValidator(object):
|
||||||
All clients should register the absolute URIs of all URIs they intend
|
All clients should register the absolute URIs of all URIs they intend
|
||||||
to redirect to. The registration is outside of the scope of oauthlib.
|
to redirect to. The registration is outside of the scope of oauthlib.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
:param client_id: Unicode client identifier.
|
||||||
:param redirect_uri: Unicode absolute URI
|
:param redirect_uri: Unicode absolute URI.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -401,9 +508,10 @@ class RequestValidator(object):
|
||||||
OBS! The request.user attribute should be set to the resource owner
|
OBS! The request.user attribute should be set to the resource owner
|
||||||
associated with this refresh token.
|
associated with this refresh token.
|
||||||
|
|
||||||
:param refresh_token: Unicode refresh token
|
:param refresh_token: Unicode refresh token.
|
||||||
:param client: Client object set by you, see authenticate_client.
|
:param client: Client object set by you, see ``.authenticate_client``.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -416,10 +524,11 @@ class RequestValidator(object):
|
||||||
def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs):
|
def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs):
|
||||||
"""Ensure client is authorized to use the response_type requested.
|
"""Ensure client is authorized to use the response_type requested.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
:param client_id: Unicode client identifier.
|
||||||
:param response_type: Unicode response type, i.e. code, token.
|
:param response_type: Unicode response type, i.e. code, token.
|
||||||
:param client: Client object set by you, see authenticate_client.
|
:param client: Client object set by you, see ``.authenticate_client``.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
|
@ -431,10 +540,11 @@ class RequestValidator(object):
|
||||||
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
|
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
|
||||||
"""Ensure the client is authorized access to requested scopes.
|
"""Ensure the client is authorized access to requested scopes.
|
||||||
|
|
||||||
:param client_id: Unicode client identifier
|
:param client_id: Unicode client identifier.
|
||||||
:param scopes: List of scopes (defined by you)
|
:param scopes: List of scopes (defined by you).
|
||||||
:param client: Client object set by you, see authenticate_client.
|
:param client: Client object set by you, see ``.authenticate_client``.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by all core grant types:
|
Method is used by all core grant types:
|
||||||
|
@ -453,13 +563,89 @@ class RequestValidator(object):
|
||||||
not set you will be unable to associate a token with a user in the
|
not set you will be unable to associate a token with a user in the
|
||||||
persistance method used (commonly, save_bearer_token).
|
persistance method used (commonly, save_bearer_token).
|
||||||
|
|
||||||
:param username: Unicode username
|
:param username: Unicode username.
|
||||||
:param password: Unicode password
|
:param password: Unicode password.
|
||||||
:param client: Client object set by you, see authenticate_client.
|
:param client: Client object set by you, see ``.authenticate_client``.
|
||||||
:param request: The HTTP Request (oauthlib.common.Request)
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
:rtype: True or False
|
:rtype: True or False
|
||||||
|
|
||||||
Method is used by:
|
Method is used by:
|
||||||
- Resource Owner Password Credentials Grant
|
- Resource Owner Password Credentials Grant
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def is_pkce_required(self, client_id, request):
|
||||||
|
"""Determine if current request requires PKCE. Default, False.
|
||||||
|
This is called for both "authorization" and "token" requests.
|
||||||
|
|
||||||
|
Override this method by ``return True`` to enable PKCE for everyone.
|
||||||
|
You might want to enable it only for public clients.
|
||||||
|
Note that PKCE can also be used in addition of a client authentication.
|
||||||
|
|
||||||
|
OAuth 2.0 public clients utilizing the Authorization Code Grant are
|
||||||
|
susceptible to the authorization code interception attack. This
|
||||||
|
specification describes the attack as well as a technique to mitigate
|
||||||
|
against the threat through the use of Proof Key for Code Exchange
|
||||||
|
(PKCE, pronounced "pixy"). See `RFC7636`_.
|
||||||
|
|
||||||
|
:param client_id: Client identifier.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: True or False
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- Authorization Code Grant
|
||||||
|
|
||||||
|
.. _`RFC7636`: https://tools.ietf.org/html/rfc7636
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_code_challenge(self, code, request):
|
||||||
|
"""Is called for every "token" requests.
|
||||||
|
|
||||||
|
When the server issues the authorization code in the authorization
|
||||||
|
response, it MUST associate the ``code_challenge`` and
|
||||||
|
``code_challenge_method`` values with the authorization code so it can
|
||||||
|
be verified later.
|
||||||
|
|
||||||
|
Typically, the ``code_challenge`` and ``code_challenge_method`` values
|
||||||
|
are stored in encrypted form in the ``code`` itself but could
|
||||||
|
alternatively be stored on the server associated with the code. The
|
||||||
|
server MUST NOT include the ``code_challenge`` value in client requests
|
||||||
|
in a form that other entities can extract.
|
||||||
|
|
||||||
|
Return the ``code_challenge`` associated to the code.
|
||||||
|
If ``None`` is returned, code is considered to not be associated to any
|
||||||
|
challenges.
|
||||||
|
|
||||||
|
:param code: Authorization code.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: code_challenge string
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- Authorization Code Grant - when PKCE is active
|
||||||
|
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_code_challenge_method(self, code, request):
|
||||||
|
"""Is called during the "token" request processing, when a
|
||||||
|
``code_verifier`` and a ``code_challenge`` has been provided.
|
||||||
|
|
||||||
|
See ``.get_code_challenge``.
|
||||||
|
|
||||||
|
Must return ``plain`` or ``S256``. You can return a custom value if you have
|
||||||
|
implemented your own ``AuthorizationCodeGrant`` class.
|
||||||
|
|
||||||
|
:param code: Authorization code.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: code_challenge_method string
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- Authorization Code Grant - when PKCE is active
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
|
@ -4,21 +4,17 @@ oauthlib.oauth2.rfc6749.tokens
|
||||||
|
|
||||||
This module contains methods for adding two types of access tokens to requests.
|
This module contains methods for adding two types of access tokens to requests.
|
||||||
|
|
||||||
- Bearer http://tools.ietf.org/html/rfc6750
|
- Bearer https://tools.ietf.org/html/rfc6750
|
||||||
- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
- MAC https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from binascii import b2a_base64
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
try:
|
import warnings
|
||||||
from urlparse import urlparse
|
from binascii import b2a_base64
|
||||||
except ImportError:
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type
|
|
||||||
from oauthlib import common
|
from oauthlib import common
|
||||||
|
from oauthlib.common import add_params_to_qs, add_params_to_uri
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
@ -26,7 +22,7 @@ from . import utils
|
||||||
class OAuth2Token(dict):
|
class OAuth2Token(dict):
|
||||||
|
|
||||||
def __init__(self, params, old_scope=None):
|
def __init__(self, params, old_scope=None):
|
||||||
super(OAuth2Token, self).__init__(params)
|
super().__init__(params)
|
||||||
self._new_scope = None
|
self._new_scope = None
|
||||||
if 'scope' in params and params['scope']:
|
if 'scope' in params and params['scope']:
|
||||||
self._new_scope = set(utils.scope_to_list(params['scope']))
|
self._new_scope = set(utils.scope_to_list(params['scope']))
|
||||||
|
@ -92,13 +88,17 @@ def prepare_mac_header(token, uri, key, http_method,
|
||||||
nonce="1336363200:dj83hs9s",
|
nonce="1336363200:dj83hs9s",
|
||||||
mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
|
mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
|
||||||
|
|
||||||
.. _`MAC Access Authentication`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
.. _`MAC Access Authentication`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
||||||
.. _`extension algorithms`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
|
.. _`extension algorithms`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
|
||||||
|
|
||||||
|
:param token:
|
||||||
:param uri: Request URI.
|
:param uri: Request URI.
|
||||||
:param headers: Request headers as a dictionary.
|
|
||||||
:param http_method: HTTP Request method.
|
|
||||||
:param key: MAC given provided by token endpoint.
|
:param key: MAC given provided by token endpoint.
|
||||||
|
:param http_method: HTTP Request method.
|
||||||
|
:param nonce:
|
||||||
|
:param headers: Request headers as a dictionary.
|
||||||
|
:param body:
|
||||||
|
:param ext:
|
||||||
:param hash_algorithm: HMAC algorithm provided by token endpoint.
|
:param hash_algorithm: HMAC algorithm provided by token endpoint.
|
||||||
:param issue_time: Time when the MAC credentials were issued (datetime).
|
:param issue_time: Time when the MAC credentials were issued (datetime).
|
||||||
:param draft: MAC authentication specification version.
|
:param draft: MAC authentication specification version.
|
||||||
|
@ -115,7 +115,7 @@ def prepare_mac_header(token, uri, key, http_method,
|
||||||
raise ValueError('unknown hash algorithm')
|
raise ValueError('unknown hash algorithm')
|
||||||
|
|
||||||
if draft == 0:
|
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())
|
common.generate_nonce())
|
||||||
else:
|
else:
|
||||||
ts = common.generate_timestamp()
|
ts = common.generate_timestamp()
|
||||||
|
@ -152,7 +152,7 @@ def prepare_mac_header(token, uri, key, http_method,
|
||||||
base_string = '\n'.join(base) + '\n'
|
base_string = '\n'.join(base) + '\n'
|
||||||
|
|
||||||
# hmac struggles with unicode strings - http://bugs.python.org/issue5285
|
# hmac struggles with unicode strings - http://bugs.python.org/issue5285
|
||||||
if isinstance(key, unicode_type):
|
if isinstance(key, str):
|
||||||
key = key.encode('utf-8')
|
key = key.encode('utf-8')
|
||||||
sign = hmac.new(key, base_string.encode('utf-8'), h)
|
sign = hmac.new(key, base_string.encode('utf-8'), h)
|
||||||
sign = b2a_base64(sign.digest())[:-1].decode('utf-8')
|
sign = b2a_base64(sign.digest())[:-1].decode('utf-8')
|
||||||
|
@ -179,7 +179,10 @@ def prepare_bearer_uri(token, uri):
|
||||||
|
|
||||||
http://www.example.com/path?access_token=h480djs93hd8
|
http://www.example.com/path?access_token=h480djs93hd8
|
||||||
|
|
||||||
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
.. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
|
||||||
|
|
||||||
|
:param token:
|
||||||
|
:param uri:
|
||||||
"""
|
"""
|
||||||
return add_params_to_uri(uri, [(('access_token', token))])
|
return add_params_to_uri(uri, [(('access_token', token))])
|
||||||
|
|
||||||
|
@ -190,7 +193,10 @@ def prepare_bearer_headers(token, headers=None):
|
||||||
|
|
||||||
Authorization: Bearer h480djs93hd8
|
Authorization: Bearer h480djs93hd8
|
||||||
|
|
||||||
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
.. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
|
||||||
|
|
||||||
|
:param token:
|
||||||
|
:param headers:
|
||||||
"""
|
"""
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
headers['Authorization'] = 'Bearer %s' % token
|
headers['Authorization'] = 'Bearer %s' % token
|
||||||
|
@ -202,16 +208,27 @@ def prepare_bearer_body(token, body=''):
|
||||||
|
|
||||||
access_token=h480djs93hd8
|
access_token=h480djs93hd8
|
||||||
|
|
||||||
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
.. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
|
||||||
|
|
||||||
|
:param token:
|
||||||
|
:param body:
|
||||||
"""
|
"""
|
||||||
return add_params_to_qs(body, [(('access_token', token))])
|
return add_params_to_qs(body, [(('access_token', token))])
|
||||||
|
|
||||||
|
|
||||||
def random_token_generator(request, refresh_token=False):
|
def random_token_generator(request, refresh_token=False):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param refresh_token:
|
||||||
|
"""
|
||||||
return common.generate_token()
|
return common.generate_token()
|
||||||
|
|
||||||
|
|
||||||
def signed_token_generator(private_pem, **kwargs):
|
def signed_token_generator(private_pem, **kwargs):
|
||||||
|
"""
|
||||||
|
:param private_pem:
|
||||||
|
"""
|
||||||
def signed_token_generator(request):
|
def signed_token_generator(request):
|
||||||
request.claims = kwargs
|
request.claims = kwargs
|
||||||
return common.generate_signed_token(private_pem, request)
|
return common.generate_signed_token(private_pem, request)
|
||||||
|
@ -219,15 +236,43 @@ def signed_token_generator(private_pem, **kwargs):
|
||||||
return signed_token_generator
|
return signed_token_generator
|
||||||
|
|
||||||
|
|
||||||
class TokenBase(object):
|
def get_token_from_header(request):
|
||||||
|
"""
|
||||||
|
Helper function to extract a token from the request header.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:return: Return the token or None if the Authorization header is malformed.
|
||||||
|
"""
|
||||||
|
token = None
|
||||||
|
|
||||||
|
if 'Authorization' in request.headers:
|
||||||
|
split_header = request.headers.get('Authorization').split()
|
||||||
|
if len(split_header) == 2 and split_header[0].lower() == 'bearer':
|
||||||
|
token = split_header[1]
|
||||||
|
else:
|
||||||
|
token = request.access_token
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
class TokenBase:
|
||||||
|
|
||||||
def __call__(self, request, refresh_token=False):
|
def __call__(self, request, refresh_token=False):
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
def validate_request(self, request):
|
def validate_request(self, request):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
def estimate_type(self, request):
|
def estimate_type(self, request):
|
||||||
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
|
||||||
|
@ -246,8 +291,18 @@ class BearerToken(TokenBase):
|
||||||
)
|
)
|
||||||
self.expires_in = expires_in or 3600
|
self.expires_in = expires_in or 3600
|
||||||
|
|
||||||
def create_token(self, request, refresh_token=False):
|
def create_token(self, request, refresh_token=False, **kwargs):
|
||||||
"""Create a BearerToken, by default without refresh token."""
|
"""
|
||||||
|
Create a BearerToken, by default without refresh token.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:param refresh_token:
|
||||||
|
"""
|
||||||
|
if "save_token" in kwargs:
|
||||||
|
warnings.warn("`save_token` has been deprecated, it was not called internally."
|
||||||
|
"If you do, call `request_validator.save_token()` instead.",
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
if callable(self.expires_in):
|
if callable(self.expires_in):
|
||||||
expires_in = self.expires_in(request)
|
expires_in = self.expires_in(request)
|
||||||
|
@ -262,12 +317,12 @@ class BearerToken(TokenBase):
|
||||||
'token_type': 'Bearer',
|
'token_type': 'Bearer',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# If provided, include - this is optional in some cases https://tools.ietf.org/html/rfc6749#section-3.3 but
|
||||||
|
# there is currently no mechanism to coordinate issuing a token for only a subset of the requested scopes so
|
||||||
|
# all tokens issued are for the entire set of requested scopes.
|
||||||
if request.scopes is not None:
|
if request.scopes is not None:
|
||||||
token['scope'] = ' '.join(request.scopes)
|
token['scope'] = ' '.join(request.scopes)
|
||||||
|
|
||||||
if request.state is not None:
|
|
||||||
token['state'] = request.state
|
|
||||||
|
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
if (request.refresh_token and
|
if (request.refresh_token and
|
||||||
not self.request_validator.rotate_refresh_token(request)):
|
not self.request_validator.rotate_refresh_token(request)):
|
||||||
|
@ -276,21 +331,23 @@ class BearerToken(TokenBase):
|
||||||
token['refresh_token'] = self.refresh_token_generator(request)
|
token['refresh_token'] = self.refresh_token_generator(request)
|
||||||
|
|
||||||
token.update(request.extra_credentials or {})
|
token.update(request.extra_credentials or {})
|
||||||
token = OAuth2Token(token)
|
return OAuth2Token(token)
|
||||||
self.request_validator.save_bearer_token(token, request)
|
|
||||||
return token
|
|
||||||
|
|
||||||
def validate_request(self, request):
|
def validate_request(self, request):
|
||||||
token = None
|
"""
|
||||||
if 'Authorization' in request.headers:
|
:param request: OAuthlib request.
|
||||||
token = request.headers.get('Authorization')[7:]
|
:type request: oauthlib.common.Request
|
||||||
else:
|
"""
|
||||||
token = request.access_token
|
token = get_token_from_header(request)
|
||||||
return self.request_validator.validate_bearer_token(
|
return self.request_validator.validate_bearer_token(
|
||||||
token, request.scopes, request)
|
token, request.scopes, request)
|
||||||
|
|
||||||
def estimate_type(self, request):
|
def estimate_type(self, request):
|
||||||
if request.headers.get('Authorization', '').startswith('Bearer'):
|
"""
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
"""
|
||||||
|
if request.headers.get('Authorization', '').split(' ')[0].lower() == 'bearer':
|
||||||
return 9
|
return 9
|
||||||
elif request.access_token is not None:
|
elif request.access_token is not None:
|
||||||
return 5
|
return 5
|
||||||
|
|
|
@ -1,31 +1,22 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
oauthlib.utils
|
oauthlib.utils
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This module contains utility methods used by various parts of the OAuth 2 spec.
|
This module contains utility methods used by various parts of the OAuth 2 spec.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import os
|
|
||||||
import datetime
|
import datetime
|
||||||
try:
|
import os
|
||||||
from urllib import quote
|
from urllib.parse import quote, urlparse
|
||||||
except ImportError:
|
|
||||||
from urllib.parse import quote
|
from oauthlib.common import urldecode
|
||||||
try:
|
|
||||||
from urlparse import urlparse
|
|
||||||
except ImportError:
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
from oauthlib.common import unicode_type, urldecode
|
|
||||||
|
|
||||||
|
|
||||||
def list_to_scope(scope):
|
def list_to_scope(scope):
|
||||||
"""Convert a list of scopes to a space separated string."""
|
"""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
|
return scope
|
||||||
elif isinstance(scope, (set, tuple, list)):
|
elif isinstance(scope, (set, tuple, list)):
|
||||||
return " ".join([unicode_type(s) for s in scope])
|
return " ".join([str(s) for s in scope])
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid scope (%s), must be string, tuple, set, or list." % scope)
|
raise ValueError("Invalid scope (%s), must be string, tuple, set, or list." % scope)
|
||||||
|
|
||||||
|
@ -33,7 +24,7 @@ def list_to_scope(scope):
|
||||||
def scope_to_list(scope):
|
def scope_to_list(scope):
|
||||||
"""Convert a space separated string to a list of scopes."""
|
"""Convert a space separated string to a list of scopes."""
|
||||||
if isinstance(scope, (tuple, list, set)):
|
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:
|
elif scope is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
@ -72,7 +63,7 @@ def escape(u):
|
||||||
TODO: verify whether this can in fact be used for OAuth 2
|
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.')
|
raise ValueError('Only unicode objects are escapable.')
|
||||||
return quote(u.encode('utf-8'), safe=b'~')
|
return quote(u.encode('utf-8'), safe=b'~')
|
||||||
|
|
||||||
|
@ -82,7 +73,7 @@ def generate_age(issue_time):
|
||||||
td = datetime.datetime.now() - issue_time
|
td = datetime.datetime.now() - issue_time
|
||||||
age = (td.microseconds + (td.seconds + td.days * 24 * 3600)
|
age = (td.microseconds + (td.seconds + td.days * 24 * 3600)
|
||||||
* 10 ** 6) / 10 ** 6
|
* 10 ** 6) / 10 ** 6
|
||||||
return unicode_type(age)
|
return str(age)
|
||||||
|
|
||||||
|
|
||||||
def is_secure_transport(uri):
|
def is_secure_transport(uri):
|
||||||
|
|
7
lib/oauthlib/openid/__init__.py
Normal file
7
lib/oauthlib/openid/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
oauthlib.openid
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
"""
|
||||||
|
from .connect.core.endpoints import Server, UserInfoEndpoint
|
||||||
|
from .connect.core.request_validator import RequestValidator
|
0
lib/oauthlib/openid/connect/__init__.py
Normal file
0
lib/oauthlib/openid/connect/__init__.py
Normal file
0
lib/oauthlib/openid/connect/core/__init__.py
Normal file
0
lib/oauthlib/openid/connect/core/__init__.py
Normal file
9
lib/oauthlib/openid/connect/core/endpoints/__init__.py
Normal file
9
lib/oauthlib/openid/connect/core/endpoints/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
"""
|
||||||
|
oauthlib.oopenid.core
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module is an implementation of various logic needed
|
||||||
|
for consuming and providing OpenID Connect
|
||||||
|
"""
|
||||||
|
from .pre_configured import Server
|
||||||
|
from .userinfo import UserInfoEndpoint
|
97
lib/oauthlib/openid/connect/core/endpoints/pre_configured.py
Normal file
97
lib/oauthlib/openid/connect/core/endpoints/pre_configured.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
"""
|
||||||
|
oauthlib.openid.connect.core.endpoints.pre_configured
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module is an implementation of various endpoints needed
|
||||||
|
for providing OpenID Connect servers.
|
||||||
|
"""
|
||||||
|
from oauthlib.oauth2.rfc6749.endpoints import (
|
||||||
|
AuthorizationEndpoint, IntrospectEndpoint, ResourceEndpoint,
|
||||||
|
RevocationEndpoint, TokenEndpoint,
|
||||||
|
)
|
||||||
|
from oauthlib.oauth2.rfc6749.grant_types import (
|
||||||
|
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||||
|
ClientCredentialsGrant, ImplicitGrant as OAuth2ImplicitGrant,
|
||||||
|
RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
|
||||||
|
)
|
||||||
|
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||||
|
|
||||||
|
from ..grant_types import AuthorizationCodeGrant, HybridGrant, ImplicitGrant
|
||||||
|
from ..grant_types.dispatchers import (
|
||||||
|
AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher,
|
||||||
|
ImplicitTokenGrantDispatcher,
|
||||||
|
)
|
||||||
|
from ..tokens import JWTToken
|
||||||
|
from .userinfo import UserInfoEndpoint
|
||||||
|
|
||||||
|
|
||||||
|
class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
|
||||||
|
ResourceEndpoint, RevocationEndpoint, UserInfoEndpoint):
|
||||||
|
|
||||||
|
"""An all-in-one endpoint featuring all four major grant types."""
|
||||||
|
|
||||||
|
def __init__(self, request_validator, token_expires_in=None,
|
||||||
|
token_generator=None, refresh_token_generator=None,
|
||||||
|
*args, **kwargs):
|
||||||
|
"""Construct a new all-grants-in-one server.
|
||||||
|
|
||||||
|
:param request_validator: An implementation of
|
||||||
|
oauthlib.oauth2.RequestValidator.
|
||||||
|
:param token_expires_in: An int or a function to generate a token
|
||||||
|
expiration offset (in seconds) given a
|
||||||
|
oauthlib.common.Request object.
|
||||||
|
:param token_generator: A function to generate a token from a request.
|
||||||
|
:param refresh_token_generator: A function to generate a token from a
|
||||||
|
request for the refresh token.
|
||||||
|
:param kwargs: Extra parameters to pass to authorization-,
|
||||||
|
token-, resource-, and revocation-endpoint constructors.
|
||||||
|
"""
|
||||||
|
self.auth_grant = OAuth2AuthorizationCodeGrant(request_validator)
|
||||||
|
self.implicit_grant = OAuth2ImplicitGrant(request_validator)
|
||||||
|
self.password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||||
|
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)
|
||||||
|
|
||||||
|
self.bearer = BearerToken(request_validator, token_generator,
|
||||||
|
token_expires_in, refresh_token_generator)
|
||||||
|
|
||||||
|
self.jwt = JWTToken(request_validator, token_generator,
|
||||||
|
token_expires_in, refresh_token_generator)
|
||||||
|
|
||||||
|
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': 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=self.bearer)
|
||||||
|
|
||||||
|
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': self.token_grant_choice,
|
||||||
|
'password': self.password_grant,
|
||||||
|
'client_credentials': self.credentials_grant,
|
||||||
|
'refresh_token': self.refresh_grant,
|
||||||
|
},
|
||||||
|
default_token_type=self.bearer)
|
||||||
|
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||||
|
token_types={'Bearer': self.bearer, 'JWT': self.jwt})
|
||||||
|
RevocationEndpoint.__init__(self, request_validator)
|
||||||
|
IntrospectEndpoint.__init__(self, request_validator)
|
||||||
|
UserInfoEndpoint.__init__(self, request_validator)
|
99
lib/oauthlib/openid/connect/core/endpoints/userinfo.py
Normal file
99
lib/oauthlib/openid/connect/core/endpoints/userinfo.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
"""
|
||||||
|
oauthlib.openid.connect.core.endpoints.userinfo
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module is an implementation of userinfo endpoint.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oauthlib.common import Request
|
||||||
|
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__)
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfoEndpoint(BaseEndpoint):
|
||||||
|
"""Authorizes access to userinfo resource.
|
||||||
|
"""
|
||||||
|
def __init__(self, request_validator):
|
||||||
|
self.bearer = BearerToken(request_validator, None, None, None)
|
||||||
|
self.request_validator = request_validator
|
||||||
|
BaseEndpoint.__init__(self)
|
||||||
|
|
||||||
|
@catch_errors_and_unavailability
|
||||||
|
def create_userinfo_response(self, uri, http_method='GET', body=None, headers=None):
|
||||||
|
"""Validate BearerToken and return userinfo from RequestValidator
|
||||||
|
|
||||||
|
The UserInfo Endpoint MUST return a
|
||||||
|
content-type header to indicate which format is being returned. The
|
||||||
|
content-type of the HTTP response MUST be application/json if the
|
||||||
|
response body is a text JSON object; the response body SHOULD be encoded
|
||||||
|
using UTF-8.
|
||||||
|
"""
|
||||||
|
request = Request(uri, http_method, body, headers)
|
||||||
|
request.scopes = ["openid"]
|
||||||
|
self.validate_userinfo_request(request)
|
||||||
|
|
||||||
|
claims = self.request_validator.get_userinfo_claims(request)
|
||||||
|
if claims is None:
|
||||||
|
log.error('Userinfo MUST have claims for %r.', request)
|
||||||
|
raise errors.ServerError(status_code=500)
|
||||||
|
|
||||||
|
if isinstance(claims, dict):
|
||||||
|
resp_headers = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
if "sub" not in claims:
|
||||||
|
log.error('Userinfo MUST have "sub" for %r.', request)
|
||||||
|
raise errors.ServerError(status_code=500)
|
||||||
|
body = json.dumps(claims)
|
||||||
|
elif isinstance(claims, str):
|
||||||
|
resp_headers = {
|
||||||
|
'Content-Type': 'application/jwt'
|
||||||
|
}
|
||||||
|
body = claims
|
||||||
|
else:
|
||||||
|
log.error('Userinfo return unknown response for %r.', request)
|
||||||
|
raise errors.ServerError(status_code=500)
|
||||||
|
log.debug('Userinfo access valid for %r.', request)
|
||||||
|
return resp_headers, body, 200
|
||||||
|
|
||||||
|
def validate_userinfo_request(self, request):
|
||||||
|
"""Ensure the request is valid.
|
||||||
|
|
||||||
|
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
|
||||||
|
Bearer Token Usage [RFC6750].
|
||||||
|
|
||||||
|
It is RECOMMENDED that the request use the HTTP GET method and the
|
||||||
|
Access Token be sent using the Authorization header field.
|
||||||
|
|
||||||
|
The following is a non-normative example of a UserInfo Request:
|
||||||
|
|
||||||
|
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
|
||||||
|
[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",
|
||||||
|
error_description="The Access Token expired"
|
||||||
|
"""
|
||||||
|
if not self.bearer.validate_request(request):
|
||||||
|
raise errors.InvalidTokenError()
|
||||||
|
if "openid" not in request.scopes:
|
||||||
|
raise errors.InsufficientScopeError()
|
149
lib/oauthlib/openid/connect/core/exceptions.py
Normal file
149
lib/oauthlib/openid/connect/core/exceptions.py
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
"""
|
||||||
|
oauthlib.oauth2.rfc6749.errors
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Error used both by OAuth 2 clients and providers to represent the spec
|
||||||
|
defined error responses for all four core grant types.
|
||||||
|
"""
|
||||||
|
from oauthlib.oauth2.rfc6749.errors import FatalClientError, OAuth2Error
|
||||||
|
|
||||||
|
|
||||||
|
class FatalOpenIDClientError(FatalClientError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpenIDClientError(OAuth2Error):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InteractionRequired(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The Authorization Server requires End-User interaction to proceed.
|
||||||
|
|
||||||
|
This error MAY be returned when the prompt parameter value in the
|
||||||
|
Authentication Request is none, but the Authentication Request cannot be
|
||||||
|
completed without displaying a user interface for End-User interaction.
|
||||||
|
"""
|
||||||
|
error = 'interaction_required'
|
||||||
|
status_code = 401
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequired(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The Authorization Server requires End-User authentication.
|
||||||
|
|
||||||
|
This error MAY be returned when the prompt parameter value in the
|
||||||
|
Authentication Request is none, but the Authentication Request cannot be
|
||||||
|
completed without displaying a user interface for End-User authentication.
|
||||||
|
"""
|
||||||
|
error = 'login_required'
|
||||||
|
status_code = 401
|
||||||
|
|
||||||
|
|
||||||
|
class AccountSelectionRequired(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The End-User is REQUIRED to select a session at the Authorization Server.
|
||||||
|
|
||||||
|
The End-User MAY be authenticated at the Authorization Server with
|
||||||
|
different associated accounts, but the End-User did not select a session.
|
||||||
|
This error MAY be returned when the prompt parameter value in the
|
||||||
|
Authentication Request is none, but the Authentication Request cannot be
|
||||||
|
completed without displaying a user interface to prompt for a session to
|
||||||
|
use.
|
||||||
|
"""
|
||||||
|
error = 'account_selection_required'
|
||||||
|
|
||||||
|
|
||||||
|
class ConsentRequired(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The Authorization Server requires End-User consent.
|
||||||
|
|
||||||
|
This error MAY be returned when the prompt parameter value in the
|
||||||
|
Authentication Request is none, but the Authentication Request cannot be
|
||||||
|
completed without displaying a user interface for End-User consent.
|
||||||
|
"""
|
||||||
|
error = 'consent_required'
|
||||||
|
status_code = 401
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidRequestURI(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The request_uri in the Authorization Request returns an error or
|
||||||
|
contains invalid data.
|
||||||
|
"""
|
||||||
|
error = 'invalid_request_uri'
|
||||||
|
description = 'The request_uri in the Authorization Request returns an ' \
|
||||||
|
'error or contains invalid data.'
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidRequestObject(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The request parameter contains an invalid Request Object.
|
||||||
|
"""
|
||||||
|
error = 'invalid_request_object'
|
||||||
|
description = 'The request parameter contains an invalid Request Object.'
|
||||||
|
|
||||||
|
|
||||||
|
class RequestNotSupported(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The OP does not support use of the request parameter.
|
||||||
|
"""
|
||||||
|
error = 'request_not_supported'
|
||||||
|
description = 'The request parameter is not supported.'
|
||||||
|
|
||||||
|
|
||||||
|
class RequestURINotSupported(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The OP does not support use of the request_uri parameter.
|
||||||
|
"""
|
||||||
|
error = 'request_uri_not_supported'
|
||||||
|
description = 'The request_uri parameter is not supported.'
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationNotSupported(OpenIDClientError):
|
||||||
|
"""
|
||||||
|
The OP does not support use of the registration parameter.
|
||||||
|
"""
|
||||||
|
error = 'registration_not_supported'
|
||||||
|
description = 'The registration parameter is not supported.'
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidTokenError(OAuth2Error):
|
||||||
|
"""
|
||||||
|
The access token provided is expired, revoked, malformed, or
|
||||||
|
invalid for other reasons. The resource SHOULD respond with
|
||||||
|
the HTTP 401 (Unauthorized) status code. The client MAY
|
||||||
|
request a new access token and retry the protected resource
|
||||||
|
request.
|
||||||
|
"""
|
||||||
|
error = 'invalid_token'
|
||||||
|
status_code = 401
|
||||||
|
description = ("The access token provided is expired, revoked, malformed, "
|
||||||
|
"or invalid for other reasons.")
|
||||||
|
|
||||||
|
|
||||||
|
class InsufficientScopeError(OAuth2Error):
|
||||||
|
"""
|
||||||
|
The request requires higher privileges than provided by the
|
||||||
|
access token. The resource server SHOULD respond with the HTTP
|
||||||
|
403 (Forbidden) status code and MAY include the "scope"
|
||||||
|
attribute with the scope necessary to access the protected
|
||||||
|
resource.
|
||||||
|
"""
|
||||||
|
error = 'insufficient_scope'
|
||||||
|
status_code = 403
|
||||||
|
description = ("The request requires higher privileges than provided by "
|
||||||
|
"the access token.")
|
||||||
|
|
||||||
|
|
||||||
|
def raise_from_error(error, params=None):
|
||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
kwargs = {
|
||||||
|
'description': params.get('error_description'),
|
||||||
|
'uri': params.get('error_uri'),
|
||||||
|
'state': params.get('state')
|
||||||
|
}
|
||||||
|
for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
|
||||||
|
if cls.error == error:
|
||||||
|
raise cls(**kwargs)
|
12
lib/oauthlib/openid/connect/core/grant_types/__init__.py
Normal file
12
lib/oauthlib/openid/connect/core/grant_types/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
"""
|
||||||
|
oauthlib.openid.connect.core.grant_types
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
"""
|
||||||
|
from .authorization_code import AuthorizationCodeGrant
|
||||||
|
from .base import GrantTypeBase
|
||||||
|
from .dispatchers import (
|
||||||
|
AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher,
|
||||||
|
ImplicitTokenGrantDispatcher,
|
||||||
|
)
|
||||||
|
from .hybrid import HybridGrant
|
||||||
|
from .implicit import ImplicitGrant
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""
|
||||||
|
oauthlib.openid.connect.core.grant_types
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
|
||||||
|
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .base import GrantTypeBase
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizationCodeGrant(GrantTypeBase):
|
||||||
|
|
||||||
|
def __init__(self, request_validator=None, **kwargs):
|
||||||
|
self.proxy_target = OAuth2AuthorizationCodeGrant(
|
||||||
|
request_validator=request_validator, **kwargs)
|
||||||
|
self.custom_validators.post_auth.append(
|
||||||
|
self.openid_authorization_validator)
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
# Treat it as normal OAuth 2 auth code request if openid is not present
|
||||||
|
if not request.scopes or 'openid' not in request.scopes:
|
||||||
|
return token
|
||||||
|
|
||||||
|
nonce = self.request_validator.get_authorization_code_nonce(
|
||||||
|
request.client_id,
|
||||||
|
request.code,
|
||||||
|
request.redirect_uri,
|
||||||
|
request
|
||||||
|
)
|
||||||
|
return super().add_id_token(token, token_handler, request, nonce=nonce)
|
327
lib/oauthlib/openid/connect/core/grant_types/base.py
Normal file
327
lib/oauthlib/openid/connect/core/grant_types/base.py
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from json import loads
|
||||||
|
|
||||||
|
from oauthlib.oauth2.rfc6749.errors import (
|
||||||
|
ConsentRequired, InvalidRequestError, LoginRequired,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GrantTypeBase:
|
||||||
|
|
||||||
|
# Just proxy the majority of method calls through to the
|
||||||
|
# proxy_target grant type handler, which will usually be either
|
||||||
|
# the standard OAuth2 AuthCode or Implicit grant types.
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.proxy_target, attr)
|
||||||
|
|
||||||
|
def __setattr__(self, attr, value):
|
||||||
|
proxied_attrs = {'refresh_token', 'response_types'}
|
||||||
|
if attr in proxied_attrs:
|
||||||
|
setattr(self.proxy_target, attr, value)
|
||||||
|
else:
|
||||||
|
super(OpenIDConnectBase, self).__setattr__(attr, value)
|
||||||
|
|
||||||
|
def validate_authorization_request(self, request):
|
||||||
|
"""Validates the OpenID Connect authorization request parameters.
|
||||||
|
|
||||||
|
:returns: (list of scopes, dict of request info)
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
if request.claims and not isinstance(request.claims, dict):
|
||||||
|
# specific claims are requested during the Authorization Request and may be requested for inclusion
|
||||||
|
# in either the id_token or the UserInfo endpoint response
|
||||||
|
# see http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
|
||||||
|
try:
|
||||||
|
request.claims = loads(request.claims)
|
||||||
|
except Exception as ex:
|
||||||
|
raise InvalidRequestError(description="Malformed claims parameter",
|
||||||
|
uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter")
|
||||||
|
|
||||||
|
def id_token_hash(self, value, hashfunc=hashlib.sha256):
|
||||||
|
"""
|
||||||
|
Its value is the base64url encoding of the left-most half of the
|
||||||
|
hash of the octets of the ASCII representation of the access_token
|
||||||
|
value, where the hash algorithm used is the hash algorithm used in
|
||||||
|
the alg Header Parameter of the ID Token's JOSE Header.
|
||||||
|
|
||||||
|
For instance, if the alg is RS256, hash the access_token value
|
||||||
|
with SHA-256, then take the left-most 128 bits and
|
||||||
|
base64url-encode them.
|
||||||
|
For instance, if the alg is HS512, hash the code value with
|
||||||
|
SHA-512, then take the left-most 256 bits and base64url-encode
|
||||||
|
them. The c_hash value is a case-sensitive string.
|
||||||
|
|
||||||
|
Example of hash from OIDC specification (bound to a JWS using RS256):
|
||||||
|
|
||||||
|
code:
|
||||||
|
Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk
|
||||||
|
|
||||||
|
c_hash:
|
||||||
|
LDktKdoQak3Pk0cnXxCltA
|
||||||
|
"""
|
||||||
|
digest = hashfunc(value.encode()).digest()
|
||||||
|
left_most = len(digest) // 2
|
||||||
|
return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=")
|
||||||
|
|
||||||
|
def add_id_token(self, token, token_handler, request, nonce=None):
|
||||||
|
"""
|
||||||
|
Construct an initial version of id_token, and let the
|
||||||
|
request_validator sign or encrypt it.
|
||||||
|
|
||||||
|
The initial version can contain the fields below, accordingly
|
||||||
|
to the spec:
|
||||||
|
- aud
|
||||||
|
- iat
|
||||||
|
- nonce
|
||||||
|
- at_hash
|
||||||
|
- c_hash
|
||||||
|
"""
|
||||||
|
# Treat it as normal OAuth 2 auth code request if openid is not present
|
||||||
|
if not request.scopes or 'openid' not in request.scopes:
|
||||||
|
return token
|
||||||
|
|
||||||
|
# Only add an id token on auth/token step if asked for.
|
||||||
|
if request.response_type and 'id_token' not in request.response_type:
|
||||||
|
return token
|
||||||
|
|
||||||
|
# Implementation mint its own id_token without help.
|
||||||
|
id_token = self.request_validator.get_id_token(token, token_handler, request)
|
||||||
|
if id_token:
|
||||||
|
token['id_token'] = id_token
|
||||||
|
return token
|
||||||
|
|
||||||
|
# Fallback for asking some help from oauthlib framework.
|
||||||
|
# Start with technicals fields bound to the specification.
|
||||||
|
id_token = {}
|
||||||
|
id_token['aud'] = request.client_id
|
||||||
|
id_token['iat'] = int(time.time())
|
||||||
|
|
||||||
|
# nonce is REQUIRED when response_type value is:
|
||||||
|
# - id_token token (Implicit)
|
||||||
|
# - id_token (Implicit)
|
||||||
|
# - code id_token (Hybrid)
|
||||||
|
# - code id_token token (Hybrid)
|
||||||
|
#
|
||||||
|
# nonce is OPTIONAL when response_type value is:
|
||||||
|
# - code (Authorization Code)
|
||||||
|
# - code token (Hybrid)
|
||||||
|
if nonce is not None:
|
||||||
|
id_token["nonce"] = nonce
|
||||||
|
|
||||||
|
# at_hash is REQUIRED when response_type value is:
|
||||||
|
# - id_token token (Implicit)
|
||||||
|
# - code id_token token (Hybrid)
|
||||||
|
#
|
||||||
|
# at_hash is OPTIONAL when:
|
||||||
|
# - code (Authorization code)
|
||||||
|
# - code id_token (Hybrid)
|
||||||
|
# - code token (Hybrid)
|
||||||
|
#
|
||||||
|
# at_hash MAY NOT be used when:
|
||||||
|
# - id_token (Implicit)
|
||||||
|
if "access_token" in token:
|
||||||
|
id_token["at_hash"] = self.id_token_hash(token["access_token"])
|
||||||
|
|
||||||
|
# c_hash is REQUIRED when response_type value is:
|
||||||
|
# - code id_token (Hybrid)
|
||||||
|
# - code id_token token (Hybrid)
|
||||||
|
#
|
||||||
|
# c_hash is OPTIONAL for others.
|
||||||
|
if "code" in token:
|
||||||
|
id_token["c_hash"] = self.id_token_hash(token["code"])
|
||||||
|
|
||||||
|
# Call request_validator to complete/sign/encrypt id_token
|
||||||
|
token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request)
|
||||||
|
|
||||||
|
return token
|
||||||
|
|
||||||
|
def openid_authorization_validator(self, request):
|
||||||
|
"""Perform OpenID Connect specific authorization request validation.
|
||||||
|
|
||||||
|
nonce
|
||||||
|
OPTIONAL. String value used to associate a Client session with
|
||||||
|
an ID Token, and to mitigate replay attacks. The value is
|
||||||
|
passed through unmodified from the Authentication Request to
|
||||||
|
the ID Token. Sufficient entropy MUST be present in the nonce
|
||||||
|
values used to prevent attackers from guessing values
|
||||||
|
|
||||||
|
display
|
||||||
|
OPTIONAL. ASCII string value that specifies how the
|
||||||
|
Authorization Server displays the authentication and consent
|
||||||
|
user interface pages to the End-User. The defined values are:
|
||||||
|
|
||||||
|
page - The Authorization Server SHOULD display the
|
||||||
|
authentication and consent UI consistent with a full User
|
||||||
|
Agent page view. If the display parameter is not specified,
|
||||||
|
this is the default display mode.
|
||||||
|
|
||||||
|
popup - The Authorization Server SHOULD display the
|
||||||
|
authentication and consent UI consistent with a popup User
|
||||||
|
Agent window. The popup User Agent window should be of an
|
||||||
|
appropriate size for a login-focused dialog and should not
|
||||||
|
obscure the entire window that it is popping up over.
|
||||||
|
|
||||||
|
touch - The Authorization Server SHOULD display the
|
||||||
|
authentication and consent UI consistent with a device that
|
||||||
|
leverages a touch interface.
|
||||||
|
|
||||||
|
wap - The Authorization Server SHOULD display the
|
||||||
|
authentication and consent UI consistent with a "feature
|
||||||
|
phone" type display.
|
||||||
|
|
||||||
|
The Authorization Server MAY also attempt to detect the
|
||||||
|
capabilities of the User Agent and present an appropriate
|
||||||
|
display.
|
||||||
|
|
||||||
|
prompt
|
||||||
|
OPTIONAL. Space delimited, case sensitive list of ASCII string
|
||||||
|
values that specifies whether the Authorization Server prompts
|
||||||
|
the End-User for reauthentication and consent. The defined
|
||||||
|
values are:
|
||||||
|
|
||||||
|
none - The Authorization Server MUST NOT display any
|
||||||
|
authentication or consent user interface pages. An error is
|
||||||
|
returned if an End-User is not already authenticated or the
|
||||||
|
Client does not have pre-configured consent for the
|
||||||
|
requested Claims or does not fulfill other conditions for
|
||||||
|
processing the request. The error code will typically be
|
||||||
|
login_required, interaction_required, or another code
|
||||||
|
defined in Section 3.1.2.6. This can be used as a method to
|
||||||
|
check for existing authentication and/or consent.
|
||||||
|
|
||||||
|
login - The Authorization Server SHOULD prompt the End-User
|
||||||
|
for reauthentication. If it cannot reauthenticate the
|
||||||
|
End-User, it MUST return an error, typically
|
||||||
|
login_required.
|
||||||
|
|
||||||
|
consent - The Authorization Server SHOULD prompt the
|
||||||
|
End-User for consent before returning information to the
|
||||||
|
Client. If it cannot obtain consent, it MUST return an
|
||||||
|
error, typically consent_required.
|
||||||
|
|
||||||
|
select_account - The Authorization Server SHOULD prompt the
|
||||||
|
End-User to select a user account. This enables an End-User
|
||||||
|
who has multiple accounts at the Authorization Server to
|
||||||
|
select amongst the multiple accounts that they might have
|
||||||
|
current sessions for. If it cannot obtain an account
|
||||||
|
selection choice made by the End-User, it MUST return an
|
||||||
|
error, typically account_selection_required.
|
||||||
|
|
||||||
|
The prompt parameter can be used by the Client to make sure
|
||||||
|
that the End-User is still present for the current session or
|
||||||
|
to bring attention to the request. If this parameter contains
|
||||||
|
none with any other value, an error is returned.
|
||||||
|
|
||||||
|
max_age
|
||||||
|
OPTIONAL. Maximum Authentication Age. Specifies the allowable
|
||||||
|
elapsed time in seconds since the last time the End-User was
|
||||||
|
actively authenticated by the OP. If the elapsed time is
|
||||||
|
greater than this value, the OP MUST attempt to actively
|
||||||
|
re-authenticate the End-User. (The max_age request parameter
|
||||||
|
corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age
|
||||||
|
request parameter.) When max_age is used, the ID Token returned
|
||||||
|
MUST include an auth_time Claim Value.
|
||||||
|
|
||||||
|
ui_locales
|
||||||
|
OPTIONAL. End-User's preferred languages and scripts for the
|
||||||
|
user interface, represented as a space-separated list of BCP47
|
||||||
|
[RFC5646] language tag values, ordered by preference. For
|
||||||
|
instance, the value "fr-CA fr en" represents a preference for
|
||||||
|
French as spoken in Canada, then French (without a region
|
||||||
|
designation), followed by English (without a region
|
||||||
|
designation). An error SHOULD NOT result if some or all of the
|
||||||
|
requested locales are not supported by the OpenID Provider.
|
||||||
|
|
||||||
|
id_token_hint
|
||||||
|
OPTIONAL. ID Token previously issued by the Authorization
|
||||||
|
Server being passed as a hint about the End-User's current or
|
||||||
|
past authenticated session with the Client. If the End-User
|
||||||
|
identified by the ID Token is logged in or is logged in by the
|
||||||
|
request, then the Authorization Server returns a positive
|
||||||
|
response; otherwise, it SHOULD return an error, such as
|
||||||
|
login_required. When possible, an id_token_hint SHOULD be
|
||||||
|
present when prompt=none is used and an invalid_request error
|
||||||
|
MAY be returned if it is not; however, the server SHOULD
|
||||||
|
respond successfully when possible, even if it is not present.
|
||||||
|
The Authorization Server need not be listed as an audience of
|
||||||
|
the ID Token when it is used as an id_token_hint value. If the
|
||||||
|
ID Token received by the RP from the OP is encrypted, to use it
|
||||||
|
as an id_token_hint, the Client MUST decrypt the signed ID
|
||||||
|
Token contained within the encrypted ID Token. The Client MAY
|
||||||
|
re-encrypt the signed ID token to the Authentication Server
|
||||||
|
using a key that enables the server to decrypt the ID Token,
|
||||||
|
and use the re-encrypted ID token as the id_token_hint value.
|
||||||
|
|
||||||
|
login_hint
|
||||||
|
OPTIONAL. Hint to the Authorization Server about the login
|
||||||
|
identifier the End-User might use to log in (if necessary).
|
||||||
|
This hint can be used by an RP if it first asks the End-User
|
||||||
|
for their e-mail address (or other identifier) and then wants
|
||||||
|
to pass that value as a hint to the discovered authorization
|
||||||
|
service. It is RECOMMENDED that the hint value match the value
|
||||||
|
used for discovery. This value MAY also be a phone number in
|
||||||
|
the format specified for the phone_number Claim. The use of
|
||||||
|
this parameter is left to the OP's discretion.
|
||||||
|
|
||||||
|
acr_values
|
||||||
|
OPTIONAL. Requested Authentication Context Class Reference
|
||||||
|
values. Space-separated string that specifies the acr values
|
||||||
|
that the Authorization Server is being requested to use for
|
||||||
|
processing this Authentication Request, with the values
|
||||||
|
appearing in order of preference. The Authentication Context
|
||||||
|
Class satisfied by the authentication performed is returned as
|
||||||
|
the acr Claim Value, as specified in Section 2. The acr Claim
|
||||||
|
is requested as a Voluntary Claim by this parameter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Treat it as normal OAuth 2 auth code request if openid is not present
|
||||||
|
if not request.scopes or 'openid' not in request.scopes:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
prompt = request.prompt if request.prompt else []
|
||||||
|
if hasattr(prompt, 'split'):
|
||||||
|
prompt = prompt.strip().split()
|
||||||
|
prompt = set(prompt)
|
||||||
|
|
||||||
|
if 'none' in prompt:
|
||||||
|
|
||||||
|
if len(prompt) > 1:
|
||||||
|
msg = "Prompt none is mutually exclusive with other values."
|
||||||
|
raise InvalidRequestError(request=request, description=msg)
|
||||||
|
|
||||||
|
if not self.request_validator.validate_silent_login(request):
|
||||||
|
raise LoginRequired(request=request)
|
||||||
|
|
||||||
|
if not self.request_validator.validate_silent_authorization(request):
|
||||||
|
raise ConsentRequired(request=request)
|
||||||
|
|
||||||
|
self._inflate_claims(request)
|
||||||
|
|
||||||
|
if not self.request_validator.validate_user_match(
|
||||||
|
request.id_token_hint, request.scopes, request.claims, request):
|
||||||
|
msg = "Session user does not match client supplied user."
|
||||||
|
raise LoginRequired(request=request, description=msg)
|
||||||
|
|
||||||
|
request_info = {
|
||||||
|
'display': request.display,
|
||||||
|
'nonce': request.nonce,
|
||||||
|
'prompt': prompt,
|
||||||
|
'ui_locales': request.ui_locales.split() if request.ui_locales else [],
|
||||||
|
'id_token_hint': request.id_token_hint,
|
||||||
|
'login_hint': request.login_hint,
|
||||||
|
'claims': request.claims
|
||||||
|
}
|
||||||
|
|
||||||
|
return request_info
|
||||||
|
|
||||||
|
|
||||||
|
OpenIDConnectBase = GrantTypeBase
|
101
lib/oauthlib/openid/connect/core/grant_types/dispatchers.py
Normal file
101
lib/oauthlib/openid/connect/core/grant_types/dispatchers.py
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
def __init__(self, default_grant=None, oidc_grant=None):
|
||||||
|
self.default_grant = default_grant
|
||||||
|
self.oidc_grant = oidc_grant
|
||||||
|
|
||||||
|
def _handler_for_request(self, request):
|
||||||
|
handler = self.default_grant
|
||||||
|
|
||||||
|
if request.scopes and "openid" in request.scopes:
|
||||||
|
handler = self.oidc_grant
|
||||||
|
|
||||||
|
log.debug('Selecting handler for request %r.', handler)
|
||||||
|
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
|
||||||
|
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
|
||||||
|
self.oidc_grant = oidc_grant
|
||||||
|
|
||||||
|
def _handler_for_request(self, request):
|
||||||
|
handler = self.default_grant
|
||||||
|
|
||||||
|
if request.scopes and "openid" in request.scopes and 'id_token' in request.response_type:
|
||||||
|
handler = self.oidc_grant
|
||||||
|
|
||||||
|
log.debug('Selecting handler for request %r.', handler)
|
||||||
|
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 AuthorizationTokenGrantDispatcher(Dispatcher):
|
||||||
|
"""
|
||||||
|
This is an adapter class that will route simple Token requests, those that authorization_code have a scope
|
||||||
|
including 'openid' to either the default_grant or the oidc_grant based on the scopes requested.
|
||||||
|
"""
|
||||||
|
def __init__(self, request_validator, default_grant=None, oidc_grant=None):
|
||||||
|
self.default_grant = default_grant
|
||||||
|
self.oidc_grant = oidc_grant
|
||||||
|
self.request_validator = request_validator
|
||||||
|
|
||||||
|
def _handler_for_request(self, request):
|
||||||
|
handler = self.default_grant
|
||||||
|
scopes = ()
|
||||||
|
parameters = dict(request.decoded_body)
|
||||||
|
client_id = parameters.get('client_id', None)
|
||||||
|
code = parameters.get('code', None)
|
||||||
|
redirect_uri = parameters.get('redirect_uri', None)
|
||||||
|
|
||||||
|
# If code is not pressent 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)
|
||||||
|
|
||||||
|
if 'openid' in scopes:
|
||||||
|
handler = self.oidc_grant
|
||||||
|
|
||||||
|
log.debug('Selecting handler for request %r.', handler)
|
||||||
|
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)
|
63
lib/oauthlib/openid/connect/core/grant_types/hybrid.py
Normal file
63
lib/oauthlib/openid/connect/core/grant_types/hybrid.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
"""
|
||||||
|
oauthlib.openid.connect.core.grant_types
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
|
||||||
|
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
|
||||||
|
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..request_validator import RequestValidator
|
||||||
|
from .base import GrantTypeBase
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HybridGrant(GrantTypeBase):
|
||||||
|
|
||||||
|
def __init__(self, request_validator=None, **kwargs):
|
||||||
|
self.request_validator = request_validator or RequestValidator()
|
||||||
|
|
||||||
|
self.proxy_target = OAuth2AuthorizationCodeGrant(
|
||||||
|
request_validator=request_validator, **kwargs)
|
||||||
|
# All hybrid response types should be fragment-encoded.
|
||||||
|
self.proxy_target.default_response_mode = "fragment"
|
||||||
|
self.register_response_type('code id_token')
|
||||||
|
self.register_response_type('code token')
|
||||||
|
self.register_response_type('code id_token token')
|
||||||
|
self.custom_validators.post_auth.append(
|
||||||
|
self.openid_authorization_validator)
|
||||||
|
# Hybrid flows can return the id_token from the authorization
|
||||||
|
# endpoint as part of the 'code' response
|
||||||
|
self.register_code_modifier(self.add_token)
|
||||||
|
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().openid_authorization_validator(request)
|
||||||
|
if not request_info: # returns immediately if OAuth2.0
|
||||||
|
return request_info
|
||||||
|
|
||||||
|
# REQUIRED if the Response Type of the request is `code
|
||||||
|
# id_token` or `code id_token token` and OPTIONAL when the
|
||||||
|
# Response Type of the request is `code token`. It is a string
|
||||||
|
# value used to associate a Client session with an ID Token,
|
||||||
|
# and to mitigate replay attacks. The value is passed through
|
||||||
|
# unmodified from the Authentication Request to the ID
|
||||||
|
# Token. Sufficient entropy MUST be present in the `nonce`
|
||||||
|
# values used to prevent attackers from guessing values. For
|
||||||
|
# implementation notes, see Section 15.5.2.
|
||||||
|
if request.response_type in ["code id_token", "code id_token token"]:
|
||||||
|
if not request.nonce:
|
||||||
|
raise InvalidRequestError(
|
||||||
|
request=request,
|
||||||
|
description='Request is missing mandatory nonce parameter.'
|
||||||
|
)
|
||||||
|
return request_info
|
51
lib/oauthlib/openid/connect/core/grant_types/implicit.py
Normal file
51
lib/oauthlib/openid/connect/core/grant_types/implicit.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
"""
|
||||||
|
oauthlib.openid.connect.core.grant_types
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
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__)
|
||||||
|
|
||||||
|
|
||||||
|
class ImplicitGrant(GrantTypeBase):
|
||||||
|
|
||||||
|
def __init__(self, request_validator=None, **kwargs):
|
||||||
|
self.proxy_target = OAuth2ImplicitGrant(
|
||||||
|
request_validator=request_validator, **kwargs)
|
||||||
|
self.register_response_type('id_token')
|
||||||
|
self.register_response_type('id_token token')
|
||||||
|
self.custom_validators.post_auth.append(
|
||||||
|
self.openid_authorization_validator)
|
||||||
|
self.register_token_modifier(self.add_id_token)
|
||||||
|
|
||||||
|
def add_id_token(self, token, token_handler, request):
|
||||||
|
if 'state' not in token and request.state:
|
||||||
|
token['state'] = request.state
|
||||||
|
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().openid_authorization_validator(request)
|
||||||
|
if not request_info: # returns immediately if OAuth2.0
|
||||||
|
return request_info
|
||||||
|
|
||||||
|
# REQUIRED. String value used to associate a Client session with an ID
|
||||||
|
# Token, and to mitigate replay attacks. The value is passed through
|
||||||
|
# unmodified from the Authentication Request to the ID Token.
|
||||||
|
# Sufficient entropy MUST be present in the nonce values used to
|
||||||
|
# prevent attackers from guessing values. For implementation notes, see
|
||||||
|
# Section 15.5.2.
|
||||||
|
if not request.nonce:
|
||||||
|
raise InvalidRequestError(
|
||||||
|
request=request,
|
||||||
|
description='Request is missing mandatory nonce parameter.'
|
||||||
|
)
|
||||||
|
return request_info
|
308
lib/oauthlib/openid/connect/core/request_validator.py
Normal file
308
lib/oauthlib/openid/connect/core/request_validator.py
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
"""
|
||||||
|
oauthlib.openid.connect.core.request_validator
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oauthlib.oauth2.rfc6749.request_validator import (
|
||||||
|
RequestValidator as OAuth2RequestValidator,
|
||||||
|
)
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestValidator(OAuth2RequestValidator):
|
||||||
|
|
||||||
|
def get_authorization_code_scopes(self, client_id, code, redirect_uri, request):
|
||||||
|
""" Extracts scopes from saved authorization code.
|
||||||
|
|
||||||
|
The scopes returned by this method is used to route token requests
|
||||||
|
based on scopes passed to Authorization Code requests.
|
||||||
|
|
||||||
|
With that the token endpoint knows when to include OpenIDConnect
|
||||||
|
id_token in token response only based on authorization code scopes.
|
||||||
|
|
||||||
|
Only code param should be sufficient to retrieve grant code from
|
||||||
|
any storage you are using, `client_id` and `redirect_uri` can have a
|
||||||
|
blank value `""` don't forget to check it before using those values
|
||||||
|
in a select query if a database is used.
|
||||||
|
|
||||||
|
:param client_id: Unicode client identifier
|
||||||
|
:param code: Unicode authorization code grant
|
||||||
|
:param redirect_uri: Unicode absolute URI
|
||||||
|
:return: A list of scope
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- Authorization Token Grant Dispatcher
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def get_authorization_code_nonce(self, client_id, code, redirect_uri, request):
|
||||||
|
""" Extracts nonce from saved authorization code.
|
||||||
|
|
||||||
|
If present in the Authentication Request, Authorization
|
||||||
|
Servers MUST include a nonce Claim in the ID Token with the
|
||||||
|
Claim Value being the nonce value sent in the Authentication
|
||||||
|
Request. Authorization Servers SHOULD perform no other
|
||||||
|
processing on nonce values used. The nonce value is a
|
||||||
|
case-sensitive string.
|
||||||
|
|
||||||
|
Only code param should be sufficient to retrieve grant code from
|
||||||
|
any storage you are using. However, `client_id` and `redirect_uri`
|
||||||
|
have been validated and can be used also.
|
||||||
|
|
||||||
|
:param client_id: Unicode client identifier
|
||||||
|
:param code: Unicode authorization code grant
|
||||||
|
:param redirect_uri: Unicode absolute URI
|
||||||
|
:return: Unicode nonce
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- Authorization Token Grant Dispatcher
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def get_jwt_bearer_token(self, token, token_handler, request):
|
||||||
|
"""Get JWT Bearer token or OpenID Connect ID token
|
||||||
|
|
||||||
|
If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`
|
||||||
|
|
||||||
|
:param token: A Bearer token dict
|
||||||
|
:param token_handler: the token handler (BearerToken class)
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:return: The JWT Bearer token or OpenID Connect ID token (a JWS signed JWT)
|
||||||
|
|
||||||
|
Method is used by JWT Bearer and OpenID Connect tokens:
|
||||||
|
- JWTToken.create_token
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def get_id_token(self, token, token_handler, request):
|
||||||
|
"""Get OpenID Connect ID token
|
||||||
|
|
||||||
|
This method is OPTIONAL and is NOT RECOMMENDED.
|
||||||
|
`finalize_id_token` SHOULD be implemented instead. However, if you
|
||||||
|
want a full control over the minting of the `id_token`, you
|
||||||
|
MAY want to override `get_id_token` instead of using
|
||||||
|
`finalize_id_token`.
|
||||||
|
|
||||||
|
In the OpenID Connect workflows when an ID Token is requested this method is called.
|
||||||
|
Subclasses should implement the construction, signing and optional encryption of the
|
||||||
|
ID Token as described in the OpenID Connect spec.
|
||||||
|
|
||||||
|
In addition to the standard OAuth2 request properties, the request may also contain
|
||||||
|
these OIDC specific properties which are useful to this method:
|
||||||
|
|
||||||
|
- nonce, if workflow is implicit or hybrid and it was provided
|
||||||
|
- claims, if provided to the original Authorization Code request
|
||||||
|
|
||||||
|
The token parameter is a dict which may contain an ``access_token`` entry, in which
|
||||||
|
case the resulting ID Token *should* include a calculated ``at_hash`` claim.
|
||||||
|
|
||||||
|
Similarly, when the request parameter has a ``code`` property defined, the ID Token
|
||||||
|
*should* include a calculated ``c_hash`` claim.
|
||||||
|
|
||||||
|
http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_)
|
||||||
|
|
||||||
|
.. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||||
|
.. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
|
||||||
|
.. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
|
||||||
|
|
||||||
|
:param token: A Bearer token dict
|
||||||
|
:param token_handler: the token handler (BearerToken class)
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:return: The ID Token (a JWS signed JWT)
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def finalize_id_token(self, id_token, token, token_handler, request):
|
||||||
|
"""Finalize OpenID Connect ID token & Sign or Encrypt.
|
||||||
|
|
||||||
|
In the OpenID Connect workflows when an ID Token is requested
|
||||||
|
this method is called. Subclasses should implement the
|
||||||
|
construction, signing and optional encryption of the ID Token
|
||||||
|
as described in the OpenID Connect spec.
|
||||||
|
|
||||||
|
The `id_token` parameter is a dict containing a couple of OIDC
|
||||||
|
technical fields related to the specification. Prepopulated
|
||||||
|
attributes are:
|
||||||
|
|
||||||
|
- `aud`, equals to `request.client_id`.
|
||||||
|
- `iat`, equals to current time.
|
||||||
|
- `nonce`, if present, is equals to the `nonce` from the
|
||||||
|
authorization request.
|
||||||
|
- `at_hash`, hash of `access_token`, if relevant.
|
||||||
|
- `c_hash`, hash of `code`, if relevant.
|
||||||
|
|
||||||
|
This method MUST provide required fields as below:
|
||||||
|
|
||||||
|
- `iss`, REQUIRED. Issuer Identifier for the Issuer of the response.
|
||||||
|
- `sub`, REQUIRED. Subject Identifier
|
||||||
|
- `exp`, REQUIRED. Expiration time on or after which the ID
|
||||||
|
Token MUST NOT be accepted by the RP when performing
|
||||||
|
authentication with the OP.
|
||||||
|
|
||||||
|
Additionals claims must be added, note that `request.scope`
|
||||||
|
should be used to determine the list of claims.
|
||||||
|
|
||||||
|
More information can be found at `OpenID Connect Core#Claims`_
|
||||||
|
|
||||||
|
.. _`OpenID Connect Core#Claims`: https://openid.net/specs/openid-connect-core-1_0.html#Claims
|
||||||
|
|
||||||
|
:param id_token: A dict containing technical fields of id_token
|
||||||
|
:param token: A Bearer token dict
|
||||||
|
:param token_handler: the token handler (BearerToken class)
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:return: The ID Token (a JWS signed JWT or JWE encrypted JWT)
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def validate_jwt_bearer_token(self, token, scopes, request):
|
||||||
|
"""Ensure the JWT Bearer token or OpenID Connect ID token are valids and authorized access to scopes.
|
||||||
|
|
||||||
|
If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`
|
||||||
|
|
||||||
|
If not using OpenID Connect this can `return None` to avoid 5xx rather 401/3 response.
|
||||||
|
|
||||||
|
OpenID connect core 1.0 describe how to validate an id_token:
|
||||||
|
- http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||||
|
- http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
|
||||||
|
- http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
|
||||||
|
- http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2
|
||||||
|
|
||||||
|
:param token: Unicode Bearer token
|
||||||
|
:param scopes: List of scopes (defined by you)
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: True or False
|
||||||
|
|
||||||
|
Method is indirectly used by all core OpenID connect JWT token issuing grant types:
|
||||||
|
- Authorization Code Grant
|
||||||
|
- Implicit Grant
|
||||||
|
- Hybrid Grant
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def validate_id_token(self, token, scopes, request):
|
||||||
|
"""Ensure the id token is valid and authorized access to scopes.
|
||||||
|
|
||||||
|
OpenID connect core 1.0 describe how to validate an id_token:
|
||||||
|
- http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||||
|
- http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
|
||||||
|
- http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
|
||||||
|
- http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2
|
||||||
|
|
||||||
|
:param token: Unicode Bearer token
|
||||||
|
:param scopes: List of scopes (defined by you)
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: True or False
|
||||||
|
|
||||||
|
Method is indirectly used by all core OpenID connect JWT token issuing grant types:
|
||||||
|
- Authorization Code Grant
|
||||||
|
- Implicit Grant
|
||||||
|
- Hybrid Grant
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def validate_silent_authorization(self, request):
|
||||||
|
"""Ensure the logged in user has authorized silent OpenID authorization.
|
||||||
|
|
||||||
|
Silent OpenID authorization allows access tokens and id tokens to be
|
||||||
|
granted to clients without any user prompt or interaction.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: True or False
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- OpenIDConnectAuthCode
|
||||||
|
- OpenIDConnectImplicit
|
||||||
|
- OpenIDConnectHybrid
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def validate_silent_login(self, request):
|
||||||
|
"""Ensure session user has authorized silent OpenID login.
|
||||||
|
|
||||||
|
If no user is logged in or has not authorized silent login, this
|
||||||
|
method should return False.
|
||||||
|
|
||||||
|
If the user is logged in but associated with multiple accounts and
|
||||||
|
not selected which one to link to the token then this method should
|
||||||
|
raise an oauthlib.oauth2.AccountSelectionRequired error.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: True or False
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- OpenIDConnectAuthCode
|
||||||
|
- OpenIDConnectImplicit
|
||||||
|
- OpenIDConnectHybrid
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def validate_user_match(self, id_token_hint, scopes, claims, request):
|
||||||
|
"""Ensure client supplied user id hint matches session user.
|
||||||
|
|
||||||
|
If the sub claim or id_token_hint is supplied then the session
|
||||||
|
user must match the given ID.
|
||||||
|
|
||||||
|
:param id_token_hint: User identifier string.
|
||||||
|
:param scopes: List of OAuth 2 scopes and OpenID claims (strings).
|
||||||
|
:param claims: OpenID Connect claims dict.
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: True or False
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
- OpenIDConnectAuthCode
|
||||||
|
- OpenIDConnectImplicit
|
||||||
|
- OpenIDConnectHybrid
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
|
def get_userinfo_claims(self, request):
|
||||||
|
"""Return the UserInfo claims in JSON or Signed or Encrypted.
|
||||||
|
|
||||||
|
The UserInfo Claims MUST be returned as the members of a JSON object
|
||||||
|
unless a signed or encrypted response was requested during Client
|
||||||
|
Registration. The Claims defined in Section 5.1 can be returned, as can
|
||||||
|
additional Claims not specified there.
|
||||||
|
|
||||||
|
For privacy reasons, OpenID Providers MAY elect to not return values for
|
||||||
|
some requested Claims.
|
||||||
|
|
||||||
|
If a Claim is not returned, that Claim Name SHOULD be omitted from the
|
||||||
|
JSON object representing the Claims; it SHOULD NOT be present with a
|
||||||
|
null or empty string value.
|
||||||
|
|
||||||
|
The sub (subject) Claim MUST always be returned in the UserInfo
|
||||||
|
Response.
|
||||||
|
|
||||||
|
Upon receipt of the UserInfo Request, the UserInfo Endpoint MUST return
|
||||||
|
the JSON Serialization of the UserInfo Response as in Section 13.3 in
|
||||||
|
the HTTP response body unless a different format was specified during
|
||||||
|
Registration [OpenID.Registration].
|
||||||
|
|
||||||
|
If the UserInfo Response is signed and/or encrypted, then the Claims are
|
||||||
|
returned in a JWT and the content-type MUST be application/jwt. The
|
||||||
|
response MAY be encrypted without also being signed. If both signing and
|
||||||
|
encryption are requested, the response MUST be signed then encrypted,
|
||||||
|
with the result being a Nested JWT, as defined in [JWT].
|
||||||
|
|
||||||
|
If signed, the UserInfo Response SHOULD contain the Claims iss (issuer)
|
||||||
|
and aud (audience) as members. The iss value SHOULD be the OP's Issuer
|
||||||
|
Identifier URL. The aud value SHOULD be or include the RP's Client ID
|
||||||
|
value.
|
||||||
|
|
||||||
|
:param request: OAuthlib request.
|
||||||
|
:type request: oauthlib.common.Request
|
||||||
|
:rtype: Claims as a dict OR JWT/JWS/JWE as a string
|
||||||
|
|
||||||
|
Method is used by:
|
||||||
|
UserInfoEndpoint
|
||||||
|
"""
|
46
lib/oauthlib/openid/connect/core/tokens.py
Normal file
46
lib/oauthlib/openid/connect/core/tokens.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
"""
|
||||||
|
authlib.openid.connect.core.tokens
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module contains methods for adding JWT tokens to requests.
|
||||||
|
"""
|
||||||
|
from oauthlib.oauth2.rfc6749.tokens import TokenBase, random_token_generator, get_token_from_header
|
||||||
|
|
||||||
|
|
||||||
|
class JWTToken(TokenBase):
|
||||||
|
__slots__ = (
|
||||||
|
'request_validator', 'token_generator',
|
||||||
|
'refresh_token_generator', 'expires_in'
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, request_validator=None, token_generator=None,
|
||||||
|
expires_in=None, refresh_token_generator=None):
|
||||||
|
self.request_validator = request_validator
|
||||||
|
self.token_generator = token_generator or random_token_generator
|
||||||
|
self.refresh_token_generator = (
|
||||||
|
refresh_token_generator or self.token_generator
|
||||||
|
)
|
||||||
|
self.expires_in = expires_in or 3600
|
||||||
|
|
||||||
|
def create_token(self, request, refresh_token=False):
|
||||||
|
"""Create a JWT Token, using requestvalidator method."""
|
||||||
|
|
||||||
|
if callable(self.expires_in):
|
||||||
|
expires_in = self.expires_in(request)
|
||||||
|
else:
|
||||||
|
expires_in = self.expires_in
|
||||||
|
|
||||||
|
request.expires_in = expires_in
|
||||||
|
|
||||||
|
return self.request_validator.get_jwt_bearer_token(None, None, request)
|
||||||
|
|
||||||
|
def validate_request(self, request):
|
||||||
|
token = get_token_from_header(request)
|
||||||
|
return self.request_validator.validate_jwt_bearer_token(
|
||||||
|
token, request.scopes, request)
|
||||||
|
|
||||||
|
def estimate_type(self, request):
|
||||||
|
token = get_token_from_header(request)
|
||||||
|
if token and token.startswith('ey') and token.count('.') in (2, 4):
|
||||||
|
return 10
|
||||||
|
return 0
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
Implements signals based on blinker if available, otherwise
|
Implements signals based on blinker if available, otherwise
|
||||||
falls silently back to a noop. Shamelessly stolen from flask.signals:
|
falls silently back to a noop. Shamelessly stolen from flask.signals:
|
||||||
|
@ -8,12 +7,12 @@ signals_available = False
|
||||||
try:
|
try:
|
||||||
from blinker import Namespace
|
from blinker import Namespace
|
||||||
signals_available = True
|
signals_available = True
|
||||||
except ImportError:
|
except ImportError: # noqa
|
||||||
class Namespace(object):
|
class Namespace:
|
||||||
def signal(self, name, doc=None):
|
def signal(self, name, doc=None):
|
||||||
return _FakeSignal(name, doc)
|
return _FakeSignal(name, doc)
|
||||||
|
|
||||||
class _FakeSignal(object):
|
class _FakeSignal:
|
||||||
"""If blinker is unavailable, create a fake class with the same
|
"""If blinker is unavailable, create a fake class with the same
|
||||||
interface that allows sending of signals but will fail with an
|
interface that allows sending of signals but will fail with an
|
||||||
error on anything else. Instead of doing anything on send, it
|
error on anything else. Instead of doing anything on send, it
|
||||||
|
|
|
@ -8,7 +8,6 @@ They should be processed with re.VERBOSE.
|
||||||
|
|
||||||
Thanks Mark Nottingham for this code - https://gist.github.com/138549
|
Thanks Mark Nottingham for this code - https://gist.github.com/138549
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# basics
|
# basics
|
||||||
|
@ -66,32 +65,8 @@ dec_octet = r"""(?: %(DIGIT)s |
|
||||||
IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s" % locals(
|
IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s" % locals(
|
||||||
)
|
)
|
||||||
|
|
||||||
# h16 = 1*4HEXDIG
|
# IPv6address
|
||||||
h16 = r"(?: %(HEXDIG)s ){1,4}" % locals()
|
IPv6address = r"([A-Fa-f0-9:]+:+)+[A-Fa-f0-9]+"
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
# IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
|
# IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
|
||||||
IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals()
|
IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue