From 00f30dabbfab57ff998d8bea7280523b0e883618 Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Wed, 15 Jul 2015 12:06:02 +0200 Subject: [PATCH 01/11] Convert times to int Make iat_time, exp_time, auth_time an integer, not a float. The spec does not explicitly forbit float times, but some clients don't accept this (mod_auth_openidc), and `timetuple()` has second precision anyway so we don't loose any information. --- oidc_provider/lib/utils/token.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oidc_provider/lib/utils/token.py b/oidc_provider/lib/utils/token.py index 1264471..32f8bfb 100644 --- a/oidc_provider/lib/utils/token.py +++ b/oidc_provider/lib/utils/token.py @@ -25,11 +25,11 @@ def create_id_token(user, aud, nonce=None): now = timezone.now() # Convert datetimes into timestamps. - iat_time = time.mktime(now.timetuple()) - exp_time = time.mktime((now + timedelta(seconds=expires_in)).timetuple()) + iat_time = int(time.mktime(now.timetuple())) + exp_time = int(time.mktime((now + timedelta(seconds=expires_in)).timetuple())) user_auth_time = user.last_login or user.date_joined - auth_time = time.mktime(user_auth_time.timetuple()) + auth_time = int(time.mktime(user_auth_time.timetuple())) dic = { 'iss': get_issuer(), From 0882c5c63bd1fc6761c58f39356ee0369bcda26a Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Wed, 15 Jul 2015 12:17:21 +0200 Subject: [PATCH 02/11] Make the `sub` a string In the default sub generator. The spec says "The sub value is a case sensitive string." --- oidc_provider/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc_provider/settings.py b/oidc_provider/settings.py index 073a37f..c2ec4c8 100644 --- a/oidc_provider/settings.py +++ b/oidc_provider/settings.py @@ -56,7 +56,7 @@ class DefaultSettings(object): OPTIONAL. """ def default_sub_generator(user): - return user.id + return str(user.id) return default_sub_generator From e030203f0b9f3b49ad02b9e763102d0ee68e5148 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Wed, 15 Jul 2015 14:38:49 -0300 Subject: [PATCH 03/11] Add .pem files to gitignore in example project. --- example_project/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example_project/.gitignore b/example_project/.gitignore index 6e9bc0c..0d9de6a 100644 --- a/example_project/.gitignore +++ b/example_project/.gitignore @@ -1 +1,2 @@ -*.sqlite3 \ No newline at end of file +*.sqlite3 +*.pem From 6fc6126a62293021693386f4c4cbbe39bd8ec95e Mon Sep 17 00:00:00 2001 From: juanifioren Date: Wed, 15 Jul 2015 15:16:51 -0300 Subject: [PATCH 04/11] Add nonce to _extract_params function. --- oidc_provider/lib/endpoints/authorize.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 54af338..7879030 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -52,13 +52,6 @@ class AuthorizeEndpoint(object): self.params.response_type = self.query_dict.get('response_type', '') self.params.scope = self.query_dict.get('scope', '').split() self.params.state = self.query_dict.get('state', '') - - def _extract_implicit_params(self): - """ - Get specific params used by the Implicit Flow. - - See: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest - """ self.params.nonce = self.query_dict.get('nonce', '') def validate_params(self): From c995da640c40ba963adc1a3bca2c9a3f0102eb88 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Wed, 15 Jul 2015 15:17:47 -0300 Subject: [PATCH 05/11] Remove _extract_implicit_params function. --- oidc_provider/lib/endpoints/authorize.py | 1 - 1 file changed, 1 deletion(-) diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 7879030..1420edd 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -36,7 +36,6 @@ class AuthorizeEndpoint(object): self.grant_type = 'authorization_code' elif self.params.response_type in ['id_token', 'id_token token']: self.grant_type = 'implicit' - self._extract_implicit_params() else: self.grant_type = None From 0de868941adfcd6b67d8066632fca5559a824874 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Wed, 15 Jul 2015 16:18:34 -0300 Subject: [PATCH 06/11] Modify create_id_token function for supporting nonce. --- oidc_provider/lib/endpoints/authorize.py | 3 ++- oidc_provider/lib/utils/token.py | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 1420edd..5c3ad70 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -106,7 +106,8 @@ class AuthorizeEndpoint(object): elif self.grant_type == 'implicit': id_token_dic = create_id_token( user=self.request.user, - aud=self.client.client_id) + aud=self.client.client_id, + nonce=self.params.nonce) token = create_token( user=self.request.user, diff --git a/oidc_provider/lib/utils/token.py b/oidc_provider/lib/utils/token.py index 32f8bfb..38a732d 100644 --- a/oidc_provider/lib/utils/token.py +++ b/oidc_provider/lib/utils/token.py @@ -10,7 +10,7 @@ from oidc_provider.models import * from oidc_provider import settings -def create_id_token(user, aud, nonce=None): +def create_id_token(user, aud, nonce): """ Receives a user object and aud (audience). Then creates the id_token dictionary. @@ -18,16 +18,14 @@ def create_id_token(user, aud, nonce=None): Return a dic. """ - sub = settings.get('OIDC_IDTOKEN_SUB_GENERATOR')( - user=user) + sub = settings.get('OIDC_IDTOKEN_SUB_GENERATOR')(user=user) expires_in = settings.get('OIDC_IDTOKEN_EXPIRE') - now = timezone.now() # Convert datetimes into timestamps. + now = timezone.now() iat_time = int(time.mktime(now.timetuple())) exp_time = int(time.mktime((now + timedelta(seconds=expires_in)).timetuple())) - user_auth_time = user.last_login or user.date_joined auth_time = int(time.mktime(user_auth_time.timetuple())) From 6dde3a59a8f82d19ab3c19cc6629c450bf84dc82 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Wed, 15 Jul 2015 16:23:36 -0300 Subject: [PATCH 07/11] Add nonce to Code model. Modify create_code function. --- oidc_provider/lib/endpoints/authorize.py | 3 ++- oidc_provider/lib/utils/token.py | 3 ++- oidc_provider/migrations/0003_code_nonce.py | 19 +++++++++++++++++++ oidc_provider/models.py | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 oidc_provider/migrations/0003_code_nonce.py diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 5c3ad70..cafb394 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -96,7 +96,8 @@ class AuthorizeEndpoint(object): code = create_code( user=self.request.user, client=self.client, - scope=self.params.scope) + scope=self.params.scope, + nonce=self.params.nonce) code.save() diff --git a/oidc_provider/lib/utils/token.py b/oidc_provider/lib/utils/token.py index 38a732d..0eac64a 100644 --- a/oidc_provider/lib/utils/token.py +++ b/oidc_provider/lib/utils/token.py @@ -76,7 +76,7 @@ def create_token(user, client, id_token_dic, scope): return token -def create_code(user, client, scope): +def create_code(user, client, scope, nonce): """ Create and populate a Code object. @@ -89,5 +89,6 @@ def create_code(user, client, scope): code.expires_at = timezone.now() + timedelta( seconds=settings.get('OIDC_CODE_EXPIRE')) code.scope = scope + code.nonce = nonce return code diff --git a/oidc_provider/migrations/0003_code_nonce.py b/oidc_provider/migrations/0003_code_nonce.py new file mode 100644 index 0000000..0d49615 --- /dev/null +++ b/oidc_provider/migrations/0003_code_nonce.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('oidc_provider', '0002_userconsent'), + ] + + operations = [ + migrations.AddField( + model_name='code', + name='nonce', + field=models.CharField(default=b'', max_length=255, blank=True), + ), + ] diff --git a/oidc_provider/models.py b/oidc_provider/models.py index 8ec7e95..709f334 100644 --- a/oidc_provider/models.py +++ b/oidc_provider/models.py @@ -71,6 +71,7 @@ class BaseCodeTokenModel(models.Model): class Code(BaseCodeTokenModel): code = models.CharField(max_length=255, unique=True) + nonce = models.CharField(max_length=255, blank=True, default='') class Token(BaseCodeTokenModel): From a690a57a03cc9fed6552cd8a24e6e6f520bcd324 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Thu, 16 Jul 2015 14:25:58 -0300 Subject: [PATCH 08/11] Fix nonce parameter inside token endpoint. --- oidc_provider/lib/endpoints/token.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 191a3f4..416d17e 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -33,7 +33,6 @@ class TokenEndpoint(object): self.params.grant_type = query_dict.get('grant_type', '') self.params.code = query_dict.get('code', '') self.params.state = query_dict.get('state', '') - self.params.nonce = query_dict.get('nonce', '') def validate_params(self): if not (self.params.grant_type == 'authorization_code'): @@ -72,7 +71,7 @@ class TokenEndpoint(object): id_token_dic = create_id_token( user=self.code.user, aud=self.client.client_id, - nonce=self.params.nonce, + nonce=self.code.nonce, ) token = create_token( From 882def8124e770489d6e5a742af1ac890f44dbf3 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Thu, 16 Jul 2015 15:04:33 -0300 Subject: [PATCH 09/11] Fix tests for using nonce parameter. --- oidc_provider/tests/test_token_endpoint.py | 9 ++++----- oidc_provider/tests/test_userinfo_endpoint.py | 3 ++- oidc_provider/tests/utils.py | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/oidc_provider/tests/test_token_endpoint.py b/oidc_provider/tests/test_token_endpoint.py index 19d2983..feb7162 100644 --- a/oidc_provider/tests/test_token_endpoint.py +++ b/oidc_provider/tests/test_token_endpoint.py @@ -52,7 +52,8 @@ class TokenTestCase(TestCase): code = create_code( user=self.user, client=self.client, - scope=['openid', 'email']) + scope=['openid', 'email'], + nonce=FAKE_NONCE) code.save() return code @@ -126,7 +127,7 @@ class TokenTestCase(TestCase): self.assertEqual(response_dic.get('error') == 'invalid_client', True, msg='"error" key value should be "invalid_client".') - def test_token_contains_nonce_if_provided(self): + def test_access_token_contains_nonce(self): """ If present in the Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token with the Claim Value being the @@ -134,7 +135,6 @@ class TokenTestCase(TestCase): See http://openid.net/specs/openid-connect-core-1_0.html#IDToken """ - code = self._create_code() post_data = { @@ -144,7 +144,6 @@ class TokenTestCase(TestCase): 'grant_type': 'authorization_code', 'code': code.code, 'state': self.state, - 'nonce': 'thisisanonce' } response = self._post_request(post_data) @@ -153,4 +152,4 @@ class TokenTestCase(TestCase): id_token = jwt.decode(response_dic['id_token'], options={'verify_signature': False, 'verify_aud': False}) - self.assertEqual(id_token['nonce'], 'thisisanonce') + self.assertEqual(id_token['nonce'], FAKE_NONCE) diff --git a/oidc_provider/tests/test_userinfo_endpoint.py b/oidc_provider/tests/test_userinfo_endpoint.py index 9fb92a3..99335aa 100644 --- a/oidc_provider/tests/test_userinfo_endpoint.py +++ b/oidc_provider/tests/test_userinfo_endpoint.py @@ -22,7 +22,8 @@ class UserInfoTestCase(TestCase): """ Generate a valid token. """ - id_token_dic = create_id_token(self.user, self.client.client_id) + id_token_dic = create_id_token(self.user, + self.client.client_id, FAKE_NONCE) token = create_token( user=self.user, diff --git a/oidc_provider/tests/utils.py b/oidc_provider/tests/utils.py index 306239a..0155787 100644 --- a/oidc_provider/tests/utils.py +++ b/oidc_provider/tests/utils.py @@ -6,6 +6,8 @@ except ImportError: from oidc_provider.models import * +FAKE_NONCE = 'cb584e44c43ed6bd0bc2d9c7e242837d' + def create_fake_user(): """ Create a test user. From 91ae9ba9ff434cb0843b1cd0e93437f54eeacefc Mon Sep 17 00:00:00 2001 From: juanifioren Date: Thu, 16 Jul 2015 15:58:33 -0300 Subject: [PATCH 10/11] Add one test for request not containing nonce parameter. --- oidc_provider/tests/test_token_endpoint.py | 54 +++++++++++++++------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/oidc_provider/tests/test_token_endpoint.py b/oidc_provider/tests/test_token_endpoint.py index feb7162..fd3fa42 100644 --- a/oidc_provider/tests/test_token_endpoint.py +++ b/oidc_provider/tests/test_token_endpoint.py @@ -27,7 +27,21 @@ class TokenTestCase(TestCase): self.factory = RequestFactory() self.user = create_fake_user() self.client = create_fake_client(response_type='code') - self.state = uuid.uuid4().hex + + def _post_data(self, code): + """ + All the data that will be POSTed to the Token Endpoint. + """ + post_data = { + 'client_id': self.client.client_id, + 'client_secret': self.client.client_secret, + 'redirect_uri': self.client.default_redirect_uri, + 'grant_type': 'authorization_code', + 'code': code, + 'state': uuid.uuid4().hex, + } + + return post_data def _post_request(self, post_data): """ @@ -95,14 +109,8 @@ class TokenTestCase(TestCase): code = self._create_code() # Test a valid request to the token endpoint. - post_data = { - 'client_id': self.client.client_id, - 'client_secret': self.client.client_secret, - 'redirect_uri': self.client.default_redirect_uri, - 'grant_type': 'authorization_code', - 'code': code.code, - 'state': self.state, - } + post_data = self._post_data(code=code.code) + response = self._post_request(post_data) response_dic = json.loads(response.content.decode('utf-8')) @@ -137,14 +145,7 @@ class TokenTestCase(TestCase): """ code = self._create_code() - post_data = { - 'client_id': self.client.client_id, - 'client_secret': self.client.client_secret, - 'redirect_uri': self.client.default_redirect_uri, - 'grant_type': 'authorization_code', - 'code': code.code, - 'state': self.state, - } + post_data = self._post_data(code=code.code) response = self._post_request(post_data) @@ -153,3 +154,22 @@ class TokenTestCase(TestCase): options={'verify_signature': False, 'verify_aud': False}) self.assertEqual(id_token['nonce'], FAKE_NONCE) + + def test_access_token_not_contains_nonce(self): + """ + If the client does not supply a nonce parameter, it SHOULD not be + included in the `id_token`. + """ + code = self._create_code() + code.nonce = '' + code.save() + + post_data = self._post_data(code=code.code) + + response = self._post_request(post_data) + + response_dic = json.loads(response.content.decode('utf-8')) + id_token = jwt.decode(response_dic['id_token'], + options={'verify_signature': False, 'verify_aud': False}) + + self.assertEqual(id_token.get('nonce'), None) From 769ffc992bc2c2d419031c549f69c1b510e4d8b4 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Thu, 16 Jul 2015 16:44:23 -0300 Subject: [PATCH 11/11] Edit changelog. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3861832..cfeedb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. ### [Unreleased] +##### Fixed +- Nonce support for both Code and Implicit flow. + ### [0.0.7] - 2015-07-06 ##### Added