Browse Source

Add temporary bans (manual)

This enables me to set a ban expiry time for a user (manually, in the
database). By doing so:

* The user's page will say that they're temporarily banned, and show the
  date their ban will be lifted.
* If the user tries to log in, it will say they're temporarily banned,
  and give a specific datetime that the ban will be lifted by.
* An hourly cronjob will lift any bans that have expired.
merge-requests/106/head
Deimos 5 years ago
parent
commit
42f99a82ba
  1. 7
      salt/salt/cronjobs.sls
  2. 27
      tildes/alembic/versions/4d86b372a8db_user_add_ban_expiry_time.py
  3. 23
      tildes/scripts/lift_expired_temporary_bans.py
  4. 1
      tildes/tildes/models/user/user.py
  5. 7
      tildes/tildes/templates/user.jinja2
  6. 13
      tildes/tildes/views/login.py

7
salt/salt/cronjobs.sls

@ -1,5 +1,12 @@
{% from 'common.jinja2' import app_dir, app_username, bin_dir %} {% from 'common.jinja2' import app_dir, app_username, bin_dir %}
lift-expired-temp-bans-cronjob:
cron.present:
- name: {{ bin_dir }}/python -c "from scripts.lift_expired_temporary_bans import lift_expired_temporary_bans; lift_expired_temporary_bans('{{ app_dir }}/{{ pillar['ini_file'] }}')"
- user: {{ app_username }}
- hour: '*'
- minute: 1
close-voting-cronjob: close-voting-cronjob:
cron.present: cron.present:
- name: {{ bin_dir }}/python -c "from scripts.close_voting_on_old_posts import close_voting_on_old_posts; close_voting_on_old_posts('{{ app_dir }}/{{ pillar['ini_file'] }}')" - name: {{ bin_dir }}/python -c "from scripts.close_voting_on_old_posts import close_voting_on_old_posts; close_voting_on_old_posts('{{ app_dir }}/{{ pillar['ini_file'] }}')"

27
tildes/alembic/versions/4d86b372a8db_user_add_ban_expiry_time.py

@ -0,0 +1,27 @@
"""User: add ban_expiry_time
Revision ID: 4d86b372a8db
Revises: 9148909b78e9
Create Date: 2020-05-09 20:05:30.503634
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "4d86b372a8db"
down_revision = "9148909b78e9"
branch_labels = None
depends_on = None
def upgrade():
op.add_column(
"users",
sa.Column("ban_expiry_time", sa.TIMESTAMP(timezone=True), nullable=True),
)
def downgrade():
op.drop_column("users", "ban_expiry_time")

23
tildes/scripts/lift_expired_temporary_bans.py

@ -0,0 +1,23 @@
# Copyright (c) 2020 Tildes contributors <code@tildes.net>
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Simple script to lift any temporary bans that have expired.
This script should be set up to run regularly (such as every hour).
"""
from tildes.lib.database import get_session_from_config
from tildes.lib.datetime import utc_now
from tildes.models.user import User
def lift_expired_temporary_bans(config_path: str) -> None:
"""Lift temporary bans that have expired."""
db_session = get_session_from_config(config_path)
db_session.query(User).filter(
User.ban_expiry_time < utc_now(), # type: ignore
User.is_banned == True, # noqa
).update({"is_banned": False, "ban_expiry_time": None}, synchronize_session=False)
db_session.commit()

1
tildes/tildes/models/user/user.py

@ -126,6 +126,7 @@ class User(DatabaseModel):
deleted_time: Optional[datetime] = Column(TIMESTAMP(timezone=True)) deleted_time: Optional[datetime] = Column(TIMESTAMP(timezone=True))
is_banned: bool = Column(Boolean, nullable=False, server_default="false") is_banned: bool = Column(Boolean, nullable=False, server_default="false")
banned_time: Optional[datetime] = Column(TIMESTAMP(timezone=True)) banned_time: Optional[datetime] = Column(TIMESTAMP(timezone=True))
ban_expiry_time: Optional[datetime] = Column(TIMESTAMP(timezone=True))
home_default_order: Optional[TopicSortOption] = Column(ENUM(TopicSortOption)) home_default_order: Optional[TopicSortOption] = Column(ENUM(TopicSortOption))
home_default_period: Optional[str] = Column(Text) home_default_period: Optional[str] = Column(Text)
filtered_topic_tags: List[str] = Column( filtered_topic_tags: List[str] = Column(

7
tildes/tildes/templates/user.jinja2

@ -31,7 +31,12 @@
{% if user.is_deleted or user.is_banned %} {% if user.is_deleted or user.is_banned %}
<div class="empty"> <div class="empty">
{% if user.is_banned %} {% if user.is_banned %}
<h2 class="empty-title">This user is banned</h2>
{% if user.ban_expiry_time %}
<h2 class="empty-title">This user is temporarily banned</h2>
<p class="empty-subtitle">The ban will be lifted on {{ user.ban_expiry_time.strftime("%B %-d, %Y") }}</p>
{% else %}
<h2 class="empty-title">This user is banned</h2>
{% endif %}
{% elif user.is_deleted %} {% elif user.is_deleted %}
<h2 class="empty-title">This user has deleted their account</h2> <h2 class="empty-title">This user has deleted their account</h2>
{% endif %} {% endif %}

13
tildes/tildes/views/login.py

@ -3,6 +3,7 @@
"""Views related to logging in/out.""" """Views related to logging in/out."""
from datetime import timedelta
from typing import NoReturn from typing import NoReturn
from urllib.parse import unquote_plus from urllib.parse import unquote_plus
@ -115,7 +116,17 @@ def post_login(
# Don't allow banned users to log in # Don't allow banned users to log in
if user.is_banned: if user.is_banned:
raise HTTPUnprocessableEntity("This account has been banned")
if user.ban_expiry_time:
# add an hour to the ban's expiry time since the cronjob runs hourly
unban_time = user.ban_expiry_time + timedelta(hours=1)
unban_time = unban_time.strftime("%Y-%m-%d %H:%M (UTC)")
raise HTTPUnprocessableEntity(
"That account is temporarily banned. "
f"The ban will be lifted at {unban_time}"
)
raise HTTPUnprocessableEntity("That account has been banned")
# If 2FA is enabled, save username to session and make user enter code # If 2FA is enabled, save username to session and make user enter code
if user.two_factor_enabled: if user.two_factor_enabled:

Loading…
Cancel
Save