diff --git a/salt/salt/cronjobs.sls b/salt/salt/cronjobs.sls index fc57a52..2d561ad 100644 --- a/salt/salt/cronjobs.sls +++ b/salt/salt/cronjobs.sls @@ -1,5 +1,12 @@ {% 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: 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'] }}')" diff --git a/tildes/alembic/versions/4d86b372a8db_user_add_ban_expiry_time.py b/tildes/alembic/versions/4d86b372a8db_user_add_ban_expiry_time.py new file mode 100644 index 0000000..f0f882f --- /dev/null +++ b/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") diff --git a/tildes/scripts/lift_expired_temporary_bans.py b/tildes/scripts/lift_expired_temporary_bans.py new file mode 100644 index 0000000..ca96b26 --- /dev/null +++ b/tildes/scripts/lift_expired_temporary_bans.py @@ -0,0 +1,23 @@ +# Copyright (c) 2020 Tildes contributors +# 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() diff --git a/tildes/tildes/models/user/user.py b/tildes/tildes/models/user/user.py index a305a5e..d74ce29 100644 --- a/tildes/tildes/models/user/user.py +++ b/tildes/tildes/models/user/user.py @@ -126,6 +126,7 @@ class User(DatabaseModel): deleted_time: Optional[datetime] = Column(TIMESTAMP(timezone=True)) is_banned: bool = Column(Boolean, nullable=False, server_default="false") 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_period: Optional[str] = Column(Text) filtered_topic_tags: List[str] = Column( diff --git a/tildes/tildes/templates/user.jinja2 b/tildes/tildes/templates/user.jinja2 index 8f70235..9faf384 100644 --- a/tildes/tildes/templates/user.jinja2 +++ b/tildes/tildes/templates/user.jinja2 @@ -31,7 +31,12 @@ {% if user.is_deleted or user.is_banned %}
{% if user.is_banned %} -

This user is banned

+ {% if user.ban_expiry_time %} +

This user is temporarily banned

+

The ban will be lifted on {{ user.ban_expiry_time.strftime("%B %-d, %Y") }}

+ {% else %} +

This user is banned

+ {% endif %} {% elif user.is_deleted %}

This user has deleted their account

{% endif %} diff --git a/tildes/tildes/views/login.py b/tildes/tildes/views/login.py index 7c330f0..d37cd32 100644 --- a/tildes/tildes/views/login.py +++ b/tildes/tildes/views/login.py @@ -3,6 +3,7 @@ """Views related to logging in/out.""" +from datetime import timedelta from typing import NoReturn from urllib.parse import unquote_plus @@ -115,7 +116,17 @@ def post_login( # Don't allow banned users to log in 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 user.two_factor_enabled: