diff --git a/api/funkwhale_api/common/validators.py b/api/funkwhale_api/common/validators.py index b5f26cac5..78a4b4c7c 100644 --- a/api/funkwhale_api/common/validators.py +++ b/api/funkwhale_api/common/validators.py @@ -1,6 +1,7 @@ import mimetypes from os.path import splitext +from django.core import validators from django.core.exceptions import ValidationError from django.core.files.images import get_image_dimensions from django.template.defaultfilters import filesizeformat @@ -150,3 +151,17 @@ class FileValidator(object): } raise ValidationError(message) + + +class DomainValidator(validators.URLValidator): + message = "Enter a valid domain name." + + def __call__(self, value): + """ + This is a bit hackish but since we don't have any built-in domain validator, + we use the url one, and prepend http:// in front of it. + + If it fails, we know the domain is not valid. + """ + super().__call__("http://{}".format(value)) + return value diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index 2fdeaaa76..59360aea1 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -13,6 +13,7 @@ from django.urls import reverse from funkwhale_api.common import session from funkwhale_api.common import utils as common_utils +from funkwhale_api.common import validators as common_validators from funkwhale_api.music import utils as music_utils from . import utils as federation_utils @@ -83,7 +84,11 @@ class DomainQuerySet(models.QuerySet): class Domain(models.Model): - name = models.CharField(primary_key=True, max_length=255) + name = models.CharField( + primary_key=True, + max_length=255, + validators=[common_validators.DomainValidator()], + ) creation_date = models.DateTimeField(default=timezone.now) nodeinfo_fetch_date = models.DateTimeField(default=None, null=True, blank=True) nodeinfo = JSONField(default=empty_dict, max_length=50000, blank=True) diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py index 0697c6c14..763b37497 100644 --- a/api/funkwhale_api/manage/views.py +++ b/api/funkwhale_api/manage/views.py @@ -98,7 +98,10 @@ class ManageInvitationViewSet( class ManageDomainViewSet( - mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet + mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, ): lookup_value_regex = r"[a-zA-Z0-9\-\.]+" queryset = ( diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py index 803820b48..74ba96ba8 100644 --- a/api/tests/manage/test_serializers.py +++ b/api/tests/manage/test_serializers.py @@ -1,3 +1,5 @@ +import pytest + from funkwhale_api.manage import serializers @@ -53,6 +55,13 @@ def test_manage_domain_serializer(factories, now): assert s.data == expected +def test_manage_domain_serializer_validates_hostname(db): + s = serializers.ManageDomainSerializer(data={"name": "hello world"}) + + with pytest.raises(serializers.serializers.ValidationError): + s.is_valid(raise_exception=True) + + def test_manage_actor_serializer(factories, now): actor = factories["federation.Actor"]() setattr(actor, "uploads_count", 66) diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py index 72e945bca..4591f7b1b 100644 --- a/api/tests/manage/test_views.py +++ b/api/tests/manage/test_views.py @@ -1,6 +1,7 @@ import pytest from django.urls import reverse +from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import tasks as federation_tasks from funkwhale_api.manage import serializers, views @@ -90,6 +91,14 @@ def test_domain_detail(factories, superuser_api_client): assert response.data["name"] == d.pk +def test_domain_create(superuser_api_client): + url = reverse("api:v1:manage:federation:domains-list") + response = superuser_api_client.post(url, {"name": "test.federation"}) + + assert response.status_code == 201 + assert federation_models.Domain.objects.filter(pk="test.federation").exists() + + def test_domain_nodeinfo(factories, superuser_api_client, mocker): domain = factories["federation.Domain"]() url = reverse( diff --git a/front/src/views/admin/moderation/DomainsList.vue b/front/src/views/admin/moderation/DomainsList.vue index 84fb1df43..259d05f0c 100644 --- a/front/src/views/admin/moderation/DomainsList.vue +++ b/front/src/views/admin/moderation/DomainsList.vue @@ -1,26 +1,70 @@