diff --git a/docs/sections/settings.rst b/docs/sections/settings.rst index 651f914..b5457c1 100644 --- a/docs/sections/settings.rst +++ b/docs/sections/settings.rst @@ -103,6 +103,13 @@ Default is:: return str(user.id) +OIDC_IDTOKEN_INCLUDE_CLAIMS +============================== + +OPTIONAL. ``bool``. If enabled, id_token will include standard claims of the user (email, first name, etc.). + +Default is ``False``. + OIDC_SESSION_MANAGEMENT_ENABLE ============================== diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index e7072b1..569d1f2 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -158,6 +158,7 @@ class AuthorizeEndpoint(object): # We don't need id_token if it's an OAuth2 request. if self.is_authentication: kwargs = { + 'token': token, 'user': self.request.user, 'aud': self.client.client_id, 'nonce': self.params['nonce'], diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 956ccaa..3fbff8e 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -171,6 +171,7 @@ class TokenEndpoint(object): id_token_dic = create_id_token( user=self.code.user, aud=self.client.client_id, + token=token, nonce=self.code.nonce, at_hash=token.at_hash, request=self.request, @@ -215,6 +216,7 @@ class TokenEndpoint(object): id_token_dic = create_id_token( user=self.token.user, aud=self.client.client_id, + token=token, nonce=None, at_hash=token.at_hash, request=self.request, @@ -249,6 +251,7 @@ class TokenEndpoint(object): self.params['scope'].split(' ')) id_token_dic = create_id_token( + token=token, user=self.user, aud=self.client.client_id, nonce='self.code.nonce', diff --git a/oidc_provider/lib/utils/token.py b/oidc_provider/lib/utils/token.py index 80868db..2da2c13 100644 --- a/oidc_provider/lib/utils/token.py +++ b/oidc_provider/lib/utils/token.py @@ -10,6 +10,7 @@ from jwkest.jws import JWS from jwkest.jwt import JWT from oidc_provider.lib.utils.common import get_issuer +from oidc_provider.lib.claims import StandardScopeClaims from oidc_provider.models import ( Code, RSAKey, @@ -18,7 +19,7 @@ from oidc_provider.models import ( from oidc_provider import settings -def create_id_token(user, aud, nonce='', at_hash='', request=None, scope=None): +def create_id_token(token, user, aud, nonce='', at_hash='', request=None, scope=None): """ Creates the id_token dictionary. See: http://openid.net/specs/openid-connect-core-1_0.html#IDToken @@ -52,8 +53,14 @@ def create_id_token(user, aud, nonce='', at_hash='', request=None, scope=None): if at_hash: dic['at_hash'] = at_hash - if ('email' in scope) and getattr(user, 'email', None): - dic['email'] = user.email + # Inlude (or not) user standard claims in the id_token. + if settings.get('OIDC_IDTOKEN_INCLUDE_CLAIMS'): + if settings.get('OIDC_EXTRA_SCOPE_CLAIMS'): + custom_claims = settings.get('OIDC_EXTRA_SCOPE_CLAIMS', import_str=True)(token) + claims = custom_claims.create_response_dic() + else: + claims = StandardScopeClaims(token).create_response_dic() + dic.update(claims) processing_hook = settings.get('OIDC_IDTOKEN_PROCESSING_HOOK') diff --git a/oidc_provider/settings.py b/oidc_provider/settings.py index 7c7da44..8c1729d 100644 --- a/oidc_provider/settings.py +++ b/oidc_provider/settings.py @@ -72,6 +72,13 @@ class DefaultSettings(object): """ return 'oidc_provider.lib.utils.common.default_sub_generator' + @property + def OIDC_IDTOKEN_INCLUDE_CLAIMS(self): + """ + OPTIONAL. If enabled, id_token will include standard claims of the user. + """ + return False + @property def OIDC_SESSION_MANAGEMENT_ENABLE(self): """ diff --git a/oidc_provider/tests/app/utils.py b/oidc_provider/tests/app/utils.py index c885e3c..47e09da 100644 --- a/oidc_provider/tests/app/utils.py +++ b/oidc_provider/tests/app/utils.py @@ -97,6 +97,7 @@ def userinfo(claims, user): claims['family_name'] = 'Doe' claims['name'] = '{0} {1}'.format(claims['given_name'], claims['family_name']) claims['email'] = user.email + claims['email_verified'] = True claims['address']['country'] = 'Argentina' return claims diff --git a/oidc_provider/tests/cases/test_end_session_endpoint.py b/oidc_provider/tests/cases/test_end_session_endpoint.py index 50958d5..fb36f8e 100644 --- a/oidc_provider/tests/cases/test_end_session_endpoint.py +++ b/oidc_provider/tests/cases/test_end_session_endpoint.py @@ -6,6 +6,7 @@ except ImportError: from django.test import TestCase from oidc_provider.lib.utils.token import ( + create_token, create_id_token, encode_id_token, ) @@ -44,8 +45,9 @@ class EndSessionTestCase(TestCase): response, settings.get('OIDC_LOGIN_URL'), fetch_redirect_response=False) + token = create_token(self.user, self.oidc_client, []) id_token_dic = create_id_token( - user=self.user, aud=self.oidc_client.client_id) + token=token, user=self.user, aud=self.oidc_client.client_id) id_token = encode_id_token(id_token_dic, self.oidc_client) query_params['id_token_hint'] = id_token @@ -59,8 +61,9 @@ class EndSessionTestCase(TestCase): query_params = { 'post_logout_redirect_uri': self.LOGOUT_URL, } + token = create_token(self.user, self.oidc_client, []) id_token_dic = create_id_token( - user=self.user, aud=self.oidc_client.client_id) + token=token, user=self.user, aud=self.oidc_client.client_id) id_token_dic['aud'] = [id_token_dic['aud']] id_token = encode_id_token(id_token_dic, self.oidc_client) query_params['id_token_hint'] = id_token diff --git a/oidc_provider/tests/cases/test_token_endpoint.py b/oidc_provider/tests/cases/test_token_endpoint.py index dad6d4b..5c787b3 100644 --- a/oidc_provider/tests/cases/test_token_endpoint.py +++ b/oidc_provider/tests/cases/test_token_endpoint.py @@ -281,17 +281,19 @@ class TokenTestCase(TestCase): self.assertEqual(id_token['sub'], str(self.user.id)) self.assertEqual(id_token['aud'], self.client.client_id) - @override_settings(OIDC_TOKEN_EXPIRE=720) + @override_settings(OIDC_TOKEN_EXPIRE=720, + OIDC_IDTOKEN_INCLUDE_CLAIMS=True) def test_scope_is_ignored_for_auth_code(self): """ Scope is ignored for token respones to auth code grant type. + This comes down to that the scopes requested in authorize are returned. """ SIGKEYS = self._get_keys() - for code_scope in [['openid'], ['openid', 'email']]: + for code_scope in [['openid'], ['openid', 'email'], ['openid', 'profile']]: code = self._create_code(code_scope) post_data = self._auth_code_post_data( - code=code.code, scope=['openid', 'profile']) + code=code.code, scope=code_scope) response = self._post_request(post_data) response_dic = json.loads(response.content.decode('utf-8')) @@ -302,9 +304,15 @@ class TokenTestCase(TestCase): if 'email' in code_scope: self.assertIn('email', id_token) + self.assertIn('email_verified', id_token) else: self.assertNotIn('email', id_token) + if 'profile' in code_scope: + self.assertIn('given_name', id_token) + else: + self.assertNotIn('given_name', id_token) + def test_refresh_token(self): """ A request to the Token Endpoint can also use a Refresh Token @@ -333,6 +341,7 @@ class TokenTestCase(TestCase): """ self.do_refresh_token_check(scope=['openid']) + @override_settings(OIDC_IDTOKEN_INCLUDE_CLAIMS=True) def do_refresh_token_check(self, scope=None): SIGKEYS = self._get_keys() diff --git a/oidc_provider/tests/cases/test_userinfo_endpoint.py b/oidc_provider/tests/cases/test_userinfo_endpoint.py index 4be59a8..dc26371 100644 --- a/oidc_provider/tests/cases/test_userinfo_endpoint.py +++ b/oidc_provider/tests/cases/test_userinfo_endpoint.py @@ -41,18 +41,20 @@ class UserInfoTestCase(TestCase): extra_scope = [] scope = ['openid', 'email'] + extra_scope + token = create_token( + user=self.user, + client=self.client, + scope=scope) + id_token_dic = create_id_token( + token=token, user=self.user, aud=self.client.client_id, nonce=FAKE_NONCE, scope=scope, ) - token = create_token( - user=self.user, - client=self.client, - id_token_dic=id_token_dic, - scope=scope) + token.id_token = id_token_dic token.save() return token diff --git a/oidc_provider/tests/cases/test_utils.py b/oidc_provider/tests/cases/test_utils.py index b09ff46..787a3f5 100644 --- a/oidc_provider/tests/cases/test_utils.py +++ b/oidc_provider/tests/cases/test_utils.py @@ -8,8 +8,8 @@ from django.utils import timezone from mock import mock from oidc_provider.lib.utils.common import get_issuer, get_browser_state_or_default -from oidc_provider.lib.utils.token import create_id_token -from oidc_provider.tests.app.utils import create_fake_user +from oidc_provider.lib.utils.token import create_token, create_id_token +from oidc_provider.tests.app.utils import create_fake_user, create_fake_client class Request(object): @@ -67,7 +67,9 @@ class TokenTest(TestCase): start_time = int(time.time()) login_timestamp = start_time - 1234 self.user.last_login = timestamp_to_datetime(login_timestamp) - id_token_data = create_id_token(self.user, aud='test-aud') + client = create_fake_client("code") + token = create_token(self.user, client, []) + id_token_data = create_id_token(token=token, user=self.user, aud='test-aud') iat = id_token_data['iat'] self.assertEqual(type(iat), int) self.assertGreaterEqual(iat, start_time)