Skip to content

Commit

Permalink
Merge pull request #674 from maykinmedia/feature/1460-sso-refactor
Browse files Browse the repository at this point in the history
[#1460] refactor Single Sign On
  • Loading branch information
alextreme authored Jun 26, 2023
2 parents 79e2425 + 4a9ab8a commit 525e5f9
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ <h1 class="h1">{% trans 'Welkom' %}</h1>

{% get_solo 'mozilla_django_oidc_db.OpenIDConnectConfig' as oidc_config %}
{% get_solo 'configurations.SiteConfiguration' as site_config %}
{% if oidc_config.enabled %}
{% if oidc_config.enabled and site_config.openid_enabled_for_regular_users %}
{% render_card tinted=True direction='horizontal' %}
{% if site_config.openid_connect_logo %}
<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 }}">
Expand Down
1 change: 1 addition & 0 deletions src/open_inwoner/configurations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class SiteConfigurarionAdmin(OrderedInlineModelAdminMixin, SingletonModelAdmin):
"fields": (
"openid_connect_logo",
"openid_connect_login_text",
"openid_display",
)
},
),
Expand Down
5 changes: 5 additions & 0 deletions src/open_inwoner/configurations/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@
class ColorTypeChoices(DjangoChoices):
light = ChoiceItem("#FFFFFF", _("light"))
dark = ChoiceItem("#4B4B4B", _("dark"))


class OpenIDDisplayChoices(DjangoChoices):
admin = ChoiceItem("admin", _("Admin"))
regular = ChoiceItem("regular", _("Regular user"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 3.2.15 on 2023-06-20 07:32

from django.db import migrations, models
import open_inwoner.configurations.validators


class Migration(migrations.Migration):

dependencies = [
("configurations", "0043_siteconfiguration_allow_messages_file_sharing"),
]

operations = [
migrations.AddField(
model_name="siteconfiguration",
name="openid_display",
field=models.CharField(
choices=[("admin", "Admin"), ("regular", "Regular user")],
default="admin",
help_text="Only selected groups will see the option to login via OpenId.",
max_length=24,
validators=[
open_inwoner.configurations.validators.validate_oidc_config
],
verbose_name="Show option to login via OpenId",
),
),
]
20 changes: 18 additions & 2 deletions src/open_inwoner/configurations/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Optional

from django.contrib.flatpages.models import FlatPage
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _

Expand All @@ -14,7 +13,8 @@

from ..utils.colors import hex_to_hsl
from ..utils.validators import FilerExactImageSizeValidator
from .choices import ColorTypeChoices
from .choices import ColorTypeChoices, OpenIDDisplayChoices
from .validators import validate_oidc_config


class SiteConfiguration(SingletonModel):
Expand Down Expand Up @@ -396,6 +396,14 @@ class SiteConfiguration(SingletonModel):
"The text that should display when OpenId connect is set as a login method"
),
)
openid_display = models.CharField(
verbose_name=_("Show option to login via OpenId"),
max_length=24,
choices=OpenIDDisplayChoices,
default=OpenIDDisplayChoices.admin,
validators=[validate_oidc_config],
help_text=_("Only selected groups will see the option to login via OpenId."),
)
redirect_to = models.CharField(
max_length=255,
null=True,
Expand Down Expand Up @@ -447,6 +455,14 @@ def matomo_enabled(self):
def siteimprove_enabled(self):
return bool(self.siteimprove_id)

@property
def openid_enabled_for_admin(self):
return self.openid_display == OpenIDDisplayChoices.admin

@property
def openid_enabled_for_regular_users(self):
return self.openid_display == OpenIDDisplayChoices.regular

def get_help_text(self, request) -> Optional[str]:
match = request.resolver_match
path = request.get_full_path()
Expand Down
101 changes: 101 additions & 0 deletions src/open_inwoner/configurations/tests/test_oidc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from django_webtest import WebTest
from mozilla_django_oidc_db.models import OpenIDConnectConfig

from open_inwoner.accounts.tests.factories import UserFactory
from open_inwoner.configurations.models import SiteConfiguration
from open_inwoner.utils.test import ClearCachesMixin

from ..choices import OpenIDDisplayChoices


class OIDCConfigTest(ClearCachesMixin, WebTest):
csrf_checks = False

@classmethod
def setUpTestData(cls):
super().setUpTestData()

openid_config = OpenIDConnectConfig.get_solo()
openid_config.enabled = True
openid_config.save()

def test_admin_only_enabled(self):
"""Assert that the OIDC login option is only displayed for login via admin"""

config = SiteConfiguration.get_solo()
config.openid_display = OpenIDDisplayChoices.admin
config.save()

# regular site
response = self.app.get(reverse("login"))

self.assertNotContains(response, _("Login with Azure AD"))

# admin login
response = self.app.get(reverse("admin:login"))

oidc_login_option = response.pyquery.find(".admin-login-option")

self.assertEqual(oidc_login_option.text(), _("Login with organization account"))

def test_admin_only_disabled(self):
"""Assert that the OIDC login option is only displayed for regular users"""

config = SiteConfiguration.get_solo()
config.openid_display = OpenIDDisplayChoices.regular
config.save()

# regular site
response = self.app.get(reverse("login"))

link = response.pyquery.find("[title='Login with Azure AD']")
link_text = link.find(".link__text").text()

self.assertEqual(link_text, _("Login with Azure AD"))

# admin login
response = self.client.get(reverse("admin:login"))

self.assertNotContains(response, _("Login with organization account"))

def test_oidc_config_validation(self):
"""
Assert that error is displayed on attempt to select OIDC login for regular users
if and only if `make_users_staff` is enabled in `OpenIDConnectConfig`.
"""

self.user = UserFactory(is_superuser=True, is_staff=True)
self.client.force_login(self.user)

# case 1: `make_users_staff` is True
openid_config = OpenIDConnectConfig.get_solo()
openid_config.enabled = True
openid_config.make_users_staff = True
openid_config.save()

url = reverse("admin:configurations_siteconfiguration_change")
response = self.app.post(
url, user=self.user, params={"openid_display": OpenIDDisplayChoices.regular}
)

field = response.pyquery(".field-openid_display")
error_text = field.find(".errorlist").text()
expected_error_text = _(
"You cannot select this option if 'Make users staff' "
"is selected in the OpenID Connect configuration."
)

self.assertEqual(expected_error_text, error_text)

# case 2: `make_users_staff` is False
openid_config.make_users_staff = False
openid_config.save()

response = self.client.post(
url, user=self.user, data={"openid_display": "regular"}
)

self.assertNotContains(response, expected_error_text)
20 changes: 20 additions & 0 deletions src/open_inwoner/configurations/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from mozilla_django_oidc_db.mixins import OpenIDConnectConfig

from .choices import OpenIDDisplayChoices


def validate_oidc_config(value):
"""Prevent display of OIDC login to regular users if `make_users_staff` is true"""

open_id_config = OpenIDConnectConfig.get_solo()

if open_id_config.make_users_staff and value == OpenIDDisplayChoices.regular:
raise ValidationError(
_(
"You cannot select this option if 'Make users staff' is selected "
"in the OpenID Connect configuration."
)
)
16 changes: 16 additions & 0 deletions src/open_inwoner/templates/admin/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends "admin/login.html" %}
{% load solo_tags i18n %}


{% block content %}
{{ block.super }}

{% get_solo 'mozilla_django_oidc_db.OpenIDConnectConfig' as oidc_config %}
{% get_solo 'configurations.SiteConfiguration' as site_config %}
{% if oidc_config.enabled and site_config.openid_enabled_for_admin %}
<div class="admin-login-option">
<a href="{% url 'oidc_authentication_init' %}">{% trans "Login with organization account" %}</a>
</div>
{% endif %}

{% endblock %}

0 comments on commit 525e5f9

Please sign in to comment.