Merge branch 'master' of https://github.com/juanifioren/django-oidc-provider into v0.1.0-dev
Conflicts: example_project/.gitignore
This commit is contained in:
commit
a08dbdb7d2
11 changed files with 82 additions and 44 deletions
|
@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### [Unreleased]
|
### [Unreleased]
|
||||||
|
|
||||||
|
##### Fixed
|
||||||
|
- Nonce support for both Code and Implicit flow.
|
||||||
|
|
||||||
### [0.0.7] - 2015-07-06
|
### [0.0.7] - 2015-07-06
|
||||||
|
|
||||||
##### Added
|
##### Added
|
||||||
|
|
|
@ -36,7 +36,6 @@ class AuthorizeEndpoint(object):
|
||||||
self.grant_type = 'authorization_code'
|
self.grant_type = 'authorization_code'
|
||||||
elif self.params.response_type in ['id_token', 'id_token token']:
|
elif self.params.response_type in ['id_token', 'id_token token']:
|
||||||
self.grant_type = 'implicit'
|
self.grant_type = 'implicit'
|
||||||
self._extract_implicit_params()
|
|
||||||
else:
|
else:
|
||||||
self.grant_type = None
|
self.grant_type = None
|
||||||
|
|
||||||
|
@ -52,13 +51,6 @@ class AuthorizeEndpoint(object):
|
||||||
self.params.response_type = self.query_dict.get('response_type', '')
|
self.params.response_type = self.query_dict.get('response_type', '')
|
||||||
self.params.scope = self.query_dict.get('scope', '').split()
|
self.params.scope = self.query_dict.get('scope', '').split()
|
||||||
self.params.state = self.query_dict.get('state', '')
|
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', '')
|
self.params.nonce = self.query_dict.get('nonce', '')
|
||||||
|
|
||||||
def validate_params(self):
|
def validate_params(self):
|
||||||
|
@ -104,7 +96,8 @@ class AuthorizeEndpoint(object):
|
||||||
code = create_code(
|
code = create_code(
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
client=self.client,
|
client=self.client,
|
||||||
scope=self.params.scope)
|
scope=self.params.scope,
|
||||||
|
nonce=self.params.nonce)
|
||||||
|
|
||||||
code.save()
|
code.save()
|
||||||
|
|
||||||
|
@ -114,7 +107,8 @@ class AuthorizeEndpoint(object):
|
||||||
elif self.grant_type == 'implicit':
|
elif self.grant_type == 'implicit':
|
||||||
id_token_dic = create_id_token(
|
id_token_dic = create_id_token(
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
aud=self.client.client_id)
|
aud=self.client.client_id,
|
||||||
|
nonce=self.params.nonce)
|
||||||
|
|
||||||
token = create_token(
|
token = create_token(
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
|
|
|
@ -33,7 +33,6 @@ class TokenEndpoint(object):
|
||||||
self.params.grant_type = query_dict.get('grant_type', '')
|
self.params.grant_type = query_dict.get('grant_type', '')
|
||||||
self.params.code = query_dict.get('code', '')
|
self.params.code = query_dict.get('code', '')
|
||||||
self.params.state = query_dict.get('state', '')
|
self.params.state = query_dict.get('state', '')
|
||||||
self.params.nonce = query_dict.get('nonce', '')
|
|
||||||
|
|
||||||
def validate_params(self):
|
def validate_params(self):
|
||||||
if not (self.params.grant_type == 'authorization_code'):
|
if not (self.params.grant_type == 'authorization_code'):
|
||||||
|
@ -72,7 +71,7 @@ class TokenEndpoint(object):
|
||||||
id_token_dic = create_id_token(
|
id_token_dic = create_id_token(
|
||||||
user=self.code.user,
|
user=self.code.user,
|
||||||
aud=self.client.client_id,
|
aud=self.client.client_id,
|
||||||
nonce=self.params.nonce,
|
nonce=self.code.nonce,
|
||||||
)
|
)
|
||||||
|
|
||||||
token = create_token(
|
token = create_token(
|
||||||
|
|
|
@ -10,7 +10,7 @@ from oidc_provider.models import *
|
||||||
from oidc_provider import settings
|
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).
|
Receives a user object and aud (audience).
|
||||||
Then creates the id_token dictionary.
|
Then creates the id_token dictionary.
|
||||||
|
@ -22,13 +22,12 @@ def create_id_token(user, aud, nonce=None):
|
||||||
|
|
||||||
expires_in = settings.get('OIDC_IDTOKEN_EXPIRE')
|
expires_in = settings.get('OIDC_IDTOKEN_EXPIRE')
|
||||||
|
|
||||||
now = timezone.now()
|
|
||||||
# Convert datetimes into timestamps.
|
# Convert datetimes into timestamps.
|
||||||
iat_time = time.mktime(now.timetuple())
|
now = timezone.now()
|
||||||
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
|
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 = {
|
dic = {
|
||||||
'iss': get_issuer(),
|
'iss': get_issuer(),
|
||||||
|
@ -75,7 +74,7 @@ def create_token(user, client, id_token_dic, scope):
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
def create_code(user, client, scope):
|
def create_code(user, client, scope, nonce):
|
||||||
"""
|
"""
|
||||||
Create and populate a Code object.
|
Create and populate a Code object.
|
||||||
|
|
||||||
|
@ -88,5 +87,6 @@ def create_code(user, client, scope):
|
||||||
code.expires_at = timezone.now() + timedelta(
|
code.expires_at = timezone.now() + timedelta(
|
||||||
seconds=settings.get('OIDC_CODE_EXPIRE'))
|
seconds=settings.get('OIDC_CODE_EXPIRE'))
|
||||||
code.scope = scope
|
code.scope = scope
|
||||||
|
code.nonce = nonce
|
||||||
|
|
||||||
return code
|
return code
|
||||||
|
|
19
oidc_provider/migrations/0003_code_nonce.py
Normal file
19
oidc_provider/migrations/0003_code_nonce.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -71,6 +71,7 @@ class BaseCodeTokenModel(models.Model):
|
||||||
class Code(BaseCodeTokenModel):
|
class Code(BaseCodeTokenModel):
|
||||||
|
|
||||||
code = models.CharField(max_length=255, unique=True)
|
code = models.CharField(max_length=255, unique=True)
|
||||||
|
nonce = models.CharField(max_length=255, blank=True, default='')
|
||||||
|
|
||||||
|
|
||||||
class Token(BaseCodeTokenModel):
|
class Token(BaseCodeTokenModel):
|
||||||
|
|
|
@ -60,7 +60,7 @@ class DefaultSettings(object):
|
||||||
which is intended to be consumed by the Client.
|
which is intended to be consumed by the Client.
|
||||||
"""
|
"""
|
||||||
def default_sub_generator(user):
|
def default_sub_generator(user):
|
||||||
return user.id
|
return str(user.id)
|
||||||
|
|
||||||
return default_sub_generator
|
return default_sub_generator
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ except ImportError:
|
||||||
from oidc_provider.models import *
|
from oidc_provider.models import *
|
||||||
|
|
||||||
|
|
||||||
|
FAKE_NONCE = 'cb584e44c43ed6bd0bc2d9c7e242837d'
|
||||||
|
|
||||||
def create_fake_user():
|
def create_fake_user():
|
||||||
"""
|
"""
|
||||||
Create a test user.
|
Create a test user.
|
||||||
|
|
|
@ -27,7 +27,21 @@ class TokenTestCase(TestCase):
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.user = create_fake_user()
|
self.user = create_fake_user()
|
||||||
self.client = create_fake_client(response_type='code')
|
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):
|
def _post_request(self, post_data):
|
||||||
"""
|
"""
|
||||||
|
@ -52,7 +66,8 @@ class TokenTestCase(TestCase):
|
||||||
code = create_code(
|
code = create_code(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
client=self.client,
|
client=self.client,
|
||||||
scope=['openid', 'email'])
|
scope=['openid', 'email'],
|
||||||
|
nonce=FAKE_NONCE)
|
||||||
code.save()
|
code.save()
|
||||||
|
|
||||||
return code
|
return code
|
||||||
|
@ -94,14 +109,8 @@ class TokenTestCase(TestCase):
|
||||||
code = self._create_code()
|
code = self._create_code()
|
||||||
|
|
||||||
# Test a valid request to the token endpoint.
|
# Test a valid request to the token endpoint.
|
||||||
post_data = {
|
post_data = self._post_data(code=code.code)
|
||||||
'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,
|
|
||||||
}
|
|
||||||
response = self._post_request(post_data)
|
response = self._post_request(post_data)
|
||||||
response_dic = json.loads(response.content.decode('utf-8'))
|
response_dic = json.loads(response.content.decode('utf-8'))
|
||||||
|
|
||||||
|
@ -126,7 +135,7 @@ class TokenTestCase(TestCase):
|
||||||
self.assertEqual(response_dic.get('error') == 'invalid_client', True,
|
self.assertEqual(response_dic.get('error') == 'invalid_client', True,
|
||||||
msg='"error" key value should be "invalid_client".')
|
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
|
If present in the Authentication Request, Authorization Servers MUST
|
||||||
include a nonce Claim in the ID Token with the Claim Value being the
|
include a nonce Claim in the ID Token with the Claim Value being the
|
||||||
|
@ -134,18 +143,9 @@ class TokenTestCase(TestCase):
|
||||||
|
|
||||||
See http://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
See http://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
||||||
"""
|
"""
|
||||||
|
|
||||||
code = self._create_code()
|
code = self._create_code()
|
||||||
|
|
||||||
post_data = {
|
post_data = self._post_data(code=code.code)
|
||||||
'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,
|
|
||||||
'nonce': 'thisisanonce'
|
|
||||||
}
|
|
||||||
|
|
||||||
response = self._post_request(post_data)
|
response = self._post_request(post_data)
|
||||||
|
|
||||||
|
@ -153,4 +153,23 @@ class TokenTestCase(TestCase):
|
||||||
id_token = jwt.decode(response_dic['id_token'],
|
id_token = jwt.decode(response_dic['id_token'],
|
||||||
options={'verify_signature': False, 'verify_aud': False})
|
options={'verify_signature': False, 'verify_aud': False})
|
||||||
|
|
||||||
self.assertEqual(id_token['nonce'], 'thisisanonce')
|
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)
|
||||||
|
|
|
@ -22,7 +22,8 @@ class UserInfoTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Generate a valid token.
|
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(
|
token = create_token(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
|
Loading…
Reference in a new issue