mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 11:31:08 +00:00
Merge pull request #928 from mouse-reeve/blocklist
Allow admins to upload domain blocklists
This commit is contained in:
commit
20cb173150
10 changed files with 175 additions and 12 deletions
12
README.md
12
README.md
|
@ -32,27 +32,27 @@ Federation makes it possible to have small, self-determining communities, in con
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
Since the project is still in its early stages, the features are growing every day, and there is plenty of room for suggestions and ideas. Open an [issue](https://github.com/mouse-reeve/bookwyrm/issues) to get the conversation going!
|
Since the project is still in its early stages, the features are growing every day, and there is plenty of room for suggestions and ideas. Open an [issue](https://github.com/mouse-reeve/bookwyrm/issues) to get the conversation going!
|
||||||
- Posting about books
|
- Posting about books
|
||||||
- Compose reviews, with or without ratings, which are aggregated in the book page
|
- Compose reviews, with or without ratings, which are aggregated in the book page
|
||||||
- Compose other kinds of statuses about books, such as:
|
- Compose other kinds of statuses about books, such as:
|
||||||
- Comments on a book
|
- Comments on a book
|
||||||
- Quotes or excerpts
|
- Quotes or excerpts
|
||||||
- Reply to statuses
|
- Reply to statuses
|
||||||
- View aggregate reviews of a book across connected BookWyrm instances
|
- View aggregate reviews of a book across connected BookWyrm instances
|
||||||
- Differentiate local and federated reviews and rating in your activity feed
|
- Differentiate local and federated reviews and rating in your activity feed
|
||||||
- Track reading activity
|
- Track reading activity
|
||||||
- Shelve books on default "to-read," "currently reading," and "read" shelves
|
- Shelve books on default "to-read," "currently reading," and "read" shelves
|
||||||
- Create custom shelves
|
- Create custom shelves
|
||||||
- Store started reading/finished reading dates, as well as progress updates along the way
|
- Store started reading/finished reading dates, as well as progress updates along the way
|
||||||
- Update followers about reading activity (optionally, and with granular privacy controls)
|
- Update followers about reading activity (optionally, and with granular privacy controls)
|
||||||
- Create lists of books which can be open to submissions from anyone, curated, or only edited by the creator
|
- Create lists of books which can be open to submissions from anyone, curated, or only edited by the creator
|
||||||
- Federation with ActivityPub
|
- Federation with ActivityPub
|
||||||
- Broadcast and receive user statuses and activity
|
- Broadcast and receive user statuses and activity
|
||||||
- Share book data between instances to create a networked database of metadata
|
- Share book data between instances to create a networked database of metadata
|
||||||
- Identify shared books across instances and aggregate related content
|
- Identify shared books across instances and aggregate related content
|
||||||
- Follow and interact with users across BookWyrm instances
|
- Follow and interact with users across BookWyrm instances
|
||||||
- Inter-operate with non-BookWyrm ActivityPub services (currently, Mastodon is supported)
|
- Inter-operate with non-BookWyrm ActivityPub services (currently, Mastodon is supported)
|
||||||
- Granular privacy controls
|
- Granular privacy controls
|
||||||
- Private, followers-only, and public privacy levels for posting, shelves, and lists
|
- Private, followers-only, and public privacy levels for posting, shelves, and lists
|
||||||
- Option for users to manually approve followers
|
- Option for users to manually approve followers
|
||||||
- Allow blocking and flagging for moderation
|
- Allow blocking and flagging for moderation
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_csv_field">{% trans "Data file:" %}</label>
|
<label class="label" for="id_csv_file">{% trans "Data file:" %}</label>
|
||||||
{{ import_form.csv_file }}
|
{{ import_form.csv_file }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,6 +9,19 @@
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
{% url 'settings-import-blocklist' as url %}
|
||||||
|
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "Import block list" %}</a>
|
||||||
|
</li>
|
||||||
|
{% url 'settings-add-federated-server' as url %}
|
||||||
|
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "Add server" %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="POST" action="{% url 'settings-add-federated-server' %}">
|
<form method="POST" action="{% url 'settings-add-federated-server' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
@ -24,8 +37,8 @@
|
||||||
<label class="label" for="id_status">{% trans "Status:" %}</label>
|
<label class="label" for="id_status">{% trans "Status:" %}</label>
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<select name="status" class="" id="id_status">
|
<select name="status" class="" id="id_status">
|
||||||
<option value="federated" {% if form.status.value == "federated" or not form.status.value %}selected=""{% endif %}>{% trans "Federated" %}</option>
|
<option value="federated" {% if form.status.value == "federated" %}selected=""{% endif %}>{% trans "Federated" %}</option>
|
||||||
<option value="blocked" {% if form.status.value == "blocked" %}selected{% endif %}>{% trans "Blocked" %}</option>
|
<option value="blocked" {% if form.status.value == "blocked" or not form.status.value %}selected{% endif %}>{% trans "Blocked" %}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% extends 'settings/admin_layout.html' %}
|
{% extends 'settings/admin_layout.html' %}
|
||||||
{% block title %}{{ server.server_name }}{% endblock %}
|
{% block title %}{{ server.server_name }}{% endblock %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load bookwyrm_tags %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
{{ server.server_name }}
|
{{ server.server_name }}
|
||||||
|
@ -81,7 +82,7 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{% if server.notes %}
|
{% if server.notes %}
|
||||||
<p id="hide-edit-notes">{{ server.notes }}</p>
|
<p id="hide-edit-notes">{{ server.notes|to_markdown|safe }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form class="box is-hidden" method="POST" action="{% url 'settings-federated-server' server.id %}" id="edit-notes">
|
<form class="box is-hidden" method="POST" action="{% url 'settings-federated-server' server.id %}" id="edit-notes">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{% block header %}{% trans "Federated Servers" %}{% endblock %}
|
{% block header %}{% trans "Federated Servers" %}{% endblock %}
|
||||||
|
|
||||||
{% block edit-button %}
|
{% block edit-button %}
|
||||||
<a href="{% url 'settings-add-federated-server' %}">
|
<a href="{% url 'settings-import-blocklist' %}">
|
||||||
<span class="icon icon-plus" title="{% trans 'Add server' %}">
|
<span class="icon icon-plus" title="{% trans 'Add server' %}">
|
||||||
<span class="is-sr-only">{% trans "Add server" %}</span>
|
<span class="is-sr-only">{% trans "Add server" %}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
67
bookwyrm/templates/settings/server_blocklist.html
Normal file
67
bookwyrm/templates/settings/server_blocklist.html
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{% extends 'settings/admin_layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Add server" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{% trans "Import Blocklist" %}
|
||||||
|
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to server list" %}</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block panel %}
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
{% url 'settings-import-blocklist' as url %}
|
||||||
|
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "Import block list" %}</a>
|
||||||
|
</li>
|
||||||
|
{% url 'settings-add-federated-server' as url %}
|
||||||
|
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "Add server" %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if succeeded and not failed %}
|
||||||
|
<p class="notification is-primary">{% trans "Success!" %}</p>
|
||||||
|
{% elif succeeded or failed %}
|
||||||
|
<div class="block content">
|
||||||
|
{% if succeeded %}
|
||||||
|
<p>{% trans "Successfully blocked:" %} {{ succeeded }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<p>{% trans "Failed:" %}</p>
|
||||||
|
<ul>
|
||||||
|
{% for item in failed %}
|
||||||
|
<li>
|
||||||
|
<pre>
|
||||||
|
{{ item }}
|
||||||
|
</pre>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="POST" action="{% url 'settings-import-blocklist' %}" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="id_file">JSON data:</label>
|
||||||
|
<aside class="help">
|
||||||
|
Expects a json file in the format provided by <a href="https://fediblock.org/" target="_blank" rel=”noopener”>FediBlock</a>, with a list of entries that have <code>instance</code> and <code>url</code> fields. For example:
|
||||||
|
<pre>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"instance": "example.server.com",
|
||||||
|
"url": "https://link.to.more/info"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
</pre>
|
||||||
|
</aside>
|
||||||
|
<input type="file" name="json_file" required="" id="id_file">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="button is-primary">{% trans "Import" %}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,5 +1,7 @@
|
||||||
""" test for app action functionality """
|
""" test for app action functionality """
|
||||||
|
import json
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
@ -127,3 +129,39 @@ class FederationViews(TestCase):
|
||||||
self.assertEqual(server.server_name, "remote.server")
|
self.assertEqual(server.server_name, "remote.server")
|
||||||
self.assertEqual(server.application_type, "coolsoft")
|
self.assertEqual(server.application_type, "coolsoft")
|
||||||
self.assertEqual(server.status, "blocked")
|
self.assertEqual(server.status, "blocked")
|
||||||
|
|
||||||
|
def test_import_blocklist(self):
|
||||||
|
""" load a json file with a list of servers to block """
|
||||||
|
server = models.FederatedServer.objects.create(server_name="hi.there.com")
|
||||||
|
self.remote_user.federated_server = server
|
||||||
|
self.remote_user.save()
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{"instance": "server.name", "url": "https://explanation.url"}, # new server
|
||||||
|
{"instance": "hi.there.com", "url": "https://explanation.url"}, # existing
|
||||||
|
{"a": "b"}, # invalid
|
||||||
|
]
|
||||||
|
json.dump(data, open("file.json", "w"))
|
||||||
|
|
||||||
|
view = views.ImportServerBlocklist.as_view()
|
||||||
|
request = self.factory.post(
|
||||||
|
"",
|
||||||
|
{
|
||||||
|
"json_file": SimpleUploadedFile(
|
||||||
|
"file.json", open("file.json", "rb").read()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
request.user = self.local_user
|
||||||
|
request.user.is_superuser = True
|
||||||
|
|
||||||
|
view(request)
|
||||||
|
server.refresh_from_db()
|
||||||
|
self.remote_user.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(models.FederatedServer.objects.count(), 2)
|
||||||
|
self.assertEqual(server.status, "blocked")
|
||||||
|
self.assertFalse(self.remote_user.is_active)
|
||||||
|
created = models.FederatedServer.objects.get(server_name="server.name")
|
||||||
|
self.assertEqual(created.status, "blocked")
|
||||||
|
self.assertEqual(created.notes, "https://explanation.url")
|
||||||
|
|
|
@ -83,6 +83,11 @@ urlpatterns = [
|
||||||
views.AddFederatedServer.as_view(),
|
views.AddFederatedServer.as_view(),
|
||||||
name="settings-add-federated-server",
|
name="settings-add-federated-server",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
r"^settings/federation/import/?$",
|
||||||
|
views.ImportServerBlocklist.as_view(),
|
||||||
|
name="settings-import-blocklist",
|
||||||
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites"
|
r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites"
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,7 +5,8 @@ from .block import Block, unblock
|
||||||
from .books import Book, EditBook, ConfirmEditBook, Editions
|
from .books import Book, EditBook, ConfirmEditBook, Editions
|
||||||
from .books import upload_cover, add_description, switch_edition, resolve_book
|
from .books import upload_cover, add_description, switch_edition, resolve_book
|
||||||
from .directory import Directory
|
from .directory import Directory
|
||||||
from .federation import Federation, FederatedServer, AddFederatedServer
|
from .federation import Federation, FederatedServer
|
||||||
|
from .federation import AddFederatedServer, ImportServerBlocklist
|
||||||
from .federation import block_server, unblock_server
|
from .federation import block_server, unblock_server
|
||||||
from .feed import DirectMessage, Feed, Replies, Status
|
from .feed import DirectMessage, Feed, Replies, Status
|
||||||
from .follow import follow, unfollow
|
from .follow import follow, unfollow
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
""" manage federated servers """
|
""" manage federated servers """
|
||||||
|
import json
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from django.db import transaction
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
@ -63,6 +65,42 @@ class AddFederatedServer(View):
|
||||||
return redirect("settings-federated-server", server.id)
|
return redirect("settings-federated-server", server.id)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name="dispatch")
|
||||||
|
@method_decorator(
|
||||||
|
permission_required("bookwyrm.control_federation", raise_exception=True),
|
||||||
|
name="dispatch",
|
||||||
|
)
|
||||||
|
class ImportServerBlocklist(View):
|
||||||
|
""" manually add a server """
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
""" add server form """
|
||||||
|
return TemplateResponse(request, "settings/server_blocklist.html")
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
""" add a server from the admin panel """
|
||||||
|
json_data = json.load(request.FILES["json_file"])
|
||||||
|
failed = []
|
||||||
|
success_count = 0
|
||||||
|
for item in json_data:
|
||||||
|
server_name = item.get("instance")
|
||||||
|
if not server_name:
|
||||||
|
failed.append(item)
|
||||||
|
continue
|
||||||
|
info_link = item.get("url")
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
server, _ = models.FederatedServer.objects.get_or_create(
|
||||||
|
server_name=server_name,
|
||||||
|
)
|
||||||
|
server.notes = info_link
|
||||||
|
server.save()
|
||||||
|
server.block()
|
||||||
|
success_count += 1
|
||||||
|
data = {"failed": failed, "succeeded": success_count}
|
||||||
|
return TemplateResponse(request, "settings/server_blocklist.html", data)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(login_required, name="dispatch")
|
@method_decorator(login_required, name="dispatch")
|
||||||
@method_decorator(
|
@method_decorator(
|
||||||
permission_required("bookwyrm.control_federation", raise_exception=True),
|
permission_required("bookwyrm.control_federation", raise_exception=True),
|
||||||
|
|
Loading…
Reference in a new issue