Merge branch 'main' into pylint-update

This commit is contained in:
Mouse Reeve 2021-09-18 07:46:12 -07:00
commit be86fbcf4d
21 changed files with 268 additions and 24 deletions

View file

@ -24,5 +24,5 @@ jobs:
pip install pylint pip install pylint
- name: Analysing the code with pylint - name: Analysing the code with pylint
run: | run: |
pylint bookwyrm/ --ignore=migrations,tests --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801 pylint bookwyrm/ --ignore=migrations,tests --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801,C0209

View file

@ -311,6 +311,12 @@ class EmailBlocklistForm(CustomForm):
fields = ["domain"] fields = ["domain"]
class IPBlocklistForm(CustomForm):
class Meta:
model = models.IPBlocklist
fields = ["address"]
class ServerForm(CustomForm): class ServerForm(CustomForm):
class Meta: class Meta:
model = models.FederatedServer model = models.FederatedServer

View file

@ -0,0 +1,3 @@
""" look at all this nice middleware! """
from .timezone_middleware import TimezoneMiddleware
from .ip_middleware import IPBlocklistMiddleware

View file

@ -0,0 +1,16 @@
""" Block IP addresses """
from django.http import Http404
from bookwyrm import models
class IPBlocklistMiddleware:
"""check incoming traffic against an IP block-list"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
address = request.META.get("REMOTE_ADDR")
if models.IPBlocklist.objects.filter(address=address).exists():
raise Http404()
return self.get_response(request)

View file

@ -0,0 +1,38 @@
# Generated by Django 3.2.4 on 2021-09-17 18:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0096_merge_20210912_0044"),
]
operations = [
migrations.CreateModel(
name="IPBlocklist",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_date", models.DateTimeField(auto_now_add=True)),
("address", models.CharField(max_length=255, unique=True)),
("is_active", models.BooleanField(default=True)),
],
options={
"ordering": ("-created_date",),
},
),
migrations.AddField(
model_name="emailblocklist",
name="is_active",
field=models.BooleanField(default=True),
),
]

View file

@ -26,8 +26,8 @@ from .import_job import ImportJob, ImportItem
from .site import SiteSettings, SiteInvite from .site import SiteSettings, SiteInvite
from .site import PasswordReset, InviteRequest from .site import PasswordReset, InviteRequest
from .site import EmailBlocklist
from .announcement import Announcement from .announcement import Announcement
from .antispam import EmailBlocklist, IPBlocklist
cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass) cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
activity_models = { activity_models = {

View file

@ -0,0 +1,35 @@
""" Lets try NOT to sell viagra """
from django.db import models
from .user import User
class EmailBlocklist(models.Model):
"""blocked email addresses"""
created_date = models.DateTimeField(auto_now_add=True)
domain = models.CharField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
class Meta:
"""default sorting"""
ordering = ("-created_date",)
@property
def users(self):
"""find the users associated with this address"""
return User.objects.filter(email__endswith=f"@{self.domain}")
class IPBlocklist(models.Model):
"""blocked ip addresses"""
created_date = models.DateTimeField(auto_now_add=True)
address = models.CharField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
class Meta:
"""default sorting"""
ordering = ("-created_date",)

View file

@ -124,23 +124,6 @@ class PasswordReset(models.Model):
return "https://{}/password-reset/{}".format(DOMAIN, self.code) return "https://{}/password-reset/{}".format(DOMAIN, self.code)
class EmailBlocklist(models.Model):
"""blocked email addresses"""
created_date = models.DateTimeField(auto_now_add=True)
domain = models.CharField(max_length=255, unique=True)
class Meta:
"""default sorting"""
ordering = ("-created_date",)
@property
def users(self):
"""find the users associated with this address"""
return User.objects.filter(email__endswith=f"@{self.domain}")
# pylint: disable=unused-argument # pylint: disable=unused-argument
@receiver(models.signals.post_save, sender=SiteSettings) @receiver(models.signals.post_save, sender=SiteSettings)
def preview_image(instance, *args, **kwargs): def preview_image(instance, *args, **kwargs):

View file

@ -77,7 +77,8 @@ MIDDLEWARE = [
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"bookwyrm.timezone_middleware.TimezoneMiddleware", "bookwyrm.middleware.TimezoneMiddleware",
"bookwyrm.middleware.IPBlocklistMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]

View file

@ -378,6 +378,13 @@ input[type=file]::file-selector-button:hover {
right: 1em; right: 1em;
} }
/** Tooltips
******************************************************************************/
.tooltip {
width: 100%;
}
/** States /** States
******************************************************************************/ ******************************************************************************/

View file

@ -3,7 +3,7 @@
{% trans "Help" as button_text %} {% trans "Help" as button_text %}
{% include 'snippets/toggle/open_button.html' with text=button_text class="ml-3 is-rounded is-small is-white p-0 pb-1" icon="question-circle is-size-6" controls_text=controls_text controls_uid=controls_uid %} {% include 'snippets/toggle/open_button.html' with text=button_text class="ml-3 is-rounded is-small is-white p-0 pb-1" icon="question-circle is-size-6" controls_text=controls_text controls_uid=controls_uid %}
<aside class="notification is-hidden transition-y is-pulled-left mb-2" id="{{ controls_text }}{% if controls_uid %}-{{ controls_uid }}{% endif %}"> <aside class="tooltip notification is-hidden transition-y is-pulled-left mb-2" id="{{ controls_text }}{% if controls_uid %}-{{ controls_uid }}{% endif %}">
{% trans "Close" as button_text %} {% trans "Close" as button_text %}
{% include 'snippets/toggle/close_button.html' with label=button_text class="delete" nonbutton=True controls_text=controls_text controls_uid=controls_uid %} {% include 'snippets/toggle/close_button.html' with label=button_text class="delete" nonbutton=True controls_text=controls_text controls_uid=controls_uid %}

View file

@ -8,7 +8,7 @@
{% block form %} {% block form %}
<form name="add-domain" method="post" action="{% url 'settings-email-blocks' %}"> <form name="add-domain" method="post" action="{% url 'settings-email-blocks' %}">
{% csrf_token %} {% csrf_token %}
<label class="label" for="id_event_date">{% trans "Domain:" %}</label> <label class="label" for="id_domain">{% trans "Domain:" %}</label>
<div class="field has-addons"> <div class="field has-addons">
<div class="control"> <div class="control">
<div class="button is-disabled">@</div> <div class="button is-disabled">@</div>

View file

@ -0,0 +1,36 @@
{% extends 'components/inline_form.html' %}
{% load i18n %}
{% block header %}
{% trans "Add IP address" %}
{% endblock %}
{% block form %}
<form name="add-address" method="post" action="{% url 'settings-ip-blocks' %}">
<div class="block">
{% trans "Use IP address blocks with caution, and consider using blocks only temporarily, as IP addresses are often shared or change hands. If you block your own IP, you will not be able to access this page." %}
</div>
{% csrf_token %}
<div class="field">
<label class="label" for="id_address">
{% trans "IP Address:" %}
</label>
</div>
<div class="field">
<input type="text" name="address" maxlength="255" class="input" required="" id="id_address" placeholder="190.0.2.0/24">
</div>
{% for error in form.address.errors %}
<p class="help is-danger">{{ error | escape }}</p>
{% endfor %}
<div class="field">
<div class="control">
<button type="submit" class="button is-primary">{% trans "Add" %}</button>
</div>
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,47 @@
{% extends 'settings/layout.html' %}
{% load i18n %}
{% load humanize %}
{% block title %}{% trans "IP Address Blocklist" %}{% endblock %}
{% block header %}{% trans "IP Address Blocklist" %}{% endblock %}
{% block edit-button %}
{% trans "Add IP address" as button_text %}
{% include 'snippets/toggle/open_button.html' with controls_text="add_address" icon_with_text="plus" text=button_text focus="add_address_header" %}
{% endblock %}
{% block panel %}
{% include 'settings/ip_address_form.html' with controls_text="add_address" class="block" %}
<p class="notification block">
{% trans "Any traffic from this IP address will get a 404 response when trying to access any part of the application." %}
</p>
<table class="table is-striped is-fullwidth">
<tr>
<th>
{% trans "Address" %}
</th>
<th>
{% trans "Options" %}
</th>
</tr>
{% for address in addresses %}
<tr>
<td>{{ address.address }}</td>
<td>
<form name="remove-{{ address.id }}" action="{% url 'settings-ip-blocks-delete' address.id %}" method="post">
{% csrf_token %}
{% trans "Delete" as button_text %}
<button class="button" type="submit">
<span class="icon icon-x" title="{{ button_text }}" aria-hidden="true"></span>
<span class="is-hidden-mobile">{{ button_text }}</span>
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends 'components/tooltip.html' %}
{% load i18n %}
{% block tooltip_content %}
{% trans "You can block IP ranges using CIDR syntax." %}
{% endblock %}

View file

@ -58,6 +58,10 @@
{% url 'settings-email-blocks' as url %} {% url 'settings-email-blocks' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Email Blocklist" %}</a> <a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Email Blocklist" %}</a>
</li> </li>
<li>
{% url 'settings-ip-blocks' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "IP Address Blocklist" %}</a>
</li>
</ul> </ul>
{% endif %} {% endif %}
{% if perms.bookwyrm.edit_instance_settings %} {% if perms.bookwyrm.edit_instance_settings %}

View file

@ -156,6 +156,16 @@ urlpatterns = [
views.EmailBlocklist.as_view(), views.EmailBlocklist.as_view(),
name="settings-email-blocks-delete", name="settings-email-blocks-delete",
), ),
re_path(
r"^settings/ip-blocklist/?$",
views.IPBlocklist.as_view(),
name="settings-ip-blocks",
),
re_path(
r"^settings/ip-blocks/(?P<block_id>\d+)/delete/?$",
views.IPBlocklist.as_view(),
name="settings-ip-blocks-delete",
),
# moderation # moderation
re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"), re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"),
re_path( re_path(

View file

@ -5,6 +5,7 @@ from .admin.federation import Federation, FederatedServer
from .admin.federation import AddFederatedServer, ImportServerBlocklist from .admin.federation import AddFederatedServer, ImportServerBlocklist
from .admin.federation import block_server, unblock_server from .admin.federation import block_server, unblock_server
from .admin.email_blocklist import EmailBlocklist from .admin.email_blocklist import EmailBlocklist
from .admin.ip_blocklist import IPBlocklist
from .admin.invite import ManageInvites, Invite, InviteRequest from .admin.invite import ManageInvites, Invite, InviteRequest
from .admin.invite import ManageInviteRequests, ignore_invite_request from .admin.invite import ManageInviteRequests, ignore_invite_request
from .admin.reports import ( from .admin.reports import (

View file

@ -1,4 +1,4 @@
""" moderation via flagged posts and users """ """ Manage email blocklist"""
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
@ -14,7 +14,7 @@ from bookwyrm import forms, models
name="dispatch", name="dispatch",
) )
class EmailBlocklist(View): class EmailBlocklist(View):
"""Block users by email address""" """Block registration by email address"""
def get(self, request): def get(self, request):
"""view and compose blocks""" """view and compose blocks"""

View file

@ -0,0 +1,49 @@
""" Manage IP blocklist """
from django.contrib.auth.decorators import login_required, permission_required
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 bookwyrm import forms, models
# pylint: disable=no-self-use
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.moderate_user", raise_exception=True),
name="dispatch",
)
class IPBlocklist(View):
"""Block registration by ip address"""
def get(self, request):
"""view and compose blocks"""
data = {
"addresses": models.IPBlocklist.objects.all(),
"form": forms.IPBlocklistForm(),
}
return TemplateResponse(request, "settings/ip_blocklist.html", data)
def post(self, request, block_id=None):
"""create a new ip address block"""
if block_id:
return self.delete(request, block_id)
form = forms.IPBlocklistForm(request.POST)
data = {
"addresses": models.IPBlocklist.objects.all(),
"form": form,
}
if not form.is_valid():
return TemplateResponse(request, "settings/ip_blocklist.html", data)
form.save()
data["form"] = forms.IPBlocklistForm()
return TemplateResponse(request, "settings/ip_blocklist.html", data)
# pylint: disable=unused-argument
def delete(self, request, domain_id):
"""remove a domain block"""
domain = get_object_or_404(models.IPBlocklist, id=domain_id)
domain.delete()
return redirect("settings-ip-blocks")