Add support to import blocklists (#617)

pull/621/head
Humberto Rocha 2023-07-24 19:59:50 -04:00 zatwierdzone przez GitHub
rodzic 4a8bdec90c
commit f3bab95827
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
9 zmienionych plików z 133 dodań i 3 usunięć

Wyświetl plik

@ -1358,6 +1358,10 @@ table.metadata td .emoji {
cursor: pointer; cursor: pointer;
} }
.message.error {
background-color: var(--color-bg-error);
}
/* Identity banner */ /* Identity banner */
.identity-banner { .identity-banner {

Wyświetl plik

@ -133,6 +133,11 @@ urlpatterns = [
admin.FederationRoot.as_view(), admin.FederationRoot.as_view(),
name="admin_federation", name="admin_federation",
), ),
path(
"admin/federation/blocklist/",
admin.FederationBlocklist.as_view(),
name="admin_federation_blocklist",
),
path( path(
"admin/federation/<domain>/", "admin/federation/<domain>/",
admin.FederationEdit.as_view(), admin.FederationEdit.as_view(),

Wyświetl plik

@ -8,6 +8,9 @@
<input type="search" name="query" value="{{ query }}" placeholder="Search by domain"> <input type="search" name="query" value="{{ query }}" placeholder="Search by domain">
<button><i class="fa-solid fa-search"></i></button> <button><i class="fa-solid fa-search"></i></button>
</form> </form>
<div class="view-options">
<a href="{% url "admin_federation_blocklist" %}?page={{ page_obj.number }}" class="button">Import Blocklist</a>
</div>
<table class="items"> <table class="items">
{% for domain in page_obj %} {% for domain in page_obj %}
<tr> <tr>

Wyświetl plik

@ -0,0 +1,18 @@
{% extends "admin/base_main.html" %}
{% block subtitle %}Federation Blocklist{% endblock %}
{% block settings_content %}
<form action="." method="POST" enctype="multipart/form-data">
{% csrf_token %}
<h1>Import Blocklist</h1>
<fieldset>
{% include "forms/_field.html" with field=form.blocklist %}
</fieldset>
<div class="buttons">
<a href="{% url "admin_federation" %}?page={{ page }}" class="button secondary left">Back</a>
<button>Save</button>
</div>
</form>
{% endblock %}

Wyświetl plik

@ -0,0 +1,11 @@
import pytest
from users.models import Domain
from users.services import DomainService
@pytest.mark.django_db
def test_block():
DomainService.block(["block1.example.com", "block2.example.com"])
assert Domain.objects.filter(blocked=True).count() == 2

Wyświetl plik

@ -1,3 +1,4 @@
from .announcement import AnnouncementService # noqa from .announcement import AnnouncementService # noqa
from .domain import DomainService # noqa
from .identity import IdentityService # noqa from .identity import IdentityService # noqa
from .user import UserService # noqa from .user import UserService # noqa

Wyświetl plik

@ -0,0 +1,22 @@
from users.models import Domain
class DomainService:
"""
High-level domain handling methods
"""
@classmethod
def block(cls, domains: list[str]) -> None:
domains_to_block = Domain.objects.filter(domain__in=domains)
domains_to_block.update(blocked=True)
already_blocked = domains_to_block.values_list("domain", flat=True)
domains_to_create = []
for domain in domains:
if domain not in already_blocked:
domains_to_create.append(
Domain(domain=domain, blocked=True, local=False)
)
Domain.objects.bulk_create(domains_to_create)

Wyświetl plik

@ -23,7 +23,11 @@ from users.views.admin.emoji import ( # noqa
EmojiEnable, EmojiEnable,
EmojiRoot, EmojiRoot,
) )
from users.views.admin.federation import FederationEdit, FederationRoot # noqa from users.views.admin.federation import ( # noqa
FederationBlocklist,
FederationEdit,
FederationRoot,
)
from users.views.admin.hashtags import HashtagEdit, HashtagEnable, Hashtags # noqa from users.views.admin.hashtags import HashtagEdit, HashtagEnable, Hashtags # noqa
from users.views.admin.identities import IdentitiesRoot, IdentityEdit # noqa from users.views.admin.identities import IdentitiesRoot, IdentityEdit # noqa
from users.views.admin.invites import InviteCreate, InvitesRoot, InviteView # noqa from users.views.admin.invites import InviteCreate, InvitesRoot, InviteView # noqa

Wyświetl plik

@ -1,4 +1,8 @@
import csv
from django import forms from django import forms
from django.contrib import messages
from django.core.validators import FileExtensionValidator, ValidationError
from django.db import models from django.db import models
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -6,11 +10,12 @@ from django.views.generic import FormView, ListView
from users.decorators import admin_required from users.decorators import admin_required
from users.models import Domain from users.models import Domain
from users.services import DomainService
from users.views.admin.domains import DomainValidator
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
class FederationRoot(ListView): class FederationRoot(ListView):
template_name = "admin/federation.html" template_name = "admin/federation.html"
paginate_by = 50 paginate_by = 50
@ -35,7 +40,6 @@ class FederationRoot(ListView):
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
class FederationEdit(FormView): class FederationEdit(FormView):
template_name = "admin/federation_edit.html" template_name = "admin/federation_edit.html"
extra_context = {"section": "federation"} extra_context = {"section": "federation"}
@ -78,3 +82,61 @@ class FederationEdit(FormView):
"blocked": self.domain.blocked, "blocked": self.domain.blocked,
"notes": self.domain.notes, "notes": self.domain.notes,
} }
@method_decorator(admin_required, name="dispatch")
class FederationBlocklist(FormView):
template_name = "admin/federation_blocklist.html"
extra_context = {"section": "federation"}
error_msg = "The uploaded file has an invalid blocklist CSV format."
success_msg = "The blocklist CSV was processed processed with success!"
class form_class(forms.Form):
blocklist = forms.FileField(
help_text=(
"Blocklist file with one domain per line. "
"Oliphant blocklist format is also supported."
),
validators=[FileExtensionValidator(allowed_extensions=["txt", "csv"])],
)
def form_valid(self, form):
validator = DomainValidator()
domains = []
try:
lines = form.cleaned_data["blocklist"].read().decode("utf-8").splitlines()
if "#domain" in lines[0]:
reader = csv.DictReader(lines)
else:
reader = csv.DictReader(lines, fieldnames=["#domain"])
for row in reader:
domain = row["#domain"].strip()
try:
validator(domain)
except ValidationError:
# skip adding invalid domain
# to the blocklist
continue
domains.append(domain)
except (TypeError, ValueError):
messages.error(self.request, self.error_msg)
return redirect(".")
if not domains:
messages.error(self.request, self.error_msg)
return redirect(".")
DomainService.block(domains)
messages.success(self.request, self.success_msg)
return redirect("admin_federation")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page"] = self.request.GET.get("page")
return context