[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