From 2b2b64f0a7251abd2e7b5655dbb9eb5ba48bfa27 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Mon, 17 Jun 2019 08:48:05 +0200
Subject: [PATCH] Model / settings for allow-listing

---
 api/funkwhale_api/federation/admin.py         |  3 ++-
 .../migrations/0019_auto_20190611_0851.py     | 19 ++++++++++++++
 api/funkwhale_api/federation/models.py        |  3 +++
 api/funkwhale_api/manage/serializers.py       |  6 +++++
 api/funkwhale_api/manage/views.py             |  8 ++++++
 .../dynamic_preferences_registry.py           | 26 +++++++++++++++++++
 api/tests/manage/test_serializers.py          |  1 +
 api/tests/manage/test_views.py                | 25 ++++++++++++++++++
 8 files changed, 90 insertions(+), 1 deletion(-)
 create mode 100644 api/funkwhale_api/federation/migrations/0019_auto_20190611_0851.py
 create mode 100644 api/funkwhale_api/moderation/dynamic_preferences_registry.py

diff --git a/api/funkwhale_api/federation/admin.py b/api/funkwhale_api/federation/admin.py
index 263af80c..40f7b4f6 100644
--- a/api/funkwhale_api/federation/admin.py
+++ b/api/funkwhale_api/federation/admin.py
@@ -26,7 +26,8 @@ redeliver_activities.short_description = "Redeliver"
 
 @admin.register(models.Domain)
 class DomainAdmin(admin.ModelAdmin):
-    list_display = ["name", "creation_date"]
+    list_display = ["name", "allowed", "creation_date"]
+    list_filter = ["allowed"]
     search_fields = ["name"]
 
 
diff --git a/api/funkwhale_api/federation/migrations/0019_auto_20190611_0851.py b/api/funkwhale_api/federation/migrations/0019_auto_20190611_0851.py
new file mode 100644
index 00000000..4ae5e31f
--- /dev/null
+++ b/api/funkwhale_api/federation/migrations/0019_auto_20190611_0851.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.2.2 on 2019-06-11 08:51
+
+import django.contrib.postgres.fields.jsonb
+import django.core.serializers.json
+from django.db import migrations, models
+import funkwhale_api.federation.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("federation", "0018_fetch")]
+
+    operations = [
+        migrations.AddField(
+            model_name="domain",
+            name="allowed",
+            field=models.BooleanField(default=None, null=True),
+        )
+    ]
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index d530c62d..df81cd50 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -118,6 +118,9 @@ class Domain(models.Model):
         null=True,
         blank=True,
     )
+    # are interactions with this domain allowed (only applies when allow-listing is on)
+    allowed = models.BooleanField(default=None, null=True)
+
     objects = DomainQuerySet.as_manager()
 
     def __str__(self):
diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py
index add9364e..25f8c01d 100644
--- a/api/funkwhale_api/manage/serializers.py
+++ b/api/funkwhale_api/manage/serializers.py
@@ -130,6 +130,7 @@ class ManageDomainSerializer(serializers.ModelSerializer):
             "nodeinfo",
             "nodeinfo_fetch_date",
             "instance_policy",
+            "allowed",
         ]
         read_only_fields = [
             "creation_date",
@@ -145,6 +146,11 @@ class ManageDomainSerializer(serializers.ModelSerializer):
         return getattr(o, "outbox_activities_count", 0)
 
 
+class ManageDomainUpdateSerializer(ManageDomainSerializer):
+    class Meta(ManageDomainSerializer.Meta):
+        read_only_fields = ["name"] + ManageDomainSerializer.Meta.read_only_fields
+
+
 class ManageDomainActionSerializer(common_serializers.ActionSerializer):
     actions = [common_serializers.Action("purge", allow_all=False)]
     filterset_class = filters.ManageDomainFilterSet
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index 83981116..fb9ceabc 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -339,6 +339,7 @@ class ManageDomainViewSet(
     mixins.CreateModelMixin,
     mixins.ListModelMixin,
     mixins.RetrieveModelMixin,
+    mixins.UpdateModelMixin,
     viewsets.GenericViewSet,
 ):
     lookup_value_regex = r"[a-zA-Z0-9\-\.]+"
@@ -361,6 +362,13 @@ class ManageDomainViewSet(
         "instance_policy",
     ]
 
+    def get_serializer_class(self):
+        if self.action in ["update", "partial_update"]:
+            # A dedicated serializer for update
+            # to ensure domain name can't be changed
+            return serializers.ManageDomainUpdateSerializer
+        return super().get_serializer_class()
+
     def perform_create(self, serializer):
         domain = serializer.save()
         federation_tasks.update_domain_nodeinfo(domain_name=domain.name)
diff --git a/api/funkwhale_api/moderation/dynamic_preferences_registry.py b/api/funkwhale_api/moderation/dynamic_preferences_registry.py
new file mode 100644
index 00000000..ff4201b5
--- /dev/null
+++ b/api/funkwhale_api/moderation/dynamic_preferences_registry.py
@@ -0,0 +1,26 @@
+from dynamic_preferences import types
+from dynamic_preferences.registries import global_preferences_registry
+
+moderation = types.Section("Moderation")
+
+
+@global_preferences_registry.register
+class AllowListEnabled(types.BooleanPreference):
+    section = moderation
+    name = "allow_list_enabled"
+    verbose_name = "Enable allow-listing"
+    help_text = "If enabled, only interactions with explicitely allowed domains will be authorized."
+    default = False
+
+
+@global_preferences_registry.register
+class AllowListPublic(types.BooleanPreference):
+    section = moderation
+    name = "allow_list_public"
+    verbose_name = "Publish your allowed-domains list"
+    help_text = (
+        "If enabled, everyone will be able to retrieve the list of domains you allowed. ",
+        "This is useful on open setups, to help people decide if they want to join your pod, or to "
+        "make your moderation policy public.",
+    )
+    default = False
diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py
index 65c75c2c..cfbb1751 100644
--- a/api/tests/manage/test_serializers.py
+++ b/api/tests/manage/test_serializers.py
@@ -51,6 +51,7 @@ def test_manage_domain_serializer(factories, now):
         "nodeinfo": {},
         "nodeinfo_fetch_date": None,
         "instance_policy": None,
+        "allowed": None,
     }
     s = serializers.ManageDomainSerializer(domain)
 
diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py
index e3d136a0..72394052 100644
--- a/api/tests/manage/test_views.py
+++ b/api/tests/manage/test_views.py
@@ -73,6 +73,31 @@ def test_domain_create(superuser_api_client, mocker):
     update_domain_nodeinfo.assert_called_once_with(domain_name="test.federation")
 
 
+def test_domain_update_allowed(superuser_api_client, factories):
+    domain = factories["federation.Domain"]()
+    url = reverse("api:v1:manage:federation:domains-detail", kwargs={"pk": domain.name})
+    response = superuser_api_client.put(url, {"allowed": True})
+
+    assert response.status_code == 200
+    domain.refresh_from_db()
+    assert domain.allowed is True
+
+
+def test_domain_update_cannot_change_name(superuser_api_client, factories):
+    domain = factories["federation.Domain"]()
+    old_name = domain.name
+    url = reverse("api:v1:manage:federation:domains-detail", kwargs={"pk": old_name})
+    response = superuser_api_client.put(url, {"name": "something.else"})
+
+    domain.refresh_from_db()
+
+    assert response.status_code == 200
+    assert domain.name == old_name
+    # changing the pk of a model and saving results in a new DB entry in django,
+    # so we check that no other entry was created
+    assert domain.__class__.objects.count() == 1
+
+
 def test_domain_nodeinfo(factories, superuser_api_client, mocker):
     domain = factories["federation.Domain"]()
     url = reverse(
-- 
GitLab