summaryrefslogtreecommitdiff
path: root/oauthlib/oauth2
diff options
context:
space:
mode:
Diffstat (limited to 'oauthlib/oauth2')
-rw-r--r--oauthlib/oauth2/__init__.py3
-rw-r--r--oauthlib/oauth2/rfc6749/clients/base.py1
-rw-r--r--oauthlib/oauth2/rfc6749/clients/web_application.py2
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/__init__.py1
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/introspect.py135
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/pre_configured.py57
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/resource.py2
-rw-r--r--oauthlib/oauth2/rfc6749/errors.py126
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/__init__.py6
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/implicit.py32
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/openid_connect.py390
-rw-r--r--oauthlib/oauth2/rfc6749/parameters.py7
-rw-r--r--oauthlib/oauth2/rfc6749/request_validator.py141
-rw-r--r--oauthlib/oauth2/rfc6749/tokens.py28
14 files changed, 370 insertions, 561 deletions
diff --git a/oauthlib/oauth2/__init__.py b/oauthlib/oauth2/__init__.py
index c8d934e..303c6a1 100644
--- a/oauthlib/oauth2/__init__.py
+++ b/oauthlib/oauth2/__init__.py
@@ -15,6 +15,7 @@ from .rfc6749.clients import LegacyApplicationClient
from .rfc6749.clients import BackendApplicationClient
from .rfc6749.clients import ServiceApplicationClient
from .rfc6749.endpoints import AuthorizationEndpoint
+from .rfc6749.endpoints import IntrospectEndpoint
from .rfc6749.endpoints import TokenEndpoint
from .rfc6749.endpoints import ResourceEndpoint
from .rfc6749.endpoints import RevocationEndpoint
@@ -23,7 +24,7 @@ from .rfc6749.endpoints import WebApplicationServer
from .rfc6749.endpoints import MobileApplicationServer
from .rfc6749.endpoints import LegacyApplicationServer
from .rfc6749.endpoints import BackendApplicationServer
-from .rfc6749.errors import AccessDeniedError, AccountSelectionRequired, ConsentRequired, FatalClientError, FatalOpenIDClientError, InsecureTransportError, InteractionRequired, InvalidClientError, InvalidClientIdError, InvalidGrantError, InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError, InvalidScopeError, LoginRequired, MismatchingRedirectURIError, MismatchingStateError, MissingClientIdError, MissingCodeError, MissingRedirectURIError, MissingResponseTypeError, MissingTokenError, MissingTokenTypeError, OAuth2Error, OpenIDClientError, ServerError, TemporarilyUnavailableError, TokenExpiredError, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError
+from .rfc6749.errors import AccessDeniedError, OAuth2Error, FatalClientError, InsecureTransportError, InvalidClientError, InvalidClientIdError, InvalidGrantError, InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError, InvalidScopeError, MismatchingRedirectURIError, MismatchingStateError, MissingClientIdError, MissingCodeError, MissingRedirectURIError, MissingResponseTypeError, MissingTokenError, MissingTokenTypeError, ServerError, TemporarilyUnavailableError, TokenExpiredError, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError
from .rfc6749.grant_types import AuthorizationCodeGrant
from .rfc6749.grant_types import ImplicitGrant
from .rfc6749.grant_types import ResourceOwnerPasswordCredentialsGrant
diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py
index 07ef894..406832d 100644
--- a/oauthlib/oauth2/rfc6749/clients/base.py
+++ b/oauthlib/oauth2/rfc6749/clients/base.py
@@ -143,6 +143,7 @@ class Client(object):
def parse_request_uri_response(self, *args, **kwargs):
"""Abstract method used to parse redirection responses."""
+ raise NotImplementedError("Must be implemented by inheriting classes.")
def add_token(self, uri, http_method='GET', body=None, headers=None,
token_placement=None, **kwargs):
diff --git a/oauthlib/oauth2/rfc6749/clients/web_application.py b/oauthlib/oauth2/rfc6749/clients/web_application.py
index 25280bf..c14a5f8 100644
--- a/oauthlib/oauth2/rfc6749/clients/web_application.py
+++ b/oauthlib/oauth2/rfc6749/clients/web_application.py
@@ -125,7 +125,7 @@ class WebApplicationClient(Client):
"""
code = code or self.code
return prepare_token_request('authorization_code', code=code, body=body,
- client_id=self.client_id, redirect_uri=redirect_uri, **kwargs)
+ client_id=client_id, redirect_uri=redirect_uri, **kwargs)
def parse_request_uri_response(self, uri, state=None):
"""Parse the URI query for code and state.
diff --git a/oauthlib/oauth2/rfc6749/endpoints/__init__.py b/oauthlib/oauth2/rfc6749/endpoints/__init__.py
index 848bec6..9557f92 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/__init__.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/__init__.py
@@ -9,6 +9,7 @@ for consuming and providing OAuth 2.0 RFC6749.
from __future__ import absolute_import, unicode_literals
from .authorization import AuthorizationEndpoint
+from .introspect import IntrospectEndpoint
from .token import TokenEndpoint
from .resource import ResourceEndpoint
from .revocation import RevocationEndpoint
diff --git a/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/oauthlib/oauth2/rfc6749/endpoints/introspect.py
new file mode 100644
index 0000000..7613acc
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/endpoints/introspect.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749.endpoint.introspect
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An implementation of the OAuth 2.0 `Token Introspection`.
+
+.. _`Token Introspection`: https://tools.ietf.org/html/rfc7662
+"""
+from __future__ import absolute_import, unicode_literals
+
+import json
+import logging
+
+from oauthlib.common import Request
+
+from ..errors import (InvalidClientError, InvalidRequestError, OAuth2Error,
+ UnsupportedTokenTypeError)
+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')
+
+ 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.
+ """
+ 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)
+ return {}, e.json, e.status_code
+
+ claims = self.request_validator.introspect_token(
+ request.token,
+ request.token_type_hint,
+ request
+ )
+ headers = {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ }
+ if claims is None:
+ return headers, json.dumps(dict(active=False)), 200
+ if "active" in claims:
+ claims.pop("active")
+ return 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
+ """
+ if not request.token:
+ raise InvalidRequestError(request=request,
+ description='Missing token parameter.')
+
+ 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)
+
+ 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)
diff --git a/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
index 6428b8d..e2cc9db 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
@@ -1,27 +1,28 @@
# -*- coding: utf-8 -*-
"""
-oauthlib.oauth2.rfc6749
-~~~~~~~~~~~~~~~~~~~~~~~
+oauthlib.oauth2.rfc6749.endpoints.pre_configured
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-This module is an implementation of various logic needed
-for consuming and providing OAuth 2.0 RFC6749.
+This module is an implementation of various endpoints needed
+for providing OAuth 2.0 RFC6749 servers.
"""
from __future__ import absolute_import, unicode_literals
-from ..grant_types import (AuthCodeGrantDispatcher, AuthorizationCodeGrant,
- ClientCredentialsGrant, ImplicitGrant,
- OpenIDConnectAuthCode, OpenIDConnectImplicit,
+from ..grant_types import (AuthorizationCodeGrant,
+ ClientCredentialsGrant,
+ ImplicitGrant,
RefreshTokenGrant,
ResourceOwnerPasswordCredentialsGrant)
from ..tokens import BearerToken
from .authorization import AuthorizationEndpoint
+from .introspect import IntrospectEndpoint
from .resource import ResourceEndpoint
from .revocation import RevocationEndpoint
from .token import TokenEndpoint
-class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
- RevocationEndpoint):
+class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
+ ResourceEndpoint, RevocationEndpoint):
"""An all-in-one endpoint featuring all four major grant types."""
@@ -47,44 +48,34 @@ class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
request_validator)
credentials_grant = ClientCredentialsGrant(request_validator)
refresh_grant = RefreshTokenGrant(request_validator)
- openid_connect_auth = OpenIDConnectAuthCode(request_validator)
- openid_connect_implicit = OpenIDConnectImplicit(request_validator)
bearer = BearerToken(request_validator, token_generator,
token_expires_in, refresh_token_generator)
- auth_grant_choice = AuthCodeGrantDispatcher( default_auth_grant=auth_grant, oidc_auth_grant=openid_connect_auth)
-
- # See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations for valid combinations
- # internally our AuthorizationEndpoint will ensure they can appear in any order for any valid combination
AuthorizationEndpoint.__init__(self, default_response_type='code',
response_types={
- 'code': auth_grant_choice,
+ 'code': auth_grant,
'token': implicit_grant,
- 'id_token': openid_connect_implicit,
- 'id_token token': openid_connect_implicit,
- 'code token': openid_connect_auth,
- 'code id_token': openid_connect_auth,
- 'code token id_token': openid_connect_auth,
'none': auth_grant
},
default_token_type=bearer)
+
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
grant_types={
'authorization_code': auth_grant,
'password': password_grant,
'client_credentials': credentials_grant,
'refresh_token': refresh_grant,
- 'openid': openid_connect_auth
},
default_token_type=bearer)
ResourceEndpoint.__init__(self, default_token='Bearer',
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator)
+ IntrospectEndpoint.__init__(self, request_validator)
-class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
- RevocationEndpoint):
+class WebApplicationServer(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
+ ResourceEndpoint, RevocationEndpoint):
"""An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
@@ -119,10 +110,11 @@ class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoin
ResourceEndpoint.__init__(self, default_token='Bearer',
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator)
+ IntrospectEndpoint.__init__(self, request_validator)
-class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint,
- RevocationEndpoint):
+class MobileApplicationServer(AuthorizationEndpoint, IntrospectEndpoint,
+ ResourceEndpoint, RevocationEndpoint):
"""An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""
@@ -152,10 +144,12 @@ class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint,
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator,
supported_token_types=['access_token'])
+ IntrospectEndpoint.__init__(self, request_validator,
+ supported_token_types=['access_token'])
-class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint,
- RevocationEndpoint):
+class LegacyApplicationServer(TokenEndpoint, IntrospectEndpoint,
+ ResourceEndpoint, RevocationEndpoint):
"""An all-in-one endpoint featuring Resource Owner Password Credentials grant and Bearer tokens."""
@@ -188,10 +182,11 @@ class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint,
ResourceEndpoint.__init__(self, default_token='Bearer',
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator)
+ IntrospectEndpoint.__init__(self, request_validator)
-class BackendApplicationServer(TokenEndpoint, ResourceEndpoint,
- RevocationEndpoint):
+class BackendApplicationServer(TokenEndpoint, IntrospectEndpoint,
+ ResourceEndpoint, RevocationEndpoint):
"""An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""
@@ -221,3 +216,5 @@ class BackendApplicationServer(TokenEndpoint, ResourceEndpoint,
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator,
supported_token_types=['access_token'])
+ IntrospectEndpoint.__init__(self, request_validator,
+ supported_token_types=['access_token'])
diff --git a/oauthlib/oauth2/rfc6749/endpoints/resource.py b/oauthlib/oauth2/rfc6749/endpoints/resource.py
index d03ed21..f19c60c 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/resource.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/resource.py
@@ -83,5 +83,5 @@ class ResourceEndpoint(BaseEndpoint):
to give an estimation based on the request.
"""
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
diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py
index 43aa38e..a15d6c5 100644
--- a/oauthlib/oauth2/rfc6749/errors.py
+++ b/oauthlib/oauth2/rfc6749/errors.py
@@ -267,113 +267,13 @@ class UnsupportedGrantTypeError(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
on a server not supporting this feature.
"""
error = 'unsupported_token_type'
-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
@@ -402,6 +302,30 @@ class InsufficientScopeError(OAuth2Error):
"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'
+ status_code = 401
+
+
+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'
+ status_code = 401
+
+
class CustomOAuth2Error(OAuth2Error):
"""
This error is a placeholder for all custom errors not described by the RFC.
diff --git a/oauthlib/oauth2/rfc6749/grant_types/__init__.py b/oauthlib/oauth2/rfc6749/grant_types/__init__.py
index 1da1281..2ec8e4f 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/__init__.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/__init__.py
@@ -10,9 +10,3 @@ from .implicit import ImplicitGrant
from .resource_owner_password_credentials import ResourceOwnerPasswordCredentialsGrant
from .client_credentials import ClientCredentialsGrant
from .refresh_token import RefreshTokenGrant
-from .openid_connect import OpenIDConnectBase
-from .openid_connect import OpenIDConnectAuthCode
-from .openid_connect import OpenIDConnectImplicit
-from .openid_connect import OpenIDConnectHybrid
-from .openid_connect import OIDCNoPrompt
-from .openid_connect import AuthCodeGrantDispatcher
diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
index 569282e..bdab814 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
@@ -11,7 +11,6 @@ from oauthlib import common
from oauthlib.uri_validate import is_absolute_uri
from .. import errors
-from ..request_validator import RequestValidator
from .base import GrantTypeBase
log = logging.getLogger(__name__)
@@ -229,7 +228,7 @@ class ImplicitGrant(GrantTypeBase):
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
fragment=True)}, None, 302
- # In OIDC implicit flow it is possible to have a request_type that does not include the access token!
+ # 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():
@@ -239,7 +238,12 @@ class ImplicitGrant(GrantTypeBase):
for modifier in self._token_modifiers:
token = modifier(token, token_handler, request)
- self.request_validator.save_token(token, 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)
@@ -317,8 +321,7 @@ class ImplicitGrant(GrantTypeBase):
# Then check for normal errors.
request_info = self._run_custom_validators(request,
- self.custom_validators.all_pre)
-
+ self.custom_validators.all_pre)
# If the resource owner denies the access request or if the request
# fails for reasons other than a missing or invalid redirection URI,
@@ -352,20 +355,21 @@ class ImplicitGrant(GrantTypeBase):
self.validate_scopes(request)
request_info.update({
- 'client_id': request.client_id,
- 'redirect_uri': request.redirect_uri,
- 'response_type': request.response_type,
- 'state': request.state,
- 'request': request,
+ 'client_id': request.client_id,
+ 'redirect_uri': request.redirect_uri,
+ 'response_type': request.response_type,
+ 'state': request.state,
+ 'request': request,
})
- request_info = self._run_custom_validators(request,
- self.custom_validators.all_post,
- request_info)
+ 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,
diff --git a/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py b/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py
deleted file mode 100644
index 4c98864..0000000
--- a/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py
+++ /dev/null
@@ -1,390 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-oauthlib.oauth2.rfc6749.grant_types.openid_connect
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"""
-from __future__ import absolute_import, unicode_literals
-
-import datetime
-import logging
-from json import loads
-
-from ..errors import ConsentRequired, InvalidRequestError, LoginRequired
-from ..request_validator import RequestValidator
-from .authorization_code import AuthorizationCodeGrant
-from .base import GrantTypeBase
-from .implicit import ImplicitGrant
-
-log = logging.getLogger(__name__)
-
-class OIDCNoPrompt(Exception):
- """Exception used to inform users that no explicit authorization is needed.
-
- Normally users authorize requests after validation of the request is done.
- Then post-authorization validation is again made and a response containing
- an auth code or token is created. However, when OIDC clients request
- no prompting of user authorization the final response is created directly.
-
- Example (without the shortcut for no prompt)
-
- scopes, req_info = endpoint.validate_authorization_request(url, ...)
- authorization_view = create_fancy_auth_form(scopes, req_info)
- return authorization_view
-
- Example (with the no prompt shortcut)
- try:
- scopes, req_info = endpoint.validate_authorization_request(url, ...)
- authorization_view = create_fancy_auth_form(scopes, req_info)
- return authorization_view
- except OIDCNoPrompt:
- # Note: Location will be set for you
- headers, body, status = endpoint.create_authorization_response(url, ...)
- redirect_view = create_redirect(headers, body, status)
- return redirect_view
- """
-
- def __init__(self):
- msg = ("OIDC request for no user interaction received. Do not ask user "
- "for authorization, it should been done using silent "
- "authentication through create_authorization_response. "
- "See OIDCNoPrompt.__doc__ for more details.")
- super(OIDCNoPrompt, self).__init__(msg)
-
-
-class AuthCodeGrantDispatcher(object):
- """
- 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_auth_grant or the oidc_auth_grant based on the scopes requested.
- """
- def __init__(self, default_auth_grant=None, oidc_auth_grant=None):
- self.default_auth_grant = default_auth_grant
- self.oidc_auth_grant = oidc_auth_grant
-
- def _handler_for_request(self, request):
- handler = self.default_auth_grant
-
- if request.scopes and "openid" in request.scopes:
- handler = self.oidc_auth_grant
-
- log.debug('Selecting handler for request %r.', handler)
- return handler
-
- def create_authorization_response(self, request, token_handler):
- return self._handler_for_request(request).create_authorization_response(request, token_handler)
-
- def validate_authorization_request(self, request):
- return self._handler_for_request(request).validate_authorization_request(request)
-
-
-class OpenIDConnectBase(object):
-
- # 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 = set(('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)
- """
- # If request.prompt is 'none' then no login/authorization form should
- # be presented to the user. Instead, a silent login/authorization
- # should be performed.
- if request.prompt == 'none':
- raise OIDCNoPrompt()
- else:
- return self.proxy_target.validate_authorization_request(request)
-
- 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 add_id_token(self, token, token_handler, request):
- # 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
-
- if 'state' not in token:
- token['state'] = request.state
-
- if request.max_age:
- d = datetime.datetime.utcnow()
- token['auth_time'] = d.isoformat("T") + "Z"
-
- # TODO: acr claims (probably better handled by server code using oauthlib in get_id_token)
-
- token['id_token'] = self.request_validator.get_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)
-
- # prompt other than 'none' should be handled by the server code that
- # uses oauthlib
- if not request.id_token_hint:
- msg = "Prompt is set to none yet id_token_hint is missing."
- 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
-
- def openid_implicit_authorization_validator(self, request):
- """Additional validation when following the implicit flow.
- """
- # Undefined in OpenID Connect, fall back to OAuth2 definition.
- if request.response_type == 'token':
- return {}
-
- # 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 {}
-
- # 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:
- desc = 'Request is missing mandatory nonce parameter.'
- raise InvalidRequestError(request=request, description=desc)
-
- return {}
-
-
-class OpenIDConnectAuthCode(OpenIDConnectBase):
-
- def __init__(self, request_validator=None, **kwargs):
- self.proxy_target = AuthorizationCodeGrant(
- request_validator=request_validator, **kwargs)
- self.custom_validators.post_auth.append(
- self.openid_authorization_validator)
- self.register_token_modifier(self.add_id_token)
-
-class OpenIDConnectImplicit(OpenIDConnectBase):
-
- def __init__(self, request_validator=None, **kwargs):
- self.proxy_target = ImplicitGrant(
- 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.custom_validators.post_auth.append(
- self.openid_implicit_authorization_validator)
- self.register_token_modifier(self.add_id_token)
-
-class OpenIDConnectHybrid(OpenIDConnectBase):
-
- def __init__(self, request_validator=None, **kwargs):
- self.request_validator = request_validator or RequestValidator()
-
- self.proxy_target = AuthorizationCodeGrant(
- 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)
diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py
index 0107933..9ea8c44 100644
--- a/oauthlib/oauth2/rfc6749/parameters.py
+++ b/oauthlib/oauth2/rfc6749/parameters.py
@@ -362,16 +362,13 @@ def parse_token_response(body, scope=None):
# https://github.com/oauthlib/oauthlib/issues/267
params = dict(urlparse.parse_qsl(body))
- for key in ('expires_in', 'expires'):
- if key in params: # cast a couple things to int
+ for key in ('expires_in',):
+ if key in params: # cast things to int
params[key] = int(params[key])
if 'scope' in params:
params['scope'] = scope_to_list(params['scope'])
- if 'expires' in params:
- params['expires_in'] = params.pop('expires')
-
if 'expires_in' in params:
params['expires_at'] = time.time() + int(params['expires_in'])
diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py
index fee7b8c..bf1515d 100644
--- a/oauthlib/oauth2/rfc6749/request_validator.py
+++ b/oauthlib/oauth2/rfc6749/request_validator.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
-oauthlib.oauth2.rfc6749.grant_types
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+oauthlib.oauth2.rfc6749.request_validator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
from __future__ import absolute_import, unicode_literals
@@ -166,6 +166,46 @@ class RequestValidator(object):
"""
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: The HTTP 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):
"""Invalidate an authorization code after use.
@@ -238,6 +278,30 @@ class RequestValidator(object):
"""
raise NotImplementedError('Subclasses must implement this method.')
+ 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 gave 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 save_token(self, token, request, *args, **kwargs):
"""Persist the token with a token type specific method.
@@ -268,7 +332,14 @@ class RequestValidator(object):
}
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.
+
+ The token dict is passed as a reference so any changes made to the dictionary
+ will go back to the user. If additional information must return to the client
+ 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
@@ -288,8 +359,24 @@ class RequestValidator(object):
"""
raise NotImplementedError('Subclasses must implement this method.')
- def get_id_token(self, token, token_handler, request):
+ 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: the HTTP 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
+
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.
@@ -320,6 +407,52 @@ class RequestValidator(object):
# the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token
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: The HTTP 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: The HTTP 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_bearer_token(self, token, scopes, request):
"""Ensure the Bearer token is valid and authorized access to scopes.
diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py
index 6cfa642..1d2b5eb 100644
--- a/oauthlib/oauth2/rfc6749/tokens.py
+++ b/oauthlib/oauth2/rfc6749/tokens.py
@@ -24,8 +24,6 @@ except ImportError:
from urllib.parse import urlparse
-
-
class OAuth2Token(dict):
def __init__(self, params, old_scope=None):
@@ -222,6 +220,24 @@ def signed_token_generator(private_pem, **kwargs):
return signed_token_generator
+def get_token_from_header(request):
+ """
+ Helper function to extract a token from the request header.
+ :param request: The request object
+ :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] == 'Bearer':
+ token = split_header[1]
+ else:
+ token = request.access_token
+
+ return token
+
+
class TokenBase(object):
def __call__(self, request, refresh_token=False):
@@ -288,16 +304,12 @@ class BearerToken(TokenBase):
return token
def validate_request(self, request):
- token = None
- if 'Authorization' in request.headers:
- token = request.headers.get('Authorization')[7:]
- else:
- token = request.access_token
+ token = get_token_from_header(request)
return self.request_validator.validate_bearer_token(
token, request.scopes, request)
def estimate_type(self, request):
- if request.headers.get('Authorization', '').startswith('Bearer'):
+ if request.headers.get('Authorization', '').split(' ')[0] == 'Bearer':
return 9
elif request.access_token is not None:
return 5