From 002b3687a81571ad3f23001c1ab036f1969d54e4 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Thu, 26 Dec 2019 11:28:12 +0100
Subject: [PATCH] Advertise list of known nodes on /api/v1/federation/domains
 and in nodeinfo if stats sharing is enabled

---
 .../federation/api_serializers.py             |  4 ++++
 api/funkwhale_api/federation/api_urls.py      |  1 +
 api/funkwhale_api/federation/api_views.py     | 21 +++++++++++++++++++
 api/funkwhale_api/instance/nodeinfo.py        |  8 +++++++
 api/tests/federation/test_api_views.py        | 18 ++++++++++++++++
 api/tests/instance/test_nodeinfo.py           |  7 +++++++
 changes/changelog.d/known-nodes.enhancement   |  1 +
 7 files changed, 60 insertions(+)
 create mode 100644 changes/changelog.d/known-nodes.enhancement

diff --git a/api/funkwhale_api/federation/api_serializers.py b/api/funkwhale_api/federation/api_serializers.py
index 93dae4e55..2a1143ec7 100644
--- a/api/funkwhale_api/federation/api_serializers.py
+++ b/api/funkwhale_api/federation/api_serializers.py
@@ -27,6 +27,10 @@ class LibraryScanSerializer(serializers.ModelSerializer):
         ]
 
 
+class DomainSerializer(serializers.Serializer):
+    name = serializers.CharField()
+
+
 class LibrarySerializer(serializers.ModelSerializer):
     actor = federation_serializers.APIActorSerializer()
     uploads_count = serializers.SerializerMethodField()
diff --git a/api/funkwhale_api/federation/api_urls.py b/api/funkwhale_api/federation/api_urls.py
index 896fa2430..4f0471b17 100644
--- a/api/funkwhale_api/federation/api_urls.py
+++ b/api/funkwhale_api/federation/api_urls.py
@@ -7,5 +7,6 @@ router.register(r"fetches", api_views.FetchViewSet, "fetches")
 router.register(r"follows/library", api_views.LibraryFollowViewSet, "library-follows")
 router.register(r"inbox", api_views.InboxItemViewSet, "inbox")
 router.register(r"libraries", api_views.LibraryViewSet, "libraries")
+router.register(r"domains", api_views.DomainViewSet, "domains")
 
 urlpatterns = router.urls
diff --git a/api/funkwhale_api/federation/api_views.py b/api/funkwhale_api/federation/api_views.py
index 5f6f50d8f..395290be9 100644
--- a/api/funkwhale_api/federation/api_views.py
+++ b/api/funkwhale_api/federation/api_views.py
@@ -9,6 +9,8 @@ from rest_framework import permissions
 from rest_framework import response
 from rest_framework import viewsets
 
+from funkwhale_api.common import preferences
+from funkwhale_api.common.permissions import ConditionalAuthentication
 from funkwhale_api.music import models as music_models
 from funkwhale_api.users.oauth import permissions as oauth_permissions
 
@@ -197,3 +199,22 @@ class FetchViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
     queryset = models.Fetch.objects.select_related("actor")
     serializer_class = api_serializers.FetchSerializer
     permission_classes = [permissions.IsAuthenticated]
+
+
+class DomainViewSet(
+    mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet
+):
+    queryset = models.Domain.objects.order_by("name").external()
+    permission_classes = [ConditionalAuthentication]
+    serializer_class = api_serializers.DomainSerializer
+    ordering_fields = ("creation_date", "name")
+    max_page_size = 100
+
+    def get_queryset(self):
+        qs = super().get_queryset()
+        qs = qs.exclude(
+            instance_policy__is_active=True, instance_policy__block_all=True
+        )
+        if preferences.get("moderation__allow_list_enabled"):
+            qs = qs.filter(allowed=True)
+        return qs
diff --git a/api/funkwhale_api/instance/nodeinfo.py b/api/funkwhale_api/instance/nodeinfo.py
index 712578c3c..9da128de8 100644
--- a/api/funkwhale_api/instance/nodeinfo.py
+++ b/api/funkwhale_api/instance/nodeinfo.py
@@ -1,5 +1,7 @@
 import memoize.djangocache
 
+from django.urls import reverse
+
 import funkwhale_api
 from funkwhale_api.common import preferences
 from funkwhale_api.federation import actors, models as federation_models
@@ -18,6 +20,7 @@ def get():
     share_stats = all_preferences.get("instance__nodeinfo_stats_enabled")
     allow_list_enabled = all_preferences.get("moderation__allow_list_enabled")
     allow_list_public = all_preferences.get("moderation__allow_list_public")
+    auth_required = all_preferences.get("common__api_authentication_required")
     banner = all_preferences.get("instance__banner")
     unauthenticated_report_types = all_preferences.get(
         "moderation__unauthenticated_report_types"
@@ -67,6 +70,7 @@ def get():
                 "instance__funkwhale_support_message_enabled"
             ),
             "instanceSupportMessage": all_preferences.get("instance__support_message"),
+            "knownNodesListUrl": None,
         },
     }
 
@@ -87,4 +91,8 @@ def get():
             "favorites": {"tracks": {"total": statistics["track_favorites"]}},
             "listenings": {"total": statistics["listenings"]},
         }
+        if not auth_required:
+            data["metadata"]["knownNodesListUrl"] = federation_utils.full_url(
+                reverse("api:v1:federation:domains-list")
+            )
     return data
diff --git a/api/tests/federation/test_api_views.py b/api/tests/federation/test_api_views.py
index c34c5e99a..1795a65e4 100644
--- a/api/tests/federation/test_api_views.py
+++ b/api/tests/federation/test_api_views.py
@@ -179,3 +179,21 @@ def test_can_detail_fetch(logged_in_api_client, factories):
 
     assert response.status_code == 200
     assert response.data == expected
+
+
+def test_user_can_list_domains(factories, api_client, preferences):
+    preferences["common__api_authentication_required"] = False
+    allowed = factories["federation.Domain"]()
+    factories["moderation.InstancePolicy"](
+        actor=None, for_domain=True, block_all=True
+    ).target_domain
+    url = reverse("api:v1:federation:domains-list")
+    response = api_client.get(url)
+
+    expected = {
+        "count": 1,
+        "next": None,
+        "previous": None,
+        "results": [api_serializers.DomainSerializer(allowed).data],
+    }
+    assert response.data == expected
diff --git a/api/tests/instance/test_nodeinfo.py b/api/tests/instance/test_nodeinfo.py
index 2e9075d26..bdca8f453 100644
--- a/api/tests/instance/test_nodeinfo.py
+++ b/api/tests/instance/test_nodeinfo.py
@@ -1,5 +1,7 @@
 import pytest
 
+from django.urls import reverse
+
 import funkwhale_api
 from funkwhale_api.instance import nodeinfo
 from funkwhale_api.federation import actors
@@ -10,6 +12,7 @@ from funkwhale_api.music import utils as music_utils
 def test_nodeinfo_dump(preferences, mocker, avatar):
     preferences["instance__banner"] = avatar
     preferences["instance__nodeinfo_stats_enabled"] = True
+    preferences["common__api_authentication_required"] = False
     preferences["moderation__unauthenticated_report_types"] = [
         "takedown_request",
         "other",
@@ -91,6 +94,9 @@ def test_nodeinfo_dump(preferences, mocker, avatar):
                 "instance__funkwhale_support_message_enabled"
             ],
             "instanceSupportMessage": preferences["instance__support_message"],
+            "knownNodesListUrl": federation_utils.full_url(
+                reverse("api:v1:federation:domains-list")
+            ),
         },
     }
     assert nodeinfo.get() == expected
@@ -159,6 +165,7 @@ def test_nodeinfo_dump_stats_disabled(preferences, mocker):
                 "instance__funkwhale_support_message_enabled"
             ],
             "instanceSupportMessage": preferences["instance__support_message"],
+            "knownNodesListUrl": None,
         },
     }
     assert nodeinfo.get() == expected
diff --git a/changes/changelog.d/known-nodes.enhancement b/changes/changelog.d/known-nodes.enhancement
new file mode 100644
index 000000000..00497de4a
--- /dev/null
+++ b/changes/changelog.d/known-nodes.enhancement
@@ -0,0 +1 @@
+Advertise list of known nodes on /api/v1/federation/domains and in nodeinfo if stats sharing is enabled
-- 
GitLab