From 1901f7e6cb2f1111ba1a8754adcde0c8636c5c36 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:16:21 -0700 Subject: [PATCH 01/39] Check if incoming domains are blocked --- bookwyrm/models/federated_server.py | 19 ++++++++++--- bookwyrm/views/inbox.py | 44 +++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index 8f7d903e4..d7dc5d960 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -2,16 +2,27 @@ from django.db import models from .base_model import BookWyrmModel +FederationStatus = models.TextChoices( + "Status", + [ + "federated", + "blocked", + ], +) + class FederatedServer(BookWyrmModel): """ store which servers we federate with """ server_name = models.CharField(max_length=255, unique=True) - # federated, blocked, whatever else - status = models.CharField(max_length=255, default="federated") + status = models.CharField( + max_length=255, default="federated", choices=FederationStatus.choices + ) # is it mastodon, bookwyrm, etc application_type = models.CharField(max_length=255, null=True) application_version = models.CharField(max_length=255, null=True) - -# TODO: blocked servers + def block(self): + """ block a server """ + self.status = "blocked" + self.save() diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 34bd2e1cc..a4ae1e998 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -1,9 +1,10 @@ """ incoming activities """ import json -from urllib.parse import urldefrag +import re +from urllib.parse import urldefrag, urlparse -from django.http import HttpResponse -from django.http import HttpResponseBadRequest, HttpResponseNotFound +from django.http import HttpResponse, HttpResponseNotFound +from django.http import HttpResponseBadRequest, HttpResponseForbidden from django.utils.decorators import method_decorator from django.views import View from django.views.decorators.csrf import csrf_exempt @@ -12,6 +13,7 @@ import requests from bookwyrm import activitypub, models from bookwyrm.tasks import app from bookwyrm.signatures import Signature +from bookwyrm.utils import regex @method_decorator(csrf_exempt, name="dispatch") @@ -21,6 +23,10 @@ class Inbox(View): def post(self, request, username=None): """ only works as POST request """ + # first check if this server is on our shitlist + if is_blocked_user_agent(request): + return HttpResponseForbidden() + # make sure the user's inbox even exists if username: try: @@ -34,6 +40,10 @@ class Inbox(View): except json.decoder.JSONDecodeError: return HttpResponseBadRequest() + # let's be extra sure we didn't block this domain + if is_blocked_activity(activity_json): + return HttpResponseForbidden() + if ( not "object" in activity_json or not "type" in activity_json @@ -54,6 +64,34 @@ class Inbox(View): return HttpResponse() +def is_blocked_user_agent(request): + """ check if a request is from a blocked server based on user agent """ + # check user agent + user_agent = request.headers.get("User-Agent") + domain = re.match(regex.domain, user_agent) + if not domain: + # idk, we'll try again later with the actor + return False + return is_blocked(domain) + + +def is_blocked_activity(activity_json): + """ get the sender out of activity json and check if it's blocked """ + actor = activity_json.get("actor") + if not actor: + # well I guess it's not even a valid activity so who knows + return False + url = urlparse(actor) + return is_blocked(url.netloc) + + +def is_blocked(domain): + """ is this domain blocked? """ + return models.FederatedServer.object.filter( + server_name=domain, status="blocked" + ).exists() + + @app.task def activity_task(activity_json): """ do something with this json we think is legit """ From e4fe47b53865fde8c404a78674fb8f2499b602d4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:26:53 -0700 Subject: [PATCH 02/39] Default blocks in initdb --- bookwyrm/management/commands/initdb.py | 13 ++++++++++++- bookwyrm/templates/settings/federated_server.html | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index d6101c877..a86a1652e 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand, CommandError from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType -from bookwyrm.models import Connector, SiteSettings, User +from bookwyrm.models import Connector, FederatedServer, SiteSettings, User from bookwyrm.settings import DOMAIN @@ -107,6 +107,16 @@ def init_connectors(): ) +def init_federated_servers(): + """ big no to nazis """ + built_in_blocks = ["gab.ai", "gab.com"] + for server in built_in_blocks: + FederatedServer.objects.create( + server_name=server, + status="blocked", + ) + + def init_settings(): SiteSettings.objects.create() @@ -118,4 +128,5 @@ class Command(BaseCommand): init_groups() init_permissions() init_connectors() + init_federated_servers() init_settings() diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 13715bfb2..edab31397 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -21,7 +21,7 @@
{% trans "Status:" %}
-
Federated
+
{{ server.status }}
From ad543f46c1728a4ec573462eeab4646b88f7a34d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:38:32 -0700 Subject: [PATCH 03/39] Adds block and unblock functionality --- .../templates/settings/federated_server.html | 16 ++++++++++++++++ bookwyrm/views/federation.py | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index edab31397..147d6514e 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -4,6 +4,10 @@ {% block header %} {{ server.server_name }} + +{% if server.status == "blocked" %}{% trans "Blocked" %} +{% endif %} + {% trans "Back to server list" %} {% endblock %} @@ -64,5 +68,17 @@ +
+

{% trans "Actions" %}

+
+ {% csrf_token %} + {% if server.status != 'blocked' %} + + {% else %} + + {% endif %} +
+
+ {% endblock %} diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 464a207ca..0405ebf53 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -1,7 +1,7 @@ """ manage federated servers """ from django.contrib.auth.decorators import login_required, permission_required from django.core.paginator import Paginator -from django.shortcuts import get_object_or_404 +from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View @@ -61,3 +61,10 @@ class FederatedServer(View): ), } return TemplateResponse(request, "settings/federated_server.html", data) + + def post(self, request, server): # pylint: disable=unused-argument + """ (un)block a server """ + server = get_object_or_404(models.FederatedServer, id=server) + server.status = "blocked" if server.status == "federated" else "federated" + server.save() + return redirect("settings-federated-server", server.id) From 194fcb8055b229843b2449f8dd8cc649e7ec45d7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:54:20 -0700 Subject: [PATCH 04/39] Adds migration for federated server table statuses --- .../migrations/0062_auto_20210405_2249.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 bookwyrm/migrations/0062_auto_20210405_2249.py diff --git a/bookwyrm/migrations/0062_auto_20210405_2249.py b/bookwyrm/migrations/0062_auto_20210405_2249.py new file mode 100644 index 000000000..8229bf7ca --- /dev/null +++ b/bookwyrm/migrations/0062_auto_20210405_2249.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1.6 on 2021-04-05 22:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0061_auto_20210402_1435"), + ] + + operations = [ + migrations.AlterField( + model_name="federatedserver", + name="status", + field=models.CharField( + choices=[("federated", "Federated"), ("blocked", "Blocked")], + default="federated", + max_length=255, + ), + ), + ] From 34b790a0864cf153100a8f6388cc5d9598bde4f2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:54:33 -0700 Subject: [PATCH 05/39] Adds tests for blocked server checks --- bookwyrm/tests/views/test_inbox.py | 25 +++++++++++++++++++++++++ bookwyrm/views/inbox.py | 2 ++ 2 files changed, 27 insertions(+) diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 37cf00ddc..889fdc329 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -6,6 +6,7 @@ from unittest.mock import patch from django.http import HttpResponseNotAllowed, HttpResponseNotFound from django.test import TestCase, Client +from django.test.client import RequestFactory import responses from bookwyrm import models, views @@ -18,6 +19,7 @@ class Inbox(TestCase): def setUp(self): """ basic user and book data """ self.client = Client() + self.factory = RequestFactory() self.local_user = models.User.objects.create_user( "mouse@example.com", "mouse@mouse.com", @@ -936,3 +938,26 @@ class Inbox(TestCase): views.inbox.activity_task(activity) self.assertTrue(redis_mock.called) self.assertFalse(models.UserBlocks.objects.exists()) + + def test_is_blocked_user_agent(self): + """ check for blocked servers """ + request = self.factory.post( + "", + HTTP_USER_AGENT="http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)", + ) + self.assertFalse(views.inbox.is_blocked_user_agent(request)) + + models.FederatedServer.objects.create( + server_name="mastodon.social", status="blocked" + ) + self.assertTrue(views.inbox.is_blocked_user_agent(request)) + + def test_is_blocked_activity(self): + """ check for blocked servers """ + activity = {"actor": "https://mastodon.social/user/whaatever/else"} + self.assertFalse(views.inbox.is_blocked_user_agent(activity)) + + models.FederatedServer.objects.create( + server_name="mastodon.social", status="blocked" + ) + self.assertTrue(views.inbox.is_blocked_user_agent(activity)) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index a4ae1e998..9e5aa0195 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -68,6 +68,8 @@ def is_blocked_user_agent(request): """ check if a request is from a blocked server based on user agent """ # check user agent user_agent = request.headers.get("User-Agent") + if not user_agent: + return False domain = re.match(regex.domain, user_agent) if not domain: # idk, we'll try again later with the actor From a4b892dfadfe915c0dc2185682fe652e1a7661f2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 16:47:48 -0700 Subject: [PATCH 06/39] Fixes domain block tests --- bookwyrm/tests/views/test_federation.py | 16 ++++++++++++++++ bookwyrm/tests/views/test_inbox.py | 4 ++-- bookwyrm/views/inbox.py | 5 +++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index a60ea4327..874e277c9 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -44,3 +44,19 @@ class FederationViews(TestCase): self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) + + def test_server_page_post(self): + """ block and unblock a server """ + server = models.FederatedServer.objects.create(server_name="hi.there.com") + self.assertEqual(server.status, "federated") + + view = views.FederatedServer.as_view() + request = self.factory.post("") + request.user = self.local_user + request.user.is_superuser = True + + view(request, server.id) + self.assertEqual(server.status, "blocked") + + view(request, server.id) + self.assertEqual(server.status, "federated") diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 889fdc329..f15b99f1a 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -955,9 +955,9 @@ class Inbox(TestCase): def test_is_blocked_activity(self): """ check for blocked servers """ activity = {"actor": "https://mastodon.social/user/whaatever/else"} - self.assertFalse(views.inbox.is_blocked_user_agent(activity)) + self.assertFalse(views.inbox.is_blocked_activity(activity)) models.FederatedServer.objects.create( server_name="mastodon.social", status="blocked" ) - self.assertTrue(views.inbox.is_blocked_user_agent(activity)) + self.assertTrue(views.inbox.is_blocked_activity(activity)) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 9e5aa0195..387efe9f0 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -70,7 +70,8 @@ def is_blocked_user_agent(request): user_agent = request.headers.get("User-Agent") if not user_agent: return False - domain = re.match(regex.domain, user_agent) + url = re.search(r"+https?://{:s}/?".format(regex.domain), user_agent) + domain = urlparse(url).netloc if not domain: # idk, we'll try again later with the actor return False @@ -89,7 +90,7 @@ def is_blocked_activity(activity_json): def is_blocked(domain): """ is this domain blocked? """ - return models.FederatedServer.object.filter( + return models.FederatedServer.objects.filter( server_name=domain, status="blocked" ).exists() From fb72db7507459829956b3ade89144912246980ec Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 16:50:36 -0700 Subject: [PATCH 07/39] Fixes federation view tests --- bookwyrm/tests/views/test_federation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 874e277c9..3783417c1 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -56,7 +56,9 @@ class FederationViews(TestCase): request.user.is_superuser = True view(request, server.id) + server.refresh_from_db() self.assertEqual(server.status, "blocked") view(request, server.id) + server.refresh_from_db() self.assertEqual(server.status, "federated") From e3e28973f1cc8b98c1d374e4f0986928bf0ee9e6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Apr 2021 11:24:09 -0700 Subject: [PATCH 08/39] Adds merge migration --- bookwyrm/migrations/0063_merge_20210407_1823.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 bookwyrm/migrations/0063_merge_20210407_1823.py diff --git a/bookwyrm/migrations/0063_merge_20210407_1823.py b/bookwyrm/migrations/0063_merge_20210407_1823.py new file mode 100644 index 000000000..97f9e6717 --- /dev/null +++ b/bookwyrm/migrations/0063_merge_20210407_1823.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1.6 on 2021-04-07 18:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0062_auto_20210405_2249'), + ('bookwyrm', '0062_auto_20210407_1545'), + ] + + operations = [ + ] From 8261fbf86ae77f2eeb015047a2519eb9da7e685e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Apr 2021 11:28:31 -0700 Subject: [PATCH 09/39] Updates federated server model with notes field Also makes it more editable, and changes the status types --- .../migrations/0062_auto_20210405_2249.py | 22 ----------- .../migrations/0063_auto_20210407_1827.py | 37 +++++++++++++++++++ .../migrations/0063_merge_20210407_1823.py | 14 ------- bookwyrm/models/federated_server.py | 5 ++- 4 files changed, 40 insertions(+), 38 deletions(-) delete mode 100644 bookwyrm/migrations/0062_auto_20210405_2249.py create mode 100644 bookwyrm/migrations/0063_auto_20210407_1827.py delete mode 100644 bookwyrm/migrations/0063_merge_20210407_1823.py diff --git a/bookwyrm/migrations/0062_auto_20210405_2249.py b/bookwyrm/migrations/0062_auto_20210405_2249.py deleted file mode 100644 index 8229bf7ca..000000000 --- a/bookwyrm/migrations/0062_auto_20210405_2249.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.1.6 on 2021-04-05 22:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("bookwyrm", "0061_auto_20210402_1435"), - ] - - operations = [ - migrations.AlterField( - model_name="federatedserver", - name="status", - field=models.CharField( - choices=[("federated", "Federated"), ("blocked", "Blocked")], - default="federated", - max_length=255, - ), - ), - ] diff --git a/bookwyrm/migrations/0063_auto_20210407_1827.py b/bookwyrm/migrations/0063_auto_20210407_1827.py new file mode 100644 index 000000000..0bd0f2ae4 --- /dev/null +++ b/bookwyrm/migrations/0063_auto_20210407_1827.py @@ -0,0 +1,37 @@ +# Generated by Django 3.1.6 on 2021-04-07 18:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0062_auto_20210407_1545"), + ] + + operations = [ + migrations.AddField( + model_name="federatedserver", + name="notes", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="federatedserver", + name="application_type", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="federatedserver", + name="application_version", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="federatedserver", + name="status", + field=models.CharField( + choices=[("federated", "Federated"), ("blocked", "Blocked")], + default="federated", + max_length=255, + ), + ), + ] diff --git a/bookwyrm/migrations/0063_merge_20210407_1823.py b/bookwyrm/migrations/0063_merge_20210407_1823.py deleted file mode 100644 index 97f9e6717..000000000 --- a/bookwyrm/migrations/0063_merge_20210407_1823.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 3.1.6 on 2021-04-07 18:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('bookwyrm', '0062_auto_20210405_2249'), - ('bookwyrm', '0062_auto_20210407_1545'), - ] - - operations = [ - ] diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index d7dc5d960..ea9920a02 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -19,8 +19,9 @@ class FederatedServer(BookWyrmModel): max_length=255, default="federated", choices=FederationStatus.choices ) # is it mastodon, bookwyrm, etc - application_type = models.CharField(max_length=255, null=True) - application_version = models.CharField(max_length=255, null=True) + application_type = models.CharField(max_length=255, null=True, blank=True) + application_version = models.CharField(max_length=255, null=True, blank=True) + notes = models.TextField(null=True, blank=True) def block(self): """ block a server """ From 74549956c2ce9bfdfe18477384f747fbee10c33d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Apr 2021 11:52:13 -0700 Subject: [PATCH 10/39] Adds edit server form --- bookwyrm/forms.py | 6 ++++++ bookwyrm/templates/settings/add_server_form.html | 15 +++++++++++++++ bookwyrm/templates/settings/admin_layout.html | 9 ++++++++- bookwyrm/templates/settings/federation.html | 9 +++++++++ bookwyrm/views/federation.py | 4 ++-- 5 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/templates/settings/add_server_form.html diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index b159a89ef..55bb66506 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -281,3 +281,9 @@ class ReportForm(CustomForm): class Meta: model = models.Report fields = ["user", "reporter", "statuses", "note"] + + +class ServerForm(CustomForm): + class Meta: + model = models.FederatedServer + exclude = [] diff --git a/bookwyrm/templates/settings/add_server_form.html b/bookwyrm/templates/settings/add_server_form.html new file mode 100644 index 000000000..a16aac2db --- /dev/null +++ b/bookwyrm/templates/settings/add_server_form.html @@ -0,0 +1,15 @@ +{% extends 'components/inline_form.html' %} +{% load i18n %} + +{% block header %} +{% trans "Add server" %} +{% endblock %} + +{% block form %} +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} + diff --git a/bookwyrm/templates/settings/admin_layout.html b/bookwyrm/templates/settings/admin_layout.html index 9340da9e1..4f71a2284 100644 --- a/bookwyrm/templates/settings/admin_layout.html +++ b/bookwyrm/templates/settings/admin_layout.html @@ -6,7 +6,14 @@ {% block content %}
-

{% block header %}{% endblock %}

+
+
+

{% block header %}{% endblock %}

+
+
+ {% block edit-button %}{% endblock %} +
+
diff --git a/bookwyrm/templates/settings/federation.html b/bookwyrm/templates/settings/federation.html index 696d7a205..1106bc128 100644 --- a/bookwyrm/templates/settings/federation.html +++ b/bookwyrm/templates/settings/federation.html @@ -4,8 +4,17 @@ {% block header %}{% trans "Federated Servers" %}{% endblock %} +{% block edit-button %} +{% trans "Add server" as button_text %} +{% include 'snippets/toggle/open_button.html' with controls_text="add-server" icon="plus" text=button_text focus="add-server-header" %} +{% endblock%} + {% block panel %} +
+ {% include 'settings/add_server_form.html' with controls_text="add-server" %} +
+ {% url 'settings-federation' as url %} diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 0405ebf53..969627684 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -6,7 +6,7 @@ from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View -from bookwyrm import models +from bookwyrm import forms, models from bookwyrm.settings import PAGE_LENGTH @@ -34,7 +34,7 @@ class Federation(View): servers = servers.order_by(sort) paginated = Paginator(servers, PAGE_LENGTH) - data = {"servers": paginated.page(page), "sort": sort} + data = {"servers": paginated.page(page), "sort": sort, "form": forms.ServerForm} return TemplateResponse(request, "settings/federation.html", data) From d383e8a61e1097535d88d573ebba5843e65e9d50 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Apr 2021 12:11:01 -0700 Subject: [PATCH 11/39] Cleans up add server form --- bookwyrm/forms.py | 2 +- .../templates/settings/add_server_form.html | 4 +- bookwyrm/templates/settings/server_form.html | 39 +++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 bookwyrm/templates/settings/server_form.html diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 55bb66506..7c41323c0 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -286,4 +286,4 @@ class ReportForm(CustomForm): class ServerForm(CustomForm): class Meta: model = models.FederatedServer - exclude = [] + exclude = ["remote_id"] diff --git a/bookwyrm/templates/settings/add_server_form.html b/bookwyrm/templates/settings/add_server_form.html index a16aac2db..ffb430438 100644 --- a/bookwyrm/templates/settings/add_server_form.html +++ b/bookwyrm/templates/settings/add_server_form.html @@ -7,9 +7,7 @@ {% block form %}
- {% csrf_token %} - {{ form.as_p }} - + {% include 'settings/server_form.html' %} {% endblock %} diff --git a/bookwyrm/templates/settings/server_form.html b/bookwyrm/templates/settings/server_form.html new file mode 100644 index 000000000..4b9f6ef03 --- /dev/null +++ b/bookwyrm/templates/settings/server_form.html @@ -0,0 +1,39 @@ +{% load i18n %} + +{% csrf_token %} + +
+
+

+ + +

+

+ +

+ +
+

+
+
+

+ + +

+

+ + +

+
+
+

+ + +

+ + From ddab9af564c8fc9542ea4e5228d3c93d796a2870 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Apr 2021 13:06:29 -0700 Subject: [PATCH 12/39] Functional add server form --- .../templates/settings/add_server_form.html | 13 -------- bookwyrm/templates/settings/edit_server.html | 16 ++++++++++ bookwyrm/templates/settings/federation.html | 14 ++++---- bookwyrm/templates/settings/server_form.html | 32 ++++++++++++------- bookwyrm/urls.py | 5 +++ bookwyrm/views/__init__.py | 2 +- bookwyrm/views/federation.py | 23 ++++++++++++- 7 files changed, 70 insertions(+), 35 deletions(-) delete mode 100644 bookwyrm/templates/settings/add_server_form.html create mode 100644 bookwyrm/templates/settings/edit_server.html diff --git a/bookwyrm/templates/settings/add_server_form.html b/bookwyrm/templates/settings/add_server_form.html deleted file mode 100644 index ffb430438..000000000 --- a/bookwyrm/templates/settings/add_server_form.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends 'components/inline_form.html' %} -{% load i18n %} - -{% block header %} -{% trans "Add server" %} -{% endblock %} - -{% block form %} -
- {% include 'settings/server_form.html' %} - -{% endblock %} - diff --git a/bookwyrm/templates/settings/edit_server.html b/bookwyrm/templates/settings/edit_server.html new file mode 100644 index 000000000..6510222d8 --- /dev/null +++ b/bookwyrm/templates/settings/edit_server.html @@ -0,0 +1,16 @@ +{% extends 'settings/admin_layout.html' %} +{% load i18n %} +{% block title %}{% trans "Add server" %}{% endblock %} + +{% block header %} +{% if form.server_name.value %}{{ form.server_name.value }}{% else %}{% trans "Add server" %}{% endif %} +{% trans "Back to server list" %} +{% endblock %} + +{% block panel %} + +
+ {% include 'settings/server_form.html' %} + + +{% endblock %} diff --git a/bookwyrm/templates/settings/federation.html b/bookwyrm/templates/settings/federation.html index 1106bc128..da5ed0849 100644 --- a/bookwyrm/templates/settings/federation.html +++ b/bookwyrm/templates/settings/federation.html @@ -5,16 +5,14 @@ {% block header %}{% trans "Federated Servers" %}{% endblock %} {% block edit-button %} -{% trans "Add server" as button_text %} -{% include 'snippets/toggle/open_button.html' with controls_text="add-server" icon="plus" text=button_text focus="add-server-header" %} -{% endblock%} + + + {% trans "Add server" %} + + +{% endblock %} {% block panel %} - -
- {% include 'settings/add_server_form.html' with controls_text="add-server" %} -
-
{% url 'settings-federation' as url %} diff --git a/bookwyrm/templates/settings/server_form.html b/bookwyrm/templates/settings/server_form.html index 4b9f6ef03..b86c3d039 100644 --- a/bookwyrm/templates/settings/server_form.html +++ b/bookwyrm/templates/settings/server_form.html @@ -1,32 +1,40 @@ {% load i18n %} {% csrf_token %} -
-

+

- -

-

+ + {% for error in form.server_name.errors %} +

{{ error | escape }}

+ {% endfor %} +
+
-

+
-

+

-

-

+ {% for error in form.application_type.errors %} +

{{ error | escape }}

+ {% endfor %} +
+
-

+ {% for error in form.application_version.errors %} +

{{ error | escape }}

+ {% endfor %} +

diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 463988065..35856a4d9 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -68,6 +68,11 @@ urlpatterns = [ views.FederatedServer.as_view(), name="settings-federated-server", ), + re_path( + r"^settings/federation/edit/?$", + views.EditFederatedServer.as_view(), + name="settings-edit-federated-server", + ), re_path( r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites" ), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index d053e971b..404c2f31a 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -5,7 +5,7 @@ from .block import Block, unblock from .books import Book, EditBook, ConfirmEditBook, Editions from .books import upload_cover, add_description, switch_edition, resolve_book from .directory import Directory -from .federation import Federation, FederatedServer +from .federation import Federation, FederatedServer, EditFederatedServer from .feed import DirectMessage, Feed, Replies, Status from .follow import follow, unfollow from .follow import accept_follow_request, delete_follow_request diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 969627684..bb33c5af3 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -34,10 +34,31 @@ class Federation(View): servers = servers.order_by(sort) paginated = Paginator(servers, PAGE_LENGTH) - data = {"servers": paginated.page(page), "sort": sort, "form": forms.ServerForm} + + data = { + "servers": paginated.page(page), "sort": sort, "form": forms.ServerForm() + } return TemplateResponse(request, "settings/federation.html", data) +class EditFederatedServer(View): + """ manually add a server """ + + def get(self, request): + """ add server form """ + data = {"form": forms.ServerForm()} + return TemplateResponse(request, "settings/edit_server.html", data) + + def post(self, request): + """ add a server from the admin panel """ + form = forms.ServerForm(request.POST) + if not form.is_valid(): + data = {"form": form} + return TemplateResponse(request, "settings/edit_server.html", data) + server = form.save() + return redirect("settings-federated-server", server.id) + + @method_decorator(login_required, name="dispatch") @method_decorator( permission_required("bookwyrm.control_federation", raise_exception=True), From 855647453717ac54b1c281997d68b4785464ea15 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Apr 2021 13:17:51 -0700 Subject: [PATCH 13/39] Add and edit servers --- bookwyrm/templates/settings/edit_server.html | 48 +++++++++++++++++-- .../templates/settings/federated_server.html | 8 ++++ bookwyrm/templates/settings/server_form.html | 47 ------------------ bookwyrm/urls.py | 5 ++ bookwyrm/views/federation.py | 15 ++++-- 5 files changed, 68 insertions(+), 55 deletions(-) delete mode 100644 bookwyrm/templates/settings/server_form.html diff --git a/bookwyrm/templates/settings/edit_server.html b/bookwyrm/templates/settings/edit_server.html index 6510222d8..dc0de7e8c 100644 --- a/bookwyrm/templates/settings/edit_server.html +++ b/bookwyrm/templates/settings/edit_server.html @@ -3,14 +3,56 @@ {% block title %}{% trans "Add server" %}{% endblock %} {% block header %} -{% if form.server_name.value %}{{ form.server_name.value }}{% else %}{% trans "Add server" %}{% endif %} +{% if server %}{{ server.server_name }}{% else %}{% trans "Add server" %}{% endif %} {% trans "Back to server list" %} {% endblock %} {% block panel %} -
- {% include 'settings/server_form.html' %} + + {% csrf_token %} +

+
+
+ + + {% for error in form.server_name.errors %} +

{{ error | escape }}

+ {% endfor %} +
+
+ +
+ +
+
+
+
+
+ + + {% for error in form.application_type.errors %} +

{{ error | escape }}

+ {% endfor %} +
+
+ + + {% for error in form.application_version.errors %} +

{{ error | escape }}

+ {% endfor %} +
+
+
+

+ + +

+ + {% endblock %} diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 147d6514e..5b41ea797 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -11,6 +11,14 @@ {% trans "Back to server list" %} {% endblock %} +{% block edit-button %} + + + {% trans "Edit server" %} + + +{% endblock %} + {% block panel %}

{% trans "Details" %}

diff --git a/bookwyrm/templates/settings/server_form.html b/bookwyrm/templates/settings/server_form.html deleted file mode 100644 index b86c3d039..000000000 --- a/bookwyrm/templates/settings/server_form.html +++ /dev/null @@ -1,47 +0,0 @@ -{% load i18n %} - -{% csrf_token %} -
-
-
- - - {% for error in form.server_name.errors %} -

{{ error | escape }}

- {% endfor %} -
-
- -
- -
-
-
-
-
- - - {% for error in form.application_type.errors %} -

{{ error | escape }}

- {% endfor %} -
-
- - - {% for error in form.application_version.errors %} -

{{ error | escape }}

- {% endfor %} -
-
-
-

- - -

- - diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 35856a4d9..4cf6fd526 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -73,6 +73,11 @@ urlpatterns = [ views.EditFederatedServer.as_view(), name="settings-edit-federated-server", ), + re_path( + r"^settings/federation/edit/(?P\d+)?$", + views.EditFederatedServer.as_view(), + name="settings-edit-federated-server", + ), re_path( r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites" ), diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index bb33c5af3..09f5040d9 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -44,16 +44,21 @@ class Federation(View): class EditFederatedServer(View): """ manually add a server """ - def get(self, request): + def get(self, request, server=None): """ add server form """ - data = {"form": forms.ServerForm()} + if server: + server = get_object_or_404(models.FederatedServer, id=server) + data = {"form": forms.ServerForm(instance=server), "server": server} return TemplateResponse(request, "settings/edit_server.html", data) - def post(self, request): + def post(self, request, server=None): """ add a server from the admin panel """ - form = forms.ServerForm(request.POST) + if server: + server = get_object_or_404(models.FederatedServer, id=server) + + form = forms.ServerForm(request.POST, instance=server) if not form.is_valid(): - data = {"form": form} + data = {"form": form, "server": server} return TemplateResponse(request, "settings/edit_server.html", data) server = form.save() return redirect("settings-federated-server", server.id) From 839ac061b7c1a9aa3f4a3c348126cf7657d62c2c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Apr 2021 13:21:46 -0700 Subject: [PATCH 14/39] Show notes --- .../templates/settings/federated_server.html | 111 ++++++++++-------- 1 file changed, 59 insertions(+), 52 deletions(-) diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 5b41ea797..0f18f6ac2 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -20,60 +20,67 @@ {% endblock %} {% block panel %} -
-

{% trans "Details" %}

-
-
-
{% trans "Software:" %}
-
{{ server.application_type }}
-
-
-
{% trans "Version:" %}
-
{{ server.application_version }}
-
-
-
{% trans "Status:" %}
-
{{ server.status }}
-
-
-
+
+
+

{% trans "Details" %}

+
+
+
{% trans "Software:" %}
+
{{ server.application_type }}
+
+
+
{% trans "Version:" %}
+
{{ server.application_version }}
+
+
+
{% trans "Status:" %}
+
{{ server.status }}
+
+
+
+ +
+

{% trans "Activity" %}

+
+
+
{% trans "Users:" %}
+
+ {{ users.count }} + {% if server.user_set.count %}({% trans "View all" %}){% endif %} +
+
+
+
{% trans "Reports:" %}
+
+ {{ reports.count }} + {% if reports.count %}({% trans "View all" %}){% endif %} +
+
+
+
{% trans "Followed by us:" %}
+
+ {{ followed_by_us.count }} +
+
+
+
{% trans "Followed by them:" %}
+
+ {{ followed_by_them.count }} +
+
+
+
{% trans "Blocked by us:" %}
+
+ {{ blocked_by_us.count }} +
+
+
+
+
-

{% trans "Activity" %}

-
-
-
{% trans "Users:" %}
-
- {{ users.count }} - {% if server.user_set.count %}({% trans "View all" %}){% endif %} -
-
-
-
{% trans "Reports:" %}
-
- {{ reports.count }} - {% if reports.count %}({% trans "View all" %}){% endif %} -
-
-
-
{% trans "Followed by us:" %}
-
- {{ followed_by_us.count }} -
-
-
-
{% trans "Followed by them:" %}
-
- {{ followed_by_them.count }} -
-
-
-
{% trans "Blocked by us:" %}
-
- {{ blocked_by_us.count }} -
-
-
+

{% trans "Notes" %}

+

{{ server.notes }}

From ddba61f1385aea16592a254627a5172aaa88ffac Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 7 Apr 2021 16:50:12 -0700 Subject: [PATCH 15/39] Block all/unblock all users on server block --- bookwyrm/templates/settings/federated_server.html | 2 ++ bookwyrm/views/federation.py | 11 ++++++++++- bookwyrm/views/inbox.py | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 0f18f6ac2..520ab5dcc 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -89,8 +89,10 @@ {% csrf_token %} {% if server.status != 'blocked' %} +

{% trans "All users from this instance will be deactivated." %}

{% else %} +

{% trans "All users from this instance will be re-activated." %}

{% endif %}
diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 09f5040d9..83bed32d5 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -36,7 +36,9 @@ class Federation(View): paginated = Paginator(servers, PAGE_LENGTH) data = { - "servers": paginated.page(page), "sort": sort, "form": forms.ServerForm() + "servers": paginated.page(page), + "sort": sort, + "form": forms.ServerForm(), } return TemplateResponse(request, "settings/federation.html", data) @@ -93,4 +95,11 @@ class FederatedServer(View): server = get_object_or_404(models.FederatedServer, id=server) server.status = "blocked" if server.status == "federated" else "federated" server.save() + + # TODO: there needs to be differentiation between types of deactivated users + if server.status == "blocked": + server.user_set.update(is_active=False) + else: + server.user_set.update(is_active=True) + return redirect("settings-federated-server", server.id) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 387efe9f0..7997bd5c8 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -70,7 +70,7 @@ def is_blocked_user_agent(request): user_agent = request.headers.get("User-Agent") if not user_agent: return False - url = re.search(r"+https?://{:s}/?".format(regex.domain), user_agent) + url = re.search(r"https?://{:s}/?".format(regex.domain), user_agent).group() domain = urlparse(url).netloc if not domain: # idk, we'll try again later with the actor From cca7c9a98af224f5999175dc111a9c9b05da73e4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 09:35:03 -0700 Subject: [PATCH 16/39] Adds merge migrations --- bookwyrm/migrations/0064_merge_20210410_1633.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 bookwyrm/migrations/0064_merge_20210410_1633.py diff --git a/bookwyrm/migrations/0064_merge_20210410_1633.py b/bookwyrm/migrations/0064_merge_20210410_1633.py new file mode 100644 index 000000000..77ad541e9 --- /dev/null +++ b/bookwyrm/migrations/0064_merge_20210410_1633.py @@ -0,0 +1,13 @@ +# Generated by Django 3.1.8 on 2021-04-10 16:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0063_auto_20210408_1556"), + ("bookwyrm", "0063_auto_20210407_1827"), + ] + + operations = [] From c4bca42f642197602fef85f191982bc1c659998d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 09:52:11 -0700 Subject: [PATCH 17/39] Adds test for edit view GET request --- bookwyrm/tests/views/test_federation.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 3783417c1..591bb62a9 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -62,3 +62,23 @@ class FederationViews(TestCase): view(request, server.id) server.refresh_from_db() self.assertEqual(server.status, "federated") + + def test_edit_view_get(self): + """ there are so many views, this just makes sure it LOADS """ + # create mode + view = views.EditFederatedServer.as_view() + request = self.factory.get("") + request.user = self.local_user + request.user.is_superuser = True + + result = view(request, server=None) + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) + + # edit mode + server = models.FederatedServer.objects.create(server_name="hi.there.com") + result = view(request, server=server.id) + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) From d076162aa629598caea8324cb5a9118b7e679e66 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 10:09:36 -0700 Subject: [PATCH 18/39] Remove edit server mode It just doesn't really make sense --- bookwyrm/templates/settings/edit_server.html | 4 +- .../templates/settings/federated_server.html | 8 ---- bookwyrm/templates/settings/federation.html | 2 +- bookwyrm/tests/views/test_federation.py | 42 +++++++++++++++---- bookwyrm/urls.py | 11 ++--- bookwyrm/views/__init__.py | 2 +- bookwyrm/views/federation.py | 22 ++++------ 7 files changed, 49 insertions(+), 42 deletions(-) diff --git a/bookwyrm/templates/settings/edit_server.html b/bookwyrm/templates/settings/edit_server.html index dc0de7e8c..6ae227898 100644 --- a/bookwyrm/templates/settings/edit_server.html +++ b/bookwyrm/templates/settings/edit_server.html @@ -3,13 +3,13 @@ {% block title %}{% trans "Add server" %}{% endblock %} {% block header %} -{% if server %}{{ server.server_name }}{% else %}{% trans "Add server" %}{% endif %} +{% trans "Add server" %} {% trans "Back to server list" %} {% endblock %} {% block panel %} -
+ {% csrf_token %}
diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 520ab5dcc..37baa4dce 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -11,14 +11,6 @@ {% trans "Back to server list" %} {% endblock %} -{% block edit-button %} - - - {% trans "Edit server" %} - - -{% endblock %} - {% block panel %}
diff --git a/bookwyrm/templates/settings/federation.html b/bookwyrm/templates/settings/federation.html index da5ed0849..99afb5418 100644 --- a/bookwyrm/templates/settings/federation.html +++ b/bookwyrm/templates/settings/federation.html @@ -5,7 +5,7 @@ {% block header %}{% trans "Federated Servers" %}{% endblock %} {% block edit-button %} - + {% trans "Add server" %} diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 591bb62a9..01504ddb4 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -1,9 +1,10 @@ """ test for app action functionality """ +from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory -from bookwyrm import models, views +from bookwyrm import forms, models, views class FederationViews(TestCase): @@ -19,6 +20,16 @@ class FederationViews(TestCase): local=True, localname="mouse", ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) models.SiteSettings.objects.create() def test_federation_page(self): @@ -48,6 +59,9 @@ class FederationViews(TestCase): def test_server_page_post(self): """ block and unblock a server """ server = models.FederatedServer.objects.create(server_name="hi.there.com") + self.remote_user.federated_server = server + self.remote_user.save() + self.assertEqual(server.status, "federated") view = views.FederatedServer.as_view() @@ -63,10 +77,10 @@ class FederationViews(TestCase): server.refresh_from_db() self.assertEqual(server.status, "federated") - def test_edit_view_get(self): + def test_add_view_get(self): """ there are so many views, this just makes sure it LOADS """ # create mode - view = views.EditFederatedServer.as_view() + view = views.AddFederatedServer.as_view() request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True @@ -76,9 +90,19 @@ class FederationViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - # edit mode - server = models.FederatedServer.objects.create(server_name="hi.there.com") - result = view(request, server=server.id) - self.assertIsInstance(result, TemplateResponse) - result.render() - self.assertEqual(result.status_code, 200) + def test_add_view_post_create(self): + """ create or edit a server """ + form = forms.ServerForm() + form.data["server_name"] = "remote.server" + form.data["software"] = "coolsoft" + + view = views.AddFederatedServer.as_view() + request = self.factory.post("", form.data) + request.user = self.local_user + request.user.is_superuser = True + + view(request, server=None) + server = models.FederatedServer.objects.get() + self.assertEqual(server.server_name, "remote.server") + self.assertEqual(server.software, "coolsoft") + self.assertEqual(server.status, "federated") diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 4cf6fd526..398caaded 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -69,14 +69,9 @@ urlpatterns = [ name="settings-federated-server", ), re_path( - r"^settings/federation/edit/?$", - views.EditFederatedServer.as_view(), - name="settings-edit-federated-server", - ), - re_path( - r"^settings/federation/edit/(?P\d+)?$", - views.EditFederatedServer.as_view(), - name="settings-edit-federated-server", + r"^settings/federation/add/?$", + views.AddFederatedServer.as_view(), + name="settings-add-federated-server", ), re_path( r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites" diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 404c2f31a..de001b31a 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -5,7 +5,7 @@ from .block import Block, unblock from .books import Book, EditBook, ConfirmEditBook, Editions from .books import upload_cover, add_description, switch_edition, resolve_book from .directory import Directory -from .federation import Federation, FederatedServer, EditFederatedServer +from .federation import Federation, FederatedServer, AddFederatedServer from .feed import DirectMessage, Feed, Replies, Status from .follow import follow, unfollow from .follow import accept_follow_request, delete_follow_request diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 83bed32d5..e4ade5542 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -30,8 +30,9 @@ class Federation(View): sort = request.GET.get("sort") sort_fields = ["created_date", "application_type", "server_name"] - if sort in sort_fields + ["-{:s}".format(f) for f in sort_fields]: - servers = servers.order_by(sort) + if not sort in sort_fields + ["-{:s}".format(f) for f in sort_fields]: + sort = "created_date" + servers = servers.order_by(sort) paginated = Paginator(servers, PAGE_LENGTH) @@ -43,24 +44,19 @@ class Federation(View): return TemplateResponse(request, "settings/federation.html", data) -class EditFederatedServer(View): +class AddFederatedServer(View): """ manually add a server """ - def get(self, request, server=None): + def get(self, request): """ add server form """ - if server: - server = get_object_or_404(models.FederatedServer, id=server) - data = {"form": forms.ServerForm(instance=server), "server": server} + data = {"form": forms.ServerForm()} return TemplateResponse(request, "settings/edit_server.html", data) - def post(self, request, server=None): + def post(self, request): """ add a server from the admin panel """ - if server: - server = get_object_or_404(models.FederatedServer, id=server) - - form = forms.ServerForm(request.POST, instance=server) + form = forms.ServerForm(request.POST) if not form.is_valid(): - data = {"form": form, "server": server} + data = {"form": form} return TemplateResponse(request, "settings/edit_server.html", data) server = form.save() return redirect("settings-federated-server", server.id) From 81bc25b01281e1678689c3373fdd0ea51b3b75c6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 10:24:09 -0700 Subject: [PATCH 19/39] Fixes create test --- bookwyrm/tests/views/test_federation.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 01504ddb4..53e5b10fc 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -85,24 +85,25 @@ class FederationViews(TestCase): request.user = self.local_user request.user.is_superuser = True - result = view(request, server=None) + result = view(request) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) def test_add_view_post_create(self): - """ create or edit a server """ + """ create a server entry """ form = forms.ServerForm() form.data["server_name"] = "remote.server" - form.data["software"] = "coolsoft" + form.data["application_type"] = "coolsoft" + form.data["status"] = "blocked" view = views.AddFederatedServer.as_view() request = self.factory.post("", form.data) request.user = self.local_user request.user.is_superuser = True - view(request, server=None) + view(request) server = models.FederatedServer.objects.get() self.assertEqual(server.server_name, "remote.server") - self.assertEqual(server.software, "coolsoft") - self.assertEqual(server.status, "federated") + self.assertEqual(server.application_type, "coolsoft") + self.assertEqual(server.status, "blocked") From 2741aa55be3084e78af5a934fa823fed8fbd38c8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 10:37:28 -0700 Subject: [PATCH 20/39] Makes blocking it's own view --- bookwyrm/models/federated_server.py | 11 +++++++++++ bookwyrm/templates/settings/federated_server.html | 10 +++++++--- bookwyrm/views/federation.py | 12 ++---------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index ea9920a02..7283cdd8e 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -27,3 +27,14 @@ class FederatedServer(BookWyrmModel): """ block a server """ self.status = "blocked" self.save() + + # deactivate all associated users + self.user_set.update(is_active=False) + + def unblock(self): + """ unblock a server """ + self.status = "federated" + self.save() + + # TODO: only reactivate users as appropriate + self.user_set.update(is_active=True) diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 37baa4dce..31e0ffb93 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -77,16 +77,20 @@

{% trans "Actions" %}

+ {% if server.status != 'blocked' %} {% csrf_token %} - {% if server.status != 'blocked' %}

{% trans "All users from this instance will be deactivated." %}

- {% else %} + + {% else %} + {% comment %} +

{% trans "All users from this instance will be re-activated." %}

- {% endif %} + {% endcomment %} + {% endif %}
{% endblock %} diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index e4ade5542..067fa0d74 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -87,15 +87,7 @@ class FederatedServer(View): return TemplateResponse(request, "settings/federated_server.html", data) def post(self, request, server): # pylint: disable=unused-argument - """ (un)block a server """ + """ block a server """ server = get_object_or_404(models.FederatedServer, id=server) - server.status = "blocked" if server.status == "federated" else "federated" - server.save() - - # TODO: there needs to be differentiation between types of deactivated users - if server.status == "blocked": - server.user_set.update(is_active=False) - else: - server.user_set.update(is_active=True) - + server.block() return redirect("settings-federated-server", server.id) From 7b60626661b24e527f360931ab19bd39638f671d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 10:51:45 -0700 Subject: [PATCH 21/39] Updates block unit test --- bookwyrm/tests/views/test_federation.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 53e5b10fc..38da37d31 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -57,7 +57,7 @@ class FederationViews(TestCase): self.assertEqual(result.status_code, 200) def test_server_page_post(self): - """ block and unblock a server """ + """ block a server """ server = models.FederatedServer.objects.create(server_name="hi.there.com") self.remote_user.federated_server = server self.remote_user.save() @@ -71,11 +71,10 @@ class FederationViews(TestCase): view(request, server.id) server.refresh_from_db() + self.remote_user.refresh_from_db() self.assertEqual(server.status, "blocked") - - view(request, server.id) - server.refresh_from_db() - self.assertEqual(server.status, "federated") + # and the user was deactivated + self.assertFalse(self.remote_user.is_active) def test_add_view_get(self): """ there are so many views, this just makes sure it LOADS """ From d5fbdacc0243db94e87bc0fb4dd5f7435e6dc118 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 11:06:28 -0700 Subject: [PATCH 22/39] Adds unblock view --- .../templates/settings/federated_server.html | 3 +-- bookwyrm/tests/views/test_federation.py | 23 ++++++++++++++++++- bookwyrm/urls.py | 5 ++++ bookwyrm/views/__init__.py | 2 +- bookwyrm/views/federation.py | 12 ++++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 31e0ffb93..249d71061 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -84,12 +84,11 @@

{% trans "All users from this instance will be deactivated." %}

{% else %} - {% comment %}
+ {% csrf_token %}

{% trans "All users from this instance will be re-activated." %}

- {% endcomment %} {% endif %}
diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 38da37d31..fdafcad06 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -56,7 +56,7 @@ class FederationViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_server_page_post(self): + def test_server_page_block(self): """ block a server """ server = models.FederatedServer.objects.create(server_name="hi.there.com") self.remote_user.federated_server = server @@ -76,6 +76,27 @@ class FederationViews(TestCase): # and the user was deactivated self.assertFalse(self.remote_user.is_active) + def test_server_page_unblock(self): + """ unblock a server """ + server = models.FederatedServer.objects.create( + server_name="hi.there.com", status="blocked" + ) + self.remote_user.federated_server = server + self.remote_user.is_active = False + self.remote_user.save() + + request = self.factory.post("") + request.user = self.local_user + request.user.is_superuser = True + + views.federation.unblock_server(request, server.id) + server.refresh_from_db() + self.remote_user.refresh_from_db() + self.assertEqual(server.status, "federated") + # and the user was re-activated + self.assertTrue(self.remote_user.is_active) + + def test_add_view_get(self): """ there are so many views, this just makes sure it LOADS """ # create mode diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 398caaded..f2d43a13c 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -68,6 +68,11 @@ urlpatterns = [ views.FederatedServer.as_view(), name="settings-federated-server", ), + re_path( + r"^settings/federation/(?P\d+)/unblock?$", + views.federation.unblock_server, + name="settings-federated-server-unblock", + ), re_path( r"^settings/federation/add/?$", views.AddFederatedServer.as_view(), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index de001b31a..2dc7a1c6c 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -5,7 +5,7 @@ from .block import Block, unblock from .books import Book, EditBook, ConfirmEditBook, Editions from .books import upload_cover, add_description, switch_edition, resolve_book from .directory import Directory -from .federation import Federation, FederatedServer, AddFederatedServer +from .federation import Federation, FederatedServer, AddFederatedServer, unblock_server from .feed import DirectMessage, Feed, Replies, Status from .follow import follow, unfollow from .follow import accept_follow_request, delete_follow_request diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 067fa0d74..8a21c5a5e 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.views import View +from django.views.decorators.http import require_POST from bookwyrm import forms, models from bookwyrm.settings import PAGE_LENGTH @@ -91,3 +92,14 @@ class FederatedServer(View): server = get_object_or_404(models.FederatedServer, id=server) server.block() return redirect("settings-federated-server", server.id) + + +@login_required +@require_POST +@permission_required("bookwyrm.control_federation", raise_exception=True) +# pylint: disable=unused-argument +def unblock_server(request, server): + """ unblock a server """ + server = get_object_or_404(models.FederatedServer, id=server) + server.unblock() + return redirect("settings-federated-server", server.id) From 8797b3d240fef9fa9431a96a51d269c20c7c1f5a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 11:07:59 -0700 Subject: [PATCH 23/39] Python formatting --- bookwyrm/tests/views/test_federation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index fdafcad06..1ba181431 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -96,7 +96,6 @@ class FederationViews(TestCase): # and the user was re-activated self.assertTrue(self.remote_user.is_active) - def test_add_view_get(self): """ there are so many views, this just makes sure it LOADS """ # create mode From 0caeb3ac3366e9acc4f480f725ff69ae72ba6303 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 11:18:22 -0700 Subject: [PATCH 24/39] fixes inbox tests --- bookwyrm/tests/views/inbox/test_inbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/views/inbox/test_inbox.py b/bookwyrm/tests/views/inbox/test_inbox.py index deba6e694..50fb1ecc0 100644 --- a/bookwyrm/tests/views/inbox/test_inbox.py +++ b/bookwyrm/tests/views/inbox/test_inbox.py @@ -6,7 +6,7 @@ from django.http import HttpResponseNotAllowed, HttpResponseNotFound from django.test import TestCase, Client from django.test.client import RequestFactory -from bookwyrm import models +from bookwyrm import models, views # pylint: disable=too-many-public-methods From 1903812b1daa611a4475228aa10a7a4eae5e8d39 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 11:38:57 -0700 Subject: [PATCH 25/39] Class method for checking if urls are blocked --- bookwyrm/connectors/abstract_connector.py | 6 ++++++ bookwyrm/models/federated_server.py | 8 ++++++++ bookwyrm/views/inbox.py | 18 +++--------------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 2483cc62b..2fe5d825c 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -219,6 +219,12 @@ def dict_from_mappings(data, mappings): def get_data(url, params=None): """ wrapper for request.get """ + # check if the url is blocked + if models.FederatedServer.is_blocked(url): + raise ConnectorException( + "Attempting to load data from blocked url: {:s}".format(url) + ) + try: resp = requests.get( url, diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index 7283cdd8e..d2b823552 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -1,4 +1,5 @@ """ connections to external ActivityPub servers """ +from urllib.parse import urlparse from django.db import models from .base_model import BookWyrmModel @@ -38,3 +39,10 @@ class FederatedServer(BookWyrmModel): # TODO: only reactivate users as appropriate self.user_set.update(is_active=True) + + @classmethod + def is_blocked(cls, url): + """ look up if a domain is blocked """ + url = urlparse(url) + domain = url.netloc + return cls.objects.filter(server_name=domain, status="blocked").exists() diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 7997bd5c8..99f9c5565 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -1,7 +1,7 @@ """ incoming activities """ import json import re -from urllib.parse import urldefrag, urlparse +from urllib.parse import urldefrag from django.http import HttpResponse, HttpResponseNotFound from django.http import HttpResponseBadRequest, HttpResponseForbidden @@ -71,11 +71,7 @@ def is_blocked_user_agent(request): if not user_agent: return False url = re.search(r"https?://{:s}/?".format(regex.domain), user_agent).group() - domain = urlparse(url).netloc - if not domain: - # idk, we'll try again later with the actor - return False - return is_blocked(domain) + return models.FederatedServer.is_blocked(url) def is_blocked_activity(activity_json): @@ -84,15 +80,7 @@ def is_blocked_activity(activity_json): if not actor: # well I guess it's not even a valid activity so who knows return False - url = urlparse(actor) - return is_blocked(url.netloc) - - -def is_blocked(domain): - """ is this domain blocked? """ - return models.FederatedServer.objects.filter( - server_name=domain, status="blocked" - ).exists() + return models.FederatedServer.is_blocked(actor) @app.task From 89d3c3e82b86a47709c0bf669695ed0ff82ba3ab Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 10 Apr 2021 11:49:45 -0700 Subject: [PATCH 26/39] Tests searching for users on blocked servers --- bookwyrm/tests/views/test_helpers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 7d2bc42c9..27d03832a 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -146,6 +146,15 @@ class ViewsHelpers(TestCase): self.assertIsInstance(result, models.User) self.assertEqual(result.username, "mouse@example.com") + def test_user_on_blocked_server(self, _): + """ find a remote user using webfinger """ + models.FederatedServer.objects.create( + server_name="example.com", status="blocked" + ) + + result = views.helpers.handle_remote_webfinger("@mouse@example.com") + self.assertIsNone(result) + def test_handle_reading_status_to_read(self, _): """ posts shelve activities """ shelf = self.local_user.shelf_set.get(identifier="to-read") From 26f16cf5a46680a52cea9d6e615312572223ea2f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 09:09:13 -0700 Subject: [PATCH 27/39] Limit broadcast with viewer-aware users Removes inactive or blocked users --- bookwyrm/models/activitypub_mixin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 8dce42e4e..efe14f97c 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -64,6 +64,7 @@ class ActivitypubMixin: ) if hasattr(self, "property_fields"): self.activity_fields += [ + # pylint: disable=cell-var-from-loop PropertyField(lambda a, o: set_activity_from_property_field(a, o, f)) for f in self.property_fields ] @@ -152,7 +153,7 @@ class ActivitypubMixin: # unless it's a dm, all the followers should receive the activity if privacy != "direct": # we will send this out to a subset of all remote users - queryset = user_model.objects.filter( + queryset = user_model.viewer_aware_objects(user).filter( local=False, ) # filter users first by whether they're using the desired software From b6a7871b045c5592c5ed48286ed8a12f9e5c20fa Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 09:26:12 -0700 Subject: [PATCH 28/39] Makes visibility evaluator a model function --- bookwyrm/models/activitypub_mixin.py | 30 ++++++++++ .../tests/models/test_activitypub_mixin.py | 60 +++++++++++++++++++ bookwyrm/tests/views/test_helpers.py | 60 ------------------- bookwyrm/views/feed.py | 4 +- bookwyrm/views/goal.py | 4 +- bookwyrm/views/helpers.py | 24 -------- bookwyrm/views/list.py | 6 +- bookwyrm/views/shelf.py | 4 +- bookwyrm/views/user.py | 4 +- 9 files changed, 101 insertions(+), 95 deletions(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index efe14f97c..2a5878d52 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -83,6 +83,36 @@ class ActivitypubMixin: super().__init__(*args, **kwargs) + def visible_to_user(self, viewer): + """ is a user authorized to view an object? """ + # make sure this is an object with privacy owned by a user + if not hasattr(self, "user") or not hasattr(self, "privacy"): + return None + + # viewer can't see it if the object's owner blocked them + if viewer in self.user.blocks.all(): + return False + + # you can see your own posts and any public or unlisted posts + if viewer == self.user or self.privacy in ["public", "unlisted"]: + return True + + # you can see the followers only posts of people you follow + if ( + self.privacy == "followers" + and self.user.followers.filter(id=viewer.id).first() + ): + return True + + # you can see dms you are tagged in + if hasattr(self, "mention_users"): + if ( + self.privacy == "direct" + and self.mention_users.filter(id=viewer.id).first() + ): + return True + return False + @classmethod def find_existing_by_remote_id(cls, remote_id): """ look up a remote id in the db """ diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 0d1acd978..4e4e9c9f0 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -44,6 +44,66 @@ class ActivitypubMixins(TestCase): "published": "2020-12-04T17:52:22.623807+00:00", } + def test_object_visible_to_user(self, _): + """ does a user have permission to view an object """ + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="public" + ) + self.assertTrue(obj.visible_to_user(self.local_user)) + + obj = models.Shelf.objects.create( + name="test", user=self.remote_user, privacy="unlisted" + ) + self.assertTrue(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="followers" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="direct" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="direct" + ) + obj.mention_users.add(self.local_user) + self.assertTrue(obj.visible_to_user(self.local_user)) + + def test_object_visible_to_user_follower(self, _): + """ what you can see if you follow a user """ + self.remote_user.followers.add(self.local_user) + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="followers" + ) + self.assertTrue(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="direct" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="direct" + ) + obj.mention_users.add(self.local_user) + self.assertTrue(obj.visible_to_user(self.local_user)) + + def test_object_visible_to_user_blocked(self, _): + """ you can't see it if they block you """ + self.remote_user.blocks.add(self.local_user) + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="public" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + + obj = models.Shelf.objects.create( + name="test", user=self.remote_user, privacy="unlisted" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + # ActivitypubMixin def test_to_activity(self, _): """ model to ActivityPub json """ diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 27d03832a..2e5ed82d4 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -199,66 +199,6 @@ class ViewsHelpers(TestCase): ) self.assertFalse(models.GeneratedNote.objects.exists()) - def test_object_visible_to_user(self, _): - """ does a user have permission to view an object """ - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="public" - ) - self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - - obj = models.Shelf.objects.create( - name="test", user=self.remote_user, privacy="unlisted" - ) - self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="followers" - ) - self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="direct" - ) - self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="direct" - ) - obj.mention_users.add(self.local_user) - self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - - def test_object_visible_to_user_follower(self, _): - """ what you can see if you follow a user """ - self.remote_user.followers.add(self.local_user) - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="followers" - ) - self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="direct" - ) - self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="direct" - ) - obj.mention_users.add(self.local_user) - self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - - def test_object_visible_to_user_blocked(self, _): - """ you can't see it if they block you """ - self.remote_user.blocks.add(self.local_user) - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="public" - ) - self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) - - obj = models.Shelf.objects.create( - name="test", user=self.remote_user, privacy="unlisted" - ) - self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) - def test_get_annotated_users(self, _): """ list of people you might know """ user_1 = models.User.objects.create_user( diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index cda115867..d5e644343 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -12,7 +12,7 @@ from bookwyrm import activitystreams, forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH, STREAMS from .helpers import get_user_from_username, privacy_filter, get_suggested_users -from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user +from .helpers import is_api_request, is_bookwyrm_request # pylint: disable= no-self-use @@ -113,7 +113,7 @@ class Status(View): return HttpResponseNotFound() # make sure the user is authorized to see the status - if not object_visible_to_user(request.user, status): + if not status.visible_to_user(request.user): return HttpResponseNotFound() if is_api_request(request): diff --git a/bookwyrm/views/goal.py b/bookwyrm/views/goal.py index 9c4e117c6..1627d3da3 100644 --- a/bookwyrm/views/goal.py +++ b/bookwyrm/views/goal.py @@ -10,7 +10,7 @@ from django.views.decorators.http import require_POST from bookwyrm import forms, models from bookwyrm.status import create_generated_note -from .helpers import get_user_from_username, object_visible_to_user +from .helpers import get_user_from_username # pylint: disable= no-self-use @@ -26,7 +26,7 @@ class Goal(View): if not goal and user != request.user: return HttpResponseNotFound() - if goal and not object_visible_to_user(request.user, goal): + if goal and not goal.visible_to_user(request.user): return HttpResponseNotFound() data = { diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 2b6501ff2..57c334377 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -32,30 +32,6 @@ def is_bookwyrm_request(request): return True -def object_visible_to_user(viewer, obj): - """ is a user authorized to view an object? """ - if not obj: - return False - - # viewer can't see it if the object's owner blocked them - if viewer in obj.user.blocks.all(): - return False - - # you can see your own posts and any public or unlisted posts - if viewer == obj.user or obj.privacy in ["public", "unlisted"]: - return True - - # you can see the followers only posts of people you follow - if obj.privacy == "followers" and obj.user.followers.filter(id=viewer.id).first(): - return True - - # you can see dms you are tagged in - if isinstance(obj, models.Status): - if obj.privacy == "direct" and obj.mention_users.filter(id=viewer.id).first(): - return True - return False - - def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False): """ filter objects that have "user" and "privacy" fields """ privacy_levels = privacy_levels or ["public", "unlisted", "followers", "direct"] diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index 7724cd137..cafc40b19 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -13,7 +13,7 @@ from django.views.decorators.http import require_POST from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.connectors import connector_manager -from .helpers import is_api_request, object_visible_to_user, privacy_filter +from .helpers import is_api_request, privacy_filter from .helpers import get_user_from_username # pylint: disable=no-self-use @@ -92,7 +92,7 @@ class List(View): def get(self, request, list_id): """ display a book list """ book_list = get_object_or_404(models.List, id=list_id) - if not object_visible_to_user(request.user, book_list): + if not book_list.visible_to_user(request.user): return HttpResponseNotFound() if is_api_request(request): @@ -176,7 +176,7 @@ class Curate(View): def add_book(request): """ put a book on a list """ book_list = get_object_or_404(models.List, id=request.POST.get("list")) - if not object_visible_to_user(request.user, book_list): + if not book_list.visible_to_user(request.user): return HttpResponseNotFound() book = get_object_or_404(models.Edition, id=request.POST.get("book")) diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 41d1f1358..888999493 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -16,7 +16,7 @@ from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH from .helpers import is_api_request, get_edition, get_user_from_username -from .helpers import handle_reading_status, privacy_filter, object_visible_to_user +from .helpers import handle_reading_status, privacy_filter # pylint: disable= no-self-use @@ -43,7 +43,7 @@ class Shelf(View): shelf = user.shelf_set.get(identifier=shelf_identifier) except models.Shelf.DoesNotExist: return HttpResponseNotFound() - if not object_visible_to_user(request.user, shelf): + if not shelf.visible_to_user(request.user): return HttpResponseNotFound() # this is a constructed "all books" view, with a fake "shelf" obj else: diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index aba804d8b..d2c9c2ebf 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -17,7 +17,7 @@ from bookwyrm import forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH from .helpers import get_user_from_username, is_api_request -from .helpers import is_blocked, privacy_filter, object_visible_to_user +from .helpers import is_blocked, privacy_filter # pylint: disable= no-self-use @@ -80,7 +80,7 @@ class User(View): goal = models.AnnualGoal.objects.filter( user=user, year=timezone.now().year ).first() - if not object_visible_to_user(request.user, goal): + if not goal.visible_to_user(request.user): goal = None data = { "user": user, From edf3fad54d9674ab9901f7fe3b520ce24463c844 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 09:42:55 -0700 Subject: [PATCH 29/39] Make sure user is available when testing blocks --- bookwyrm/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index c519f76c9..c244d4aed 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -138,7 +138,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): def viewer_aware_objects(cls, viewer): """ the user queryset filtered for the context of the logged in user """ queryset = cls.objects.filter(is_active=True) - if viewer.is_authenticated: + if viewer and viewer.is_authenticated: queryset = queryset.exclude(blocks=viewer) return queryset From b552634a878b9e1fee4237a0815ad109a58cab0d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 09:58:03 -0700 Subject: [PATCH 30/39] Make sure goal exists before checking perms --- bookwyrm/views/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index d2c9c2ebf..d666f064e 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -80,7 +80,7 @@ class User(View): goal = models.AnnualGoal.objects.filter( user=user, year=timezone.now().year ).first() - if not goal.visible_to_user(request.user): + if goal and not goal.visible_to_user(request.user): goal = None data = { "user": user, From 96d15d3d57f469128160835a9fa63fcf70ed3e1c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 10:05:36 -0700 Subject: [PATCH 31/39] Adds merge migration --- bookwyrm/migrations/0065_merge_20210411_1702.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 bookwyrm/migrations/0065_merge_20210411_1702.py diff --git a/bookwyrm/migrations/0065_merge_20210411_1702.py b/bookwyrm/migrations/0065_merge_20210411_1702.py new file mode 100644 index 000000000..2bdc425dc --- /dev/null +++ b/bookwyrm/migrations/0065_merge_20210411_1702.py @@ -0,0 +1,13 @@ +# Generated by Django 3.1.8 on 2021-04-11 17:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0064_auto_20210408_2208"), + ("bookwyrm", "0064_merge_20210410_1633"), + ] + + operations = [] From 659986771f3ffde3c360754ce356dbcffa420cfe Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 10:36:40 -0700 Subject: [PATCH 32/39] Edit notes on federated servers --- .../templates/settings/federated_server.html | 26 ++++++++++++++++--- bookwyrm/tests/views/test_federation.py | 2 +- bookwyrm/urls.py | 5 ++++ bookwyrm/views/__init__.py | 3 ++- bookwyrm/views/federation.py | 16 ++++++++++-- 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/bookwyrm/templates/settings/federated_server.html b/bookwyrm/templates/settings/federated_server.html index 249d71061..6996557d8 100644 --- a/bookwyrm/templates/settings/federated_server.html +++ b/bookwyrm/templates/settings/federated_server.html @@ -71,14 +71,34 @@
-

{% trans "Notes" %}

-

{{ server.notes }}

+
+
+

{% trans "Notes" %}

+
+
+ {% trans "Edit" as button_text %} + {% include 'snippets/toggle/open_button.html' with text=button_text icon="pencil" controls_text="edit-notes" %} +
+
+ {% if server.notes %} +

{{ server.notes }}

+ {% endif %} + + {% csrf_token %} +

+ + +

+ + {% trans "Cancel" as button_text %} + {% include 'snippets/toggle/close_button.html' with text=button_text controls_text="edit-notes" %} +

{% trans "Actions" %}

{% if server.status != 'blocked' %} -
+ {% csrf_token %}

{% trans "All users from this instance will be deactivated." %}

diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 1ba181431..b31aaf094 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -64,7 +64,7 @@ class FederationViews(TestCase): self.assertEqual(server.status, "federated") - view = views.FederatedServer.as_view() + view = views.federation.block_server request = self.factory.post("") request.user = self.local_user request.user.is_superuser = True diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index f2d43a13c..c5c528002 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -68,6 +68,11 @@ urlpatterns = [ views.FederatedServer.as_view(), name="settings-federated-server", ), + re_path( + r"^settings/federation/(?P\d+)/block?$", + views.federation.block_server, + name="settings-federated-server-block", + ), re_path( r"^settings/federation/(?P\d+)/unblock?$", views.federation.unblock_server, diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 2dc7a1c6c..d7bc4e130 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -5,7 +5,8 @@ from .block import Block, unblock from .books import Book, EditBook, ConfirmEditBook, Editions from .books import upload_cover, add_description, switch_edition, resolve_book from .directory import Directory -from .federation import Federation, FederatedServer, AddFederatedServer, unblock_server +from .federation import Federation, FederatedServer, AddFederatedServer +from .federation import block_server, unblock_server from .feed import DirectMessage, Feed, Replies, Status from .follow import follow, unfollow from .follow import accept_follow_request, delete_follow_request diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 8a21c5a5e..f34f7d191 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -88,12 +88,24 @@ class FederatedServer(View): return TemplateResponse(request, "settings/federated_server.html", data) def post(self, request, server): # pylint: disable=unused-argument - """ block a server """ + """ update note """ server = get_object_or_404(models.FederatedServer, id=server) - server.block() + server.notes = request.POST.get("notes") + server.save() return redirect("settings-federated-server", server.id) +@login_required +@require_POST +@permission_required("bookwyrm.control_federation", raise_exception=True) +# pylint: disable=unused-argument +def block_server(request, server): + """ block a server """ + server = get_object_or_404(models.FederatedServer, id=server) + server.block() + return redirect("settings-federated-server", server.id) + + @login_required @require_POST @permission_required("bookwyrm.control_federation", raise_exception=True) From db4519b2e13be98f665c1d97efb9fce006f3bd9c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 10:45:08 -0700 Subject: [PATCH 33/39] Fixes visible_to_user check for non-federated objs why did this cause a problem _now_?? --- bookwyrm/models/activitypub_mixin.py | 30 ---------- bookwyrm/models/base_model.py | 30 ++++++++++ .../tests/models/test_activitypub_mixin.py | 60 ------------------- bookwyrm/tests/models/test_base_model.py | 60 +++++++++++++++++++ 4 files changed, 90 insertions(+), 90 deletions(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index ec4c89dfc..ce16460e6 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -83,36 +83,6 @@ class ActivitypubMixin: super().__init__(*args, **kwargs) - def visible_to_user(self, viewer): - """ is a user authorized to view an object? """ - # make sure this is an object with privacy owned by a user - if not hasattr(self, "user") or not hasattr(self, "privacy"): - return None - - # viewer can't see it if the object's owner blocked them - if viewer in self.user.blocks.all(): - return False - - # you can see your own posts and any public or unlisted posts - if viewer == self.user or self.privacy in ["public", "unlisted"]: - return True - - # you can see the followers only posts of people you follow - if ( - self.privacy == "followers" - and self.user.followers.filter(id=viewer.id).first() - ): - return True - - # you can see dms you are tagged in - if hasattr(self, "mention_users"): - if ( - self.privacy == "direct" - and self.mention_users.filter(id=viewer.id).first() - ): - return True - return False - @classmethod def find_existing_by_remote_id(cls, remote_id): """ look up a remote id in the db """ diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index cb2fc851e..261c96868 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -31,6 +31,36 @@ class BookWyrmModel(models.Model): """ how to link to this object in the local app """ return self.get_remote_id().replace("https://%s" % DOMAIN, "") + def visible_to_user(self, viewer): + """ is a user authorized to view an object? """ + # make sure this is an object with privacy owned by a user + if not hasattr(self, "user") or not hasattr(self, "privacy"): + return None + + # viewer can't see it if the object's owner blocked them + if viewer in self.user.blocks.all(): + return False + + # you can see your own posts and any public or unlisted posts + if viewer == self.user or self.privacy in ["public", "unlisted"]: + return True + + # you can see the followers only posts of people you follow + if ( + self.privacy == "followers" + and self.user.followers.filter(id=viewer.id).first() + ): + return True + + # you can see dms you are tagged in + if hasattr(self, "mention_users"): + if ( + self.privacy == "direct" + and self.mention_users.filter(id=viewer.id).first() + ): + return True + return False + @receiver(models.signals.post_save) # pylint: disable=unused-argument diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 4e4e9c9f0..0d1acd978 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -44,66 +44,6 @@ class ActivitypubMixins(TestCase): "published": "2020-12-04T17:52:22.623807+00:00", } - def test_object_visible_to_user(self, _): - """ does a user have permission to view an object """ - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="public" - ) - self.assertTrue(obj.visible_to_user(self.local_user)) - - obj = models.Shelf.objects.create( - name="test", user=self.remote_user, privacy="unlisted" - ) - self.assertTrue(obj.visible_to_user(self.local_user)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="followers" - ) - self.assertFalse(obj.visible_to_user(self.local_user)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="direct" - ) - self.assertFalse(obj.visible_to_user(self.local_user)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="direct" - ) - obj.mention_users.add(self.local_user) - self.assertTrue(obj.visible_to_user(self.local_user)) - - def test_object_visible_to_user_follower(self, _): - """ what you can see if you follow a user """ - self.remote_user.followers.add(self.local_user) - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="followers" - ) - self.assertTrue(obj.visible_to_user(self.local_user)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="direct" - ) - self.assertFalse(obj.visible_to_user(self.local_user)) - - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="direct" - ) - obj.mention_users.add(self.local_user) - self.assertTrue(obj.visible_to_user(self.local_user)) - - def test_object_visible_to_user_blocked(self, _): - """ you can't see it if they block you """ - self.remote_user.blocks.add(self.local_user) - obj = models.Status.objects.create( - content="hi", user=self.remote_user, privacy="public" - ) - self.assertFalse(obj.visible_to_user(self.local_user)) - - obj = models.Shelf.objects.create( - name="test", user=self.remote_user, privacy="unlisted" - ) - self.assertFalse(obj.visible_to_user(self.local_user)) - # ActivitypubMixin def test_to_activity(self, _): """ model to ActivityPub json """ diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index 25a2e7ee6..30335b1c6 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -42,3 +42,63 @@ class BaseModel(TestCase): instance.remote_id = None base_model.set_remote_id(None, instance, False) self.assertIsNone(instance.remote_id) + + def test_object_visible_to_user(self, _): + """ does a user have permission to view an object """ + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="public" + ) + self.assertTrue(obj.visible_to_user(self.local_user)) + + obj = models.Shelf.objects.create( + name="test", user=self.remote_user, privacy="unlisted" + ) + self.assertTrue(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="followers" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="direct" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="direct" + ) + obj.mention_users.add(self.local_user) + self.assertTrue(obj.visible_to_user(self.local_user)) + + def test_object_visible_to_user_follower(self, _): + """ what you can see if you follow a user """ + self.remote_user.followers.add(self.local_user) + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="followers" + ) + self.assertTrue(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="direct" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="direct" + ) + obj.mention_users.add(self.local_user) + self.assertTrue(obj.visible_to_user(self.local_user)) + + def test_object_visible_to_user_blocked(self, _): + """ you can't see it if they block you """ + self.remote_user.blocks.add(self.local_user) + obj = models.Status.objects.create( + content="hi", user=self.remote_user, privacy="public" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) + + obj = models.Shelf.objects.create( + name="test", user=self.remote_user, privacy="unlisted" + ) + self.assertFalse(obj.visible_to_user(self.local_user)) From 08586e348c3733321898cfa23dceba1e52f916a3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 10:55:13 -0700 Subject: [PATCH 34/39] Adds users to base model mock --- bookwyrm/tests/models/test_base_model.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index 30335b1c6..a7d705df3 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -1,4 +1,5 @@ """ testing models """ +from unittest.mock import patch from django.test import TestCase from bookwyrm import models @@ -9,6 +10,22 @@ from bookwyrm.settings import DOMAIN class BaseModel(TestCase): """ functionality shared across models """ + def setUp(self): + """ shared data """ + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + def test_remote_id(self): """ these should be generated """ instance = base_model.BookWyrmModel() From c8812c48c1eb2a5266d88afcaec9e7e994c9debf Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 11:58:00 -0700 Subject: [PATCH 35/39] Use local user in test --- bookwyrm/tests/models/test_base_model.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index a7d705df3..bbc6c4889 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -35,11 +35,8 @@ class BaseModel(TestCase): def test_remote_id_with_user(self): """ format of remote id when there's a user object """ - user = models.User.objects.create_user( - "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" - ) instance = base_model.BookWyrmModel() - instance.user = user + instance.user = self.local_user instance.id = 1 expected = instance.get_remote_id() self.assertEqual(expected, "https://%s/user/mouse/bookwyrmmodel/1" % DOMAIN) From 8f3601d4cdfb0ea6a1dcd2f34250711ffd19c1b1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 12 Apr 2021 06:44:50 -0700 Subject: [PATCH 36/39] Adds redis mocks to base model tests --- bookwyrm/tests/models/test_base_model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index bbc6c4889..442f98ca1 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -57,6 +57,7 @@ class BaseModel(TestCase): base_model.set_remote_id(None, instance, False) self.assertIsNone(instance.remote_id) + @patch("bookwyrm.activitystreams.ActivityStream.add_status") def test_object_visible_to_user(self, _): """ does a user have permission to view an object """ obj = models.Status.objects.create( @@ -85,6 +86,7 @@ class BaseModel(TestCase): obj.mention_users.add(self.local_user) self.assertTrue(obj.visible_to_user(self.local_user)) + @patch("bookwyrm.activitystreams.ActivityStream.add_status") def test_object_visible_to_user_follower(self, _): """ what you can see if you follow a user """ self.remote_user.followers.add(self.local_user) @@ -104,6 +106,7 @@ class BaseModel(TestCase): obj.mention_users.add(self.local_user) self.assertTrue(obj.visible_to_user(self.local_user)) + @patch("bookwyrm.activitystreams.ActivityStream.add_status") def test_object_visible_to_user_blocked(self, _): """ you can't see it if they block you """ self.remote_user.blocks.add(self.local_user) From 93fe0910348aa58beac6b26944b7bb4f9fc523c1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 12 Apr 2021 09:51:10 -0700 Subject: [PATCH 37/39] Adds field for user deactivation reason --- .../0066_user_deactivation_reason.py | 18 ++++++++++++++++++ bookwyrm/models/user.py | 13 +++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 bookwyrm/migrations/0066_user_deactivation_reason.py diff --git a/bookwyrm/migrations/0066_user_deactivation_reason.py b/bookwyrm/migrations/0066_user_deactivation_reason.py new file mode 100644 index 000000000..292f8306e --- /dev/null +++ b/bookwyrm/migrations/0066_user_deactivation_reason.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.8 on 2021-04-12 15:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0065_merge_20210411_1702'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='deactivation_reason', + field=models.CharField(blank=True, choices=[('self_deletion', 'Self Deletion'), ('moderator_deletion', 'Moderator Deletion'), ('domain_block', 'Domain Block')], max_length=255, null=True), + ), + ] diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index c244d4aed..15ceb19bd 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -24,6 +24,16 @@ from .federated_server import FederatedServer from . import fields, Review +DeactivationReason = models.TextChoices( + "DeactivationReason", + [ + "self_deletion", + "moderator_deletion", + "domain_block", + ], +) + + class User(OrderedCollectionPageMixin, AbstractUser): """ a user who wants to read books """ @@ -111,6 +121,9 @@ class User(OrderedCollectionPageMixin, AbstractUser): default=str(pytz.utc), max_length=255, ) + deactivation_reason = models.CharField( + max_length=255, choices=DeactivationReason.choices, null=True, blank=True + ) name_field = "username" property_fields = [("following_link", "following")] From 878b3c6fe8d6add1982a8725e44e2937345c5435 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 12 Apr 2021 10:13:38 -0700 Subject: [PATCH 38/39] Only reactivate appropriate users when undoing domain block --- .../0066_user_deactivation_reason.py | 17 +++-- bookwyrm/models/federated_server.py | 9 ++- .../tests/models/test_federated_server.py | 67 +++++++++++++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 bookwyrm/tests/models/test_federated_server.py diff --git a/bookwyrm/migrations/0066_user_deactivation_reason.py b/bookwyrm/migrations/0066_user_deactivation_reason.py index 292f8306e..bb3173a7c 100644 --- a/bookwyrm/migrations/0066_user_deactivation_reason.py +++ b/bookwyrm/migrations/0066_user_deactivation_reason.py @@ -6,13 +6,22 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0065_merge_20210411_1702'), + ("bookwyrm", "0065_merge_20210411_1702"), ] operations = [ migrations.AddField( - model_name='user', - name='deactivation_reason', - field=models.CharField(blank=True, choices=[('self_deletion', 'Self Deletion'), ('moderator_deletion', 'Moderator Deletion'), ('domain_block', 'Domain Block')], max_length=255, null=True), + model_name="user", + name="deactivation_reason", + field=models.CharField( + blank=True, + choices=[ + ("self_deletion", "Self Deletion"), + ("moderator_deletion", "Moderator Deletion"), + ("domain_block", "Domain Block"), + ], + max_length=255, + null=True, + ), ), ] diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index d2b823552..aa2b2f6af 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -30,15 +30,18 @@ class FederatedServer(BookWyrmModel): self.save() # deactivate all associated users - self.user_set.update(is_active=False) + self.user_set.filter(is_active=True).update( + is_active=False, deactivation_reason="domain_block" + ) def unblock(self): """ unblock a server """ self.status = "federated" self.save() - # TODO: only reactivate users as appropriate - self.user_set.update(is_active=True) + self.user_set.filter(deactivation_reason="domain_block").update( + is_active=True, deactivation_reason=None + ) @classmethod def is_blocked(cls, url): diff --git a/bookwyrm/tests/models/test_federated_server.py b/bookwyrm/tests/models/test_federated_server.py new file mode 100644 index 000000000..4e9e8b686 --- /dev/null +++ b/bookwyrm/tests/models/test_federated_server.py @@ -0,0 +1,67 @@ +""" testing models """ +from unittest.mock import patch +from django.test import TestCase + +from bookwyrm import models + + +class FederatedServer(TestCase): + """ federate server management """ + + def setUp(self): + """ we'll need a user """ + self.server = models.FederatedServer.objects.create(server_name="test.server") + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + federated_server=self.server, + local=False, + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) + self.inactive_remote_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.com", + "nutriaword", + federated_server=self.server, + local=False, + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", + is_active=False, + deactivation_reason="self_deletion", + ) + + def test_block_unblock(self): + """ block a server and all users on it """ + self.assertEqual(self.server.status, "federated") + self.assertTrue(self.remote_user.is_active) + self.assertFalse(self.inactive_remote_user.is_active) + + self.server.block() + + self.assertEqual(self.server.status, "blocked") + self.remote_user.refresh_from_db() + self.assertFalse(self.remote_user.is_active) + self.assertEqual(self.remote_user.deactivation_reason, "domain_block") + + self.inactive_remote_user.refresh_from_db() + self.assertFalse(self.inactive_remote_user.is_active) + self.assertEqual(self.inactive_remote_user.deactivation_reason, "self_deletion") + + # UNBLOCK + self.server.unblock() + + self.assertEqual(self.server.status, "federated") + # user blocked in deactivation is reactivated + self.remote_user.refresh_from_db() + self.assertTrue(self.remote_user.is_active) + self.assertIsNone(self.remote_user.deactivation_reason) + + # deleted user remains deleted + self.inactive_remote_user.refresh_from_db() + self.assertFalse(self.inactive_remote_user.is_active) + self.assertEqual(self.inactive_remote_user.deactivation_reason, "self_deletion") From bb352439d34d96344af6acd277e77cc0c69e50c3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 12 Apr 2021 10:27:29 -0700 Subject: [PATCH 39/39] Fixes views test for federation unblock --- bookwyrm/tests/views/test_federation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index b31aaf094..4dc5d048f 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -83,6 +83,7 @@ class FederationViews(TestCase): ) self.remote_user.federated_server = server self.remote_user.is_active = False + self.remote_user.deactivation_reason = "domain_block" self.remote_user.save() request = self.factory.post("")