mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-22 09:31:08 +00:00
Merge pull request #881 from mouse-reeve/domain-block
Block federation at the domain level
This commit is contained in:
commit
a1e28d810d
30 changed files with 753 additions and 167 deletions
|
@ -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,
|
||||
|
|
|
@ -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 = ["remote_id"]
|
||||
|
|
|
@ -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()
|
||||
|
|
37
bookwyrm/migrations/0063_auto_20210407_1827.py
Normal file
37
bookwyrm/migrations/0063_auto_20210407_1827.py
Normal file
|
@ -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,
|
||||
),
|
||||
),
|
||||
]
|
13
bookwyrm/migrations/0064_merge_20210410_1633.py
Normal file
13
bookwyrm/migrations/0064_merge_20210410_1633.py
Normal file
|
@ -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 = []
|
13
bookwyrm/migrations/0065_merge_20210411_1702.py
Normal file
13
bookwyrm/migrations/0065_merge_20210411_1702.py
Normal file
|
@ -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 = []
|
27
bookwyrm/migrations/0066_user_deactivation_reason.py
Normal file
27
bookwyrm/migrations/0066_user_deactivation_reason.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# 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,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -153,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,17 +1,51 @@
|
|||
""" connections to external ActivityPub servers """
|
||||
from urllib.parse import urlparse
|
||||
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)
|
||||
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 """
|
||||
self.status = "blocked"
|
||||
self.save()
|
||||
|
||||
# TODO: blocked servers
|
||||
# deactivate all associated users
|
||||
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()
|
||||
|
||||
self.user_set.filter(deactivation_reason="domain_block").update(
|
||||
is_active=True, deactivation_reason=None
|
||||
)
|
||||
|
||||
@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()
|
||||
|
|
|
@ -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")]
|
||||
|
@ -138,7 +151,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
|
||||
|
||||
|
|
|
@ -6,7 +6,14 @@
|
|||
{% block content %}
|
||||
|
||||
<header class="block column is-offset-one-quarter pl-1">
|
||||
<h1 class="title">{% block header %}{% endblock %}</h1>
|
||||
<div class="columns is-mobile">
|
||||
<div class="column">
|
||||
<h1 class="title">{% block header %}{% endblock %}</h1>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
{% block edit-button %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="block columns">
|
||||
|
|
58
bookwyrm/templates/settings/edit_server.html
Normal file
58
bookwyrm/templates/settings/edit_server.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add server" %}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
{% trans "Add server" %}
|
||||
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to server list" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
|
||||
<form method="POST" action="{% url 'settings-add-federated-server' %}">
|
||||
{% csrf_token %}
|
||||
<div class="columns">
|
||||
<div class="column is-half">
|
||||
<div>
|
||||
<label class="label" for="id_server_name">{% trans "Instance:" %}</label>
|
||||
<input type="text" name="server_name" maxlength="255" class="input" required id="id_server_name" value="{{ form.server_name.value|default:'' }}" placeholder="domain.com">
|
||||
{% for error in form.server_name.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="id_status">{% trans "Status:" %}</label>
|
||||
<div class="select">
|
||||
<select name="status" class="" id="id_status">
|
||||
<option value="federated" {% if form.status.value == "federated" or not form.status.value %}selected=""{% endif %}>{% trans "Federated" %}</option>
|
||||
<option value="blocked" {% if form.status.value == "blocked" %}selected{% endif %}>{% trans "Blocked" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
<div>
|
||||
<label class="label" for="id_application_type">{% trans "Software:" %}</label>
|
||||
<input type="text" name="application_type" maxlength="255" class="input" id="id_application_type" value="{{ form.application_type.value|default:'' }}">
|
||||
{% for error in form.application_type.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="id_application_version">{% trans "Version:" %}</label>
|
||||
<input type="text" name="application_version" maxlength="255" class="input" id="id_application_version" value="{{ form.application_version.value|default:'' }}">
|
||||
{% for error in form.application_version.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<label class="label" for="id_notes">{% trans "Notes:" %}</label>
|
||||
<textarea name="notes" cols="None" rows="None" class="textarea" id="id_notes">{{ form.notes.value|default:'' }}</textarea>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="button is-primary">{% trans "Save" %}</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -4,64 +4,112 @@
|
|||
|
||||
{% block header %}
|
||||
{{ server.server_name }}
|
||||
|
||||
{% if server.status == "blocked" %}<span class="icon icon-x has-text-danger is-size-5" title="{% trans 'Blocked' %}"><span class="is-sr-only">{% trans "Blocked" %}</span></span>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to server list" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<div class="columns">
|
||||
<section class="column is-half content">
|
||||
<h2 class="title is-4">{% trans "Details" %}</h2>
|
||||
<dl>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Software:" %}</dt>
|
||||
<dd>{{ server.application_type }}</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Version:" %}</dt>
|
||||
<dd>{{ server.application_version }}</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Status:" %}</dt>
|
||||
<dd>{{ server.status }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="column is-half content">
|
||||
<h2 class="title is-4">{% trans "Activity" %}</h2>
|
||||
<dl>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Users:" %}</dt>
|
||||
<dd>
|
||||
{{ users.count }}
|
||||
{% if server.user_set.count %}(<a href="{% url 'settings-users' %}?server={{ server.id }}">{% trans "View all" %}</a>){% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Reports:" %}</dt>
|
||||
<dd>
|
||||
{{ reports.count }}
|
||||
{% if reports.count %}(<a href="{% url 'settings-reports' %}?server={{ server.id }}">{% trans "View all" %}</a>){% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Followed by us:" %}</dt>
|
||||
<dd>
|
||||
{{ followed_by_us.count }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Followed by them:" %}</dt>
|
||||
<dd>
|
||||
{{ followed_by_them.count }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Blocked by us:" %}</dt>
|
||||
<dd>
|
||||
{{ blocked_by_us.count }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="block content">
|
||||
<h2 class="title is-4">{% trans "Details" %}</h2>
|
||||
<dl>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Software:" %}</dt>
|
||||
<dd>{{ server.application_type }}</dd>
|
||||
<header class="columns is-mobile">
|
||||
<div class="column">
|
||||
<h2 class="title is-4 mb-0">{% trans "Notes" %}</h2>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Version:" %}</dt>
|
||||
<dd>{{ server.application_version }}</dd>
|
||||
<div class="column is-narrow">
|
||||
{% trans "Edit" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon="pencil" controls_text="edit-notes" %}
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Status:" %}</dt>
|
||||
<dd>Federated</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</header>
|
||||
{% if server.notes %}
|
||||
<p id="hide-edit-notes">{{ server.notes }}</p>
|
||||
{% endif %}
|
||||
<form class="box is-hidden" method="POST" action="{% url 'settings-federated-server' server.id %}" id="edit-notes">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<label class="is-sr-only" for="id_notes">Notes:</label>
|
||||
<textarea name="notes" cols="None" rows="None" class="textarea" id="id_notes">{{ server.notes|default:"" }}</textarea>
|
||||
</p>
|
||||
<button type="submit" class="button is-primary">{% trans "Save" %}</button>
|
||||
{% trans "Cancel" as button_text %}
|
||||
{% include 'snippets/toggle/close_button.html' with text=button_text controls_text="edit-notes" %}
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="block content">
|
||||
<h2 class="title is-4">{% trans "Activity" %}</h2>
|
||||
<dl>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Users:" %}</dt>
|
||||
<dd>
|
||||
{{ users.count }}
|
||||
{% if server.user_set.count %}(<a href="{% url 'settings-users' %}?server={{ server.id }}">{% trans "View all" %}</a>){% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Reports:" %}</dt>
|
||||
<dd>
|
||||
{{ reports.count }}
|
||||
{% if reports.count %}(<a href="{% url 'settings-reports' %}?server={{ server.id }}">{% trans "View all" %}</a>){% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Followed by us:" %}</dt>
|
||||
<dd>
|
||||
{{ followed_by_us.count }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Followed by them:" %}</dt>
|
||||
<dd>
|
||||
{{ followed_by_them.count }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Blocked by us:" %}</dt>
|
||||
<dd>
|
||||
{{ blocked_by_us.count }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<h2 class="title is-4">{% trans "Actions" %}</h2>
|
||||
{% if server.status != 'blocked' %}
|
||||
<form class="block" method="post" action="{% url 'settings-federated-server-block' server.id %}">
|
||||
{% csrf_token %}
|
||||
<button class="button is-danger">{% trans "Block" %}</button>
|
||||
<p class="help">{% trans "All users from this instance will be deactivated." %}</p>
|
||||
</form>
|
||||
{% else %}
|
||||
<form class="block" method="post" action="{% url 'settings-federated-server-unblock' server.id %}">
|
||||
{% csrf_token %}
|
||||
<button class="button">{% trans "Un-block" %}</button>
|
||||
<p class="help">{% trans "All users from this instance will be re-activated." %}</p>
|
||||
</form>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,8 +4,15 @@
|
|||
|
||||
{% block header %}{% trans "Federated Servers" %}{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{% block edit-button %}
|
||||
<a href="{% url 'settings-add-federated-server' %}">
|
||||
<span class="icon icon-plus" title="{% trans 'Add server' %}">
|
||||
<span class="is-sr-only">{% trans "Add server" %}</span>
|
||||
</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<table class="table is-striped">
|
||||
<tr>
|
||||
{% url 'settings-federation' as url %}
|
||||
|
|
|
@ -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()
|
||||
|
@ -18,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)
|
||||
|
@ -42,3 +56,66 @@ class BaseModel(TestCase):
|
|||
instance.remote_id = None
|
||||
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(
|
||||
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))
|
||||
|
||||
@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)
|
||||
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))
|
||||
|
||||
@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)
|
||||
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))
|
||||
|
|
67
bookwyrm/tests/models/test_federated_server.py
Normal file
67
bookwyrm/tests/models/test_federated_server.py
Normal file
|
@ -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")
|
|
@ -4,8 +4,9 @@ from unittest.mock import patch
|
|||
|
||||
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
|
||||
|
@ -15,6 +16,7 @@ class Inbox(TestCase):
|
|||
def setUp(self):
|
||||
""" basic user and book data """
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -106,3 +108,26 @@ class Inbox(TestCase):
|
|||
"/inbox", json.dumps(activity), content_type="application/json"
|
||||
)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
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_activity(activity))
|
||||
|
||||
models.FederatedServer.objects.create(
|
||||
server_name="mastodon.social", status="blocked"
|
||||
)
|
||||
self.assertTrue(views.inbox.is_blocked_activity(activity))
|
||||
|
|
|
@ -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):
|
||||
|
@ -44,3 +55,75 @@ class FederationViews(TestCase):
|
|||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
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
|
||||
self.remote_user.save()
|
||||
|
||||
self.assertEqual(server.status, "federated")
|
||||
|
||||
view = views.federation.block_server
|
||||
request = self.factory.post("")
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
|
||||
view(request, server.id)
|
||||
server.refresh_from_db()
|
||||
self.remote_user.refresh_from_db()
|
||||
self.assertEqual(server.status, "blocked")
|
||||
# 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.deactivation_reason = "domain_block"
|
||||
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
|
||||
view = views.AddFederatedServer.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_add_view_post_create(self):
|
||||
""" create a server entry """
|
||||
form = forms.ServerForm()
|
||||
form.data["server_name"] = "remote.server"
|
||||
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 = models.FederatedServer.objects.get()
|
||||
self.assertEqual(server.server_name, "remote.server")
|
||||
self.assertEqual(server.application_type, "coolsoft")
|
||||
self.assertEqual(server.status, "blocked")
|
||||
|
|
|
@ -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")
|
||||
|
@ -190,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(
|
||||
|
|
|
@ -68,6 +68,21 @@ urlpatterns = [
|
|||
views.FederatedServer.as_view(),
|
||||
name="settings-federated-server",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/federation/(?P<server>\d+)/block?$",
|
||||
views.federation.block_server,
|
||||
name="settings-federated-server-block",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/federation/(?P<server>\d+)/unblock?$",
|
||||
views.federation.unblock_server,
|
||||
name="settings-federated-server-unblock",
|
||||
),
|
||||
re_path(
|
||||
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"
|
||||
),
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
""" 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
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
|
||||
|
||||
|
@ -30,14 +31,38 @@ 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)
|
||||
data = {"servers": paginated.page(page), "sort": sort}
|
||||
|
||||
data = {
|
||||
"servers": paginated.page(page),
|
||||
"sort": sort,
|
||||
"form": forms.ServerForm(),
|
||||
}
|
||||
return TemplateResponse(request, "settings/federation.html", data)
|
||||
|
||||
|
||||
class AddFederatedServer(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),
|
||||
|
@ -61,3 +86,32 @@ class FederatedServer(View):
|
|||
),
|
||||
}
|
||||
return TemplateResponse(request, "settings/federated_server.html", data)
|
||||
|
||||
def post(self, request, server): # pylint: disable=unused-argument
|
||||
""" update note """
|
||||
server = get_object_or_404(models.FederatedServer, id=server)
|
||||
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)
|
||||
# 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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
""" incoming activities """
|
||||
import json
|
||||
import re
|
||||
from urllib.parse import urldefrag
|
||||
|
||||
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,25 @@ 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")
|
||||
if not user_agent:
|
||||
return False
|
||||
url = re.search(r"https?://{:s}/?".format(regex.domain), user_agent).group()
|
||||
return models.FederatedServer.is_blocked(url)
|
||||
|
||||
|
||||
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
|
||||
return models.FederatedServer.is_blocked(actor)
|
||||
|
||||
|
||||
@app.task
|
||||
def activity_task(activity_json):
|
||||
""" do something with this json we think is legit """
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 goal and not goal.visible_to_user(request.user):
|
||||
goal = None
|
||||
data = {
|
||||
"user": user,
|
||||
|
|
Loading…
Reference in a new issue