Browse Source

Add SSO admin settings (#1271)

* Add SSO to admin interface

* Display SSO warning on user settings

* Disable warning about SSO on social auth admin list

* Format code with black
Rafał Pitoń 5 years ago
parent
commit
a13ad43415

+ 15 - 1
misago/conf/admin/__init__.py

@@ -6,6 +6,7 @@ from .views import (
     ChangeAnalyticsSettingsView,
     ChangeCaptchaSettingsView,
     ChangeGeneralSettingsView,
+    ChangeSSOSettingsView,
     ChangeThreadsSettingsView,
     ChangeUsersSettingsView,
 )
@@ -30,6 +31,9 @@ class MisagoAdminExtension:
             r"^general/", "general", "settings", ChangeGeneralSettingsView.as_view()
         )
         urlpatterns.single_pattern(
+            r"^sso/", "sso", "settings", ChangeSSOSettingsView.as_view()
+        )
+        urlpatterns.single_pattern(
             r"^threads/", "threads", "settings", ChangeThreadsSettingsView.as_view()
         )
         urlpatterns.single_pattern(
@@ -69,11 +73,21 @@ class MisagoAdminExtension:
             after="users:index",
         )
         site.add_node(
+            name=_("Single Sign-On"),
+            description=_(
+                "SSO enables you to delegate user login and registration from Misago to"
+                "the third party site."
+            ),
+            parent="settings",
+            namespace="sso",
+            after="captcha:index",
+        )
+        site.add_node(
             name=_("Analytics"),
             description=_("Enable Google Analytics or setup Google Site Verification."),
             parent="settings",
             namespace="analytics",
-            after="captcha:index",
+            after="sso:index",
         )
         site.add_node(
             name=_("Threads"),

+ 1 - 0
misago/conf/admin/forms/__init__.py

@@ -2,5 +2,6 @@ from .analytics import ChangeAnalyticsSettingsForm
 from .base import ChangeSettingsForm
 from .captcha import ChangeCaptchaSettingsForm
 from .general import ChangeGeneralSettingsForm
+from .sso import ChangeSSOSettingsForm
 from .threads import ChangeThreadsSettingsForm
 from .users import ChangeUsersSettingsForm

+ 53 - 0
misago/conf/admin/forms/sso.py

@@ -0,0 +1,53 @@
+from django import forms
+from django.utils.crypto import get_random_string
+from django.utils.translation import gettext_lazy as _
+
+from ....admin.forms import YesNoSwitch
+from .base import ChangeSettingsForm
+
+
+class ChangeSSOSettingsForm(ChangeSettingsForm):
+    settings = ["enable_sso", "sso_public_key", "sso_private_key", "sso_url"]
+
+    enable_sso = YesNoSwitch(
+        label=_("Enable Single Sign-On"),
+        help_text=_(
+            "Enabling SSO will make login option redirect users to the server URL "
+            "configured below. It will also disable option to register on forum, "
+            "change username, email or passward, as those features will be delegated "
+            "to the 3rd party site."
+        ),
+    )
+    sso_public_key = forms.CharField(
+        label=_("Public key"),
+        help_text=_(
+            "Leave this field empty for Misago to generate this key on form submission."
+        ),
+        max_length=64,
+        required=False,
+    )
+    sso_private_key = forms.CharField(
+        label=_("Private key"),
+        help_text=_(
+            "Leave this field empty for Misago to generate this key on form submission."
+        ),
+        max_length=64,
+        required=False,
+    )
+    sso_url = forms.URLField(label=_("Server URL"), max_length=255, required=False)
+
+    def clean(self):
+        cleaned_data = super().clean()
+
+        if cleaned_data.get("enable_sso"):
+            if not cleaned_data.get("sso_public_key"):
+                cleaned_data["sso_public_key"] = get_random_string(64)
+            if not cleaned_data.get("sso_private_key"):
+                cleaned_data["sso_private_key"] = get_random_string(64)
+
+            if not cleaned_data.get("sso_url"):
+                self.add_error(
+                    "sso_url", _("You need to enter server URL to enable SSO.")
+                )
+
+        return cleaned_data

+ 5 - 0
misago/conf/admin/tests/test_change_settings_views.py

@@ -90,6 +90,11 @@ def test_general_settings_form_renders(admin_client):
     assert response.status_code == 200
 
 
+def test_sso_settings_form_renders(admin_client):
+    response = admin_client.get(reverse("misago:admin:settings:sso:index"))
+    assert response.status_code == 200
+
+
 def test_threads_settings_form_renders(admin_client):
     response = admin_client.get(reverse("misago:admin:settings:threads:index"))
     assert response.status_code == 200

+ 47 - 0
misago/conf/admin/tests/test_enabling_sso.py

@@ -0,0 +1,47 @@
+from django.urls import reverse
+
+from ...models import Setting
+
+admin_link = reverse("misago:admin:settings:sso:index")
+
+
+def test_sso_form_generates_public_key_when_enabling_sso(admin_client):
+    response = admin_client.post(
+        admin_link, {"enable_sso": "1", "sso_url": "https://test.com"}
+    )
+    setting = Setting.objects.get(setting="sso_public_key")
+    assert setting.value
+
+
+def test_sso_form_generates_private_key_when_enabling_sso(admin_client):
+    response = admin_client.post(
+        admin_link, {"enable_sso": "1", "sso_url": "https://test.com"}
+    )
+    setting = Setting.objects.get(setting="sso_private_key")
+    assert setting.value
+
+
+def test_sso_public_key_can_be_set_explicitly_when_enabling_sso(admin_client):
+    response = admin_client.post(
+        admin_link,
+        {"enable_sso": "1", "sso_public_key": "custom", "sso_url": "https://test.com"},
+    )
+    setting = Setting.objects.get(setting="sso_public_key")
+    assert setting.value == "custom"
+
+
+def test_sso_private_key_can_be_set_explicitly_when_enabling_sso(admin_client):
+    response = admin_client.post(
+        admin_link,
+        {"enable_sso": "1", "sso_private_key": "custom", "sso_url": "https://test.com"},
+    )
+    setting = Setting.objects.get(setting="sso_private_key")
+    assert setting.value == "custom"
+
+
+def test_form_requires_sso_url_when_enabling_sso(admin_client):
+    response = admin_client.post(
+        admin_link, {"enable_sso": "1", "sso_private_key": "custom", "sso_url": ""}
+    )
+    setting = Setting.objects.get(setting="enable_sso")
+    assert not setting.value

+ 6 - 0
misago/conf/admin/views.py

@@ -9,6 +9,7 @@ from .forms import (
     ChangeAnalyticsSettingsForm,
     ChangeCaptchaSettingsForm,
     ChangeGeneralSettingsForm,
+    ChangeSSOSettingsForm,
     ChangeThreadsSettingsForm,
     ChangeUsersSettingsForm,
 )
@@ -75,6 +76,11 @@ class ChangeGeneralSettingsView(ChangeSettingsView):
     template_name = "misago/admin/conf/general_settings.html"
 
 
+class ChangeSSOSettingsView(ChangeSettingsView):
+    form_class = ChangeSSOSettingsForm
+    template_name = "misago/admin/conf/sso_settings.html"
+
+
 class ChangeThreadsSettingsView(ChangeSettingsView):
     form_class = ChangeThreadsSettingsForm
     template_name = "misago/admin/conf/threads_settings.html"

+ 35 - 0
misago/conf/migrations/0005_add_sso_settings.py

@@ -0,0 +1,35 @@
+# Generated by Django 2.2.1 on 2019-05-19 00:16
+
+from django.conf import settings
+from django.db import migrations
+
+from ..hydrators import dehydrate_value
+
+settings = [
+    {
+        "setting": "enable_sso",
+        "python_type": "bool",
+        "dry_value": False,
+        "is_public": True,
+    },
+    {"setting": "sso_public_key", "is_public": False},
+    {"setting": "sso_private_key", "is_public": False},
+    {"setting": "sso_url", "is_public": False},
+]
+
+
+def create_settings(apps, _):
+    Setting = apps.get_model("misago_conf", "Setting")
+    for setting in settings:
+        data = setting.copy()
+        if "python_type" in data and "dry_value" in data:
+            data["dry_value"] = dehydrate_value(data["python_type"], data["dry_value"])
+
+        Setting.objects.create(**setting)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("misago_conf", "0004_create_settings")]
+
+    operations = [migrations.RunPython(create_settings)]

+ 10 - 0
misago/templates/misago/admin/conf/sso_settings.html

@@ -0,0 +1,10 @@
+{% extends "misago/admin/conf/form.html" %}
+{% load i18n misago_admin_form %}
+
+
+{% block form-body %}
+{% form_row form.enable_sso %}
+{% form_row form.sso_public_key %}
+{% form_row form.sso_private_key %}
+{% form_row form.sso_url %}
+{% endblock form-body %}

+ 6 - 0
misago/templates/misago/admin/conf/users_settings.html

@@ -4,6 +4,12 @@
 
 {% block form-body %}
 <div class="form-fieldset">
+  {% if settings.enable_sso %}  
+    <div class="alert alert-warning" role="alert">
+      <strong>{% trans "Note" %}:</strong> {% trans "Single Sign-On is enabled. Site's registration, password and deletion features have been disabled and delegated to the third-party site." %}
+    </div>
+  {% endif %}
+
   <fieldset>
     <legend>{% trans "New accounts" %}</legend>
 

+ 6 - 0
misago/templates/misago/admin/socialauth/list.html

@@ -92,6 +92,12 @@
 
 
 {% block view %}
+{% if settings.enable_sso %}  
+  <div class="alert alert-warning" role="alert">
+    <strong>{% trans "Note" %}:</strong> {% trans "Single Sign-On is enabled. Social authentication has been disabled and delegated to the third-party site." %}
+  </div>
+{% endif %}
+
 {{ block.super }}
 
 <div class="card card-admin-table mt-3">