From b1eb3ae8e1f3a4af5ad37f4f09e5677be2e3e826 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 3 Dec 2018 10:31:29 -0300 Subject: [PATCH 01/75] Fix flake W605. --- oidc_provider/lib/utils/oauth2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oidc_provider/lib/utils/oauth2.py b/oidc_provider/lib/utils/oauth2.py index fcdd68a3..a3fe7a09 100644 --- a/oidc_provider/lib/utils/oauth2.py +++ b/oidc_provider/lib/utils/oauth2.py @@ -21,7 +21,7 @@ def extract_access_token(request): """ auth_header = request.META.get('HTTP_AUTHORIZATION', '') - if re.compile('^[Bb]earer\s{1}.+$').match(auth_header): + if re.compile(r'^[Bb]earer\s{1}.+$').match(auth_header): access_token = auth_header.split()[1] else: access_token = request.GET.get('access_token', '') @@ -39,7 +39,7 @@ def extract_client_auth(request): """ auth_header = request.META.get('HTTP_AUTHORIZATION', '') - if re.compile('^Basic\s{1}.+$').match(auth_header): + if re.compile(r'^Basic\s{1}.+$').match(auth_header): b64_user_pass = auth_header.split()[1] try: user_pass = b64decode(b64_user_pass).decode('utf-8').split(':') From 0636c8ea70a257d3f1426895430af0b5f88e1cae Mon Sep 17 00:00:00 2001 From: Yuta Harima Date: Thu, 6 Dec 2018 22:30:07 +0900 Subject: [PATCH 02/75] support Django 2.1 in an exmaple project --- example/app/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/app/urls.py b/example/app/urls.py index 2b0a6248..c9755745 100644 --- a/example/app/urls.py +++ b/example/app/urls.py @@ -9,8 +9,8 @@ urlpatterns = [ url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), - url(r'^accounts/login/$', auth_views.login, {'template_name': 'login.html'}, name='login'), - url(r'^accounts/logout/$', auth_views.logout, {'next_page': '/'}, name='logout'), + url(r'^accounts/login/$', auth_views.LoginView.as_view(template_name='login.html'), name='login'), + url(r'^accounts/logout/$', auth_views.LogoutView.as_view(next_page='/'), name='logout'), url(r'^', include('oidc_provider.urls', namespace='oidc_provider')), url(r'^admin/', admin.site.urls), ] From 1177d4cfdca38905fc285a0c10ecb06303e47a41 Mon Sep 17 00:00:00 2001 From: Stefan Foulis Date: Fri, 30 Nov 2018 16:33:34 +0100 Subject: [PATCH 03/75] Fix example in docs for translatable scopes Since `_()` is evaluated at import time the translation would always be the default translation of the Django installation as of process startup time. With `ugettext_lazy` evaluation is delayed until the translated strings are forced to strings in the template, so the current language of the request will be used. --- docs/sections/scopesclaims.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sections/scopesclaims.rst b/docs/sections/scopesclaims.rst index e0a283e8..977027f0 100644 --- a/docs/sections/scopesclaims.rst +++ b/docs/sections/scopesclaims.rst @@ -82,7 +82,7 @@ Somewhere in your Django ``settings.py``:: Inside your oidc_provider_settings.py file add the following class:: - from django.utils.translation import ugettext as _ + from django.utils.translation import ugettext_lazy as _ from oidc_provider.lib.claims import ScopeClaims class CustomScopeClaims(ScopeClaims): From 53f86a8dbd3a2ba923619807dd99c1b2bbc88c16 Mon Sep 17 00:00:00 2001 From: Eirik Martiniussen Sylliaas Date: Fri, 12 Jan 2018 16:20:58 +0100 Subject: [PATCH 04/75] Add OIDC_CLIENT_MODEL setting to enable client model swapping Co-authored-by: Eirik Martiniussen Sylliaas Co-authored-by: Stefan Foulis --- docs/sections/settings.rst | 9 ++++ oidc_provider/lib/endpoints/authorize.py | 3 +- oidc_provider/lib/endpoints/token.py | 3 +- oidc_provider/migrations/0001_initial.py | 8 +++- oidc_provider/migrations/0002_userconsent.py | 4 +- .../0016_userconsent_and_verbosenames.py | 8 ++-- .../migrations/0027_auto_20181207_1311.py | 22 +++++++++ oidc_provider/models.py | 23 ++++++++-- oidc_provider/settings.py | 8 ++++ oidc_provider/tests/models.py | 7 +++ oidc_provider/tests/test_models.py | 45 +++++++++++++++++++ oidc_provider/views.py | 6 ++- 12 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 oidc_provider/migrations/0027_auto_20181207_1311.py create mode 100644 oidc_provider/tests/models.py create mode 100644 oidc_provider/tests/test_models.py diff --git a/docs/sections/settings.rst b/docs/sections/settings.rst index eae2e8b7..69c1e7f1 100644 --- a/docs/sections/settings.rst +++ b/docs/sections/settings.rst @@ -21,6 +21,15 @@ If not specified, it will be automatically generated using ``request.scheme`` an For example ``http://localhost:8000``. +OIDC_CLIENT_MODEL +================= + +OPTIONAL. ``str``. The client model. + +If not specified, the default oidc_provider.Client model is used. This is typically used when +you need to override the Client model to add custom properties on the class. The custom class +should override the oidc_provider.AbstractClient model. + OIDC_AFTER_USERLOGIN_HOOK ========================= diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 51a75c6f..bee35673 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -26,13 +26,14 @@ encode_id_token, ) from oidc_provider.models import ( - Client, UserConsent, + get_client_model ) from oidc_provider import settings from oidc_provider.lib.utils.common import get_browser_state_or_default logger = logging.getLogger(__name__) +Client = get_client_model() class AuthorizeEndpoint(object): diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 8c320462..2844f16c 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -17,13 +17,14 @@ encode_id_token, ) from oidc_provider.models import ( - Client, Code, Token, + get_client_model ) from oidc_provider import settings logger = logging.getLogger(__name__) +Client = get_client_model() class TokenEndpoint(object): diff --git a/oidc_provider/migrations/0001_initial.py b/oidc_provider/migrations/0001_initial.py index 2af079ab..0e737082 100644 --- a/oidc_provider/migrations/0001_initial.py +++ b/oidc_provider/migrations/0001_initial.py @@ -3,6 +3,7 @@ from django.db import models, migrations from django.conf import settings +from oidc_provider import settings as oidc_settings class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): dependencies = [ ('auth', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + migrations.swappable_dependency(oidc_settings.get('OIDC_CLIENT_MODEL')), ] operations = [ @@ -26,6 +28,8 @@ class Migration(migrations.Migration): ('_redirect_uris', models.TextField(default=b'')), ], options={ + 'abstract': False, + 'swappable': 'OIDC_CLIENT_MODEL' }, bases=(models.Model,), ), @@ -36,7 +40,7 @@ class Migration(migrations.Migration): ('expires_at', models.DateTimeField()), ('_scope', models.TextField(default=b'')), ('code', models.CharField(unique=True, max_length=255)), - ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)), + ('client', models.ForeignKey(oidc_settings.get('OIDC_CLIENT_MODEL'), on_delete=models.CASCADE)), ], options={ 'abstract': False, @@ -51,7 +55,7 @@ class Migration(migrations.Migration): ('_scope', models.TextField(default=b'')), ('access_token', models.CharField(unique=True, max_length=255)), ('_id_token', models.TextField()), - ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)), + ('client', models.ForeignKey(oidc_settings.get('OIDC_CLIENT_MODEL'), on_delete=models.CASCADE)), ], options={ 'abstract': False, diff --git a/oidc_provider/migrations/0002_userconsent.py b/oidc_provider/migrations/0002_userconsent.py index d2a0f12b..f214a247 100644 --- a/oidc_provider/migrations/0002_userconsent.py +++ b/oidc_provider/migrations/0002_userconsent.py @@ -4,6 +4,8 @@ from django.db import models, migrations from django.conf import settings +from oidc_provider import settings as oidc_settings + class Migration(migrations.Migration): @@ -19,7 +21,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('expires_at', models.DateTimeField()), ('_scope', models.TextField(default=b'')), - ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)), + ('client', models.ForeignKey(to=oidc_settings.get('OIDC_CLIENT_MODEL'), on_delete=models.CASCADE)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], options={ diff --git a/oidc_provider/migrations/0016_userconsent_and_verbosenames.py b/oidc_provider/migrations/0016_userconsent_and_verbosenames.py index a6983624..4849bec6 100644 --- a/oidc_provider/migrations/0016_userconsent_and_verbosenames.py +++ b/oidc_provider/migrations/0016_userconsent_and_verbosenames.py @@ -8,6 +8,8 @@ import django.db.models.deletion from django.utils.timezone import utc +from oidc_provider import settings as oidc_settings + class Migration(migrations.Migration): @@ -79,7 +81,7 @@ class Migration(migrations.Migration): model_name='code', name='client', field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client', verbose_name='Client'), + on_delete=django.db.models.deletion.CASCADE, to=oidc_settings.get('OIDC_CLIENT_MODEL'), verbose_name='Client'), ), migrations.AlterField( model_name='code', @@ -141,7 +143,7 @@ class Migration(migrations.Migration): model_name='token', name='client', field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client', verbose_name='Client'), + on_delete=django.db.models.deletion.CASCADE, to=oidc_settings.get('OIDC_CLIENT_MODEL'), verbose_name='Client'), ), migrations.AlterField( model_name='token', @@ -168,7 +170,7 @@ class Migration(migrations.Migration): model_name='userconsent', name='client', field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client', verbose_name='Client'), + on_delete=django.db.models.deletion.CASCADE, to=oidc_settings.get('OIDC_CLIENT_MODEL'), verbose_name='Client'), ), migrations.AlterField( model_name='userconsent', diff --git a/oidc_provider/migrations/0027_auto_20181207_1311.py b/oidc_provider/migrations/0027_auto_20181207_1311.py new file mode 100644 index 00000000..a28b52da --- /dev/null +++ b/oidc_provider/migrations/0027_auto_20181207_1311.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-07 13:11 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('oidc_provider', '0026_client_multiple_response_types'), + ] + + operations = [ + migrations.AlterField( + model_name='client', + name='owner', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='oidc_provider_client_set', to=settings.AUTH_USER_MODEL, verbose_name='Owner'), + ), + ] diff --git a/oidc_provider/models.py b/oidc_provider/models.py index 65042389..093dd0fd 100644 --- a/oidc_provider/models.py +++ b/oidc_provider/models.py @@ -4,11 +4,14 @@ from hashlib import md5, sha256 import json +from django.apps import apps from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from oidc_provider import settings as oidc_settings + CLIENT_TYPE_CHOICES = [ ('confidential', 'Confidential'), @@ -54,12 +57,13 @@ def __str__(self): return u'{0}'.format(self.description) -class Client(models.Model): +class AbstractClient(models.Model): name = models.CharField(max_length=100, default='', verbose_name=_(u'Name')) owner = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_(u'Owner'), blank=True, - null=True, default=None, on_delete=models.SET_NULL, related_name='oidc_clients_set') + null=True, default=None, on_delete=models.SET_NULL, + related_name='%(app_label)s_%(class)s_set') client_type = models.CharField( max_length=30, choices=CLIENT_TYPE_CHOICES, @@ -115,6 +119,7 @@ class Client(models.Model): class Meta: verbose_name = _(u'Client') verbose_name_plural = _(u'Clients') + abstract = True def __str__(self): return u'{0}'.format(self.name) @@ -158,9 +163,21 @@ def default_redirect_uri(self): return self.redirect_uris[0] if self.redirect_uris else '' +class Client(AbstractClient): + class Meta(AbstractClient.Meta): + swappable = 'OIDC_CLIENT_MODEL' + + +def get_client_model(): + """ Return the Application model that is active in this project. """ + return apps.get_model(oidc_settings.get('OIDC_CLIENT_MODEL')) + + class BaseCodeTokenModel(models.Model): - client = models.ForeignKey(Client, verbose_name=_(u'Client'), on_delete=models.CASCADE) + client = models.ForeignKey( + oidc_settings.get('OIDC_CLIENT_MODEL'), verbose_name=_(u'Client'), + on_delete=models.CASCADE) expires_at = models.DateTimeField(verbose_name=_(u'Expiration Date')) _scope = models.TextField(default='', verbose_name=_(u'Scopes')) diff --git a/oidc_provider/settings.py b/oidc_provider/settings.py index 6d0607ee..297926c9 100644 --- a/oidc_provider/settings.py +++ b/oidc_provider/settings.py @@ -25,6 +25,14 @@ def SITE_URL(self): """ return None + @property + def OIDC_CLIENT_MODEL(self): + """ + OPTIONAL. Use a custom client model, typically used to extend the client model + with custom fields. The custom model should override oidc_provider.AbstractClient. + """ + return 'oidc_provider.Client' + @property def OIDC_AFTER_USERLOGIN_HOOK(self): """ diff --git a/oidc_provider/tests/models.py b/oidc_provider/tests/models.py new file mode 100644 index 00000000..933a7f63 --- /dev/null +++ b/oidc_provider/tests/models.py @@ -0,0 +1,7 @@ +from django.db import models + +from oidc_provider.models import AbstractClient + + +class CustomClient(AbstractClient): + custom_field = models.CharField(max_length=255) diff --git a/oidc_provider/tests/test_models.py b/oidc_provider/tests/test_models.py new file mode 100644 index 00000000..9b090c21 --- /dev/null +++ b/oidc_provider/tests/test_models.py @@ -0,0 +1,45 @@ +from unittest import skip +import django +from django.test import TestCase, override_settings +from django.contrib.auth import get_user_model + +from oidc_provider.models import get_client_model, Client +from oidc_provider.tests.models import CustomClient + +UserModel = get_user_model() + + +class TestModels(TestCase): + + def test_retrieve_default_client_model(self): + client = get_client_model() + self.assertEqual(Client, client) + + @override_settings(OIDC_CLIENT_MODEL='tests.CustomClient') + def test_retrireve_custom_client_model(self): + client = get_client_model() + self.assertEqual(CustomClient, client) + + +@override_settings(OIDC_CLIENT_MODEL='tests.CustomClient') +class TestCustomClientModel(TestCase): + + @skip(django.VERSION <= (1, 7)) + def test_custom_client_model(self): + """ + If a custom client model is installed, it should be present in + the related objects. + """ + related_object_names = [ + f.name for f in UserModel._meta.get_fields() + if (f.one_to_many or f.one_to_one) and f.auto_created and not f.concrete + ] + self.assertIn("tests_customclient_set", related_object_names) + + @override_settings(OIDC_CLIENT_MODEL='IncorrectModelFormat') + def test_custom_application_model_incorrect_format(self): + self.assertRaises(ValueError, get_client_model) + + @override_settings(OIDC_CLIENT_MODEL='tests.ClientNotInstalled') + def test_custom_application_model_incorrect_format(self): + self.assertRaises(LookupError, get_client_model) diff --git a/oidc_provider/views.py b/oidc_provider/views.py index def720f5..f7628578 100644 --- a/oidc_provider/views.py +++ b/oidc_provider/views.py @@ -49,9 +49,10 @@ from oidc_provider.lib.utils.oauth2 import protected_resource_view from oidc_provider.lib.utils.token import client_id_from_id_token from oidc_provider.models import ( - Client, RSAKey, - ResponseType) + ResponseType, + get_client_model, +) from oidc_provider import settings from oidc_provider import signals @@ -59,6 +60,7 @@ logger = logging.getLogger(__name__) OIDC_TEMPLATES = settings.get('OIDC_TEMPLATES') +Client = get_client_model() class AuthorizeView(View): From 3cc27bd84518ed53ecbec82d2627d3c262dca464 Mon Sep 17 00:00:00 2001 From: Stefan Foulis Date: Fri, 7 Dec 2018 15:20:12 +0100 Subject: [PATCH 05/75] Fix test setup for client model swapping --- ...207_1311.py => 0027_swappable_client_model.py} | 7 ++++++- oidc_provider/models.py | 3 ++- oidc_provider/tests/{ => cases}/test_models.py | 15 ++++++--------- oidc_provider/tests/models.py | 2 +- oidc_provider/tests/settings.py | 1 + 5 files changed, 16 insertions(+), 12 deletions(-) rename oidc_provider/migrations/{0027_auto_20181207_1311.py => 0027_swappable_client_model.py} (70%) rename oidc_provider/tests/{ => cases}/test_models.py (71%) diff --git a/oidc_provider/migrations/0027_auto_20181207_1311.py b/oidc_provider/migrations/0027_swappable_client_model.py similarity index 70% rename from oidc_provider/migrations/0027_auto_20181207_1311.py rename to oidc_provider/migrations/0027_swappable_client_model.py index a28b52da..5f536e79 100644 --- a/oidc_provider/migrations/0027_auto_20181207_1311.py +++ b/oidc_provider/migrations/0027_swappable_client_model.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2018-12-07 13:11 +# Generated by Django 1.11.4 on 2018-12-07 14:12 from __future__ import unicode_literals from django.conf import settings @@ -19,4 +19,9 @@ class Migration(migrations.Migration): name='owner', field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='oidc_provider_client_set', to=settings.AUTH_USER_MODEL, verbose_name='Owner'), ), + migrations.AlterField( + model_name='client', + name='response_types', + field=models.ManyToManyField(related_name='oidc_provider_client_set', to='oidc_provider.ResponseType'), + ), ] diff --git a/oidc_provider/models.py b/oidc_provider/models.py index 093dd0fd..94cf4e72 100644 --- a/oidc_provider/models.py +++ b/oidc_provider/models.py @@ -73,7 +73,8 @@ class AbstractClient(models.Model): u' of their credentials. Public clients are incapable.')) client_id = models.CharField(max_length=255, unique=True, verbose_name=_(u'Client ID')) client_secret = models.CharField(max_length=255, blank=True, verbose_name=_(u'Client SECRET')) - response_types = models.ManyToManyField(ResponseType) + response_types = models.ManyToManyField( + ResponseType, related_name='%(app_label)s_%(class)s_set') jwt_alg = models.CharField( max_length=10, choices=JWT_ALGS, diff --git a/oidc_provider/tests/test_models.py b/oidc_provider/tests/cases/test_models.py similarity index 71% rename from oidc_provider/tests/test_models.py rename to oidc_provider/tests/cases/test_models.py index 9b090c21..ac6ed143 100644 --- a/oidc_provider/tests/test_models.py +++ b/oidc_provider/tests/cases/test_models.py @@ -1,10 +1,8 @@ -from unittest import skip -import django from django.test import TestCase, override_settings from django.contrib.auth import get_user_model from oidc_provider.models import get_client_model, Client -from oidc_provider.tests.models import CustomClient +from oidc_provider.tests.models import Client as CustomClient UserModel = get_user_model() @@ -15,16 +13,15 @@ def test_retrieve_default_client_model(self): client = get_client_model() self.assertEqual(Client, client) - @override_settings(OIDC_CLIENT_MODEL='tests.CustomClient') + @override_settings(OIDC_CLIENT_MODEL='tests.Client') def test_retrireve_custom_client_model(self): client = get_client_model() self.assertEqual(CustomClient, client) -@override_settings(OIDC_CLIENT_MODEL='tests.CustomClient') +@override_settings(OIDC_CLIENT_MODEL='tests.Client') class TestCustomClientModel(TestCase): - @skip(django.VERSION <= (1, 7)) def test_custom_client_model(self): """ If a custom client model is installed, it should be present in @@ -34,12 +31,12 @@ def test_custom_client_model(self): f.name for f in UserModel._meta.get_fields() if (f.one_to_many or f.one_to_one) and f.auto_created and not f.concrete ] - self.assertIn("tests_customclient_set", related_object_names) + self.assertIn("tests_client_set", related_object_names) @override_settings(OIDC_CLIENT_MODEL='IncorrectModelFormat') - def test_custom_application_model_incorrect_format(self): + def test_custom_application_model_incorrect_format_1(self): self.assertRaises(ValueError, get_client_model) @override_settings(OIDC_CLIENT_MODEL='tests.ClientNotInstalled') - def test_custom_application_model_incorrect_format(self): + def test_custom_application_model_incorrect_format_2(self): self.assertRaises(LookupError, get_client_model) diff --git a/oidc_provider/tests/models.py b/oidc_provider/tests/models.py index 933a7f63..898470ec 100644 --- a/oidc_provider/tests/models.py +++ b/oidc_provider/tests/models.py @@ -3,5 +3,5 @@ from oidc_provider.models import AbstractClient -class CustomClient(AbstractClient): +class Client(AbstractClient): custom_field = models.CharField(max_length=255) diff --git a/oidc_provider/tests/settings.py b/oidc_provider/tests/settings.py index ea61262f..3a72f398 100644 --- a/oidc_provider/tests/settings.py +++ b/oidc_provider/tests/settings.py @@ -47,6 +47,7 @@ 'django.contrib.messages', 'django.contrib.admin', 'oidc_provider', + 'oidc_provider.tests', ] ROOT_URLCONF = 'oidc_provider.tests.app.urls' From b96c09d3aaf499e70092d8ccf58317ce7bc8b9a0 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Sun, 9 Dec 2018 13:00:17 -0300 Subject: [PATCH 06/75] Update changelog.rst --- docs/sections/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 63a483fb..9dc717b8 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file. Unreleased ========== +* Added: OIDC_CLIENT_MODEL setting to enable client model swapping. +* Fixed: example in docs for translatable scopes (ugettext). + 0.7.0 ===== From 272b1ae589baee0469fd926683b6572da7b52048 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Wed, 12 Dec 2018 17:03:38 -0300 Subject: [PATCH 07/75] Update changelog.rst --- docs/sections/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 9dc717b8..d623c1a9 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -9,6 +9,7 @@ Unreleased ========== * Added: OIDC_CLIENT_MODEL setting to enable client model swapping. +* Fixed: example project on Django 2.1. * Fixed: example in docs for translatable scopes (ugettext). 0.7.0 From e93ed562254606aa18fe67d579b0fc939e31255a Mon Sep 17 00:00:00 2001 From: Stefan Foulis Date: Mon, 17 Dec 2018 14:47:47 +0100 Subject: [PATCH 08/75] Add .coverage* to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c0574194..86b9bb1c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ docs/_build/ .eggs/ .python-version .pytest_cache/ +.coverage* From 90cd71154d317281348bf70fb3158cdd4c52f4e0 Mon Sep 17 00:00:00 2001 From: Stefan Foulis Date: Fri, 14 Dec 2018 20:07:28 +0100 Subject: [PATCH 09/75] Consistently use endpoint class set on views Consistently use the *_endpoint_class pattern on AuthorizeView, TokenView and TokenInstrospectionView. --- oidc_provider/views.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/oidc_provider/views.py b/oidc_provider/views.py index f7628578..468ccd43 100644 --- a/oidc_provider/views.py +++ b/oidc_provider/views.py @@ -171,7 +171,7 @@ def get(self, request, *args, **kwargs): return redirect(uri) def post(self, request, *args, **kwargs): - authorize = AuthorizeEndpoint(request) + authorize = self.authorize_endpoint_class(request) try: authorize.validate_params() @@ -205,20 +205,22 @@ def post(self, request, *args, **kwargs): class TokenView(View): + token_endpoint_class = TokenEndpoint + def post(self, request, *args, **kwargs): - token = TokenEndpoint(request) + token = self.token_endpoint_class(request) try: token.validate_params() dic = token.create_response_dic() - return TokenEndpoint.response(dic) + return self.token_endpoint_class.response(dic) except TokenError as error: - return TokenEndpoint.response(error.create_dict(), status=400) + return self.token_endpoint_class.response(error.create_dict(), status=400) except UserAuthError as error: - return TokenEndpoint.response(error.create_dict(), status=403) + return self.token_endpoint_class.response(error.create_dict(), status=403) @require_http_methods(['GET', 'POST', 'OPTIONS']) @@ -364,16 +366,18 @@ def get(self, request, *args, **kwargs): class TokenIntrospectionView(View): + token_instrospection_endpoint_class = TokenIntrospectionEndpoint + @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(TokenIntrospectionView, self).dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): - introspection = TokenIntrospectionEndpoint(request) + introspection = self.token_instrospection_endpoint_class(request) try: introspection.validate_params() dic = introspection.create_response_dic() - return TokenIntrospectionEndpoint.response(dic) + return self.token_instrospection_endpoint_class.response(dic) except TokenIntrospectionError: - return TokenIntrospectionEndpoint.response({'active': False}) + return self.token_instrospection_endpoint_class.response({'active': False}) From c4960d385f83920d75e69235c8f76b18be54145e Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Sat, 5 Jan 2019 15:45:14 -0500 Subject: [PATCH 10/75] Set up with tox-travis This means we don't need to specify the Tox environments in two places anymore. --- .travis.yml | 13 ++----------- tox.ini | 5 ++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fa9ac70..0cd3321e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,13 @@ language: python install: - - pip install tox coveralls + - pip install tox tox-travis coveralls matrix: include: - python: 2.7 - env: - - ENV=docs - - python: 2.7 - env: - - ENV=py27-django111 - python: 3.5 - env: - - ENV=py35-django111,py35-django20,py35-django21 - python: 3.6 - env: - - ENV=py36-django111,py36-django20,py36-django21 script: - - tox -e $ENV + - tox after_success: - coveralls diff --git a/tox.ini b/tox.ini index b2f8dcfe..ee528eba 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist= - docs, + py27-docs, py27-django{111}, py35-django{111,20,21}, py36-django{111,20,21}, @@ -22,8 +22,7 @@ deps = commands = pytest --flake8 --cov=oidc_provider {posargs} -[testenv:docs] -basepython = python2.7 +[testenv:py27-docs] changedir = docs whitelist_externals = mkdir From feff5e2ad83eeaee0d656f6a2f7a2d5dff334b3a Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Sat, 5 Jan 2019 15:47:42 -0500 Subject: [PATCH 11/75] Use standard Python definition for test matrix --- .travis.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0cd3321e..caa99efe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,11 @@ language: python +python: + - 2.7 + - 3.5 + - 3.6 install: - pip install tox tox-travis coveralls -matrix: - include: - - python: 2.7 - - python: 3.5 - - python: 3.6 script: - tox after_success: From 836f27511cde5fc6227586bd112b7386edde3581 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Sat, 5 Jan 2019 15:54:04 -0500 Subject: [PATCH 12/75] Test on Python 3.7 --- .travis.yml | 4 ++++ setup.py | 1 + tox.ini | 1 + 3 files changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index caa99efe..e533551c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,12 @@ language: python +dist: xenial +sudo: false + python: - 2.7 - 3.5 - 3.6 + - 3.7 install: - pip install tox tox-travis coveralls diff --git a/setup.py b/setup.py index 70663bc4..38c4bf73 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], diff --git a/tox.ini b/tox.ini index ee528eba..abbf65ad 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist= py27-django{111}, py35-django{111,20,21}, py36-django{111,20,21}, + py37-django{111,20,21}, [testenv] changedir= From f8c073b59beb02c14171a97e4cbda5fc8fd6770c Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 10 Jan 2019 17:57:16 -0300 Subject: [PATCH 13/75] Revert "Add OIDC_CLIENT_MODEL setting to enable client model swapping (rebased)" --- docs/sections/settings.rst | 9 ---- oidc_provider/lib/endpoints/authorize.py | 3 +- oidc_provider/lib/endpoints/token.py | 3 +- oidc_provider/migrations/0001_initial.py | 8 +--- oidc_provider/migrations/0002_userconsent.py | 4 +- .../0016_userconsent_and_verbosenames.py | 8 ++-- .../migrations/0027_swappable_client_model.py | 27 ------------ oidc_provider/models.py | 26 ++---------- oidc_provider/settings.py | 8 ---- oidc_provider/tests/cases/test_models.py | 42 ------------------- oidc_provider/tests/models.py | 7 ---- oidc_provider/tests/settings.py | 1 - oidc_provider/views.py | 6 +-- 13 files changed, 14 insertions(+), 138 deletions(-) delete mode 100644 oidc_provider/migrations/0027_swappable_client_model.py delete mode 100644 oidc_provider/tests/cases/test_models.py delete mode 100644 oidc_provider/tests/models.py diff --git a/docs/sections/settings.rst b/docs/sections/settings.rst index 69c1e7f1..eae2e8b7 100644 --- a/docs/sections/settings.rst +++ b/docs/sections/settings.rst @@ -21,15 +21,6 @@ If not specified, it will be automatically generated using ``request.scheme`` an For example ``http://localhost:8000``. -OIDC_CLIENT_MODEL -================= - -OPTIONAL. ``str``. The client model. - -If not specified, the default oidc_provider.Client model is used. This is typically used when -you need to override the Client model to add custom properties on the class. The custom class -should override the oidc_provider.AbstractClient model. - OIDC_AFTER_USERLOGIN_HOOK ========================= diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index bee35673..51a75c6f 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -26,14 +26,13 @@ encode_id_token, ) from oidc_provider.models import ( + Client, UserConsent, - get_client_model ) from oidc_provider import settings from oidc_provider.lib.utils.common import get_browser_state_or_default logger = logging.getLogger(__name__) -Client = get_client_model() class AuthorizeEndpoint(object): diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 2844f16c..8c320462 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -17,14 +17,13 @@ encode_id_token, ) from oidc_provider.models import ( + Client, Code, Token, - get_client_model ) from oidc_provider import settings logger = logging.getLogger(__name__) -Client = get_client_model() class TokenEndpoint(object): diff --git a/oidc_provider/migrations/0001_initial.py b/oidc_provider/migrations/0001_initial.py index 0e737082..2af079ab 100644 --- a/oidc_provider/migrations/0001_initial.py +++ b/oidc_provider/migrations/0001_initial.py @@ -3,7 +3,6 @@ from django.db import models, migrations from django.conf import settings -from oidc_provider import settings as oidc_settings class Migration(migrations.Migration): @@ -11,7 +10,6 @@ class Migration(migrations.Migration): dependencies = [ ('auth', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - migrations.swappable_dependency(oidc_settings.get('OIDC_CLIENT_MODEL')), ] operations = [ @@ -28,8 +26,6 @@ class Migration(migrations.Migration): ('_redirect_uris', models.TextField(default=b'')), ], options={ - 'abstract': False, - 'swappable': 'OIDC_CLIENT_MODEL' }, bases=(models.Model,), ), @@ -40,7 +36,7 @@ class Migration(migrations.Migration): ('expires_at', models.DateTimeField()), ('_scope', models.TextField(default=b'')), ('code', models.CharField(unique=True, max_length=255)), - ('client', models.ForeignKey(oidc_settings.get('OIDC_CLIENT_MODEL'), on_delete=models.CASCADE)), + ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)), ], options={ 'abstract': False, @@ -55,7 +51,7 @@ class Migration(migrations.Migration): ('_scope', models.TextField(default=b'')), ('access_token', models.CharField(unique=True, max_length=255)), ('_id_token', models.TextField()), - ('client', models.ForeignKey(oidc_settings.get('OIDC_CLIENT_MODEL'), on_delete=models.CASCADE)), + ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)), ], options={ 'abstract': False, diff --git a/oidc_provider/migrations/0002_userconsent.py b/oidc_provider/migrations/0002_userconsent.py index f214a247..d2a0f12b 100644 --- a/oidc_provider/migrations/0002_userconsent.py +++ b/oidc_provider/migrations/0002_userconsent.py @@ -4,8 +4,6 @@ from django.db import models, migrations from django.conf import settings -from oidc_provider import settings as oidc_settings - class Migration(migrations.Migration): @@ -21,7 +19,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('expires_at', models.DateTimeField()), ('_scope', models.TextField(default=b'')), - ('client', models.ForeignKey(to=oidc_settings.get('OIDC_CLIENT_MODEL'), on_delete=models.CASCADE)), + ('client', models.ForeignKey(to='oidc_provider.Client', on_delete=models.CASCADE)), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], options={ diff --git a/oidc_provider/migrations/0016_userconsent_and_verbosenames.py b/oidc_provider/migrations/0016_userconsent_and_verbosenames.py index 4849bec6..a6983624 100644 --- a/oidc_provider/migrations/0016_userconsent_and_verbosenames.py +++ b/oidc_provider/migrations/0016_userconsent_and_verbosenames.py @@ -8,8 +8,6 @@ import django.db.models.deletion from django.utils.timezone import utc -from oidc_provider import settings as oidc_settings - class Migration(migrations.Migration): @@ -81,7 +79,7 @@ class Migration(migrations.Migration): model_name='code', name='client', field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to=oidc_settings.get('OIDC_CLIENT_MODEL'), verbose_name='Client'), + on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client', verbose_name='Client'), ), migrations.AlterField( model_name='code', @@ -143,7 +141,7 @@ class Migration(migrations.Migration): model_name='token', name='client', field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to=oidc_settings.get('OIDC_CLIENT_MODEL'), verbose_name='Client'), + on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client', verbose_name='Client'), ), migrations.AlterField( model_name='token', @@ -170,7 +168,7 @@ class Migration(migrations.Migration): model_name='userconsent', name='client', field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to=oidc_settings.get('OIDC_CLIENT_MODEL'), verbose_name='Client'), + on_delete=django.db.models.deletion.CASCADE, to='oidc_provider.Client', verbose_name='Client'), ), migrations.AlterField( model_name='userconsent', diff --git a/oidc_provider/migrations/0027_swappable_client_model.py b/oidc_provider/migrations/0027_swappable_client_model.py deleted file mode 100644 index 5f536e79..00000000 --- a/oidc_provider/migrations/0027_swappable_client_model.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.4 on 2018-12-07 14:12 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('oidc_provider', '0026_client_multiple_response_types'), - ] - - operations = [ - migrations.AlterField( - model_name='client', - name='owner', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='oidc_provider_client_set', to=settings.AUTH_USER_MODEL, verbose_name='Owner'), - ), - migrations.AlterField( - model_name='client', - name='response_types', - field=models.ManyToManyField(related_name='oidc_provider_client_set', to='oidc_provider.ResponseType'), - ), - ] diff --git a/oidc_provider/models.py b/oidc_provider/models.py index 94cf4e72..65042389 100644 --- a/oidc_provider/models.py +++ b/oidc_provider/models.py @@ -4,14 +4,11 @@ from hashlib import md5, sha256 import json -from django.apps import apps from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from oidc_provider import settings as oidc_settings - CLIENT_TYPE_CHOICES = [ ('confidential', 'Confidential'), @@ -57,13 +54,12 @@ def __str__(self): return u'{0}'.format(self.description) -class AbstractClient(models.Model): +class Client(models.Model): name = models.CharField(max_length=100, default='', verbose_name=_(u'Name')) owner = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_(u'Owner'), blank=True, - null=True, default=None, on_delete=models.SET_NULL, - related_name='%(app_label)s_%(class)s_set') + null=True, default=None, on_delete=models.SET_NULL, related_name='oidc_clients_set') client_type = models.CharField( max_length=30, choices=CLIENT_TYPE_CHOICES, @@ -73,8 +69,7 @@ class AbstractClient(models.Model): u' of their credentials. Public clients are incapable.')) client_id = models.CharField(max_length=255, unique=True, verbose_name=_(u'Client ID')) client_secret = models.CharField(max_length=255, blank=True, verbose_name=_(u'Client SECRET')) - response_types = models.ManyToManyField( - ResponseType, related_name='%(app_label)s_%(class)s_set') + response_types = models.ManyToManyField(ResponseType) jwt_alg = models.CharField( max_length=10, choices=JWT_ALGS, @@ -120,7 +115,6 @@ class AbstractClient(models.Model): class Meta: verbose_name = _(u'Client') verbose_name_plural = _(u'Clients') - abstract = True def __str__(self): return u'{0}'.format(self.name) @@ -164,21 +158,9 @@ def default_redirect_uri(self): return self.redirect_uris[0] if self.redirect_uris else '' -class Client(AbstractClient): - class Meta(AbstractClient.Meta): - swappable = 'OIDC_CLIENT_MODEL' - - -def get_client_model(): - """ Return the Application model that is active in this project. """ - return apps.get_model(oidc_settings.get('OIDC_CLIENT_MODEL')) - - class BaseCodeTokenModel(models.Model): - client = models.ForeignKey( - oidc_settings.get('OIDC_CLIENT_MODEL'), verbose_name=_(u'Client'), - on_delete=models.CASCADE) + client = models.ForeignKey(Client, verbose_name=_(u'Client'), on_delete=models.CASCADE) expires_at = models.DateTimeField(verbose_name=_(u'Expiration Date')) _scope = models.TextField(default='', verbose_name=_(u'Scopes')) diff --git a/oidc_provider/settings.py b/oidc_provider/settings.py index 297926c9..6d0607ee 100644 --- a/oidc_provider/settings.py +++ b/oidc_provider/settings.py @@ -25,14 +25,6 @@ def SITE_URL(self): """ return None - @property - def OIDC_CLIENT_MODEL(self): - """ - OPTIONAL. Use a custom client model, typically used to extend the client model - with custom fields. The custom model should override oidc_provider.AbstractClient. - """ - return 'oidc_provider.Client' - @property def OIDC_AFTER_USERLOGIN_HOOK(self): """ diff --git a/oidc_provider/tests/cases/test_models.py b/oidc_provider/tests/cases/test_models.py deleted file mode 100644 index ac6ed143..00000000 --- a/oidc_provider/tests/cases/test_models.py +++ /dev/null @@ -1,42 +0,0 @@ -from django.test import TestCase, override_settings -from django.contrib.auth import get_user_model - -from oidc_provider.models import get_client_model, Client -from oidc_provider.tests.models import Client as CustomClient - -UserModel = get_user_model() - - -class TestModels(TestCase): - - def test_retrieve_default_client_model(self): - client = get_client_model() - self.assertEqual(Client, client) - - @override_settings(OIDC_CLIENT_MODEL='tests.Client') - def test_retrireve_custom_client_model(self): - client = get_client_model() - self.assertEqual(CustomClient, client) - - -@override_settings(OIDC_CLIENT_MODEL='tests.Client') -class TestCustomClientModel(TestCase): - - def test_custom_client_model(self): - """ - If a custom client model is installed, it should be present in - the related objects. - """ - related_object_names = [ - f.name for f in UserModel._meta.get_fields() - if (f.one_to_many or f.one_to_one) and f.auto_created and not f.concrete - ] - self.assertIn("tests_client_set", related_object_names) - - @override_settings(OIDC_CLIENT_MODEL='IncorrectModelFormat') - def test_custom_application_model_incorrect_format_1(self): - self.assertRaises(ValueError, get_client_model) - - @override_settings(OIDC_CLIENT_MODEL='tests.ClientNotInstalled') - def test_custom_application_model_incorrect_format_2(self): - self.assertRaises(LookupError, get_client_model) diff --git a/oidc_provider/tests/models.py b/oidc_provider/tests/models.py deleted file mode 100644 index 898470ec..00000000 --- a/oidc_provider/tests/models.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.db import models - -from oidc_provider.models import AbstractClient - - -class Client(AbstractClient): - custom_field = models.CharField(max_length=255) diff --git a/oidc_provider/tests/settings.py b/oidc_provider/tests/settings.py index 3a72f398..ea61262f 100644 --- a/oidc_provider/tests/settings.py +++ b/oidc_provider/tests/settings.py @@ -47,7 +47,6 @@ 'django.contrib.messages', 'django.contrib.admin', 'oidc_provider', - 'oidc_provider.tests', ] ROOT_URLCONF = 'oidc_provider.tests.app.urls' diff --git a/oidc_provider/views.py b/oidc_provider/views.py index 468ccd43..06501d5d 100644 --- a/oidc_provider/views.py +++ b/oidc_provider/views.py @@ -49,10 +49,9 @@ from oidc_provider.lib.utils.oauth2 import protected_resource_view from oidc_provider.lib.utils.token import client_id_from_id_token from oidc_provider.models import ( + Client, RSAKey, - ResponseType, - get_client_model, -) + ResponseType) from oidc_provider import settings from oidc_provider import signals @@ -60,7 +59,6 @@ logger = logging.getLogger(__name__) OIDC_TEMPLATES = settings.get('OIDC_TEMPLATES') -Client = get_client_model() class AuthorizeView(View): From a7b07e29dfb3c8e1c8f33920942ffad3cc34db4c Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 10 Jan 2019 18:47:12 -0300 Subject: [PATCH 14/75] Update changelog.rst --- docs/sections/changelog.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index d623c1a9..9508aa20 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,7 +8,6 @@ All notable changes to this project will be documented in this file. Unreleased ========== -* Added: OIDC_CLIENT_MODEL setting to enable client model swapping. * Fixed: example project on Django 2.1. * Fixed: example in docs for translatable scopes (ugettext). From 8a15137d2a700d1c811460053b0255b2f42b629e Mon Sep 17 00:00:00 2001 From: Evgeni Gordeev Date: Mon, 1 Jul 2019 17:46:28 -0500 Subject: [PATCH 15/75] Fixes #328 --- docs/sections/contribute.rst | 2 +- docs/sections/settings.rst | 11 ++ oidc_provider/lib/endpoints/introspection.py | 3 +- oidc_provider/lib/endpoints/token.py | 45 +++++-- oidc_provider/settings.py | 7 + oidc_provider/tests/app/utils.py | 2 +- .../cases/test_introspection_endpoint.py | 5 + .../tests/cases/test_token_endpoint.py | 124 +++++++++++++++--- 8 files changed, 166 insertions(+), 33 deletions(-) diff --git a/docs/sections/contribute.rst b/docs/sections/contribute.rst index e67769c2..9a190421 100644 --- a/docs/sections/contribute.rst +++ b/docs/sections/contribute.rst @@ -32,7 +32,7 @@ We also use `travis `_ Improve Documentation ===================== -We use `Sphinx `_ for generate this documentation. I you want to add or modify something just: +We use `Sphinx `_ for generate this documentation. If you want to add or modify something just: * Install Sphinx (``pip install sphinx``) and the auto-build tool (``pip install sphinx-autobuild``). * Move inside the docs folder. ``cd docs/`` diff --git a/docs/sections/settings.rst b/docs/sections/settings.rst index eae2e8b7..984d2df2 100644 --- a/docs/sections/settings.rst +++ b/docs/sections/settings.rst @@ -234,3 +234,14 @@ Default is:: See the :ref:`templates` section. The templates that are not specified here will use the default ones. + +OIDC_INTROSPECTION_RESPONSE_SCOPE_ENABLE +========================================== + +OPTIONAL ``bool`` + +A flag which toggles whether the scope is returned with successful response on introspection request. + +Must be ``True`` to include ``scope`` into the successful response + +Default is ``False``. \ No newline at end of file diff --git a/oidc_provider/lib/endpoints/introspection.py b/oidc_provider/lib/endpoints/introspection.py index 8f41de93..c1e8a8e6 100644 --- a/oidc_provider/lib/endpoints/introspection.py +++ b/oidc_provider/lib/endpoints/introspection.py @@ -85,7 +85,8 @@ def create_response_dic(self): response_dic[k] = self.id_token[k] response_dic['active'] = True response_dic['client_id'] = self.token.client.client_id - + if settings.get('OIDC_INTROSPECTION_RESPONSE_SCOPE_ENABLE'): + response_dic['scope'] = ' '.join(self.token.scope) response_dic = run_processing_hook(response_dic, 'OIDC_INTROSPECTION_PROCESSING_HOOK', client=self.client, diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 8c320462..ddba302f 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -1,11 +1,12 @@ -import inspect -from base64 import urlsafe_b64encode import hashlib +import inspect import logging -from django.contrib.auth import authenticate +from base64 import urlsafe_b64encode +from django.contrib.auth import authenticate from django.http import JsonResponse +from oidc_provider import settings from oidc_provider.lib.errors import ( TokenError, UserAuthError, @@ -21,7 +22,6 @@ Code, Token, ) -from oidc_provider import settings logger = logging.getLogger(__name__) @@ -76,7 +76,7 @@ def validate_params(self): raise TokenError('invalid_grant') if not (self.code.client == self.client) \ - or self.code.has_expired(): + or self.code.has_expired(): logger.debug('[Token] Invalid code: invalid client or code has expired') raise TokenError('invalid_grant') @@ -84,8 +84,8 @@ def validate_params(self): if self.params['code_verifier']: if self.code.code_challenge_method == 'S256': new_code_challenge = urlsafe_b64encode( - hashlib.sha256(self.params['code_verifier'].encode('ascii')).digest() - ).decode('utf-8').replace('=', '') + hashlib.sha256(self.params['code_verifier'].encode('ascii')).digest() + ).decode('utf-8').replace('=', '') else: new_code_challenge = self.params['code_verifier'] @@ -135,6 +135,27 @@ def validate_params(self): logger.debug('[Token] Invalid grant type: %s', self.params['grant_type']) raise TokenError('unsupported_grant_type') + def validate_requested_scopes(self): + """ + Handling validation of requested scope for grant_type=[password|client_credentials] + """ + token_scopes = [] + if self.params['scope']: + # See https://tools.ietf.org/html/rfc6749#section-3.3 + # The value of the scope parameter is expressed + # as a list of space-delimited, case-sensitive strings + for scope_requested in self.params['scope'].split(' '): + if scope_requested in self.client.scope: + token_scopes.append(scope_requested) + else: + logger.debug(f'[Token] The request scope {scope_requested} ' + f'is not supported by client {self.client.client_id}') + raise TokenError('invalid_scope') + # if no scopes requested assign client's scopes + else: + token_scopes.extend(self.client.scope) + return token_scopes + def create_response_dic(self): if self.params['grant_type'] == 'authorization_code': return self.create_code_response_dic() @@ -230,11 +251,11 @@ def create_refresh_response_dic(self): def create_access_token_response_dic(self): # See https://tools.ietf.org/html/rfc6749#section-4.3 - + token_scopes = self.validate_requested_scopes() token = create_token( self.user, self.client, - self.params['scope'].split(' ')) + token_scopes) id_token_dic = create_id_token( token=token, @@ -255,15 +276,17 @@ def create_access_token_response_dic(self): 'expires_in': settings.get('OIDC_TOKEN_EXPIRE'), 'token_type': 'bearer', 'id_token': encode_id_token(id_token_dic, token.client), + 'scope': ' '.join(token.scope) } def create_client_credentials_response_dic(self): # See https://tools.ietf.org/html/rfc6749#section-4.4.3 + token_scopes = self.validate_requested_scopes() token = create_token( user=None, client=self.client, - scope=self.client.scope) + scope=token_scopes) token.save() @@ -271,7 +294,7 @@ def create_client_credentials_response_dic(self): 'access_token': token.access_token, 'expires_in': settings.get('OIDC_TOKEN_EXPIRE'), 'token_type': 'bearer', - 'scope': self.client._scope, + 'scope': ' '.join(token.scope), } @classmethod diff --git a/oidc_provider/settings.py b/oidc_provider/settings.py index 6d0607ee..90750fda 100644 --- a/oidc_provider/settings.py +++ b/oidc_provider/settings.py @@ -168,6 +168,13 @@ def OIDC_TEMPLATES(self): 'error': 'oidc_provider/error.html' } + @property + def OIDC_INTROSPECTION_RESPONSE_SCOPE_ENABLE(self): + """ + OPTIONAL: A boolean to specify whether or not to include scope in introspection response. + """ + return False + default_settings = DefaultSettings() diff --git a/oidc_provider/tests/app/utils.py b/oidc_provider/tests/app/utils.py index 6a65b64e..51f51d4e 100644 --- a/oidc_provider/tests/app/utils.py +++ b/oidc_provider/tests/app/utils.py @@ -61,7 +61,7 @@ def create_fake_client(response_type, is_public=False, require_consent=True): client.client_secret = str(random.randint(1, 999999)).zfill(6) client.redirect_uris = ['http://example.com/'] client.require_consent = require_consent - + client.scope = ['openid', 'email'] client.save() # check if response_type is a string in a python 2 and 3 compatible way diff --git a/oidc_provider/tests/cases/test_introspection_endpoint.py b/oidc_provider/tests/cases/test_introspection_endpoint.py index 38b3c3c0..13fb2ab1 100644 --- a/oidc_provider/tests/cases/test_introspection_endpoint.py +++ b/oidc_provider/tests/cases/test_introspection_endpoint.py @@ -130,3 +130,8 @@ def test_valid_client_grant_token_without_aud_validation(self): 'active': True, 'client_id': self.client.client_id, }) + + @override_settings(OIDC_INTROSPECTION_RESPONSE_SCOPE_ENABLE=True) + def test_enable_scope(self): + response = self._make_request() + self._assert_active(response, scope='openid email') diff --git a/oidc_provider/tests/cases/test_token_endpoint.py b/oidc_provider/tests/cases/test_token_endpoint.py index dab90e76..91f950d3 100644 --- a/oidc_provider/tests/cases/test_token_endpoint.py +++ b/oidc_provider/tests/cases/test_token_endpoint.py @@ -11,6 +11,7 @@ from django.core.management import call_command from django.http import JsonResponse + try: from django.urls import reverse except ImportError: @@ -51,6 +52,8 @@ class TokenTestCase(TestCase): Token Request to the Token Endpoint to obtain a Token Response when using the Authorization Code Flow. """ + SCOPE = 'openid email' + SCOPE_LIST = SCOPE.split(' ') def setUp(self): call_command('creatersakey') @@ -64,7 +67,7 @@ def _password_grant_post_data(self, scope=None): 'username': 'johndoe', 'password': '1234', 'grant_type': 'password', - 'scope': 'openid email', + 'scope': TokenTestCase.SCOPE, } if scope is not None: result['scope'] = ' '.join(scope) @@ -102,6 +105,16 @@ def _refresh_token_post_data(self, refresh_token, scope=None): return post_data + def _client_credentials_post_data(self, scope=None): + post_data = { + 'client_id': self.client.client_id, + 'client_secret': self.client.client_secret, + 'grant_type': 'client_credentials', + } + if scope is not None: + post_data['scope'] = ' '.join(scope) + return post_data + def _post_request(self, post_data, extras={}): """ Makes a request to the token endpoint by sending the @@ -127,7 +140,7 @@ def _create_code(self, scope=None): code = create_code( user=self.user, client=self.client, - scope=(scope if scope else ['openid', 'email']), + scope=(scope if scope else TokenTestCase.SCOPE_LIST), nonce=FAKE_NONCE, is_authentication=True) code.save() @@ -227,7 +240,11 @@ def test_password_grant_full_response(self): self.check_password_grant(scope=['openid', 'email']) def test_password_grant_scope(self): - self.check_password_grant(scope=['openid', 'profile']) + scopes_list = ['openid', 'profile'] + + self.client.scope = scopes_list + self.client.save() + self.check_password_grant(scope=scopes_list) @override_settings(OIDC_TOKEN_EXPIRE=120, OIDC_GRANT_TYPE_PASSWORD_ENABLE=True) @@ -361,7 +378,7 @@ def do_refresh_token_check(self, scope=None): # Retrieve refresh token code = self._create_code() - self.assertEqual(code.scope, ['openid', 'email']) + self.assertEqual(code.scope, TokenTestCase.SCOPE_LIST) post_data = self._auth_code_post_data(code=code.code) start_time = time.time() with patch('oidc_provider.lib.utils.token.time.time') as time_func: @@ -661,7 +678,7 @@ def test_additional_idtoken_processing_hook_one_element_in_tuple(self): @override_settings( OIDC_IDTOKEN_PROCESSING_HOOK=[ - 'oidc_provider.tests.app.utils.fake_idtoken_processing_hook', + 'oidc_provider.tests.app.utils.fake_idtoken_processing_hook', ] ) def test_additional_idtoken_processing_hook_one_element_in_list(self): @@ -682,8 +699,8 @@ def test_additional_idtoken_processing_hook_one_element_in_list(self): @override_settings( OIDC_IDTOKEN_PROCESSING_HOOK=[ - 'oidc_provider.tests.app.utils.fake_idtoken_processing_hook', - 'oidc_provider.tests.app.utils.fake_idtoken_processing_hook2', + 'oidc_provider.tests.app.utils.fake_idtoken_processing_hook', + 'oidc_provider.tests.app.utils.fake_idtoken_processing_hook2', ] ) def test_additional_idtoken_processing_hook_two_elements_in_list(self): @@ -754,7 +771,7 @@ def test_additional_idtoken_processing_hook_kwargs(self): kwargs_passed = id_token.get('kwargs_passed_to_processing_hook') assert kwargs_passed self.assertTrue(kwargs_passed.get('token').startswith( - '") self.assertEqual(set(kwargs_passed.keys()), {'token', 'request'}) @@ -797,11 +814,7 @@ def test_client_credentials_grant_type(self): self.client.scope = fake_scopes_list self.client.save() - post_data = { - 'client_id': self.client.client_id, - 'client_secret': self.client.client_secret, - 'grant_type': 'client_credentials', - } + post_data = self._client_credentials_post_data() response = self._post_request(post_data) response_dict = json.loads(response.content.decode('utf-8')) @@ -857,12 +870,85 @@ def test_printing_token_used_by_client_credentials_grant_type(self): self.client.scope = ['something'] self.client.save() - post_data = { - 'client_id': self.client.client_id, - 'client_secret': self.client.client_secret, - 'grant_type': 'client_credentials', - } - response = self._post_request(post_data) + response = self._post_request(self._client_credentials_post_data()) response_dict = json.loads(response.content.decode('utf-8')) token = Token.objects.get(access_token=response_dict['access_token']) self.assertTrue(str(token)) + + @override_settings(OIDC_GRANT_TYPE_PASSWORD_ENABLE=True) + def test_requested_scope(self): + # GRANT_TYPE=PASSWORD + response = self._post_request( + post_data=self._password_grant_post_data(['openid', 'invalid_scope']), + extras=self._password_grant_auth_header() + ) + + response_dict = json.loads(response.content.decode('utf-8')) + + # It should fail when client requested an invalid scope. + self.assertEqual(400, response.status_code) + self.assertEqual('invalid_scope', response_dict['error']) + + # happy path: no scope + response = self._post_request( + post_data=self._password_grant_post_data([]), + extras=self._password_grant_auth_header() + ) + + response_dict = json.loads(response.content.decode('utf-8')) + self.assertEqual(200, response.status_code) + self.assertEqual(TokenTestCase.SCOPE, response_dict['scope']) + + # happy path: single scope + response = self._post_request( + post_data=self._password_grant_post_data(['email']), + extras=self._password_grant_auth_header() + ) + + response_dict = json.loads(response.content.decode('utf-8')) + self.assertEqual(200, response.status_code) + self.assertEqual('email', response_dict['scope']) + + # happy path: multiple scopes + response = self._post_request( + post_data=self._password_grant_post_data(['email', 'openid']), + extras=self._password_grant_auth_header() + ) + + # GRANT_TYPE=CLIENT_CREDENTIALS + response_dict = json.loads(response.content.decode('utf-8')) + self.assertEqual(200, response.status_code) + self.assertEqual('email openid', response_dict['scope']) + + response = self._post_request( + post_data=self._client_credentials_post_data(['openid', 'invalid_scope']) + ) + + response_dict = json.loads(response.content.decode('utf-8')) + + # It should fail when client requested an invalid scope. + self.assertEqual(400, response.status_code) + self.assertEqual('invalid_scope', response_dict['error']) + + # happy path: no scope + response = self._post_request(post_data=self._client_credentials_post_data()) + + response_dict = json.loads(response.content.decode('utf-8')) + self.assertEqual(200, response.status_code) + self.assertEqual(TokenTestCase.SCOPE, response_dict['scope']) + + # happy path: single scope + response = self._post_request(post_data=self._client_credentials_post_data(['email'])) + + response_dict = json.loads(response.content.decode('utf-8')) + self.assertEqual(200, response.status_code) + self.assertEqual('email', response_dict['scope']) + + # happy path: multiple scopes + response = self._post_request( + post_data=self._client_credentials_post_data(['email', 'openid']) + ) + + response_dict = json.loads(response.content.decode('utf-8')) + self.assertEqual(200, response.status_code) + self.assertEqual('email openid', response_dict['scope']) From 1238b853634500a9c56d065efa0fe9f3756225a1 Mon Sep 17 00:00:00 2001 From: Evgeni Gordeev Date: Mon, 1 Jul 2019 18:46:37 -0500 Subject: [PATCH 16/75] fix tests for python 2.7 and 3.5 - f strings not supported. --- oidc_provider/lib/endpoints/token.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index ddba302f..7ecde44a 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -148,8 +148,8 @@ def validate_requested_scopes(self): if scope_requested in self.client.scope: token_scopes.append(scope_requested) else: - logger.debug(f'[Token] The request scope {scope_requested} ' - f'is not supported by client {self.client.client_id}') + logger.debug('[Token] The request scope %s is not supported by client %s', + scope_requested, self.client.client_id) raise TokenError('invalid_scope') # if no scopes requested assign client's scopes else: From fba4dc893efdada56aaed147389d2f2614898f39 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Sat, 6 Jul 2019 19:44:58 -0300 Subject: [PATCH 17/75] Update changelog.rst --- docs/sections/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 9508aa20..23889724 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. Unreleased ========== +* Added: scope on token and introspection endpoints. * Fixed: example project on Django 2.1. * Fixed: example in docs for translatable scopes (ugettext). From 269cc0dc1fac6795f938383b9e5b3f6288c8ec3e Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Sat, 6 Jul 2019 19:46:18 -0300 Subject: [PATCH 18/75] Update installation.rst --- docs/sections/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sections/installation.rst b/docs/sections/installation.rst index 3a926266..bf0745b6 100644 --- a/docs/sections/installation.rst +++ b/docs/sections/installation.rst @@ -7,7 +7,7 @@ Requirements ============ * Python: ``2.7`` ``3.4`` ``3.5`` ``3.6`` -* Django: ``1.8`` ``1.9`` ``1.10`` ``1.11`` ``2.0`` +* Django: ``1.11`` ``2.0`` ``2.1`` Quick Installation ================== From 55b3a0a01b97385f28870e49a0524cba5ee6ed0b Mon Sep 17 00:00:00 2001 From: Juani Fiorentino Date: Sat, 6 Jul 2019 20:41:54 -0300 Subject: [PATCH 19/75] Add raw_id_fields[user] for token and code. --- oidc_provider/admin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/oidc_provider/admin.py b/oidc_provider/admin.py index 8525de82..8e67ac77 100644 --- a/oidc_provider/admin.py +++ b/oidc_provider/admin.py @@ -75,6 +75,8 @@ class ClientAdmin(admin.ModelAdmin): @admin.register(Code) class CodeAdmin(admin.ModelAdmin): + raw_id_fields = ['user'] + def has_add_permission(self, request): return False @@ -82,6 +84,8 @@ def has_add_permission(self, request): @admin.register(Token) class TokenAdmin(admin.ModelAdmin): + raw_id_fields = ['user'] + def has_add_permission(self, request): return False From 85611efe0db169ff22f01383dbe828841d6997fb Mon Sep 17 00:00:00 2001 From: Babak Ghadiri Date: Mon, 14 Dec 2020 15:01:12 +0330 Subject: [PATCH 20/75] Use static instead of deprecated staticfiles template tag --- oidc_provider/templates/oidc_provider/check_session_iframe.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc_provider/templates/oidc_provider/check_session_iframe.html b/oidc_provider/templates/oidc_provider/check_session_iframe.html index e04d5ce1..a0bed2f5 100644 --- a/oidc_provider/templates/oidc_provider/check_session_iframe.html +++ b/oidc_provider/templates/oidc_provider/check_session_iframe.html @@ -1,4 +1,4 @@ -{% load staticfiles %} +{% load static %} From 3c61863a13de9b708b20de2155586dc26a28b163 Mon Sep 17 00:00:00 2001 From: Babak Ghadiri Date: Mon, 14 Dec 2020 21:12:20 +0330 Subject: [PATCH 21/75] Update tox.ini --- tox.ini | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tox.ini b/tox.ini index b2f8dcfe..df2efec5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,8 @@ [tox] envlist= docs, - py27-django{111}, - py35-django{111,20,21}, - py36-django{111,20,21}, + py35-django{21}, + py36-django{21}, [testenv] changedir= @@ -15,15 +14,13 @@ deps = pytest-django pytest-flake8 pytest-cov - django111: django>=1.11,<1.12 - django20: django>=2.0,<2.1 - django21: django>=2.1,<2.2 + django21: django>=2.1 commands = pytest --flake8 --cov=oidc_provider {posargs} [testenv:docs] -basepython = python2.7 +basepython = python3.5 changedir = docs whitelist_externals = mkdir From c00e4ed5c7d45dc67e5d2bfee67df4d5fc0f20cb Mon Sep 17 00:00:00 2001 From: Babak Ghadiri Date: Mon, 14 Dec 2020 21:13:19 +0330 Subject: [PATCH 22/75] Update .travis.yml --- .travis.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fa9ac70..3f0b84ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,12 @@ install: - pip install tox coveralls matrix: include: - - python: 2.7 - env: - - ENV=docs - - python: 2.7 - env: - - ENV=py27-django111 - python: 3.5 env: - - ENV=py35-django111,py35-django20,py35-django21 + - ENV=py35-django21 - python: 3.6 env: - - ENV=py36-django111,py36-django20,py36-django21 + - ENV=py36-django21 script: - tox -e $ENV after_success: From 42f0d42e1501040e50edcccca645b35eb68d6c55 Mon Sep 17 00:00:00 2001 From: Babak Ghadiri Date: Mon, 14 Dec 2020 21:15:18 +0330 Subject: [PATCH 23/75] Update .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3f0b84ef..2f5e909c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ install: - pip install tox coveralls matrix: include: + - python: 2.7 + env: + - ENV=docs - python: 3.5 env: - ENV=py35-django21 From f68b0496ea71028520cf659da85a38049418b378 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 17 Dec 2020 10:37:33 -0300 Subject: [PATCH 24/75] Update changelog.rst --- docs/sections/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 23889724..194e87ae 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -9,6 +9,7 @@ Unreleased ========== * Added: scope on token and introspection endpoints. +* Changed: Use static instead of deprecated staticfiles template tag. * Fixed: example project on Django 2.1. * Fixed: example in docs for translatable scopes (ugettext). From da3a5526fa0328bc6b1bbe513b39ca83a85e9594 Mon Sep 17 00:00:00 2001 From: Andreu Vallbona Date: Wed, 1 Dec 2021 18:21:29 +0100 Subject: [PATCH 25/75] Added support for django 3.0, django 3.1 and django 3.2 and python 3.9 Dropped support for python 2.7 --- .travis.yml | 19 ++++++---- oidc_provider/admin.py | 2 +- oidc_provider/lib/claims.py | 2 +- oidc_provider/models.py | 3 +- oidc_provider/tests/app/urls.py | 22 +++++++----- .../tests/cases/test_authorize_endpoint.py | 6 ++-- oidc_provider/tests/cases/test_claims.py | 5 +-- oidc_provider/tests/cases/test_commands.py | 3 +- .../cases/test_introspection_endpoint.py | 8 ++--- oidc_provider/tests/cases/test_middleware.py | 10 +++--- oidc_provider/tests/cases/test_settings.py | 2 +- oidc_provider/urls.py | 21 +++++------ setup.py | 5 +-- tox.ini | 35 ++++++++++++------- 14 files changed, 84 insertions(+), 59 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fa9ac70..d96a1c14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,25 @@ install: - pip install tox coveralls matrix: include: - - python: 2.7 + - python: 3.8 env: - ENV=docs - - python: 2.7 - env: - - ENV=py27-django111 - python: 3.5 env: - - ENV=py35-django111,py35-django20,py35-django21 + - ENV=py35-django111,py35-django20,py35-django21,py35-django22 - python: 3.6 env: - - ENV=py36-django111,py36-django20,py36-django21 + - ENV=py36-django111,py36-django20,py36-django21,py36-django22,py36-django30,py36-django31,py36-django32 + - python: 3.7 + env: + - ENV=py37-django111,py37-django20,py37-django21,py37-django22,py37-django30,py37-django31,py37-django32 + - python: 3.8 + env: + - ENV=py38-django20,py38-django21,py38-django22,py38-django30,py38-django31,py38-django32 + - python: 3.9 + env: + - ENV=py39-django20,py39-django21,py39-django22,py39-django30,py39-django31,py39-django32 + script: - tox -e $ENV after_success: diff --git a/oidc_provider/admin.py b/oidc_provider/admin.py index 8525de82..c0349998 100644 --- a/oidc_provider/admin.py +++ b/oidc_provider/admin.py @@ -4,7 +4,7 @@ from django.forms import ModelForm from django.contrib import admin -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from oidc_provider.models import Client, Code, Token, RSAKey diff --git a/oidc_provider/lib/claims.py b/oidc_provider/lib/claims.py index a1dc1c1a..c641e5b5 100644 --- a/oidc_provider/lib/claims.py +++ b/oidc_provider/lib/claims.py @@ -1,6 +1,6 @@ import copy -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from oidc_provider import settings diff --git a/oidc_provider/models.py b/oidc_provider/models.py index 94cf4e72..640e2d27 100644 --- a/oidc_provider/models.py +++ b/oidc_provider/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import base64 import binascii from hashlib import md5, sha256 @@ -7,7 +6,7 @@ from django.apps import apps from django.db import models from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.conf import settings from oidc_provider import settings as oidc_settings diff --git a/oidc_provider/tests/app/urls.py b/oidc_provider/tests/app/urls.py index cbaadf59..a7f8c943 100644 --- a/oidc_provider/tests/app/urls.py +++ b/oidc_provider/tests/app/urls.py @@ -1,18 +1,22 @@ from django.contrib.auth import views as auth_views + try: - from django.urls import include, url + from django.urls import include, re_path except ImportError: - from django.conf.urls import include, url + from django.conf.urls import include + from django.conf.urls import url as re_path from django.contrib import admin from django.views.generic import TemplateView urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), - url(r'^accounts/login/$', - auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'), - url(r'^accounts/logout/$', - auth_views.LogoutView.as_view(template_name='accounts/logout.html'), name='logout'), - url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')), - url(r'^admin/', admin.site.urls), + re_path(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), + re_path(r'^accounts/login/$', + auth_views.LoginView.as_view(template_name='accounts/login.html'), + name='login'), + re_path(r'^accounts/logout/$', + auth_views.LogoutView.as_view(template_name='accounts/logout.html'), + name='logout'), + re_path(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')), + re_path(r'^admin/', admin.site.urls), ] diff --git a/oidc_provider/tests/cases/test_authorize_endpoint.py b/oidc_provider/tests/cases/test_authorize_endpoint.py index 7bbd390f..c18596c3 100644 --- a/oidc_provider/tests/cases/test_authorize_endpoint.py +++ b/oidc_provider/tests/cases/test_authorize_endpoint.py @@ -276,14 +276,14 @@ def test_response_uri_is_properly_constructed(self): parsed = urlsplit(response['Location']) params = parse_qs(parsed.query or parsed.fragment) state = params['state'][0] - self.assertEquals(self.state, state, msg="State returned is invalid or missing") + self.assertEqual(self.state, state, msg="State returned is invalid or missing") is_code_ok = is_code_valid(url=response['Location'], user=self.user, client=self.client) self.assertTrue(is_code_ok, msg='Code returned is invalid or missing') - self.assertEquals( + self.assertEqual( set(params.keys()), {'state', 'code'}, msg='More than state or code appended as query params') @@ -662,7 +662,7 @@ def test_public_client_implicit_auto_approval(self): response = self._auth_request('get', data, is_user_authenticated=True) response_text = response.content.decode('utf-8') - self.assertEquals(response_text, '') + self.assertEqual(response_text, '') components = urlsplit(response['Location']) fragment = parse_qs(components[4]) self.assertIn('access_token', fragment) diff --git a/oidc_provider/tests/cases/test_claims.py b/oidc_provider/tests/cases/test_claims.py index 4c274dcc..1610f773 100644 --- a/oidc_provider/tests/cases/test_claims.py +++ b/oidc_provider/tests/cases/test_claims.py @@ -1,8 +1,9 @@ from __future__ import unicode_literals from django.test import TestCase -from django.utils.six import text_type + from django.utils.translation import override as override_language +from six import text_type from oidc_provider.lib.claims import ScopeClaims, StandardScopeClaims, STANDARD_CLAIMS from oidc_provider.tests.app.utils import create_fake_user, create_fake_client, create_fake_token @@ -49,7 +50,7 @@ def test_clean_dic(self): 'phone_number': '', } clean_dict = self.scopeClaims._clean_dic(dict_to_clean) - self.assertEquals( + self.assertEqual( clean_dict, { 'family_name': 'Doe', diff --git a/oidc_provider/tests/cases/test_commands.py b/oidc_provider/tests/cases/test_commands.py index cb070ec1..2f9248fe 100644 --- a/oidc_provider/tests/cases/test_commands.py +++ b/oidc_provider/tests/cases/test_commands.py @@ -1,6 +1,7 @@ +from io import StringIO + from django.core.management import call_command from django.test import TestCase -from django.utils.six import StringIO class CommandsTest(TestCase): diff --git a/oidc_provider/tests/cases/test_introspection_endpoint.py b/oidc_provider/tests/cases/test_introspection_endpoint.py index 38b3c3c0..68dae6ef 100644 --- a/oidc_provider/tests/cases/test_introspection_endpoint.py +++ b/oidc_provider/tests/cases/test_introspection_endpoint.py @@ -6,7 +6,7 @@ from urllib.parse import urlencode except ImportError: from urllib import urlencode -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.core.management import call_command from django.test import TestCase, RequestFactory, override_settings from django.utils import timezone @@ -45,7 +45,7 @@ def setUp(self): def _assert_inactive(self, response): self.assertEqual(response.status_code, 200) - self.assertJSONEqual(force_text(response.content), {'active': False}) + self.assertJSONEqual(force_str(response.content), {'active': False}) def _assert_active(self, response, **kwargs): self.assertEqual(response.status_code, 200) @@ -59,7 +59,7 @@ def _assert_active(self, response, **kwargs): 'iss': 'http://localhost:8000/openid', } expected_content.update(kwargs) - self.assertJSONEqual(force_text(response.content), expected_content) + self.assertJSONEqual(force_str(response.content), expected_content) def _make_request(self, **kwargs): url = reverse('oidc_provider:token-introspection') @@ -126,7 +126,7 @@ def test_valid_client_grant_token_without_aud_validation(self): self.resource.save() response = self._make_request() self.assertEqual(response.status_code, 200) - self.assertJSONEqual(force_text(response.content), { + self.assertJSONEqual(force_str(response.content), { 'active': True, 'client_id': self.client.client_id, }) diff --git a/oidc_provider/tests/cases/test_middleware.py b/oidc_provider/tests/cases/test_middleware.py index 4c93b0c5..a966cd0b 100644 --- a/oidc_provider/tests/cases/test_middleware.py +++ b/oidc_provider/tests/cases/test_middleware.py @@ -1,17 +1,19 @@ +import mock + try: - from django.urls import url + from django.urls import include, re_path except ImportError: - from django.conf.urls import url + from django.conf.urls import include + from django.conf.urls import url as re_path from django.test import TestCase, override_settings from django.views.generic import View -from mock import mock class StubbedViews: class SampleView(View): pass - urlpatterns = [url('^test/', SampleView.as_view())] + urlpatterns = [re_path('^test/', SampleView.as_view())] MW_CLASSES = ('django.contrib.sessions.middleware.SessionMiddleware', diff --git a/oidc_provider/tests/cases/test_settings.py b/oidc_provider/tests/cases/test_settings.py index 00510bff..1a8a0f7b 100644 --- a/oidc_provider/tests/cases/test_settings.py +++ b/oidc_provider/tests/cases/test_settings.py @@ -16,7 +16,7 @@ def test_override_templates(self): def test_unauthenticated_session_management_key_has_default(self): key = settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY') - self.assertRegexpMatches(key, r'[a-zA-Z0-9]+') + self.assertRegex(key, r'[a-zA-Z0-9]+') self.assertGreater(len(key), 50) def test_unauthenticated_session_management_key_has_constant_value(self): diff --git a/oidc_provider/urls.py b/oidc_provider/urls.py index 44cc9143..26c4c3c3 100644 --- a/oidc_provider/urls.py +++ b/oidc_provider/urls.py @@ -1,7 +1,8 @@ try: - from django.urls import url + from django.urls import include, re_path except ImportError: - from django.conf.urls import url + from django.conf.urls import include + from django.conf.urls import url as re_path from django.views.decorators.csrf import csrf_exempt from oidc_provider import ( @@ -11,18 +12,18 @@ app_name = 'oidc_provider' urlpatterns = [ - url(r'^authorize/?$', views.AuthorizeView.as_view(), name='authorize'), - url(r'^token/?$', csrf_exempt(views.TokenView.as_view()), name='token'), - url(r'^userinfo/?$', csrf_exempt(views.userinfo), name='userinfo'), - url(r'^end-session/?$', views.EndSessionView.as_view(), name='end-session'), - url(r'^\.well-known/openid-configuration/?$', views.ProviderInfoView.as_view(), + re_path(r'^authorize/?$', views.AuthorizeView.as_view(), name='authorize'), + re_path(r'^token/?$', csrf_exempt(views.TokenView.as_view()), name='token'), + re_path(r'^userinfo/?$', csrf_exempt(views.userinfo), name='userinfo'), + re_path(r'^end-session/?$', views.EndSessionView.as_view(), name='end-session'), + re_path(r'^\.well-known/openid-configuration/?$', views.ProviderInfoView.as_view(), name='provider-info'), - url(r'^introspect/?$', views.TokenIntrospectionView.as_view(), name='token-introspection'), - url(r'^jwks/?$', views.JwksView.as_view(), name='jwks'), + re_path(r'^introspect/?$', views.TokenIntrospectionView.as_view(), name='token-introspection'), + re_path(r'^jwks/?$', views.JwksView.as_view(), name='jwks'), ] if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'): urlpatterns += [ - url(r'^check-session-iframe/?$', views.CheckSessionIframeView.as_view(), + re_path(r'^check-session-iframe/?$', views.CheckSessionIframeView.as_view(), name='check-session-iframe'), ] diff --git a/setup.py b/setup.py index 70663bc4..b08f709d 100644 --- a/setup.py +++ b/setup.py @@ -31,11 +31,12 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], diff --git a/tox.ini b/tox.ini index b2f8dcfe..27a20605 100644 --- a/tox.ini +++ b/tox.ini @@ -1,29 +1,36 @@ [tox] envlist= docs, - py27-django{111}, - py35-django{111,20,21}, - py36-django{111,20,21}, + py35-django{111,20,21,22}, + py36-django{111,20,21,22,30,31,32}, + py37-django{111,20,21,22,30,31,32}, + py38-django{20,21,22,30,31,32}, + py39-django{20,21,22,30,31,32}, + flake8 [testenv] changedir= oidc_provider deps = mock - psycopg2 - pytest==3.6.4 + psycopg2-binary + pytest==4.6.0 pytest-django pytest-flake8 pytest-cov django111: django>=1.11,<1.12 django20: django>=2.0,<2.1 django21: django>=2.1,<2.2 + django22: django>=2.2,<3.0 + django30: django>=3.0,<3.1 + django31: django>=3.1,<3.2 + django32: django>=3.2,<3.3 commands = - pytest --flake8 --cov=oidc_provider {posargs} + pytest --cov=oidc_provider {posargs} [testenv:docs] -basepython = python2.7 +basepython = python3.8 changedir = docs whitelist_externals = mkdir @@ -37,9 +44,11 @@ commands = [pytest] DJANGO_SETTINGS_MODULE = oidc_provider.tests.settings python_files = test_*.py -flake8-max-line-length = 100 -flake8-ignore = - .git ALL - __pycache__ ALL - .ropeproject ALL - migrations/* ALL + + +[testenv:flake8] +basepython = python3.8 +deps = + flake8 +commands = + flake8 . --exclude=venv/,.tox/,migrations --max-line-length 100 \ No newline at end of file From a33bb40e2e11e0125929fc7afdcf82986c54c6f4 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Thu, 4 May 2023 16:37:55 -0300 Subject: [PATCH 26/75] Clean travis + fix tox versions + remove Signal parameter. --- .travis.yml | 37 ------------------- README.md | 1 - oidc_provider/signals.py | 4 +- .../tests/cases/test_authorize_endpoint.py | 2 +- setup.py | 5 +-- tox.ini | 21 ++++------- 6 files changed, 13 insertions(+), 57 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cc48cd33..00000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: python -dist: xenial -sudo: false - -python: - - 2.7 - - 3.5 - - 3.6 - - 3.7 - -install: - - pip install tox coveralls -matrix: - include: - - python: 3.8 - env: - - ENV=docs - - python: 3.5 - env: - - ENV=py35-django111,py35-django20,py35-django21,py35-django22 - - python: 3.6 - env: - - ENV=py36-django111,py36-django20,py36-django21,py36-django22,py36-django30,py36-django31,py36-django32 - - python: 3.7 - env: - - ENV=py37-django111,py37-django20,py37-django21,py37-django22,py37-django30,py37-django31,py37-django32 - - python: 3.8 - env: - - ENV=py38-django20,py38-django21,py38-django22,py38-django30,py38-django31,py38-django32 - - python: 3.9 - env: - - ENV=py39-django20,py39-django21,py39-django22,py39-django30,py39-django31,py39-django32 - -script: - - tox -after_success: - - coveralls diff --git a/README.md b/README.md index 074dc01a..42cb020d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Python Versions](https://img.shields.io/pypi/pyversions/django-oidc-provider.svg)](https://pypi.python.org/pypi/django-oidc-provider) [![PyPI Versions](https://img.shields.io/pypi/v/django-oidc-provider.svg)](https://pypi.python.org/pypi/django-oidc-provider) [![Documentation Status](https://readthedocs.org/projects/django-oidc-provider/badge/?version=master)](http://django-oidc-provider.readthedocs.io/) -[![Travis](https://travis-ci.org/juanifioren/django-oidc-provider.svg?branch=master)](https://travis-ci.org/juanifioren/django-oidc-provider) ## About OpenID diff --git a/oidc_provider/signals.py b/oidc_provider/signals.py index 679417cc..ba3d5e50 100644 --- a/oidc_provider/signals.py +++ b/oidc_provider/signals.py @@ -2,5 +2,5 @@ from django.dispatch import Signal -user_accept_consent = Signal(providing_args=['user', 'client', 'scope']) -user_decline_consent = Signal(providing_args=['user', 'client', 'scope']) +user_accept_consent = Signal() +user_decline_consent = Signal() diff --git a/oidc_provider/tests/cases/test_authorize_endpoint.py b/oidc_provider/tests/cases/test_authorize_endpoint.py index c18596c3..508b3e70 100644 --- a/oidc_provider/tests/cases/test_authorize_endpoint.py +++ b/oidc_provider/tests/cases/test_authorize_endpoint.py @@ -395,7 +395,7 @@ def test_prompt_login_parameter(self, logout_function): response = self._auth_request('get', data, is_user_authenticated=True) self.assertIn(settings.get('OIDC_LOGIN_URL'), response['Location']) - self.assertTrue(logout_function.called_once()) + logout_function.assert_called_once() self.assertNotIn( quote('prompt=login'), response['Location'], diff --git a/setup.py b/setup.py index b08f709d..f20b52b8 100644 --- a/setup.py +++ b/setup.py @@ -32,11 +32,10 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], diff --git a/tox.ini b/tox.ini index 236b896c..9f59e6a5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,10 @@ [tox] envlist= docs, - - py35-django{111,20,21,22}, - py36-django{111,20,21,22,30,31,32}, - py37-django{111,20,21,22,30,31,32}, - py38-django{20,21,22,30,31,32}, - py39-django{20,21,22,30,31,32}, + py38-django{32,40,41,42}, + py39-django{32,40,41,42}, + py310-django{32,40,41,42}, + py311-django{32,40,41,42}, flake8 [testenv] @@ -15,17 +13,14 @@ changedir= deps = mock psycopg2-binary - pytest==4.6.0 + pytest pytest-django pytest-flake8 pytest-cov - django111: django>=1.11,<1.12 - django20: django>=2.0,<2.1 - django21: django>=2.1,<2.2 - django22: django>=2.2,<3.0 - django30: django>=3.0,<3.1 - django31: django>=3.1,<3.2 django32: django>=3.2,<3.3 + django40: django>=4.0,<4.1 + django41: django>=4.1,<4.2 + django42: django>=4.2,<4.3 commands = pytest --cov=oidc_provider {posargs} From 997c219316584a1d1fdb63aa032055a19e2a2b66 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Fri, 5 May 2023 17:14:30 -0300 Subject: [PATCH 27/75] Bump version v0.8.0. --- docs/sections/changelog.rst | 8 +++++++- oidc_provider/version.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 194e87ae..f973ceee 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,9 +8,15 @@ All notable changes to this project will be documented in this file. Unreleased ========== +0.8.0 +===== + +*2023-05-05* + +* Changed: now supporting latest versions of Django. +* Changed: drop support for Python 2 and Django lower than 3.2. * Added: scope on token and introspection endpoints. * Changed: Use static instead of deprecated staticfiles template tag. -* Fixed: example project on Django 2.1. * Fixed: example in docs for translatable scopes (ugettext). 0.7.0 diff --git a/oidc_provider/version.py b/oidc_provider/version.py index a71c5c7f..32a90a3b 100644 --- a/oidc_provider/version.py +++ b/oidc_provider/version.py @@ -1 +1 @@ -__version__ = '0.7.0' +__version__ = '0.8.0' From ad3054388b5f5fc11988503b6121ff39af0636b9 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Fri, 5 May 2023 17:20:33 -0300 Subject: [PATCH 28/75] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 42cb020d..86847553 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Django OpenID Connect Provider [![Python Versions](https://img.shields.io/pypi/pyversions/django-oidc-provider.svg)](https://pypi.python.org/pypi/django-oidc-provider) +[![Django Versions](https://img.shields.io/badge/Django-3.2%20%7C%204.2-green)](https://pypi.python.org/pypi/django-oidc-provider) [![PyPI Versions](https://img.shields.io/pypi/v/django-oidc-provider.svg)](https://pypi.python.org/pypi/django-oidc-provider) [![Documentation Status](https://readthedocs.org/projects/django-oidc-provider/badge/?version=master)](http://django-oidc-provider.readthedocs.io/) From 266579810272f7d01eb8f7aab45c8358b0f72765 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Sat, 6 May 2023 16:11:10 -0300 Subject: [PATCH 29/75] Update docs with Django 4. --- docs/sections/installation.rst | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/docs/sections/installation.rst b/docs/sections/installation.rst index bf0745b6..45f23457 100644 --- a/docs/sections/installation.rst +++ b/docs/sections/installation.rst @@ -6,8 +6,8 @@ Installation Requirements ============ -* Python: ``2.7`` ``3.4`` ``3.5`` ``3.6`` -* Django: ``1.11`` ``2.0`` ``2.1`` +* Python: ``3.8`` ``3.9`` ``3.10`` ``3.11`` +* Django: ``3.2`` ``4.2`` Quick Installation ================== @@ -20,24 +20,19 @@ Install the package using pip:: Add it to your apps in your project's django settings:: - INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + INSTALLED_APPS = [ + # ... 'oidc_provider', # ... - ) + ] Include our urls to your project's ``urls.py``:: - urlpatterns = patterns('', + urlpatterns = [ # ... - url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')), + path('openid/', include('oidc_provider.urls', namespace='oidc_provider')), # ... - ) + ] Run the migrations and generate a server RSA key:: From d2c56b40983dd32d1e1a65f0774ed0cced39e11f Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 11 May 2023 13:44:06 -0300 Subject: [PATCH 30/75] Create main.yml --- .github/workflows/main.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..792537f1 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,29 @@ +name: Django Tests CI + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [3.10] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install tox + - name: Run Tests + run: tox From aaa376378aa238f62c6b8f46190f1d3d13d877af Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 11 May 2023 13:45:59 -0300 Subject: [PATCH 31/75] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 792537f1..7decdf00 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.10] + python-version: ["3.10"] steps: - uses: actions/checkout@v3 From 86e95965fabf2e08846e92a32c1e588aaf871e5c Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 11 May 2023 14:40:00 -0300 Subject: [PATCH 32/75] Update main.yml --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7decdf00..d264fe6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,9 @@ name: Django Tests CI on: push: - branches: [ "develop" ] + branches: ["master", "develop"] pull_request: - branches: [ "develop" ] + branches: ["develop"] jobs: build: @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ["3.10"] + python-version: ["3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v3 From 57b09f0028dcb304b72af447e7da7a089013eae0 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 11 May 2023 14:48:09 -0300 Subject: [PATCH 33/75] Update main.yml --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d264fe6d..4077a78a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,8 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] + name: Python ${{ python-version }} steps: - uses: actions/checkout@v3 From 8278a62d341492e322f66b7952260c0cc40d3cbd Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 11 May 2023 14:49:03 -0300 Subject: [PATCH 34/75] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4077a78a..07cecb1c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: max-parallel: 4 matrix: python-version: ["3.8", "3.9", "3.10", "3.11"] - name: Python ${{ python-version }} + name: Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v3 From 61504d49c012b294b280a3e0c3946c4a4f8679c9 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Thu, 11 May 2023 16:09:11 -0300 Subject: [PATCH 35/75] Modify tox file. --- tox.ini | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tox.ini b/tox.ini index 9f59e6a5..d35a6e71 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ commands = pytest --cov=oidc_provider {posargs} [testenv:docs] -basepython = python3.8 +basepython = python3.11 changedir = docs whitelist_externals = mkdir @@ -37,14 +37,13 @@ commands = mkdir -p _static/ sphinx-build -v -W -b html -d {envtmpdir}/doctrees -D html_static_path="_static" . {envtmpdir}/html -[pytest] -DJANGO_SETTINGS_MODULE = oidc_provider.tests.settings -python_files = test_*.py - - [testenv:flake8] -basepython = python3.8 +basepython = python3.11 deps = flake8 commands = - flake8 . --exclude=venv/,.tox/,migrations --max-line-length 100 \ No newline at end of file + flake8 . --exclude=venv/,.tox/,migrations --max-line-length 100 + +[pytest] +DJANGO_SETTINGS_MODULE = oidc_provider.tests.settings +python_files = test_*.py \ No newline at end of file From eeebb8c94ca01d4060dfbb2754a17d9dd3111b36 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Thu, 11 May 2023 16:17:06 -0300 Subject: [PATCH 36/75] Fix flake8. --- oidc_provider/tests/cases/test_middleware.py | 6 +----- oidc_provider/urls.py | 10 +++------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/oidc_provider/tests/cases/test_middleware.py b/oidc_provider/tests/cases/test_middleware.py index a966cd0b..17339285 100644 --- a/oidc_provider/tests/cases/test_middleware.py +++ b/oidc_provider/tests/cases/test_middleware.py @@ -1,10 +1,6 @@ import mock -try: - from django.urls import include, re_path -except ImportError: - from django.conf.urls import include - from django.conf.urls import url as re_path +from django.urls import re_path from django.test import TestCase, override_settings from django.views.generic import View diff --git a/oidc_provider/urls.py b/oidc_provider/urls.py index 26c4c3c3..08d219f0 100644 --- a/oidc_provider/urls.py +++ b/oidc_provider/urls.py @@ -1,8 +1,4 @@ -try: - from django.urls import include, re_path -except ImportError: - from django.conf.urls import include - from django.conf.urls import url as re_path +from django.urls import re_path from django.views.decorators.csrf import csrf_exempt from oidc_provider import ( @@ -17,7 +13,7 @@ re_path(r'^userinfo/?$', csrf_exempt(views.userinfo), name='userinfo'), re_path(r'^end-session/?$', views.EndSessionView.as_view(), name='end-session'), re_path(r'^\.well-known/openid-configuration/?$', views.ProviderInfoView.as_view(), - name='provider-info'), + name='provider-info'), re_path(r'^introspect/?$', views.TokenIntrospectionView.as_view(), name='token-introspection'), re_path(r'^jwks/?$', views.JwksView.as_view(), name='jwks'), ] @@ -25,5 +21,5 @@ if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'): urlpatterns += [ re_path(r'^check-session-iframe/?$', views.CheckSessionIframeView.as_view(), - name='check-session-iframe'), + name='check-session-iframe'), ] From e28fbb65847058f4e55e6042c9f120f94f701780 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Thu, 11 May 2023 17:49:10 -0300 Subject: [PATCH 37/75] Fix tox. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d35a6e71..088c1390 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,7 @@ commands = [testenv:docs] basepython = python3.11 changedir = docs -whitelist_externals = +allowlist_externals = mkdir deps = sphinx From 608bd6239457b5d4fa9639f723d69c885fd929f8 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Fri, 12 May 2023 12:35:44 -0300 Subject: [PATCH 38/75] Update docs. --- docs/conf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 62ca4c11..f4fe2ecb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,7 +45,7 @@ # General information about the project. project = u'django-oidc-provider' -copyright = u'2016, Juan Ignacio Fiorentino' +copyright = u'2023, Juan Ignacio Fiorentino' author = u'Juan Ignacio Fiorentino' # The version info for the project you're documenting, acts as replacement for @@ -53,16 +53,16 @@ # built documents. # # The short X.Y version. -version = u'0.5' +version = u'0.8' # The full version, including alpha/beta/rc tags. -release = u'0.5.x' +release = u'0.8.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: From 2a0b84655174dc9484b0179b9a05632ad5fe4ee2 Mon Sep 17 00:00:00 2001 From: Charles Chan Date: Tue, 8 Oct 2019 13:02:46 -0700 Subject: [PATCH 39/75] Remove extra backticks in doc --- docs/sections/relyingparties.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sections/relyingparties.rst b/docs/sections/relyingparties.rst index 3a3028ce..b31b2822 100644 --- a/docs/sections/relyingparties.rst +++ b/docs/sections/relyingparties.rst @@ -19,7 +19,7 @@ Properties * ``client_type``: Values are ``confidential`` and ``public``. * ``client_id``: Client unique identifier. * ``client_secret``: Client secret for confidential applications. -* ``response_types``: The flows and associated ```response_type``` values that can be used by the client. +* ``response_types``: The flows and associated ``response_type`` values that can be used by the client. * ``jwt_alg``: Clients can choose which algorithm will be used to sign id_tokens. Values are ``HS256`` and ``RS256``. * ``date_created``: Date automatically added when created. * ``redirect_uris``: List of redirect URIs. From 516e3d3c5a57a2032280279f60dfac066ea97102 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 12:30:03 -0300 Subject: [PATCH 40/75] Update main.yml --- .github/workflows/main.yml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07cecb1c..52fc6146 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,21 +10,16 @@ jobs: build: runs-on: ubuntu-latest - strategy: - max-parallel: 4 - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] - name: Python ${{ matrix.python-version }} + name: Run pytest with tox steps: - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + - name: Setup pyenv + uses: "gabrielfalcao/pyenv-action@v14" with: - python-version: ${{ matrix.python-version }} - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install tox - - name: Run Tests + versions: 3.8.13, 3.9.12, 3.10.4 + command: | + python -m pip install --upgrade pip + pip install tox + - name: Run tests run: tox From 5c1f68c1cf74e1d38b0e09c0daa419b190ed9ab0 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 12:49:45 -0300 Subject: [PATCH 41/75] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 52fc6146..6a317ac2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: - name: Setup pyenv uses: "gabrielfalcao/pyenv-action@v14" with: - versions: 3.8.13, 3.9.12, 3.10.4 + versions: 3.8.13, 3.9.12 command: | python -m pip install --upgrade pip pip install tox From 514e60c46c579cbef744b1436b0b28f6f27ba902 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 13:04:21 -0300 Subject: [PATCH 42/75] Update main.yml --- .github/workflows/main.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a317ac2..b9b7427b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,16 +10,14 @@ jobs: build: runs-on: ubuntu-latest - name: Run pytest with tox steps: - uses: actions/checkout@v3 - name: Setup pyenv - uses: "gabrielfalcao/pyenv-action@v14" - with: - versions: 3.8.13, 3.9.12 - command: | - python -m pip install --upgrade pip - pip install tox - - name: Run tests - run: tox + command: | + curl https://pyenv.run | bash + export PATH="$HOME/.pyenv/bin:$PATH" + export PATH="$HOME/.pyenv/bin:$PATH" + eval "$(pyenv virtualenv-init -)" + exec $SHELL + pyenv --version From 742041a0c17c8ea45b2d3ed7dc8b128f271875d3 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 13:04:59 -0300 Subject: [PATCH 43/75] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b9b7427b..4a3a0393 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Setup pyenv - command: | + run: | curl https://pyenv.run | bash export PATH="$HOME/.pyenv/bin:$PATH" export PATH="$HOME/.pyenv/bin:$PATH" From b6fb866c2a76e02f2e8103cf26c99c7eb84f5305 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 13:07:12 -0300 Subject: [PATCH 44/75] Update main.yml --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a3a0393..5b0b7bf3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,5 +19,4 @@ jobs: export PATH="$HOME/.pyenv/bin:$PATH" export PATH="$HOME/.pyenv/bin:$PATH" eval "$(pyenv virtualenv-init -)" - exec $SHELL pyenv --version From 14d4e6e921921bcb257be165c9c5093811025540 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 13:13:07 -0300 Subject: [PATCH 45/75] Update main.yml --- .github/workflows/main.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b0b7bf3..b1d3748c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,3 +20,11 @@ jobs: export PATH="$HOME/.pyenv/bin:$PATH" eval "$(pyenv virtualenv-init -)" pyenv --version + - name: Install pyenv versions + run: | + pyenv install 3.8.13 + pyenv activate 3.8.13 + python -m pip install --upgrade pip + pip install tox + - name: Run tox + run: tox From 48e9e717f95733f85b66df87f3a5aa0d2f471dff Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 13:15:09 -0300 Subject: [PATCH 46/75] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b1d3748c..45fb0c2a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: export PATH="$HOME/.pyenv/bin:$PATH" export PATH="$HOME/.pyenv/bin:$PATH" eval "$(pyenv virtualenv-init -)" - pyenv --version + exec "$SHELL" - name: Install pyenv versions run: | pyenv install 3.8.13 From f7afb003671a6e676a6f2bd11cde1ac49d0507c4 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 13:20:54 -0300 Subject: [PATCH 47/75] Update main.yml --- .github/workflows/main.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 45fb0c2a..644ae3cf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,17 +13,14 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup pyenv + - uses: actions/setup-python@v4 + with: + python-version: | + 3.8 + 3.9 + 3.10 + - name: Install tox run: | - curl https://pyenv.run | bash - export PATH="$HOME/.pyenv/bin:$PATH" - export PATH="$HOME/.pyenv/bin:$PATH" - eval "$(pyenv virtualenv-init -)" - exec "$SHELL" - - name: Install pyenv versions - run: | - pyenv install 3.8.13 - pyenv activate 3.8.13 python -m pip install --upgrade pip pip install tox - name: Run tox From a1c7d87ac9c332ef170e278a027b9e2c4f2ecc2a Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 13:46:35 -0300 Subject: [PATCH 48/75] Update main.yml --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 644ae3cf..4b1c725c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,6 +19,7 @@ jobs: 3.8 3.9 3.10 + 3.11 - name: Install tox run: | python -m pip install --upgrade pip From 7a1a4740c5c00eba149f70a338229b72d2bf0646 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 14:29:32 -0300 Subject: [PATCH 49/75] Update main.yml --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4b1c725c..662af4f4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,10 +7,8 @@ on: branches: ["develop"] jobs: - build: - + tests: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 From 03d41fab98b1fc6b24074f8a55a844641265c209 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 21:27:37 -0300 Subject: [PATCH 50/75] Update contribute.rst --- docs/sections/contribute.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sections/contribute.rst b/docs/sections/contribute.rst index 9a190421..7e040f0a 100644 --- a/docs/sections/contribute.rst +++ b/docs/sections/contribute.rst @@ -21,13 +21,13 @@ Use `tox `_ for running tests in each of the e # Run all tests. $ tox - # Run with Python 3.5 and Django 2.0. - $ tox -e py35-django20 + # Run with Python 3.11 and Django 4.2. + $ tox -e py311-django42 # Run single test file on specific environment. - $ tox -e py35-django20 tests/cases/test_authorize_endpoint.py + $ tox -e py311-django42 tests/cases/test_authorize_endpoint.py -We also use `travis `_ to automatically test every commit to the project. +We use `Github Actions `_ to automatically test every commit to the project. Improve Documentation ===================== From 6c33e8f67c46ba7fcbb77d4873139fcb8003fa1f Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 23:19:00 -0300 Subject: [PATCH 51/75] Update contribute.rst --- docs/sections/contribute.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sections/contribute.rst b/docs/sections/contribute.rst index 7e040f0a..e996c3a9 100644 --- a/docs/sections/contribute.rst +++ b/docs/sections/contribute.rst @@ -32,7 +32,7 @@ We use `Github Actions `_ for generate this documentation. If you want to add or modify something just: +We use `Sphinx `_ to generate this documentation. If you want to add or modify something just: * Install Sphinx (``pip install sphinx``) and the auto-build tool (``pip install sphinx-autobuild``). * Move inside the docs folder. ``cd docs/`` From c786f815dbc2baea3c46d3c0c0fd5b7715776366 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Mon, 15 May 2023 23:20:15 -0300 Subject: [PATCH 52/75] Update contribute.rst --- docs/sections/contribute.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sections/contribute.rst b/docs/sections/contribute.rst index e996c3a9..b9bf830d 100644 --- a/docs/sections/contribute.rst +++ b/docs/sections/contribute.rst @@ -7,10 +7,10 @@ We love contributions, so please feel free to fix bugs, improve things, provide * Create an issue and explain your feature/bugfix. * Wait collaborators comments. -* Fork the project and create new branch from `develop`. +* Fork the project and create new branch from ``develop``. * Make your feature addition or bug fix. * Add tests and documentation if needed. -* Create pull request for the issue to the `develop` branch. +* Create pull request for the issue to the ``develop`` branch. * Wait collaborators reviews. Running Tests From 3c5e05f79ca7638e53f5c5ae6ab8fdf73a3aad5e Mon Sep 17 00:00:00 2001 From: Mikko Keskinen Date: Fri, 30 Apr 2021 10:22:05 +0300 Subject: [PATCH 53/75] Extract token creations to their own methods This enables subclasses to customize the token and code creation. --- oidc_provider/lib/endpoints/authorize.py | 37 +++++++++++++++--------- oidc_provider/lib/endpoints/token.py | 30 +++++++++++++------ 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 51a75c6f..9d6adc55 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -126,6 +126,28 @@ def validate_params(self): raise AuthorizeError( self.params['redirect_uri'], 'invalid_request', self.grant_type) + def create_code(self): + code = create_code( + user=self.request.user, + client=self.client, + scope=self.params['scope'], + nonce=self.params['nonce'], + is_authentication=self.is_authentication, + code_challenge=self.params['code_challenge'], + code_challenge_method=self.params['code_challenge_method'], + ) + + return code + + def create_token(self): + token = create_token( + user=self.request.user, + client=self.client, + scope=self.params['scope'], + ) + + return token + def create_response_uri(self): uri = urlsplit(self.params['redirect_uri']) query_params = parse_qs(uri.query) @@ -133,24 +155,13 @@ def create_response_uri(self): try: if self.grant_type in ['authorization_code', 'hybrid']: - code = create_code( - user=self.request.user, - client=self.client, - scope=self.params['scope'], - nonce=self.params['nonce'], - is_authentication=self.is_authentication, - code_challenge=self.params['code_challenge'], - code_challenge_method=self.params['code_challenge_method']) + code = self.create_code() code.save() - if self.grant_type == 'authorization_code': query_params['code'] = code.code query_params['state'] = self.params['state'] if self.params['state'] else '' elif self.grant_type in ['implicit', 'hybrid']: - token = create_token( - user=self.request.user, - client=self.client, - scope=self.params['scope']) + token = self.create_token() # Check if response_type must include access_token in the response. if (self.params['response_type'] in diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 7ecde44a..991a6675 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -166,13 +166,23 @@ def create_response_dic(self): elif self.params['grant_type'] == 'client_credentials': return self.create_client_credentials_response_dic() + def create_token(self, user, client, scope): + token = create_token( + user=user, + client=client, + scope=scope, + ) + + return token + def create_code_response_dic(self): # See https://tools.ietf.org/html/rfc6749#section-4.1 - token = create_token( + token = self.create_token( user=self.code.user, client=self.code.client, - scope=self.code.scope) + scope=self.code.scope, + ) if self.code.is_authentication: id_token_dic = create_id_token( @@ -213,10 +223,11 @@ def create_refresh_response_dic(self): if unauthorized_scopes: raise TokenError('invalid_scope') - token = create_token( + token = self.create_token( user=self.token.user, client=self.token.client, - scope=scope) + scope=scope, + ) # If the Token has an id_token it's an Authentication request. if self.token.id_token: @@ -252,10 +263,11 @@ def create_refresh_response_dic(self): def create_access_token_response_dic(self): # See https://tools.ietf.org/html/rfc6749#section-4.3 token_scopes = self.validate_requested_scopes() - token = create_token( + token = self.create_token( self.user, self.client, - token_scopes) + token_scopes, + ) id_token_dic = create_id_token( token=token, @@ -283,11 +295,11 @@ def create_client_credentials_response_dic(self): # See https://tools.ietf.org/html/rfc6749#section-4.4.3 token_scopes = self.validate_requested_scopes() - token = create_token( + token = self.create_token( user=None, client=self.client, - scope=token_scopes) - + scope=token_scopes, + ) token.save() return { From da7a00c64bb7934003544f53d4bb00d512285ef5 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Tue, 23 May 2023 12:37:54 -0300 Subject: [PATCH 54/75] Update changelog.rst --- docs/sections/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index f973ceee..d99269da 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file. Unreleased ========== +* Changed: create_token and create_code are now methods on base classes to enable customization. + 0.8.0 ===== From dd6fdbf866127233b0339c4c0514f841e7b34980 Mon Sep 17 00:00:00 2001 From: Mikko Keskinen Date: Fri, 4 Jun 2021 16:04:12 +0300 Subject: [PATCH 55/75] Extract "is consent skip allowed" decision from the view to the endpoint --- oidc_provider/lib/endpoints/authorize.py | 7 +++++++ oidc_provider/views.py | 9 ++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 9d6adc55..4728158e 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -281,6 +281,13 @@ def client_has_user_consent(self): return value + def is_client_allowed_to_skip_consent(self): + implicit_flow_resp_types = {'id_token', 'id_token token'} + return ( + self.client.client_type != 'public' or + self.params['response_type'] in implicit_flow_resp_types + ) + def get_scopes_information(self): """ Return a list with the description of all the scopes requested. diff --git a/oidc_provider/views.py b/oidc_provider/views.py index 06501d5d..2e2cef59 100644 --- a/oidc_provider/views.py +++ b/oidc_provider/views.py @@ -103,20 +103,15 @@ def get(self, request, *args, **kwargs): raise AuthorizeError( authorize.params['redirect_uri'], 'consent_required', authorize.grant_type) - implicit_flow_resp_types = {'id_token', 'id_token token'} - allow_skipping_consent = ( - authorize.client.client_type != 'public' or - authorize.params['response_type'] in implicit_flow_resp_types) - if not authorize.client.require_consent and ( - allow_skipping_consent and + authorize.is_client_allowed_to_skip_consent() and 'consent' not in authorize.params['prompt']): return redirect(authorize.create_response_uri()) if authorize.client.reuse_consent: # Check if user previously give consent. if authorize.client_has_user_consent() and ( - allow_skipping_consent and + authorize.is_client_allowed_to_skip_consent() and 'consent' not in authorize.params['prompt']): return redirect(authorize.create_response_uri()) From d5f06f1467c38799095256183344f33132f0c42b Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Wed, 31 May 2023 13:35:13 -0300 Subject: [PATCH 56/75] Update changelog.rst --- docs/sections/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index d99269da..378715a6 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -9,6 +9,7 @@ Unreleased ========== * Changed: create_token and create_code are now methods on base classes to enable customization. +* Changed: extract "is consent skip allowed" decision from the view to the endpoint. 0.8.0 ===== From 3203ce791115c01047d4339a20e1da800e196019 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Fri, 22 Sep 2023 17:00:29 -0300 Subject: [PATCH 57/75] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..d970e28b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: juanifioren From 03dad413f74a5efc64e5ce66bd32af7a0af639a2 Mon Sep 17 00:00:00 2001 From: Javier Paniagua Date: Thu, 8 Oct 2020 14:24:27 +0200 Subject: [PATCH 58/75] avoid race condition on code exchange for token (fixes #410) --- oidc_provider/lib/endpoints/token.py | 6 +++++- .../tests/cases/test_token_endpoint.py | 20 +++++++++++++++++++ oidc_provider/views.py | 7 ++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 991a6675..048365ba 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -4,6 +4,7 @@ from base64 import urlsafe_b64encode from django.contrib.auth import authenticate +from django.db import DatabaseError from django.http import JsonResponse from oidc_provider import settings @@ -70,7 +71,10 @@ def validate_params(self): raise TokenError('invalid_client') try: - self.code = Code.objects.get(code=self.params['code']) + self.code = Code.objects.select_for_update(nowait=True).get(code=self.params['code']) + except DatabaseError: + logger.debug('[Token] Code cannot be reused: %s', self.params['code']) + raise TokenError('invalid_grant') except Code.DoesNotExist: logger.debug('[Token] Code does not exist: %s', self.params['code']) raise TokenError('invalid_grant') diff --git a/oidc_provider/tests/cases/test_token_endpoint.py b/oidc_provider/tests/cases/test_token_endpoint.py index 91f950d3..b092fe97 100644 --- a/oidc_provider/tests/cases/test_token_endpoint.py +++ b/oidc_provider/tests/cases/test_token_endpoint.py @@ -4,6 +4,8 @@ from base64 import b64encode +from django.db import DatabaseError + try: from urllib.parse import urlencode except ImportError: @@ -312,6 +314,24 @@ def test_authorization_code(self): self.assertEqual(id_token['sub'], str(self.user.id)) self.assertEqual(id_token['aud'], self.client.client_id) + @override_settings(OIDC_TOKEN_EXPIRE=720) + def test_authorization_code_cant_be_reused(self): + """ + Authorization codes MUST be short lived and single-use, + as described in Section 10.5 of OAuth 2.0 [RFC6749]. + """ + code = self._create_code() + post_data = self._auth_code_post_data(code=code.code) + + with patch('django.db.models.query.QuerySet.select_for_update') as select_for_update_func: + select_for_update_func.side_effect = DatabaseError() + response = self._post_request(post_data) + select_for_update_func.assert_called_once() + + self.assertEqual(response.status_code, 400) + response_dic = json.loads(response.content.decode('utf-8')) + self.assertEqual(response_dic['error'], 'invalid_grant') + @override_settings(OIDC_TOKEN_EXPIRE=720, OIDC_IDTOKEN_INCLUDE_CLAIMS=True) def test_scope_is_ignored_for_auth_code(self): diff --git a/oidc_provider/views.py b/oidc_provider/views.py index 2e2cef59..79f0fb88 100644 --- a/oidc_provider/views.py +++ b/oidc_provider/views.py @@ -18,6 +18,7 @@ from django.urls import reverse except ImportError: from django.core.urlresolvers import reverse +from django.db import transaction from django.contrib.auth import logout as django_user_logout from django.http import JsonResponse, HttpResponse from django.shortcuts import render @@ -204,9 +205,9 @@ def post(self, request, *args, **kwargs): token = self.token_endpoint_class(request) try: - token.validate_params() - - dic = token.create_response_dic() + with transaction.atomic(): + token.validate_params() + dic = token.create_response_dic() return self.token_endpoint_class.response(dic) From e67826cd3a8cb613beec72b3e628bd9323c92ce6 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Sat, 23 Sep 2023 14:38:55 -0300 Subject: [PATCH 59/75] Update token.py --- oidc_provider/lib/endpoints/token.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 048365ba..c2d3987f 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -71,7 +71,8 @@ def validate_params(self): raise TokenError('invalid_client') try: - self.code = Code.objects.select_for_update(nowait=True).get(code=self.params['code']) + self.code = Code.objects.select_for_update(nowait=True).get( + code=self.params['code']) except DatabaseError: logger.debug('[Token] Code cannot be reused: %s', self.params['code']) raise TokenError('invalid_grant') From 6627e33e2995c20357f52f8a63553c8bdbe5ea4b Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Sat, 23 Sep 2023 15:22:00 -0300 Subject: [PATCH 60/75] Update changelog.rst --- docs/sections/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 378715a6..51f10197 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -10,6 +10,7 @@ Unreleased * Changed: create_token and create_code are now methods on base classes to enable customization. * Changed: extract "is consent skip allowed" decision from the view to the endpoint. +* Fixed: race condition in authorization code, parallel requests may reuse same token. 0.8.0 ===== From efc90a6a2146e7c814b4d846207b12e3465a15be Mon Sep 17 00:00:00 2001 From: juanifioren Date: Sun, 22 Oct 2023 20:03:31 -0300 Subject: [PATCH 61/75] Fix UTC warning. --- oidc_provider/migrations/0007_auto_20160111_1844.py | 3 +-- oidc_provider/migrations/0016_userconsent_and_verbosenames.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/oidc_provider/migrations/0007_auto_20160111_1844.py b/oidc_provider/migrations/0007_auto_20160111_1844.py index 263c4c5e..e9f06c27 100644 --- a/oidc_provider/migrations/0007_auto_20160111_1844.py +++ b/oidc_provider/migrations/0007_auto_20160111_1844.py @@ -4,7 +4,6 @@ import datetime from django.db import migrations, models -from django.utils.timezone import utc class Migration(migrations.Migration): @@ -30,7 +29,7 @@ class Migration(migrations.Migration): model_name='client', name='date_created', field=models.DateField( - auto_now_add=True, default=datetime.datetime(2016, 1, 11, 18, 44, 32, 192477, tzinfo=utc)), + auto_now_add=True, default=datetime.datetime(2016, 1, 11, 18, 44, 32, 192477, tzinfo=datetime.timezone.utc)), preserve_default=False, ), migrations.AlterField( diff --git a/oidc_provider/migrations/0016_userconsent_and_verbosenames.py b/oidc_provider/migrations/0016_userconsent_and_verbosenames.py index a6983624..bdcca3d4 100644 --- a/oidc_provider/migrations/0016_userconsent_and_verbosenames.py +++ b/oidc_provider/migrations/0016_userconsent_and_verbosenames.py @@ -6,7 +6,6 @@ from django.conf import settings from django.db import migrations, models import django.db.models.deletion -from django.utils.timezone import utc class Migration(migrations.Migration): @@ -20,7 +19,7 @@ class Migration(migrations.Migration): model_name='userconsent', name='date_given', field=models.DateTimeField( - default=datetime.datetime(2016, 6, 10, 17, 53, 48, 889808, tzinfo=utc), verbose_name='Date Given'), + default=datetime.datetime(2016, 6, 10, 17, 53, 48, 889808, tzinfo=datetime.timezone.utc), verbose_name='Date Given'), preserve_default=False, ), migrations.AlterField( From b72435a7b3fbbe319ea71d3f211709d3e1b87aab Mon Sep 17 00:00:00 2001 From: juanifioren Date: Sun, 22 Oct 2023 20:50:57 -0300 Subject: [PATCH 62/75] Bump version 0.8.1. --- docs/sections/changelog.rst | 7 +++++++ oidc_provider/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 51f10197..ba29077b 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,6 +8,13 @@ All notable changes to this project will be documented in this file. Unreleased ========== +N/A + +0.8.1 +===== + +*2023-10-22* + * Changed: create_token and create_code are now methods on base classes to enable customization. * Changed: extract "is consent skip allowed" decision from the view to the endpoint. * Fixed: race condition in authorization code, parallel requests may reuse same token. diff --git a/oidc_provider/version.py b/oidc_provider/version.py index 32a90a3b..ef72cc0f 100644 --- a/oidc_provider/version.py +++ b/oidc_provider/version.py @@ -1 +1 @@ -__version__ = '0.8.0' +__version__ = '0.8.1' From 4eb8e3c967622ab87858c9227e4115c60cf4fa1e Mon Sep 17 00:00:00 2001 From: juanifioren Date: Sun, 22 Oct 2023 21:25:17 -0300 Subject: [PATCH 63/75] ReadTheDocs new configuration file. --- .readthedocs.yaml | 24 ++++++++++++++++++++++++ docs/requirements.txt | 2 ++ 2 files changed, 26 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..0a0d2837 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,24 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +formats: + - pdf + +# Python requirements required to build your documentation +python: + install: + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..856b3f4c --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx +sphinx_rtd_theme \ No newline at end of file From bed4d9a6386bbf4fafa88f45f80dd0c71a14ad16 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Wed, 25 Oct 2023 12:19:14 -0300 Subject: [PATCH 64/75] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 86847553..e9daf3a9 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ OpenID Connect is a simple identity layer on top of the OAuth 2.0 protocol, whic `django-oidc-provider` can help you providing out of the box all the endpoints, data and logic needed to add OpenID Connect (and OAuth2) capabilities to your Django projects. -Support for Python 3 and 2. Also latest versions of django. +Support for Python 3 and latest versions of django. [Read documentation for more info.](http://django-oidc-provider.readthedocs.org/) -[Do you want to contribute? Please read this.](http://django-oidc-provider.readthedocs.io/en/latest/sections/contribute.html) +[Do you want to contribute? Please read this.](http://django-oidc-provider.readthedocs.io/en/master/sections/contribute.html) From fe9b0315eb25f7bd16ee52532ab1890b5e66cfe0 Mon Sep 17 00:00:00 2001 From: Harm Geerts Date: Wed, 21 Oct 2020 17:08:37 +0200 Subject: [PATCH 65/75] Fix ResponseType data migration If oidc provider is used in a multi database setup it may not be used on the default database alone. And when used in a database mirror setup the migration could be executed on a different db alias/transaction. This can cause migration failures because the ReponseType table is created on the database passed to the migrate command while the data is inserted in the database returned from the database router. Depending on the configuration the ResponseType table may not exist for that database yet or the ResponseType data was already migrated resulting in a DatabaseError and IntegrityError respectively. --- .../migrations/0026_client_multiple_response_types.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/oidc_provider/migrations/0026_client_multiple_response_types.py b/oidc_provider/migrations/0026_client_multiple_response_types.py index f572f0c7..067d2910 100644 --- a/oidc_provider/migrations/0026_client_multiple_response_types.py +++ b/oidc_provider/migrations/0026_client_multiple_response_types.py @@ -16,10 +16,11 @@ def migrate_response_type(apps, schema_editor): # importing directly yields the latest without response_type ResponseType = apps.get_model('oidc_provider', 'ResponseType') Client = apps.get_model('oidc_provider', 'Client') + db = schema_editor.connection.alias for value, description in RESPONSE_TYPES: - ResponseType.objects.create(value=value, description=description) - for client in Client.objects.all(): - client.response_types.add(ResponseType.objects.get(value=client.response_type)) + ResponseType.objects.using(db).create(value=value, description=description) + for client in Client.objects.using(db).all(): + client.response_types.add(ResponseType.objects.using(db).get(value=client.response_type)) class Migration(migrations.Migration): From 935c90d59fa900663f902ff2b5f322e1620887a2 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 14 Dec 2023 11:43:38 -0300 Subject: [PATCH 66/75] Update changelog.rst --- docs/sections/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index ba29077b..539f4630 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. Unreleased ========== -N/A +* Fixed: ResponseType data migration. 0.8.1 ===== From 8bfcd479ccfad563bf0c68e6dfdecf0c66b67880 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Mon, 27 Jan 2020 13:49:47 +1300 Subject: [PATCH 67/75] Correctly verify PKCE secret in token endpoint Before this change the PKCE secret would be verified only if it was sent by the client. This defeats the point of PKCE as a malicious actor which intercepted the code returned from the authorization endpoint would be able to send a request to the token endpoint without the code_verifier. This only affects public clients and is subject to the preconditions described by: https://tools.ietf.org/html/rfc7636#section-1 --- oidc_provider/lib/endpoints/token.py | 5 ++++- .../tests/cases/test_token_endpoint.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index c2d3987f..73a29937 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -86,7 +86,10 @@ def validate_params(self): raise TokenError('invalid_grant') # Validate PKCE parameters. - if self.params['code_verifier']: + if self.code.code_challenge: + if self.params['code_verifier'] is None: + raise TokenError('invalid_grant') + if self.code.code_challenge_method == 'S256': new_code_challenge = urlsafe_b64encode( hashlib.sha256(self.params['code_verifier'].encode('ascii')).digest() diff --git a/oidc_provider/tests/cases/test_token_endpoint.py b/oidc_provider/tests/cases/test_token_endpoint.py index b092fe97..a11b1c69 100644 --- a/oidc_provider/tests/cases/test_token_endpoint.py +++ b/oidc_provider/tests/cases/test_token_endpoint.py @@ -826,6 +826,25 @@ def test_pkce_parameters(self): json.loads(response.content.decode('utf-8')) + def test_pkce_missing_code_verifier(self): + """ + Test that a request to the token endpoint without the PKCE parameter + fails when PKCE was used during the authorization request. + """ + + code = create_code(user=self.user, client=self.client, + scope=['openid', 'email'], nonce=FAKE_NONCE, is_authentication=True, + code_challenge=FAKE_CODE_CHALLENGE, code_challenge_method='S256') + code.save() + + post_data = self._auth_code_post_data(code=code.code) + + assert 'code_verifier' not in post_data + + response = self._post_request(post_data) + + assert json.loads(response.content.decode('utf-8')).get('error') == 'invalid_grant' + @override_settings(OIDC_INTROSPECTION_VALIDATE_AUDIENCE_SCOPE=False) def test_client_credentials_grant_type(self): fake_scopes_list = ['scopeone', 'scopetwo', INTROSPECTION_SCOPE] From 0f236e8a825a2c6ff98d8b872541ac9f47e45240 Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Thu, 13 Feb 2020 10:05:02 +1300 Subject: [PATCH 68/75] Improve happy path test assertion --- oidc_provider/tests/cases/test_token_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc_provider/tests/cases/test_token_endpoint.py b/oidc_provider/tests/cases/test_token_endpoint.py index a11b1c69..8990d3d2 100644 --- a/oidc_provider/tests/cases/test_token_endpoint.py +++ b/oidc_provider/tests/cases/test_token_endpoint.py @@ -824,7 +824,7 @@ def test_pkce_parameters(self): response = self._post_request(post_data) - json.loads(response.content.decode('utf-8')) + self.assertIn('access_token', json.loads(response.content.decode('utf-8'))) def test_pkce_missing_code_verifier(self): """ From b2a22e1e4d1b2c6346d64d77f5ac5cf3fad513b7 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Thu, 14 Dec 2023 17:05:01 -0300 Subject: [PATCH 69/75] Update changelog.rst --- docs/sections/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 539f4630..dfa95975 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -9,6 +9,7 @@ Unreleased ========== * Fixed: ResponseType data migration. +* Fixed: correctly verify PKCE secret in token endpoint. 0.8.1 ===== From 90b9f54f04cb2f0a8350b865e7cf11bb9fe3e9b9 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Fri, 15 Dec 2023 12:50:21 -0300 Subject: [PATCH 70/75] Update contribute.rst --- docs/sections/contribute.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sections/contribute.rst b/docs/sections/contribute.rst index b9bf830d..a804ae61 100644 --- a/docs/sections/contribute.rst +++ b/docs/sections/contribute.rst @@ -25,7 +25,7 @@ Use `tox `_ for running tests in each of the e $ tox -e py311-django42 # Run single test file on specific environment. - $ tox -e py311-django42 tests/cases/test_authorize_endpoint.py + $ tox -e py311-django42 -- tests/cases/test_authorize_endpoint.py We use `Github Actions `_ to automatically test every commit to the project. From 5d4980ae6c2954ab51d83f5e5670d4d58694f8ea Mon Sep 17 00:00:00 2001 From: juanifioren Date: Fri, 15 Dec 2023 16:01:02 -0300 Subject: [PATCH 71/75] Add Discovery endpoint response caching. --- docs/sections/settings.rst | 14 +++++++ example/app/urls.py | 2 +- oidc_provider/settings.py | 16 +++++++- .../tests/cases/test_authorize_endpoint.py | 7 ++-- .../cases/test_provider_info_endpoint.py | 38 +++++++++++++++++- oidc_provider/views.py | 40 +++++++++++++++++-- 6 files changed, 105 insertions(+), 12 deletions(-) diff --git a/docs/sections/settings.rst b/docs/sections/settings.rst index 984d2df2..f7dadd5a 100644 --- a/docs/sections/settings.rst +++ b/docs/sections/settings.rst @@ -55,6 +55,20 @@ OPTIONAL. ``int``. Code object expiration after been delivered. Expressed in seconds. Default is ``60*10``. +OIDC_DISCOVERY_CACHE_ENABLE +================ + +OPTIONAL. ``bool``. Enable caching the response on the discovery endpoint, by using default cache. Cache key will be a combination of site URL and types supported by the provider, changing any of these will invalidate stored value. + +Default is ``False``. + +OIDC_DISCOVERY_CACHE_EXPIRE +================ + +OPTIONAL. ``int``. Discovery endpoint cache expiration time expressed in seconds. + +Expressed in seconds. Default is ``60*10``. + OIDC_EXTRA_SCOPE_CLAIMS ======================= diff --git a/example/app/urls.py b/example/app/urls.py index c9755745..acc75adc 100644 --- a/example/app/urls.py +++ b/example/app/urls.py @@ -9,7 +9,7 @@ urlpatterns = [ url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), - url(r'^accounts/login/$', auth_views.LoginView.as_view(template_name='login.html'), name='login'), + url(r'^accounts/login/$', auth_views.LoginView.as_view(template_name='login.html'), name='login'), # noqa url(r'^accounts/logout/$', auth_views.LogoutView.as_view(next_page='/'), name='logout'), url(r'^', include('oidc_provider.urls', namespace='oidc_provider')), url(r'^admin/', admin.site.urls), diff --git a/oidc_provider/settings.py b/oidc_provider/settings.py index 90750fda..08274042 100644 --- a/oidc_provider/settings.py +++ b/oidc_provider/settings.py @@ -14,7 +14,7 @@ def __init__(self): @property def OIDC_LOGIN_URL(self): """ - REQUIRED. Used to log the user in. By default Django's LOGIN_URL will be used. + OPTIONAL. Used to log the user in. By default Django's LOGIN_URL will be used. """ return settings.LOGIN_URL @@ -48,6 +48,20 @@ def OIDC_CODE_EXPIRE(self): """ return 60*10 + @property + def OIDC_DISCOVERY_CACHE_ENABLE(self): + """ + OPTIONAL. Enable caching the response on the discovery endpoint. + """ + return False + + @property + def OIDC_DISCOVERY_CACHE_EXPIRE(self): + """ + OPTIONAL. Discovery endpoint cache expiration time expressed in seconds. + """ + return 60*60*24 + @property def OIDC_EXTRA_SCOPE_CLAIMS(self): """ diff --git a/oidc_provider/tests/cases/test_authorize_endpoint.py b/oidc_provider/tests/cases/test_authorize_endpoint.py index 508b3e70..7ccd0a23 100644 --- a/oidc_provider/tests/cases/test_authorize_endpoint.py +++ b/oidc_provider/tests/cases/test_authorize_endpoint.py @@ -1,5 +1,3 @@ -from oidc_provider.lib.errors import RedirectUriError - try: from urllib.parse import urlencode, quote except ImportError: @@ -25,15 +23,16 @@ from jwkest.jwt import JWT from oidc_provider import settings +from oidc_provider.lib.endpoints.authorize import AuthorizeEndpoint +from oidc_provider.lib.errors import RedirectUriError +from oidc_provider.lib.utils.authorize import strip_prompt_login from oidc_provider.tests.app.utils import ( create_fake_user, create_fake_client, FAKE_CODE_CHALLENGE, is_code_valid, ) -from oidc_provider.lib.utils.authorize import strip_prompt_login from oidc_provider.views import AuthorizeView -from oidc_provider.lib.endpoints.authorize import AuthorizeEndpoint class AuthorizeEndpointMixin(object): diff --git a/oidc_provider/tests/cases/test_provider_info_endpoint.py b/oidc_provider/tests/cases/test_provider_info_endpoint.py index 2265ef66..1dfe2777 100644 --- a/oidc_provider/tests/cases/test_provider_info_endpoint.py +++ b/oidc_provider/tests/cases/test_provider_info_endpoint.py @@ -1,9 +1,13 @@ +from mock import patch + +from django.core.cache import cache try: from django.urls import reverse except ImportError: from django.core.urlresolvers import reverse from django.test import RequestFactory -from django.test import TestCase +from django.test import TestCase, override_settings + from oidc_provider.views import ProviderInfoView @@ -13,7 +17,11 @@ class ProviderInfoTestCase(TestCase): def setUp(self): self.factory = RequestFactory() - def test_response(self): + def tearDown(self): + cache.clear() + + @patch('oidc_provider.views.ProviderInfoView._build_cache_key') + def test_response(self, build_cache_key): """ See if the endpoint is returning the corresponding server information by checking status, content type, etc. @@ -24,6 +32,32 @@ def test_response(self): response = ProviderInfoView.as_view()(request) + # Caching not available by default. + build_cache_key.assert_not_called() + + self.assertEqual(response.status_code, 200) + self.assertEqual(response['Content-Type'] == 'application/json', True) + self.assertEqual(bool(response.content), True) + + @override_settings(OIDC_DISCOVERY_CACHE_ENABLE=True) + @patch('oidc_provider.views.ProviderInfoView._build_cache_key') + def test_response_with_cache_enabled(self, build_cache_key): + """ + Enable caching on the discovery endpoint and ensure data is being saved on cache. + """ + build_cache_key.return_value = 'key' + + url = reverse('oidc_provider:provider-info') + + request = self.factory.get(url) + + response = ProviderInfoView.as_view()(request) + self.assertEqual(response.status_code, 200) + build_cache_key.assert_called_once() + + assert 'authorization_endpoint' in cache.get('key') + + response = ProviderInfoView.as_view()(request) self.assertEqual(response.status_code, 200) self.assertEqual(response['Content-Type'] == 'application/json', True) self.assertEqual(bool(response.content), True) diff --git a/oidc_provider/views.py b/oidc_provider/views.py index 79f0fb88..4a2c94ce 100644 --- a/oidc_provider/views.py +++ b/oidc_provider/views.py @@ -1,3 +1,4 @@ +import hashlib import logging from django.views.decorators.csrf import csrf_exempt @@ -20,6 +21,7 @@ from django.core.urlresolvers import reverse from django.db import transaction from django.contrib.auth import logout as django_user_logout +from django.core.cache import cache from django.http import JsonResponse, HttpResponse from django.shortcuts import render from django.template.loader import render_to_string @@ -256,7 +258,16 @@ def set_headers(response): class ProviderInfoView(View): - def get(self, request, *args, **kwargs): + _types_supported = None + + @property + def types_supported(self): + if self._types_supported is None: + self._types_supported = [ + response_type.value for response_type in ResponseType.objects.all()] + return self._types_supported + + def _build_response_dict(self, request): dic = dict() site_url = get_site_url(request=request) @@ -268,8 +279,7 @@ def get(self, request, *args, **kwargs): dic['end_session_endpoint'] = site_url + reverse('oidc_provider:end-session') dic['introspection_endpoint'] = site_url + reverse('oidc_provider:token-introspection') - types_supported = [response_type.value for response_type in ResponseType.objects.all()] - dic['response_types_supported'] = types_supported + dic['response_types_supported'] = self.types_supported dic['jwks_uri'] = site_url + reverse('oidc_provider:jwks') @@ -284,7 +294,29 @@ def get(self, request, *args, **kwargs): if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'): dic['check_session_iframe'] = site_url + reverse('oidc_provider:check-session-iframe') - response = JsonResponse(dic) + return dic + + def _build_cache_key(self, request): + """ + Cache key will be a combination of site URL and types supported by the provider. + """ + key_data = get_site_url(request=request) + ''.join(self.types_supported) + key_hash = hashlib.md5(key_data.encode('utf-8')).hexdigest() + return f'oidc_discovery_{key_hash}' + + def get(self, request): + if settings.get('OIDC_DISCOVERY_CACHE_ENABLE'): + cache_key = self._build_cache_key(request) + cached_dict = cache.get(cache_key) + if cached_dict: + response_dict = cached_dict + else: + response_dict = self._build_response_dict(request) + cache.set(cache_key, response_dict, settings.get('OIDC_DISCOVERY_CACHE_EXPIRE')) + else: + response_dict = self._build_response_dict(request) + + response = JsonResponse(response_dict) response['Access-Control-Allow-Origin'] = '*' return response From 87c4dc997f27c2cdcf83a5f13751f25e7b692f47 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Fiorentino Date: Fri, 15 Dec 2023 16:02:59 -0300 Subject: [PATCH 72/75] Update changelog.rst --- docs/sections/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index dfa95975..0db4aafd 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. Unreleased ========== +* Added: Discovery endpoint response caching. Introducing OIDC_DISCOVERY_CACHE_ENABLE. * Fixed: ResponseType data migration. * Fixed: correctly verify PKCE secret in token endpoint. From 65e7c17ebd9bc998393b5303b5f5901c1fda8167 Mon Sep 17 00:00:00 2001 From: juanifioren Date: Fri, 15 Dec 2023 16:18:36 -0300 Subject: [PATCH 73/75] Bump version 0.8.2. --- docs/sections/changelog.rst | 6 ++++++ oidc_provider/version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/sections/changelog.rst b/docs/sections/changelog.rst index 0db4aafd..99b837e6 100644 --- a/docs/sections/changelog.rst +++ b/docs/sections/changelog.rst @@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file. Unreleased ========== + +0.8.2 +===== + +*2023-12-15* + * Added: Discovery endpoint response caching. Introducing OIDC_DISCOVERY_CACHE_ENABLE. * Fixed: ResponseType data migration. * Fixed: correctly verify PKCE secret in token endpoint. diff --git a/oidc_provider/version.py b/oidc_provider/version.py index ef72cc0f..4ca39e7c 100644 --- a/oidc_provider/version.py +++ b/oidc_provider/version.py @@ -1 +1 @@ -__version__ = '0.8.1' +__version__ = '0.8.2' From c0bf815e6b52ebac26f0b2b1ce12ae84762feb57 Mon Sep 17 00:00:00 2001 From: Freddy Garcia Date: Fri, 1 Mar 2024 14:56:51 -0500 Subject: [PATCH 74/75] build: remove .github folder --- .github/FUNDING.yml | 1 - .github/workflows/main.yml | 26 -------------------------- 2 files changed, 27 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .github/workflows/main.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index d970e28b..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: juanifioren diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 662af4f4..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Django Tests CI - -on: - push: - branches: ["master", "develop"] - pull_request: - branches: ["develop"] - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: | - 3.8 - 3.9 - 3.10 - 3.11 - - name: Install tox - run: | - python -m pip install --upgrade pip - pip install tox - - name: Run tox - run: tox From 068b29c5160e48a20249fb7b5771269cef1880e1 Mon Sep 17 00:00:00 2001 From: Freddy Garcia Date: Fri, 1 Mar 2024 14:57:46 -0500 Subject: [PATCH 75/75] build: revert change in version file --- oidc_provider/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oidc_provider/version.py b/oidc_provider/version.py index 4ca39e7c..cd7ca498 100644 --- a/oidc_provider/version.py +++ b/oidc_provider/version.py @@ -1 +1 @@ -__version__ = '0.8.2' +__version__ = '1.0.1'