From 1901f7e6cb2f1111ba1a8754adcde0c8636c5c36 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 15:16:21 -0700 Subject: [PATCH 01/42] 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 8f7d903e..d7dc5d96 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 34bd2e1c..a4ae1e99 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/42] 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 d6101c87..a86a1652 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 13715bfb..edab3139 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/42] 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 edab3139..147d6514 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 464a207c..0405ebf5 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/42] 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 00000000..8229bf7c --- /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/42] 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 37cf00dd..889fdc32 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 a4ae1e99..9e5aa019 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/42] 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 a60ea432..874e277c 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 889fdc32..f15b99f1 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 9e5aa019..387efe9f 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/42] 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 874e277c..3783417c 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/42] 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 00000000..97f9e671 --- /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/42] 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 8229bf7c..00000000 --- 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 00000000..0bd0f2ae --- /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 97f9e671..00000000 --- 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 d7dc5d96..ea9920a0 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/42] 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 b159a89e..55bb6650 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 00000000..a16aac2d --- /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 9340da9e..4f71a228 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 696d7a20..1106bc12 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 0405ebf5..96962768 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/42] 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 55bb6650..7c41323c 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 a16aac2d..ffb43043 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 00000000..4b9f6ef0 --- /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/42] 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 ffb43043..00000000 --- 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 00000000..6510222d --- /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 1106bc12..da5ed084 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 4b9f6ef0..b86c3d03 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 46398806..35856a4d 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 d053e971..404c2f31 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 96962768..bb33c5af 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/42] 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 6510222d..dc0de7e8 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 147d6514..5b41ea79 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 b86c3d03..00000000 --- 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 35856a4d..4cf6fd52 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 bb33c5af..09f5040d 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/42] 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 5b41ea79..0f18f6ac 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/42] 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 0f18f6ac..520ab5dc 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 09f5040d..83bed32d 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 387efe9f..7997bd5c 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/42] 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 00000000..77ad541e --- /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/42] 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 3783417c..591bb62a 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/42] 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 dc0de7e8..6ae22789 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 520ab5dc..37baa4dc 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 da5ed084..99afb541 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 591bb62a..01504ddb 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 4cf6fd52..398caade 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 404c2f31..de001b31 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 83bed32d..e4ade554 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/42] 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 01504ddb..53e5b10f 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/42] 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 ea9920a0..7283cdd8 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 37baa4dc..31e0ffb9 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 e4ade554..067fa0d7 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/42] 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 53e5b10f..38da37d3 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/42] 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 31e0ffb9..249d7106 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 38da37d3..fdafcad0 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 398caade..f2d43a13 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 de001b31..2dc7a1c6 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 067fa0d7..8a21c5a5 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/42] 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 fdafcad0..1ba18143 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/42] 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 deba6e69..50fb1ecc 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/42] 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 2483cc62..2fe5d825 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 7283cdd8..d2b82355 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 7997bd5c..99f9c556 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/42] 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 7d2bc42c..27d03832 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 b2939014172800d030621868805b5c5b907430c6 Mon Sep 17 00:00:00 2001 From: n2no1 <7995366-n2no1@users.noreply.gitlab.com> Date: Sat, 10 Apr 2021 20:18:57 -0400 Subject: [PATCH 27/42] add regression tests for published dates not being saved closes #894 --- bookwyrm/tests/views/test_book.py | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/bookwyrm/tests/views/test_book.py b/bookwyrm/tests/views/test_book.py index ade6131d..a0fa0367 100644 --- a/bookwyrm/tests/views/test_book.py +++ b/bookwyrm/tests/views/test_book.py @@ -47,6 +47,39 @@ class BookViews(TestCase): ) models.SiteSettings.objects.create() + def test_date_regression(self): + """ensure that creating a new book actually saves the published date fields + + this was initially a regression due to using a custom date picker tag + """ + first_published_date = "2021-04-20" + published_date = "2022-04-20" + self.local_user.groups.add(self.group) + view = views.EditBook.as_view() + form = forms.EditionForm( + { + "title": "New Title", + "last_edited_by": self.local_user.id, + "first_published_date": first_published_date, + "published_date": published_date, + } + ) + request = self.factory.post("", form.data) + request.user = self.local_user + + with patch("bookwyrm.connectors.connector_manager.local_search"): + result = view(request) + result.render() + + self.assertContains( + result, + f'', + ) + self.assertContains( + result, + f'', + ) + def test_book_page(self): """ there are so many views, this just makes sure it LOADS """ view = views.Book.as_view() From 26f16cf5a46680a52cea9d6e615312572223ea2f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 09:09:13 -0700 Subject: [PATCH 28/42] 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 8dce42e4..efe14f97 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 29/42] 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 efe14f97..2a5878d5 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 0d1acd97..4e4e9c9f 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 27d03832..2e5ed82d 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 cda11586..d5e64434 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 9c4e117c..1627d3da 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 2b6501ff..57c33437 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 7724cd13..cafc40b1 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 41d1f135..88899949 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 aba804d8..d2c9c2eb 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 30/42] 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 c519f76c..c244d4ae 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 31/42] 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 d2c9c2eb..d666f064 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 32/42] 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 00000000..2bdc425d --- /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 d38efa9a9d0aac0b966a128fe3b522d6741fc1dc Mon Sep 17 00:00:00 2001 From: Pablo Barton Date: Tue, 30 Mar 2021 09:28:13 -0400 Subject: [PATCH 33/42] Easier to deploy by avoiding merge conflicts in production --- .env.example => .env.dev.example | 13 +++++- .env.prod.example | 50 ++++++++++++++++++++ .gitignore | 3 ++ README.md | 40 ++++++++++------ bookwyrm/settings.py | 1 + certbot.sh | 19 ++++++++ docker-compose.yml | 2 + nginx/{default.conf => development} | 0 nginx/production | 72 +++++++++++++++++++++++++++++ 9 files changed, 186 insertions(+), 14 deletions(-) rename .env.example => .env.dev.example (75%) create mode 100644 .env.prod.example create mode 100644 certbot.sh rename nginx/{default.conf => development} (100%) create mode 100644 nginx/production diff --git a/.env.example b/.env.dev.example similarity index 75% rename from .env.example rename to .env.dev.example index 2397a5b1..5e605d74 100644 --- a/.env.example +++ b/.env.dev.example @@ -5,6 +5,7 @@ SECRET_KEY="7(2w1sedok=aznpq)ta1mc4i%4h=xx@hxwx*o57ctsuml0x%fr" DEBUG=true DOMAIN=your.domain.here +#EMAIL=your@email.here ## Leave unset to allow all hosts # ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" @@ -26,14 +27,24 @@ POSTGRES_HOST=db MAX_STREAM_LENGTH=200 REDIS_ACTIVITY_HOST=redis_activity REDIS_ACTIVITY_PORT=6379 +#REDIS_ACTIVITY_PASSWORD=redispassword345 -# Celery config with redis broker +# Redis as celery broker +#REDIS_BROKER_PORT=6379 +#REDIS_BROKER_PASSWORD=redispassword123 CELERY_BROKER=redis://redis_broker:6379/0 CELERY_RESULT_BACKEND=redis://redis_broker:6379/0 +FLOWER_PORT=8888 +#FLOWER_USER=mouse +#FLOWER_PASSWORD=changeme + EMAIL_HOST="smtp.mailgun.org" EMAIL_PORT=587 EMAIL_HOST_USER=mail@your.domain.here EMAIL_HOST_PASSWORD=emailpassword123 EMAIL_USE_TLS=true EMAIL_USE_SSL=false + +# Set this to true when initializing certbot for domain, false when not +CERTBOT_INIT=false diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 00000000..0013bf9d --- /dev/null +++ b/.env.prod.example @@ -0,0 +1,50 @@ +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY="7(2w1sedok=aznpq)ta1mc4i%4h=xx@hxwx*o57ctsuml0x%fr" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG=false + +DOMAIN=your.domain.here +EMAIL=your@email.here + +## Leave unset to allow all hosts +# ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" + +OL_URL=https://openlibrary.org + +## Database backend to use. +## Default is postgres, sqlite is for dev quickstart only (NOT production!!!) +BOOKWYRM_DATABASE_BACKEND=postgres + +MEDIA_ROOT=images/ + +POSTGRES_PASSWORD=securedbpassword123 +POSTGRES_USER=fedireads +POSTGRES_DB=fedireads +POSTGRES_HOST=db + +# Redis activity stream manager +MAX_STREAM_LENGTH=200 +REDIS_ACTIVITY_HOST=redis_activity +REDIS_ACTIVITY_PORT=6379 +REDIS_ACTIVITY_PASSWORD=redispassword345 + +# Redis as celery broker +REDIS_BROKER_PORT=6379 +REDIS_BROKER_PASSWORD=redispassword123 +CELERY_BROKER=redis://:${REDIS_BROKER_PASSWORD}@redis_broker:${REDIS_BROKER_PORT}/0 +CELERY_RESULT_BACKEND=redis://:${REDIS_BROKER_PASSWORD}@redis_broker:${REDIS_BROKER_PORT}/0 + +FLOWER_PORT=8888 +FLOWER_USER=mouse +FLOWER_PASSWORD=changeme + +EMAIL_HOST="smtp.mailgun.org" +EMAIL_PORT=587 +EMAIL_HOST_USER=mail@your.domain.here +EMAIL_HOST_PASSWORD=emailpassword123 +EMAIL_USE_TLS=true +EMAIL_USE_SSL=false + +# Set this to true when initializing certbot for domain, false when not +CERTBOT_INIT=false diff --git a/.gitignore b/.gitignore index 71fa61bf..cf88e987 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ #Node tools /node_modules/ + +#nginx +nginx/default.conf diff --git a/README.md b/README.md index 6dad4178..c11de3dc 100644 --- a/README.md +++ b/README.md @@ -91,10 +91,15 @@ Deployment ## Setting up the developer environment -Set up the environment file: +Set up the development environment file: ``` bash -cp .env.example .env +cp .env.dev.example .env +``` + +Set up nginx for development `nginx/default.conf`: +``` bash +cp nginx/development nginx/default.conf ``` For most testing, you'll want to use ngrok. Remember to set the DOMAIN in `.env` to your ngrok domain. @@ -108,7 +113,7 @@ docker-compose run --rm web python manage.py initdb docker-compose up ``` -Once the build is complete, you can access the instance at `localhost:1333` +Once the build is complete, you can access the instance at `http://localhost:1333` ### Editing static files If you edit the CSS or JavaScript, you will need to run Django's `collectstatic` command in order for your changes to have effect. You can do this by running: @@ -160,26 +165,35 @@ Instructions for running BookWyrm in production: - Get the application code: `git clone git@github.com:mouse-reeve/bookwyrm.git` -- Switch to the `production` branch +- Switch to the `production` branch: `git checkout production` -- Create your environment variables file - `cp .env.example .env` - - Add your domain, email address, SMTP credentials - - Set a secure redis password and secret key - - Set a secure database password for postgres +- Create your environment variables file, `cp .env.prod.example .env`, and update the following: + - `SECRET_KEY` | A difficult to guess, secret string of characers + - `DOMAIN` | Your web domain + - `EMAIL` | Email address to be used for certbot domain verification + - `POSTGRES_PASSWORD` | Set a secure password for the database + - `REDIS_ACTIVITY_PASSWORD` | Set a secure password for Redis Activity subsystem + - `REDIS_BROKER_PASSWORD` | Set a secure password for Redis queue broker subsystem + - `FLOWER_USER` | Your own username for accessing Flower queue monitor + - `FLOWER_PASSWORD` | Your own secure password for accessing Flower queue monitor - Update your nginx configuration in `nginx/default.conf` - Replace `your-domain.com` with your domain name - - If you aren't using the `www` subdomain, remove the www.your-domain.com version of the domain from the `server_name` in the first server block in `nginx/default.conf` and remove the `-d www.${DOMAIN}` flag at the end of the `certbot` command in `docker-compose.yml`. - - If you are running another web-server on your host machine, you will need to follow the [reverse-proxy instructions](#running-bookwyrm-behind-a-reverse-proxy) +- Configure nginx + - Make a copy of the production template config and set it for use in nginx `cp nginx/production nginx/default.conf` + - Update `nginx/default.conf`: + - Replace `your-domain.com` with your domain name + - If you aren't using the `www` subdomain, remove the www.your-domain.com version of the domain from the `server_name` in the first server block in `nginx/default.conf` and remove the `-d www.${DOMAIN}` flag at the end of the `certbot` command in `docker-compose.yml`. + - If you are running another web-server on your host machine, you will need to follow the [reverse-proxy instructions](#running-bookwyrm-behind-a-reverse-proxy) +- If you need to initialize your certbot for your domain, set `CERTBOT_INIT=true` in your `.env` file - Run the application (this should also set up a Certbot ssl cert for your domain) with `docker-compose up --build`, and make sure all the images build successfully - If you are running other services on your host machine, you may run into errors where services fail when attempting to bind to a port. See the [troubleshooting guide](#port-conflicts) for advice on resolving this. - When docker has built successfully, stop the process with `CTRL-C` -- Comment out the `command: certonly...` line in `docker-compose.yml`, and uncomment the following line (`command: renew ...`) so that the certificate will be automatically renewed. -- Uncomment the https redirect and `server` block in `nginx/default.conf` (lines 17-48). +- If you set `CERTBOT_INIT=true` earlier, set it now as `CERTBOT_INIT=false` so that certbot runs in renew mode - Run docker-compose in the background with: `docker-compose up -d` - Initialize the database with: `./bw-dev initdb` +- Set up schedule backups with cron that runs that `docker-compose exec db pg_dump -U ` and saves the backup to a safe location Congrats! You did it, go to your domain and enjoy the fruits of your labors. diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 146d4fff..ded119f8 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -98,6 +98,7 @@ WSGI_APPLICATION = "bookwyrm.wsgi.application" # redis/activity streams settings REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost") REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379) +REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD", None) MAX_STREAM_LENGTH = int(env("MAX_STREAM_LENGTH", 200)) STREAMS = ["home", "local", "federated"] diff --git a/certbot.sh b/certbot.sh new file mode 100644 index 00000000..6d2c3cd9 --- /dev/null +++ b/certbot.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +source .env; + +if [ "$CERTBOT_INIT" = "true" ] +then + certonly \ + --webroot \ + --webroot-path=/var/www/certbot \ + --email ${EMAIL} \ + --agree-tos \ + --no-eff-email \ + -d ${DOMAIN} \ + -d www.${DOMAIN} +else + renew \ + --webroot \ + --webroot-path \ + /var/www/certbot +fi diff --git a/docker-compose.yml b/docker-compose.yml index 3ee9037f..60816cc0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,8 @@ services: - pgdata:/var/lib/postgresql/data networks: - main + ports: + - 5432:5432 web: build: . env_file: .env diff --git a/nginx/default.conf b/nginx/development similarity index 100% rename from nginx/default.conf rename to nginx/development diff --git a/nginx/production b/nginx/production new file mode 100644 index 00000000..c5d83cbf --- /dev/null +++ b/nginx/production @@ -0,0 +1,72 @@ +upstream web { + server web:8000; +} + +server { + listen [::]:80; + listen 80; + + server_name your-domain.com www.your-domain.com; + + location ~ /.well-known/acme-challenge { + allow all; + root /var/www/certbot; + } + +# # redirect http to https +# return 301 https://your-domain.com$request_uri; +# } +# +# server { +# listen [::]:443 ssl http2; +# listen 443 ssl http2; +# +# server_name your-domain.com; +# +# # SSL code +# ssl_certificate /etc/nginx/ssl/live/your-domain.com/fullchain.pem; +# ssl_certificate_key /etc/nginx/ssl/live/your-domain.com/privkey.pem; +# +# location ~ /.well-known/acme-challenge { +# allow all; +# root /var/www/certbot; +# } +# +# location / { +# proxy_pass http://web; +# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +# proxy_set_header Host $host; +# proxy_redirect off; +# } +# +# location /images/ { +# alias /app/images/; +# } +# +# location /static/ { +# alias /app/static/; +# } +} + +# Reverse-Proxy server +# server { +# listen [::]:8001; +# listen 8001; + +# server_name your-domain.com www.your-domain.com; + +# location / { +# proxy_pass http://web; +# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +# proxy_set_header Host $host; +# proxy_redirect off; +# } + +# location /images/ { +# alias /app/images/; +# } + +# location /static/ { +# alias /app/static/; +# } +# } From 659986771f3ffde3c360754ce356dbcffa420cfe Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 11 Apr 2021 10:36:40 -0700 Subject: [PATCH 34/42] 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 249d7106..6996557d 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 1ba18143..b31aaf09 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 f2d43a13..c5c52800 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 2dc7a1c6..d7bc4e13 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 8a21c5a5..f34f7d19 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 35/42] 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 ec4c89df..ce16460e 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 cb2fc851..261c9686 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 4e4e9c9f..0d1acd97 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 25a2e7ee..30335b1c 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 36/42] 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 30335b1c..a7d705df 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 37/42] 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 a7d705df..bbc6c488 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 38/42] 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 bbc6c488..442f98ca 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 92e9ff6cff26ac76b20932aa4f45f203b7127406 Mon Sep 17 00:00:00 2001 From: n2no1 <7995366-n2no1@users.noreply.gitlab.com> Date: Mon, 12 Apr 2021 11:56:26 -0400 Subject: [PATCH 39/42] update links in README and {bookwyrm,celerywyrm}/settings.py files the link to celery's homepage has been dead for at least several months activitypub.rocks now supports tls (yay!) the links to django's docs were for older versions of django than what bookwyrm uses --- README.md | 4 ++-- bookwyrm/settings.py | 2 +- celerywyrm/settings.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6dad4178..f70dd2ad 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,8 @@ Since the project is still in its early stages, the features are growing every d Web backend - [Django](https://www.djangoproject.com/) web server - [PostgreSQL](https://www.postgresql.org/) database -- [ActivityPub](http://activitypub.rocks/) federation -- [Celery](http://celeryproject.org/) task queuing +- [ActivityPub](https://activitypub.rocks/) federation +- [Celery](https://docs.celeryproject.org/) task queuing - [Redis](https://redis.io/) task backend - [Redis (again)](https://redis.io/) activity stream manager diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 146d4fff..73159c71 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -166,7 +166,7 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/2.0/howto/static-files/ +# https://docs.djangoproject.com/en/3.1/howto/static-files/ PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) STATIC_URL = "/static/" diff --git a/celerywyrm/settings.py b/celerywyrm/settings.py index 952fe5b1..cd5b00ba 100644 --- a/celerywyrm/settings.py +++ b/celerywyrm/settings.py @@ -149,7 +149,7 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.0/howto/static-files/ +# https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = "/static/" STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static")) From 93fe0910348aa58beac6b26944b7bb4f9fc523c1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 12 Apr 2021 09:51:10 -0700 Subject: [PATCH 40/42] 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 00000000..292f8306 --- /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 c244d4ae..15ceb19b 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 41/42] 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 292f8306..bb3173a7 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 d2b82355..aa2b2f6a 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 00000000..4e9e8b68 --- /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 42/42] 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 b31aaf09..4dc5d048 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("")