Preparing v0.5.2 (#201)

* Fix infinite login loop if "prompt=login" (#198)
* Fix Django 2.0 deprecation warnings (#185)
This commit is contained in:
Wojciech Bartosiak 2017-08-22 17:33:13 +02:00 committed by GitHub
parent 2e1efc41ed
commit 8e26248022
7 changed files with 56 additions and 16 deletions

View file

@ -1,6 +1,12 @@
from hashlib import sha224 from hashlib import sha224
from django.core.urlresolvers import reverse import django
if django.VERSION >= (1, 11):
from django.urls import reverse
else:
from django.core.urlresolvers import reverse
from django.http import HttpResponse from django.http import HttpResponse
from oidc_provider import settings from oidc_provider import settings

View file

@ -34,7 +34,7 @@ class Migration(migrations.Migration):
('expires_at', models.DateTimeField()), ('expires_at', models.DateTimeField()),
('_scope', models.TextField(default=b'')), ('_scope', models.TextField(default=b'')),
('code', models.CharField(unique=True, max_length=255)), ('code', models.CharField(unique=True, max_length=255)),
('client', models.ForeignKey(to='oidc_provider.Client')), ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)),
], ],
options={ options={
'abstract': False, 'abstract': False,
@ -49,7 +49,7 @@ class Migration(migrations.Migration):
('_scope', models.TextField(default=b'')), ('_scope', models.TextField(default=b'')),
('access_token', models.CharField(unique=True, max_length=255)), ('access_token', models.CharField(unique=True, max_length=255)),
('_id_token', models.TextField()), ('_id_token', models.TextField()),
('client', models.ForeignKey(to='oidc_provider.Client')), ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)),
], ],
options={ options={
'abstract': False, 'abstract': False,
@ -59,7 +59,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='UserInfo', name='UserInfo',
fields=[ fields=[
('user', models.OneToOneField(primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), ('user', models.OneToOneField(primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
('given_name', models.CharField(max_length=255, null=True, blank=True)), ('given_name', models.CharField(max_length=255, null=True, blank=True)),
('family_name', models.CharField(max_length=255, null=True, blank=True)), ('family_name', models.CharField(max_length=255, null=True, blank=True)),
('middle_name', models.CharField(max_length=255, null=True, blank=True)), ('middle_name', models.CharField(max_length=255, null=True, blank=True)),
@ -89,13 +89,13 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='token', model_name='token',
name='user', name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='code', model_name='code',
name='user', name='user',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL), field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -19,8 +19,8 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('expires_at', models.DateTimeField()), ('expires_at', models.DateTimeField()),
('_scope', models.TextField(default=b'')), ('_scope', models.TextField(default=b'')),
('client', models.ForeignKey(to='oidc_provider.Client')), ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
], ],
options={ options={
'abstract': False, 'abstract': False,

View file

@ -83,8 +83,8 @@ class Client(models.Model):
class BaseCodeTokenModel(models.Model): class BaseCodeTokenModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_(u'User')) user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_(u'User'), on_delete=models.CASCADE)
client = models.ForeignKey(Client, verbose_name=_(u'Client')) client = models.ForeignKey(Client, verbose_name=_(u'Client'), on_delete=models.CASCADE)
expires_at = models.DateTimeField(verbose_name=_(u'Expiration Date')) expires_at = models.DateTimeField(verbose_name=_(u'Expiration Date'))
_scope = models.TextField(default='', verbose_name=_(u'Scopes')) _scope = models.TextField(default='', verbose_name=_(u'Scopes'))

View file

@ -1,9 +1,9 @@
from oidc_provider.lib.errors import RedirectUriError from oidc_provider.lib.errors import RedirectUriError
try: try:
from urllib.parse import urlencode from urllib.parse import urlencode, quote
except ImportError: except ImportError:
from urllib import urlencode from urllib import urlencode, quote
try: try:
from urllib.parse import parse_qs, urlsplit from urllib.parse import parse_qs, urlsplit
except ImportError: except ImportError:
@ -369,10 +369,20 @@ class AuthorizationCodeFlowTestCase(TestCase, AuthorizeEndpointMixin):
response = self._auth_request('get', data) response = self._auth_request('get', data)
self.assertIn(settings.get('OIDC_LOGIN_URL'), response['Location']) self.assertIn(settings.get('OIDC_LOGIN_URL'), response['Location'])
self.assertNotIn(
quote('prompt=login'),
response['Location'],
"Found prompt=login, this leads to infinite login loop. See https://github.com/juanifioren/django-oidc-provider/issues/197."
)
response = self._auth_request('get', data, is_user_authenticated=True) response = self._auth_request('get', data, is_user_authenticated=True)
self.assertIn(settings.get('OIDC_LOGIN_URL'), response['Location']) self.assertIn(settings.get('OIDC_LOGIN_URL'), response['Location'])
self.assertTrue(logout_function.called_once()) self.assertTrue(logout_function.called_once())
self.assertNotIn(
quote('prompt=login'),
response['Location'],
"Found prompt=login, this leads to infinite login loop. See https://github.com/juanifioren/django-oidc-provider/issues/197."
)
def test_prompt_login_none_parameter(self): def test_prompt_login_none_parameter(self):
""" """

View file

@ -6,7 +6,7 @@ from oidc_provider import (
views, views,
) )
app_name = 'oidc_provider'
urlpatterns = [ urlpatterns = [
url(r'^authorize/?$', views.AuthorizeView.as_view(), name='authorize'), url(r'^authorize/?$', views.AuthorizeView.as_view(), name='authorize'),
url(r'^token/?$', csrf_exempt(views.TokenView.as_view()), name='token'), url(r'^token/?$', csrf_exempt(views.TokenView.as_view()), name='token'),

View file

@ -11,8 +11,14 @@ from django.contrib.auth.views import (
redirect_to_login, redirect_to_login,
logout, logout,
) )
import django
if django.VERSION >= (1, 11):
from django.urls import reverse
else:
from django.core.urlresolvers import reverse
from django.contrib.auth import logout as django_user_logout from django.contrib.auth import logout as django_user_logout
from django.core.urlresolvers import reverse
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -72,7 +78,8 @@ class AuthorizeView(View):
raise AuthorizeError(authorize.params['redirect_uri'], 'login_required', authorize.grant_type) raise AuthorizeError(authorize.params['redirect_uri'], 'login_required', authorize.grant_type)
else: else:
django_user_logout(request) django_user_logout(request)
return redirect_to_login(request.get_full_path(), settings.get('OIDC_LOGIN_URL')) next_page = self.strip_prompt_login(request.get_full_path())
return redirect_to_login(next_page, settings.get('OIDC_LOGIN_URL'))
if 'select_account' in authorize.params['prompt']: if 'select_account' in authorize.params['prompt']:
# TODO: see how we can support multiple accounts for the end-user. # TODO: see how we can support multiple accounts for the end-user.
@ -127,6 +134,9 @@ class AuthorizeView(View):
else: else:
if 'none' in authorize.params['prompt']: if 'none' in authorize.params['prompt']:
raise AuthorizeError(authorize.params['redirect_uri'], 'login_required', authorize.grant_type) raise AuthorizeError(authorize.params['redirect_uri'], 'login_required', authorize.grant_type)
if 'login' in authorize.params['prompt']:
next_page = self.strip_prompt_login(request.get_full_path())
return redirect_to_login(next_page, settings.get('OIDC_LOGIN_URL'))
return redirect_to_login(request.get_full_path(), settings.get('OIDC_LOGIN_URL')) return redirect_to_login(request.get_full_path(), settings.get('OIDC_LOGIN_URL'))
@ -174,6 +184,20 @@ class AuthorizeView(View):
return redirect(uri) return redirect(uri)
@staticmethod
def strip_prompt_login(path):
"""
Strips 'login' from the 'prompt' query parameter.
"""
uri = urlsplit(path)
query_params = parse_qs(uri.query)
if 'login' in query_params['prompt']:
query_params['prompt'].remove('login')
if not query_params['prompt']:
del query_params['prompt']
uri = uri._replace(query=urlencode(query_params, doseq=True))
return urlunsplit(uri)
class TokenView(View): class TokenView(View):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):