[Python-modules-commits] [python-oauthlib] 02/04: Import python-oauthlib_2.0.0.orig.tar.gz
Daniele Tricoli
eriol-guest at moszumanska.debian.org
Mon Nov 21 00:35:39 UTC 2016
This is an automated email from the git hooks/post-receive script.
eriol-guest pushed a commit to branch master
in repository python-oauthlib.
commit 959f64688adc505ac6c808d541cf8cb1d3d3c2ff
Author: Daniele Tricoli <eriol at mornie.org>
Date: Fri Oct 7 01:42:53 2016 +0200
Import python-oauthlib_2.0.0.orig.tar.gz
---
CHANGELOG.rst | 5 +
PKG-INFO | 2 +-
oauthlib.egg-info/PKG-INFO | 2 +-
oauthlib.egg-info/SOURCES.txt | 3 +
oauthlib/__init__.py | 2 +-
oauthlib/common.py | 12 +
oauthlib/oauth2/rfc6749/clients/base.py | 2 +-
oauthlib/oauth2/rfc6749/endpoints/authorization.py | 1 -
.../oauth2/rfc6749/endpoints/pre_configured.py | 20 +-
oauthlib/oauth2/rfc6749/endpoints/token.py | 13 +-
oauthlib/oauth2/rfc6749/errors.py | 54 +++
oauthlib/oauth2/rfc6749/grant_types/__init__.py | 6 +
.../rfc6749/grant_types/authorization_code.py | 58 ++-
oauthlib/oauth2/rfc6749/grant_types/base.py | 45 +++
.../rfc6749/grant_types/client_credentials.py | 11 +-
oauthlib/oauth2/rfc6749/grant_types/implicit.py | 52 ++-
.../oauth2/rfc6749/grant_types/openid_connect.py | 442 +++++++++++++++++++++
.../oauth2/rfc6749/grant_types/refresh_token.py | 11 +-
.../resource_owner_password_credentials.py | 11 +-
oauthlib/oauth2/rfc6749/request_validator.py | 108 +++++
oauthlib/oauth2/rfc6749/tokens.py | 5 +-
setup.cfg | 2 +-
.../rfc6749/endpoints/test_claims_handling.py | 107 +++++
.../rfc6749/grant_types/test_authorization_code.py | 13 +
.../rfc6749/grant_types/test_client_credentials.py | 2 +
tests/oauth2/rfc6749/grant_types/test_implicit.py | 23 +-
.../rfc6749/grant_types/test_openid_connect.py | 281 +++++++++++++
.../rfc6749/grant_types/test_refresh_token.py | 6 +
.../grant_types/test_resource_owner_password.py | 11 +
tests/oauth2/rfc6749/test_server.py | 33 ++
30 files changed, 1300 insertions(+), 43 deletions(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 91e0565..4055a14 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,11 @@
Changelog
=========
+2.0.0 (2016-09-03)
+------------------
+* (New Feature) **OpenID** support.
+* Documentation improvements and fixes.
+
1.1.2 (2016-06-01)
------------------
* (Fix) Query strings should be able to include colons.
diff --git a/PKG-INFO b/PKG-INFO
index 81ee955..aff62f3 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: oauthlib
-Version: 1.1.2
+Version: 2.0.0
Summary: A generic, spec-compliant, thorough implementation of the OAuth request-signing logic
Home-page: https://github.com/idan/oauthlib
Author: Ib Lundgren
diff --git a/oauthlib.egg-info/PKG-INFO b/oauthlib.egg-info/PKG-INFO
index 81ee955..aff62f3 100644
--- a/oauthlib.egg-info/PKG-INFO
+++ b/oauthlib.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: oauthlib
-Version: 1.1.2
+Version: 2.0.0
Summary: A generic, spec-compliant, thorough implementation of the OAuth request-signing logic
Home-page: https://github.com/idan/oauthlib
Author: Ib Lundgren
diff --git a/oauthlib.egg-info/SOURCES.txt b/oauthlib.egg-info/SOURCES.txt
index 0fe204d..075ca59 100644
--- a/oauthlib.egg-info/SOURCES.txt
+++ b/oauthlib.egg-info/SOURCES.txt
@@ -53,6 +53,7 @@ oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
oauthlib/oauth2/rfc6749/grant_types/base.py
oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
oauthlib/oauth2/rfc6749/grant_types/implicit.py
+oauthlib/oauth2/rfc6749/grant_types/openid_connect.py
oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
tests/__init__.py
@@ -87,6 +88,7 @@ tests/oauth2/rfc6749/clients/test_service_application.py
tests/oauth2/rfc6749/clients/test_web_application.py
tests/oauth2/rfc6749/endpoints/__init__.py
tests/oauth2/rfc6749/endpoints/test_base_endpoint.py
+tests/oauth2/rfc6749/endpoints/test_claims_handling.py
tests/oauth2/rfc6749/endpoints/test_client_authentication.py
tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py
tests/oauth2/rfc6749/endpoints/test_error_responses.py
@@ -99,6 +101,7 @@ tests/oauth2/rfc6749/grant_types/__init__.py
tests/oauth2/rfc6749/grant_types/test_authorization_code.py
tests/oauth2/rfc6749/grant_types/test_client_credentials.py
tests/oauth2/rfc6749/grant_types/test_implicit.py
+tests/oauth2/rfc6749/grant_types/test_openid_connect.py
tests/oauth2/rfc6749/grant_types/test_refresh_token.py
tests/oauth2/rfc6749/grant_types/test_resource_owner_password.py
tests/unittest/__init__.py
\ No newline at end of file
diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py
index 0c857aa..0bb9096 100644
--- a/oauthlib/__init__.py
+++ b/oauthlib/__init__.py
@@ -10,7 +10,7 @@
"""
__author__ = 'Idan Gazit <idan at gazit.me>'
-__version__ = '1.1.2'
+__version__ = '2.0.0'
import logging
diff --git a/oauthlib/common.py b/oauthlib/common.py
index e5ab6eb..5d999b2 100644
--- a/oauthlib/common.py
+++ b/oauthlib/common.py
@@ -401,6 +401,18 @@ class Request(object):
"token": None,
"user": None,
"token_type_hint": None,
+
+ # OpenID Connect
+ "response_mode": None,
+ "nonce": None,
+ "display": None,
+ "prompt": None,
+ "claims": None,
+ "max_age": None,
+ "ui_locales": None,
+ "id_token_hint": None,
+ "login_hint": None,
+ "acr_values": None
}
self._params.update(dict(urldecode(self.uri_query)))
self._params.update(dict(self.decoded_body or []))
diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py
index 4af9e76..e804b9a 100644
--- a/oauthlib/oauth2/rfc6749/clients/base.py
+++ b/oauthlib/oauth2/rfc6749/clients/base.py
@@ -80,7 +80,7 @@ class Client(object):
``token`` dict parameter.
:param refresh_token: A refresh token (string) used to refresh expired
- tokens. Can also be supplide inside the ``token`` dict parameter.
+ tokens. Can also be supplied inside the ``token`` dict parameter.
:param mac_key: Encryption key used with MAC tokens.
diff --git a/oauthlib/oauth2/rfc6749/endpoints/authorization.py b/oauthlib/oauth2/rfc6749/endpoints/authorization.py
index ada24d7..adc9f85 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/authorization.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/authorization.py
@@ -7,7 +7,6 @@ This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC6749.
"""
from __future__ import absolute_import, unicode_literals
-
import logging
from oauthlib.common import Request
diff --git a/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
index 21ed2dd..7463484 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
@@ -8,12 +8,15 @@ for consuming and providing OAuth 2.0 RFC6749.
"""
from __future__ import absolute_import, unicode_literals
+from ..grant_types import OpenIDConnectAuthCode
from ..tokens import BearerToken
from ..grant_types import AuthorizationCodeGrant
from ..grant_types import ImplicitGrant
from ..grant_types import ResourceOwnerPasswordCredentialsGrant
from ..grant_types import ClientCredentialsGrant
from ..grant_types import RefreshTokenGrant
+from ..grant_types import OpenIDConnectImplicit
+from ..grant_types import AuthCodeGrantDispatcher
from .authorization import AuthorizationEndpoint
from .token import TokenEndpoint
@@ -48,12 +51,26 @@ 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,
+ 'code': auth_grant_choice,
'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',
@@ -62,6 +79,7 @@ class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
'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',
diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py
index cda56ad..c093e1e 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/token.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/token.py
@@ -86,12 +86,23 @@ class TokenEndpoint(BaseEndpoint):
@catch_errors_and_unavailability
def create_token_response(self, uri, http_method='GET', body=None,
- headers=None, credentials=None):
+ headers=None, credentials=None, grant_type_for_scope=None,
+ claims=None):
"""Extract grant_type and route to the designated handler."""
request = Request(
uri, http_method=http_method, body=body, headers=headers)
request.scopes = None
request.extra_credentials = credentials
+ if grant_type_for_scope:
+ request.grant_type = grant_type_for_scope
+
+ # OpenID Connect claims, if provided. The server using oauthlib might choose
+ # to implement the claims parameter of the Authorization Request. In this case
+ # it should retrieve those claims and pass them via the claims argument here,
+ # as a dict.
+ if claims:
+ request.claims = claims
+
grant_type_handler = self.grant_types.get(request.grant_type,
self.default_grant_type_handler)
log.debug('Dispatching grant_type %s request to %r.',
diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py
index 88f5375..9f2ccef 100644
--- a/oauthlib/oauth2/rfc6749/errors.py
+++ b/oauthlib/oauth2/rfc6749/errors.py
@@ -258,6 +258,60 @@ class UnsupportedTokenTypeError(OAuth2Error):
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 AccountSelectionRequried(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
+
+
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 2ec8e4f..1da1281 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/__init__.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/__init__.py
@@ -10,3 +10,9 @@ 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/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
index 31f06fc..25e8816 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
@@ -95,10 +95,33 @@ class AuthorizationCodeGrant(GrantTypeBase):
.. _`Authorization Code Grant`: http://tools.ietf.org/html/rfc6749#section-4.1
"""
+ default_response_mode = 'query'
+
def __init__(self, request_validator=None, refresh_token=True):
self.request_validator = request_validator or RequestValidator()
self.refresh_token = refresh_token
+ self._authorization_validators = []
+ self._token_validators = []
+ self._code_modifiers = []
+ self._token_modifiers = []
+ self.response_types = ['code']
+
+ def register_response_type(self, response_type):
+ self.response_types.append(response_type)
+
+ def register_authorization_validator(self, validator):
+ self._authorization_validators.append(validator)
+
+ def register_token_validator(self, validator):
+ self._token_validators.append(validator)
+
+ def register_code_modifier(self, modifier):
+ self._code_modifiers.append(modifier)
+
+ def register_token_modifier(self, modifier):
+ self._token_modifiers.append(modifier)
+
def create_authorization_code(self, request):
"""Generates an authorization grant represented as a dictionary."""
grant = {'code': common.generate_token()}
@@ -115,7 +138,10 @@ class AuthorizationCodeGrant(GrantTypeBase):
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
response_type
- REQUIRED. Value MUST be set to "code".
+ REQUIRED. Value MUST be set to "code" for standard OAuth2
+ authorization flow. For OpenID Connect it must be one of
+ "code token", "code id_token", or "code token id_token" - we
+ essentially test that "code" appears in the response_type.
client_id
REQUIRED. The client identifier as described in `Section 2.2`_.
redirect_uri
@@ -212,10 +238,13 @@ class AuthorizationCodeGrant(GrantTypeBase):
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples)}, None, 302
grant = self.create_authorization_code(request)
+ for modifier in self._code_modifiers:
+ grant = modifier(grant, token_handler, request)
log.debug('Saving grant %r for %r.', grant, request)
self.request_validator.save_authorization_code(
request.client_id, grant, request)
- return {'Location': common.add_params_to_uri(request.redirect_uri, grant.items())}, None, 302
+ return self.prepare_authorization_response(
+ request, grant, {}, None, 302)
def create_token_response(self, request, token_handler):
"""Validate the authorization code.
@@ -238,7 +267,10 @@ class AuthorizationCodeGrant(GrantTypeBase):
log.debug('Client error during validation of %r. %r.', request, e)
return headers, e.json, e.status_code
- token = token_handler.create_token(request, refresh_token=self.refresh_token)
+ token = token_handler.create_token(request, refresh_token=self.refresh_token, save_token=False)
+ for modifier in self._token_modifiers:
+ token = modifier(token, token_handler, request)
+ self.request_validator.save_token(token, request)
self.request_validator.invalidate_authorization_code(
request.client_id, request.code, request)
return headers, json.dumps(token), 200
@@ -318,8 +350,9 @@ class AuthorizationCodeGrant(GrantTypeBase):
# REQUIRED.
if request.response_type is None:
raise errors.MissingResponseTypeError(request=request)
- # Value MUST be set to "code".
- elif request.response_type != 'code':
+ # Value MUST be set to "code" or one of the OpenID authorization code including
+ # response_types "code token", "code id_token", "code token id_token"
+ elif not 'code' in request.response_type and request.response_type != 'none':
raise errors.UnsupportedResponseTypeError(request=request)
if not self.request_validator.validate_response_type(request.client_id,
@@ -334,17 +367,22 @@ class AuthorizationCodeGrant(GrantTypeBase):
# http://tools.ietf.org/html/rfc6749#section-3.3
self.validate_scopes(request)
- return request.scopes, {
+ request_info = {
'client_id': request.client_id,
'redirect_uri': request.redirect_uri,
'response_type': request.response_type,
'state': request.state,
- 'request': request,
+ 'request': request
}
+ for validator in self._authorization_validators:
+ request_info.update(validator(request))
+
+ return request.scopes, request_info
+
def validate_token_request(self, request):
# REQUIRED. Value MUST be set to "authorization_code".
- if request.grant_type != 'authorization_code':
+ if request.grant_type not in ('authorization_code', 'openid'):
raise errors.UnsupportedGrantTypeError(request=request)
if request.code is None:
@@ -400,3 +438,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
log.debug('Redirect_uri (%r) invalid for client %r (%r).',
request.redirect_uri, request.client_id, request.client)
raise errors.AccessDeniedError(request=request)
+
+ for validator in self._token_validators:
+ validator(request)
+
diff --git a/oauthlib/oauth2/rfc6749/grant_types/base.py b/oauthlib/oauth2/rfc6749/grant_types/base.py
index 9fc632f..36b06eb 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/base.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/base.py
@@ -7,6 +7,7 @@ from __future__ import unicode_literals, absolute_import
import logging
+from oauthlib.common import add_params_to_uri
from oauthlib.oauth2.rfc6749 import errors, utils
log = logging.getLogger(__name__)
@@ -15,6 +16,7 @@ log = logging.getLogger(__name__)
class GrantTypeBase(object):
error_uri = None
request_validator = None
+ default_response_mode = 'fragment'
def create_authorization_response(self, request, token_handler):
raise NotImplementedError('Subclasses must implement this method.')
@@ -22,6 +24,14 @@ class GrantTypeBase(object):
def create_token_response(self, request, token_handler):
raise NotImplementedError('Subclasses must implement this method.')
+ def add_token(self, token, token_handler, request):
+ # Only add a hybrid access token on auth step if asked for
+ if not request.response_type in ["token", "code token", "id_token token", "code id_token token"]:
+ return token
+
+ token.update(token_handler.create_token(request, refresh_token=False))
+ return token
+
def validate_grant_type(self, request):
client_id = getattr(request, 'client_id', None)
if not self.request_validator.validate_grant_type(client_id,
@@ -39,3 +49,38 @@ class GrantTypeBase(object):
if not self.request_validator.validate_scopes(request.client_id,
request.scopes, request.client, request):
raise errors.InvalidScopeError(request=request)
+
+ def prepare_authorization_response(self, request, token, headers, body, status):
+ """Place token according to response mode.
+
+ Base classes can define a default response mode for their authorization
+ response by overriding the static `default_response_mode` member.
+ """
+ request.response_mode = request.response_mode or self.default_response_mode
+
+ if request.response_mode not in ('query', 'fragment'):
+ log.debug('Overriding invalid response mode %s with %s',
+ request.response_mode, self.default_response_mode)
+ request.response_mode = self.default_response_mode
+
+ token_items = token.items()
+
+ if request.response_type == 'none':
+ state = token.get('state', None)
+ if state:
+ token_items = [('state', state)]
+ else:
+ token_items = []
+
+ if request.response_mode == 'query':
+ headers['Location'] = add_params_to_uri(
+ request.redirect_uri, token_items, fragment=False)
+ return headers, body, status
+
+ if request.response_mode == 'fragment':
+ headers['Location'] = add_params_to_uri(
+ request.redirect_uri, token_items, fragment=True)
+ return headers, body, status
+
+ raise NotImplementedError(
+ 'Subclasses must set a valid default_response_mode')
diff --git a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
index 49173cc..91c17a6 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
@@ -52,6 +52,10 @@ class ClientCredentialsGrant(GrantTypeBase):
def __init__(self, request_validator=None):
self.request_validator = request_validator or RequestValidator()
+ self._token_modifiers = []
+
+ def register_token_modifier(self, modifier):
+ self._token_modifiers.append(modifier)
def create_token_response(self, request, token_handler):
"""Return token or error in JSON format.
@@ -77,7 +81,12 @@ class ClientCredentialsGrant(GrantTypeBase):
log.debug('Client error in token request. %s.', e)
return headers, e.json, e.status_code
- token = token_handler.create_token(request, refresh_token=False)
+ token = token_handler.create_token(request, refresh_token=False, save_token=False)
+
+ for modifier in self._token_modifiers:
+ token = modifier(token)
+ self.request_validator.save_token(token, request)
+
log.debug('Issuing token to client id %r (%r), %r.',
request.client_id, request.client, token)
return headers, json.dumps(token), 200
diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
index 2a92a02..7366b94 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
@@ -119,6 +119,18 @@ class ImplicitGrant(GrantTypeBase):
def __init__(self, request_validator=None):
self.request_validator = request_validator or RequestValidator()
+ self._authorization_validators = []
+ self._token_modifiers = []
+ self.response_types = ['token']
+
+ def register_response_type(self, response_type):
+ self.response_types.append(response_type)
+
+ def register_authorization_validator(self, validator):
+ self._authorization_validators.append(validator)
+
+ def register_token_modifier(self, modifier):
+ self._token_modifiers.append(modifier)
def create_authorization_response(self, request, token_handler):
"""Create an authorization response.
@@ -127,7 +139,8 @@ class ImplicitGrant(GrantTypeBase):
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
response_type
- REQUIRED. Value MUST be set to "token".
+ REQUIRED. Value MUST be set to "token" for standard OAuth2 implicit flow
+ or "id_token token" or just "id_token" for OIDC implicit flow
client_id
REQUIRED. The client identifier as described in `Section 2.2`_.
@@ -228,9 +241,19 @@ class ImplicitGrant(GrantTypeBase):
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
fragment=True)}, None, 302
- token = token_handler.create_token(request, refresh_token=False)
- return {'Location': common.add_params_to_uri(request.redirect_uri, token.items(),
- fragment=True)}, None, 302
+ # In OIDC implicit flow it is possible to have a request_type that does not include the access token!
+ # "id_token token" - return the access token and the id token
+ # "id_token" - don't return the access token
+ if "token" in request.response_type.split():
+ token = token_handler.create_token(request, refresh_token=False, save_token=False)
+ else:
+ token = {}
+
+ for modifier in self._token_modifiers:
+ token = modifier(token, token_handler, request)
+ self.request_validator.save_token(token, request)
+ return self.prepare_authorization_response(
+ request, token, {}, None, 302)
def validate_authorization_request(self, request):
return self.validate_token_request(request)
@@ -318,8 +341,8 @@ class ImplicitGrant(GrantTypeBase):
# REQUIRED.
if request.response_type is None:
raise errors.MissingResponseTypeError(request=request)
- # Value MUST be set to "token".
- elif request.response_type != 'token':
+ # Value MUST be one of our registered types: "token" by default or if using OIDC "id_token" or "id_token token"
+ elif not set(request.response_type.split()).issubset(self.response_types):
raise errors.UnsupportedResponseTypeError(request=request)
log.debug('Validating use of response_type token for client %r (%r).',
@@ -336,10 +359,15 @@ class ImplicitGrant(GrantTypeBase):
# http://tools.ietf.org/html/rfc6749#section-3.3
self.validate_scopes(request)
- return request.scopes, {
- 'client_id': request.client_id,
- 'redirect_uri': request.redirect_uri,
- 'response_type': request.response_type,
- 'state': request.state,
- 'request': request,
+ request_info = {
+ 'client_id': request.client_id,
+ 'redirect_uri': request.redirect_uri,
+ 'response_type': request.response_type,
+ 'state': request.state,
+ 'request': request,
}
+
+ for validator in self._authorization_validators:
+ request_info.update(validator(request))
+
+ return request.scopes, request_info
diff --git a/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py b/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py
new file mode 100644
index 0000000..d09893e
--- /dev/null
+++ b/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py
@@ -0,0 +1,442 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749.grant_types.openid_connect
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from __future__ import unicode_literals, absolute_import
+
+from json import loads
+import logging
+
+import datetime
+
+from .base import GrantTypeBase
+from .authorization_code import AuthorizationCodeGrant
+from .implicit import ImplicitGrant
+from ..errors import InvalidRequestError, LoginRequired, ConsentRequired
+from ..request_validator import RequestValidator
+
+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 "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(GrantTypeBase):
+
+ def __init__(self, request_validator=None):
+ self.request_validator = request_validator or RequestValidator()
+
+ 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 '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.
+
+ 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 'openid' in request.scopes:
+ return {}
+
+ # prompt other than 'none' should be handled by the server code that uses oauthlib
+ if request.prompt == 'none' and not request.id_token_hint:
+ msg = "Prompt is set to none yet id_token_hint is missing."
+ raise InvalidRequestError(request=request, description=msg)
+
+ if request.prompt == 'none':
+ 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,
+ 'prompt': request.prompt.split() if request.prompt else [],
+ '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 {}
+
+ if not 'openid' 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)
+
+ self._inflate_claims(request)
+
+ return {'nonce': request.nonce, 'claims': request.claims}
+
+class OpenIDConnectAuthCode(OpenIDConnectBase):
+
+ def __init__(self, request_validator=None):
+ self.request_validator = request_validator or RequestValidator()
+ super(OpenIDConnectAuthCode, self).__init__(
+ request_validator=self.request_validator)
+ self.auth_code = AuthorizationCodeGrant(
+ request_validator=self.request_validator)
+ self.auth_code.register_authorization_validator(
+ self.openid_authorization_validator)
+ self.auth_code.register_token_modifier(self.add_id_token)
+
+ @property
+ def refresh_token(self):
+ return self.auth_code.refresh_token
+
+ @refresh_token.setter
+ def refresh_token(self, value):
+ self.auth_code.refresh_token = value
+
+ def create_authorization_code(self, request):
+ return self.auth_code.create_authorization_code(request)
+
+ def create_authorization_response(self, request, token_handler):
+ return self.auth_code.create_authorization_response(
+ request, token_handler)
+
+ def create_token_response(self, request, token_handler):
+ return self.auth_code.create_token_response(request, token_handler)
+
+ 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':
... 1028 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-oauthlib.git
More information about the Python-modules-commits
mailing list