summaryrefslogtreecommitdiff
path: root/oauthlib/oauth2/rfc6749
diff options
context:
space:
mode:
authorJonathan Huot <JonathanHuot@users.noreply.github.com>2018-06-26 09:33:28 +0200
committerGitHub <noreply@github.com>2018-06-26 09:33:28 +0200
commit048befd55de7924fd3414fe6a24a28eaaaba2a66 (patch)
tree91b0ffe20afa4362dd806f8e57b577d3d3ed5529 /oauthlib/oauth2/rfc6749
parentdd120a5c4ccdcc5e3de85746266990192202203d (diff)
parentd5a4d5ea0eab04ddddefac7d1e7a4902fc469286 (diff)
downloadoauthlib-048befd55de7924fd3414fe6a24a28eaaaba2a66.tar.gz
Merge branch 'master' into master
Diffstat (limited to 'oauthlib/oauth2/rfc6749')
-rw-r--r--oauthlib/oauth2/rfc6749/clients/base.py1
-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.py71
-rw-r--r--oauthlib/oauth2/rfc6749/errors.py125
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/__init__.py8
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/openid_connect.py451
-rw-r--r--oauthlib/oauth2/rfc6749/request_validator.py44
-rw-r--r--oauthlib/oauth2/rfc6749/tokens.py70
9 files changed, 252 insertions, 654 deletions
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/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 0c26986..e2cc9db 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
@@ -1,30 +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,
- AuthTokenGrantDispatcher,
+from ..grant_types import (AuthorizationCodeGrant,
ClientCredentialsGrant,
- ImplicitTokenGrantDispatcher, ImplicitGrant,
- OpenIDConnectAuthCode, OpenIDConnectImplicit,
- OpenIDConnectHybrid,
+ ImplicitGrant,
RefreshTokenGrant,
ResourceOwnerPasswordCredentialsGrant)
-from ..tokens import BearerToken, JWTToken
+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."""
@@ -50,51 +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)
- openid_connect_hybrid = OpenIDConnectHybrid(request_validator)
bearer = BearerToken(request_validator, token_generator,
token_expires_in, refresh_token_generator)
- jwt = JWTToken(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)
- implicit_grant_choice = ImplicitTokenGrantDispatcher(default_implicit_grant=implicit_grant, oidc_implicit_grant=openid_connect_implicit)
-
- # See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations for valid combinations
- # internally our AuthorizationEndpoint will ensure they can appear in any order for any valid combination
AuthorizationEndpoint.__init__(self, default_response_type='code',
response_types={
- 'code': auth_grant_choice,
- 'token': implicit_grant_choice,
- 'id_token': openid_connect_implicit,
- 'id_token token': openid_connect_implicit,
- 'code token': openid_connect_hybrid,
- 'code id_token': openid_connect_hybrid,
- 'code id_token token': openid_connect_hybrid,
+ 'code': auth_grant,
+ 'token': implicit_grant,
'none': auth_grant
},
default_token_type=bearer)
- token_grant_choice = AuthTokenGrantDispatcher(request_validator, default_token_grant=auth_grant, oidc_token_grant=openid_connect_auth)
-
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
grant_types={
- 'authorization_code': token_grant_choice,
+ 'authorization_code': auth_grant,
'password': password_grant,
'client_credentials': credentials_grant,
'refresh_token': refresh_grant,
},
default_token_type=bearer)
ResourceEndpoint.__init__(self, default_token='Bearer',
- token_types={'Bearer': bearer, 'JWT': jwt})
+ 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."""
@@ -129,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."""
@@ -162,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."""
@@ -198,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."""
@@ -231,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/errors.py b/oauthlib/oauth2/rfc6749/errors.py
index 180f636..5a0cca2 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,29 @@ 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
+
+
def raise_from_error(error, params=None):
import inspect
import sys
diff --git a/oauthlib/oauth2/rfc6749/grant_types/__init__.py b/oauthlib/oauth2/rfc6749/grant_types/__init__.py
index 2e4bfe4..2ec8e4f 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/__init__.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/__init__.py
@@ -10,11 +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
-from .openid_connect import AuthTokenGrantDispatcher
-from .openid_connect import ImplicitTokenGrantDispatcher
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 4371b28..0000000
--- a/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py
+++ /dev/null
@@ -1,451 +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 .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 ImplicitTokenGrantDispatcher(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_implicit_grant=None, oidc_implicit_grant=None):
- self.default_implicit_grant = default_implicit_grant
- self.oidc_implicit_grant = oidc_implicit_grant
-
- def _handler_for_request(self, request):
- handler = self.default_implicit_grant
-
- if request.scopes and "openid" in request.scopes and 'id_token' in request.response_type:
- handler = self.oidc_implicit_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 AuthTokenGrantDispatcher(object):
- """
- This is an adapter class that will route simple Token requests, those that authorization_code have a scope
- including 'openid' to either the default_token_grant or the oidc_token_grant based on the scopes requested.
- """
- def __init__(self, request_validator, default_token_grant=None, oidc_token_grant=None):
- self.default_token_grant = default_token_grant
- self.oidc_token_grant = oidc_token_grant
- self.request_validator = request_validator
-
- def _handler_for_request(self, request):
- handler = self.default_token_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_token_grant` wich 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_token_grant
-
- log.debug('Selecting handler for request %r.', handler)
- return handler
-
- def create_token_response(self, request, token_handler):
- handler = self._handler_for_request(request)
- return handler.create_token_response(request, token_handler)
-
-
-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/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py
index c0b69a1..92edba6 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.
diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py
index 4ae20e0..1d2b5eb 100644
--- a/oauthlib/oauth2/rfc6749/tokens.py
+++ b/oauthlib/oauth2/rfc6749/tokens.py
@@ -220,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):
@@ -286,62 +304,14 @@ 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
else:
return 0
-
-
-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, save_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 = None
- if 'Authorization' in request.headers:
- token = request.headers.get('Authorization')[7:]
- else:
- token = request.access_token
- return self.request_validator.validate_jwt_bearer_token(
- token, request.scopes, request)
-
- def estimate_type(self, request):
- token = request.headers.get('Authorization', '')[7:]
- if token.startswith('ey') and token.count('.') in (2, 4):
- return 10
- else:
- return 0