Skip to content

Commit 525e5f9

Browse files
authored
Merge pull request #674 from maykinmedia/feature/1460-sso-refactor
[#1460] refactor Single Sign On
2 parents 79e2425 + 4a9ab8a commit 525e5f9

File tree

8 files changed

+190
-3
lines changed

8 files changed

+190
-3
lines changed

src/open_inwoner/accounts/templates/registration/login.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ <h1 class="h1">{% trans 'Welkom' %}</h1>
2929

3030
{% get_solo 'mozilla_django_oidc_db.OpenIDConnectConfig' as oidc_config %}
3131
{% get_solo 'configurations.SiteConfiguration' as site_config %}
32-
{% if oidc_config.enabled %}
32+
{% if oidc_config.enabled and site_config.openid_enabled_for_regular_users %}
3333
{% render_card tinted=True direction='horizontal' %}
3434
{% if site_config.openid_connect_logo %}
3535
<a href="{% url 'oidc_authentication_init' %}" class="link digid-logo" aria-describedby="{{ site_config.openid_connect_logo.default_alt_text }}" title="{{ site_config.openid_connect_logo.default_alt_text }}">

src/open_inwoner/configurations/admin.py

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ class SiteConfigurarionAdmin(OrderedInlineModelAdminMixin, SingletonModelAdmin):
163163
"fields": (
164164
"openid_connect_logo",
165165
"openid_connect_login_text",
166+
"openid_display",
166167
)
167168
},
168169
),

src/open_inwoner/configurations/choices.py

+5
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@
66
class ColorTypeChoices(DjangoChoices):
77
light = ChoiceItem("#FFFFFF", _("light"))
88
dark = ChoiceItem("#4B4B4B", _("dark"))
9+
10+
11+
class OpenIDDisplayChoices(DjangoChoices):
12+
admin = ChoiceItem("admin", _("Admin"))
13+
regular = ChoiceItem("regular", _("Regular user"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 3.2.15 on 2023-06-20 07:32
2+
3+
from django.db import migrations, models
4+
import open_inwoner.configurations.validators
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("configurations", "0043_siteconfiguration_allow_messages_file_sharing"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="siteconfiguration",
16+
name="openid_display",
17+
field=models.CharField(
18+
choices=[("admin", "Admin"), ("regular", "Regular user")],
19+
default="admin",
20+
help_text="Only selected groups will see the option to login via OpenId.",
21+
max_length=24,
22+
validators=[
23+
open_inwoner.configurations.validators.validate_oidc_config
24+
],
25+
verbose_name="Show option to login via OpenId",
26+
),
27+
),
28+
]

src/open_inwoner/configurations/models.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from typing import Optional
22

33
from django.contrib.flatpages.models import FlatPage
4-
from django.core.exceptions import ValidationError
54
from django.db import models
65
from django.utils.translation import ugettext_lazy as _
76

@@ -14,7 +13,8 @@
1413

1514
from ..utils.colors import hex_to_hsl
1615
from ..utils.validators import FilerExactImageSizeValidator
17-
from .choices import ColorTypeChoices
16+
from .choices import ColorTypeChoices, OpenIDDisplayChoices
17+
from .validators import validate_oidc_config
1818

1919

2020
class SiteConfiguration(SingletonModel):
@@ -396,6 +396,14 @@ class SiteConfiguration(SingletonModel):
396396
"The text that should display when OpenId connect is set as a login method"
397397
),
398398
)
399+
openid_display = models.CharField(
400+
verbose_name=_("Show option to login via OpenId"),
401+
max_length=24,
402+
choices=OpenIDDisplayChoices,
403+
default=OpenIDDisplayChoices.admin,
404+
validators=[validate_oidc_config],
405+
help_text=_("Only selected groups will see the option to login via OpenId."),
406+
)
399407
redirect_to = models.CharField(
400408
max_length=255,
401409
null=True,
@@ -447,6 +455,14 @@ def matomo_enabled(self):
447455
def siteimprove_enabled(self):
448456
return bool(self.siteimprove_id)
449457

458+
@property
459+
def openid_enabled_for_admin(self):
460+
return self.openid_display == OpenIDDisplayChoices.admin
461+
462+
@property
463+
def openid_enabled_for_regular_users(self):
464+
return self.openid_display == OpenIDDisplayChoices.regular
465+
450466
def get_help_text(self, request) -> Optional[str]:
451467
match = request.resolver_match
452468
path = request.get_full_path()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from django.urls import reverse
2+
from django.utils.translation import gettext_lazy as _
3+
4+
from django_webtest import WebTest
5+
from mozilla_django_oidc_db.models import OpenIDConnectConfig
6+
7+
from open_inwoner.accounts.tests.factories import UserFactory
8+
from open_inwoner.configurations.models import SiteConfiguration
9+
from open_inwoner.utils.test import ClearCachesMixin
10+
11+
from ..choices import OpenIDDisplayChoices
12+
13+
14+
class OIDCConfigTest(ClearCachesMixin, WebTest):
15+
csrf_checks = False
16+
17+
@classmethod
18+
def setUpTestData(cls):
19+
super().setUpTestData()
20+
21+
openid_config = OpenIDConnectConfig.get_solo()
22+
openid_config.enabled = True
23+
openid_config.save()
24+
25+
def test_admin_only_enabled(self):
26+
"""Assert that the OIDC login option is only displayed for login via admin"""
27+
28+
config = SiteConfiguration.get_solo()
29+
config.openid_display = OpenIDDisplayChoices.admin
30+
config.save()
31+
32+
# regular site
33+
response = self.app.get(reverse("login"))
34+
35+
self.assertNotContains(response, _("Login with Azure AD"))
36+
37+
# admin login
38+
response = self.app.get(reverse("admin:login"))
39+
40+
oidc_login_option = response.pyquery.find(".admin-login-option")
41+
42+
self.assertEqual(oidc_login_option.text(), _("Login with organization account"))
43+
44+
def test_admin_only_disabled(self):
45+
"""Assert that the OIDC login option is only displayed for regular users"""
46+
47+
config = SiteConfiguration.get_solo()
48+
config.openid_display = OpenIDDisplayChoices.regular
49+
config.save()
50+
51+
# regular site
52+
response = self.app.get(reverse("login"))
53+
54+
link = response.pyquery.find("[title='Login with Azure AD']")
55+
link_text = link.find(".link__text").text()
56+
57+
self.assertEqual(link_text, _("Login with Azure AD"))
58+
59+
# admin login
60+
response = self.client.get(reverse("admin:login"))
61+
62+
self.assertNotContains(response, _("Login with organization account"))
63+
64+
def test_oidc_config_validation(self):
65+
"""
66+
Assert that error is displayed on attempt to select OIDC login for regular users
67+
if and only if `make_users_staff` is enabled in `OpenIDConnectConfig`.
68+
"""
69+
70+
self.user = UserFactory(is_superuser=True, is_staff=True)
71+
self.client.force_login(self.user)
72+
73+
# case 1: `make_users_staff` is True
74+
openid_config = OpenIDConnectConfig.get_solo()
75+
openid_config.enabled = True
76+
openid_config.make_users_staff = True
77+
openid_config.save()
78+
79+
url = reverse("admin:configurations_siteconfiguration_change")
80+
response = self.app.post(
81+
url, user=self.user, params={"openid_display": OpenIDDisplayChoices.regular}
82+
)
83+
84+
field = response.pyquery(".field-openid_display")
85+
error_text = field.find(".errorlist").text()
86+
expected_error_text = _(
87+
"You cannot select this option if 'Make users staff' "
88+
"is selected in the OpenID Connect configuration."
89+
)
90+
91+
self.assertEqual(expected_error_text, error_text)
92+
93+
# case 2: `make_users_staff` is False
94+
openid_config.make_users_staff = False
95+
openid_config.save()
96+
97+
response = self.client.post(
98+
url, user=self.user, data={"openid_display": "regular"}
99+
)
100+
101+
self.assertNotContains(response, expected_error_text)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from django.core.exceptions import ValidationError
2+
from django.utils.translation import gettext_lazy as _
3+
4+
from mozilla_django_oidc_db.mixins import OpenIDConnectConfig
5+
6+
from .choices import OpenIDDisplayChoices
7+
8+
9+
def validate_oidc_config(value):
10+
"""Prevent display of OIDC login to regular users if `make_users_staff` is true"""
11+
12+
open_id_config = OpenIDConnectConfig.get_solo()
13+
14+
if open_id_config.make_users_staff and value == OpenIDDisplayChoices.regular:
15+
raise ValidationError(
16+
_(
17+
"You cannot select this option if 'Make users staff' is selected "
18+
"in the OpenID Connect configuration."
19+
)
20+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{% extends "admin/login.html" %}
2+
{% load solo_tags i18n %}
3+
4+
5+
{% block content %}
6+
{{ block.super }}
7+
8+
{% get_solo 'mozilla_django_oidc_db.OpenIDConnectConfig' as oidc_config %}
9+
{% get_solo 'configurations.SiteConfiguration' as site_config %}
10+
{% if oidc_config.enabled and site_config.openid_enabled_for_admin %}
11+
<div class="admin-login-option">
12+
<a href="{% url 'oidc_authentication_init' %}">{% trans "Login with organization account" %}</a>
13+
</div>
14+
{% endif %}
15+
16+
{% endblock %}

0 commit comments

Comments
 (0)