diff --git a/messages.pot b/messages.pot index 124cf87..8542934 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-05-09 22:22+0200\n" +"POT-Creation-Date: 2023-05-11 11:19+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -229,21 +229,29 @@ msgstr "" msgid "Save" msgstr "" -#: project/forms/admin.py:21 project/forms/admin_unit_member.py:14 +#: project/forms/admin.py:22 +msgid "Reset for all users" +msgstr "" + +#: project/forms/admin.py:24 +msgid "Reset" +msgstr "" + +#: project/forms/admin.py:28 project/forms/admin_unit_member.py:14 #: project/forms/admin_unit_member.py:34 msgid "Roles" msgstr "" -#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:29 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "" -#: project/forms/admin.py:26 project/templates/admin/delete_user.html:6 +#: project/forms/admin.py:33 project/templates/admin/delete_user.html:6 msgid "Delete user" msgstr "" -#: project/forms/admin.py:27 project/forms/admin_unit.py:53 +#: project/forms/admin.py:34 project/forms/admin_unit.py:53 #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 @@ -255,60 +263,60 @@ msgstr "" msgid "Email" msgstr "" -#: project/forms/admin.py:32 +#: project/forms/admin.py:39 msgid "Incoming reference requests allowed" msgstr "" -#: project/forms/admin.py:33 +#: project/forms/admin.py:40 msgid "" "If set, other organizations can ask this organization to reference their " "event." msgstr "" -#: project/forms/admin.py:39 +#: project/forms/admin.py:46 msgid "Suggestions enabled" msgstr "" -#: project/forms/admin.py:40 +#: project/forms/admin.py:47 msgid "If set, the organization can work with suggestions." msgstr "" -#: project/forms/admin.py:44 +#: project/forms/admin.py:51 msgid "Create other organizations" msgstr "" -#: project/forms/admin.py:45 +#: project/forms/admin.py:52 msgid "If set, members of the organization can create other organizations." msgstr "" -#: project/forms/admin.py:51 +#: project/forms/admin.py:58 msgid "Invite other organizations" msgstr "" -#: project/forms/admin.py:52 +#: project/forms/admin.py:59 msgid "If set, members of the organization can invite other organizations." msgstr "" -#: project/forms/admin.py:58 +#: project/forms/admin.py:65 msgid "Verify other organizations" msgstr "" -#: project/forms/admin.py:59 +#: project/forms/admin.py:66 msgid "If set, members of the organization can verify other organizations." msgstr "" -#: project/forms/admin.py:64 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:71 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "" -#: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/forms/admin.py:75 project/templates/admin/delete_admin_unit.html:6 #: project/templates/admin_unit/request_deletion.html:6 #: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "" -#: project/forms/admin.py:69 project/forms/admin_unit.py:34 +#: project/forms/admin.py:76 project/forms/admin_unit.py:34 #: project/forms/admin_unit.py:142 project/forms/admin_unit.py:147 #: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/event.py:114 project/forms/event_place.py:30 @@ -323,27 +331,27 @@ msgstr "" msgid "Name" msgstr "" -#: project/forms/admin.py:73 project/forms/admin.py:80 +#: project/forms/admin.py:80 project/forms/admin.py:87 msgid "Recipient" msgstr "" -#: project/forms/admin.py:75 +#: project/forms/admin.py:82 msgid "Send test mail synchronously" msgstr "" -#: project/forms/admin.py:82 project/forms/admin.py:88 +#: project/forms/admin.py:89 project/forms/admin.py:95 msgid "Test recipient" msgstr "" -#: project/forms/admin.py:83 +#: project/forms/admin.py:90 msgid "All users with enabled newsletter setting" msgstr "" -#: project/forms/admin.py:89 +#: project/forms/admin.py:96 msgid "Message" msgstr "" -#: project/forms/admin.py:90 +#: project/forms/admin.py:97 msgid "Send newsletter" msgstr "" @@ -1243,6 +1251,10 @@ msgstr "" msgid "Deny" msgstr "" +#: project/forms/security.py:79 +msgid "Confirm" +msgstr "" + #: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:93 @@ -1337,7 +1349,7 @@ msgstr "" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 #: project/templates/admin/admin_units.html:36 -#: project/templates/admin/users.html:34 +#: project/templates/admin/users.html:36 #: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 @@ -1725,7 +1737,7 @@ msgid "View" msgstr "" #: project/templates/admin/admin_units.html:37 -#: project/templates/admin/users.html:35 +#: project/templates/admin/users.html:37 #: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 @@ -1742,7 +1754,7 @@ msgstr "" msgid "User" msgstr "" -#: project/templates/admin/email.html:47 project/views/admin.py:144 +#: project/templates/admin/email.html:47 project/views/admin.py:165 msgid "Mail sent successfully" msgstr "" @@ -1758,6 +1770,13 @@ msgstr "" msgid "Mails sent successfully" msgstr "" +#: project/templates/admin/reset_tos_accepted.html:4 +#: project/templates/admin/reset_tos_accepted.html:8 +#: project/templates/admin/settings.html:28 +#: project/templates/admin/users.html:48 +msgid "Reset acceptance of terms of service and privacy" +msgstr "" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 @@ -2293,6 +2312,10 @@ msgstr "" msgid "Register for free" msgstr "" +#: project/templates/user/accept_tos.html:6 +msgid "Confirmation required" +msgstr "" + #: project/templates/user/request_deletion.html:8 msgid "" "The account is not deleted immediately. After a period of time, the " @@ -2319,43 +2342,43 @@ msgstr "" msgid "Preview" msgstr "" -#: project/views/admin.py:63 +#: project/views/admin.py:64 msgid "Organization successfully updated" msgstr "" -#: project/views/admin.py:85 project/views/admin_unit.py:187 +#: project/views/admin.py:86 project/views/admin_unit.py:187 #: project/views/admin_unit.py:220 project/views/manage.py:316 msgid "Entered name does not match organization name" msgstr "" -#: project/views/admin.py:89 +#: project/views/admin.py:90 msgid "Organization successfully deleted" msgstr "" -#: project/views/admin.py:113 project/views/manage.py:486 -#: project/views/user.py:41 +#: project/views/admin.py:134 project/views/manage.py:486 +#: project/views/user.py:63 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:133 +#: project/views/admin.py:154 #, python-format msgid "Test mail from %(site_name)s" msgstr "" -#: project/views/admin.py:162 +#: project/views/admin.py:183 #, python-format msgid "Newsletter from %(site_name)s" msgstr "" -#: project/views/admin.py:212 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "" -#: project/views/admin.py:232 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "" -#: project/views/admin.py:236 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "" @@ -2576,17 +2599,17 @@ msgid "" "verified automatically." msgstr "" -#: project/views/user.py:85 +#: project/views/user.py:107 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." msgstr "" -#: project/views/user.py:92 project/views/user.py:119 +#: project/views/user.py:114 project/views/user.py:141 msgid "Entered email does not match your email" msgstr "" -#: project/views/user.py:138 +#: project/views/user.py:160 msgid "User deletion requested" msgstr "" diff --git a/migrations/versions/becc71f97606_.py b/migrations/versions/becc71f97606_.py new file mode 100644 index 0000000..f94db50 --- /dev/null +++ b/migrations/versions/becc71f97606_.py @@ -0,0 +1,27 @@ +"""empty message + +Revision ID: becc71f97606 +Revises: cceaf9b28134 +Create Date: 2023-05-10 14:25:57.157442 + +""" +import sqlalchemy as sa +import sqlalchemy_utils +from alembic import op + +from project import dbtypes + +# revision identifiers, used by Alembic. +revision = "becc71f97606" +down_revision = "cceaf9b28134" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("user", sa.Column("tos_accepted_at", sa.DateTime(), nullable=True)) + op.execute("UPDATE public.user SET tos_accepted_at = CURRENT_TIMESTAMP;") + + +def downgrade(): + op.drop_column("user", "tos_accepted_at") diff --git a/project/__init__.py b/project/__init__.py index 155b7db..932f37c 100644 --- a/project/__init__.py +++ b/project/__init__.py @@ -1,6 +1,6 @@ import logging import os -from datetime import timedelta +from datetime import datetime, timedelta from flask import Flask from flask_babel import Babel @@ -9,7 +9,7 @@ from flask_gzip import Gzip from flask_mail import Mail, email_dispatched from flask_migrate import Migrate from flask_qrcode import QRcode -from flask_security import Security, SQLAlchemySessionUserDatastore +from flask_security import Security, SQLAlchemySessionUserDatastore, user_registered from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect from sqlalchemy import MetaData @@ -228,6 +228,16 @@ security = Security( ) app.session_interface = CustomSessionInterface() + +@user_registered.connect_via(app) +def user_registered_sighandler(app, user, confirm_token, confirmation_token, form_data): + if "accept_tos" in form_data and form_data["accept_tos"]: + from project.services.user import set_user_accepted_tos + + set_user_accepted_tos(user) + db.session.commit() + + # OAuth2 from project.oauth2 import config_oauth @@ -276,9 +286,9 @@ from project.views import ( reference_request, reference_request_review, root, - user, - widget, ) +from project.views import user as user_view +from project.views import widget if __name__ == "__main__": # pragma: no cover app.run() diff --git a/project/cli/test.py b/project/cli/test.py index 7c83bfb..ff0048e 100644 --- a/project/cli/test.py +++ b/project/cli/test.py @@ -35,7 +35,12 @@ from project.services.event_suggestion import insert_event_suggestion from project.services.oauth2_client import complete_oauth2_client from project.services.organizer import get_event_organizer, upsert_event_organizer from project.services.place import get_event_places, upsert_event_place -from project.services.user import create_user, find_user_by_email, get_user +from project.services.user import ( + create_user, + find_user_by_email, + get_user, + set_user_accepted_tos, +) test_cli = AppGroup("test") @@ -61,13 +66,19 @@ def _get_default_organizer_id(admin_unit_id): def _create_user( - email="test@test.de", password="MeinPasswortIstDasBeste", confirm=True + email="test@test.de", + password="MeinPasswortIstDasBeste", + confirm=True, + tos_accepted=True, ): user = create_user(email, password) if confirm: confirm_user(user) + if tos_accepted: + set_user_accepted_tos(user) + db.session.commit() return user.id diff --git a/project/forms/admin.py b/project/forms/admin.py index 1d8c8ca..93b7cb1 100644 --- a/project/forms/admin.py +++ b/project/forms/admin.py @@ -17,6 +17,13 @@ class AdminSettingsForm(FlaskForm): submit = SubmitField(lazy_gettext("Save")) +class ResetTosAceptedForm(FlaskForm): + reset_for_users = BooleanField( + lazy_gettext("Reset for all users"), validators=[DataRequired()] + ) + submit = SubmitField(lazy_gettext("Reset")) + + class UpdateUserForm(FlaskForm): roles = MultiCheckboxField(lazy_gettext("Roles")) submit = SubmitField(lazy_gettext("Update user")) diff --git a/project/forms/security.py b/project/forms/security.py index 093021d..7c77570 100644 --- a/project/forms/security.py +++ b/project/forms/security.py @@ -72,3 +72,12 @@ class ExtendedForgotPasswordForm(ForgotPasswordForm): class AuthorizeForm(FlaskForm): allow = SubmitField(lazy_gettext("Allow")) deny = SubmitField(lazy_gettext("Deny")) + + +class AcceptTosForm(FlaskForm): + accept_tos = BooleanField(validators=[DataRequired()]) + submit = SubmitField(lazy_gettext("Confirm")) + + def __init__(self, **kwargs): + super(AcceptTosForm, self).__init__(**kwargs) + self._fields["accept_tos"].label.text = get_accept_tos_markup() diff --git a/project/models/user.py b/project/models/user.py index 1158d7d..0978bcd 100644 --- a/project/models/user.py +++ b/project/models/user.py @@ -59,7 +59,10 @@ class User(db.Model, UserMixin): server_default="1", ) ) - created_at = Column(DateTime, default=datetime.datetime.utcnow) + tos_accepted_at = Column( + DateTime(), + nullable=True, + ) created_at = deferred(Column(DateTime, default=datetime.datetime.utcnow)) deletion_requested_at = deferred(Column(DateTime, nullable=True)) diff --git a/project/requests.py b/project/requests.py index 609910e..d1f72d0 100644 --- a/project/requests.py +++ b/project/requests.py @@ -1,11 +1,29 @@ from datetime import datetime, timedelta -from flask import g, request +from flask import g, redirect, request, url_for from flask_login.utils import encode_cookie +from flask_security import current_user from project import app +@app.after_request +def check_tos_accepted(response): + if ( + response.status_code == 200 + and request.endpoint + and not request.endpoint.startswith("api_") + and not request.endpoint.startswith("widget_") + and request.endpoint not in ["static", "user_accept_tos"] + and current_user + and current_user.is_authenticated + and not current_user.tos_accepted_at + ): + return redirect(url_for("user_accept_tos", next=request.url)) + + return response + + @app.after_request def set_manage_admin_unit_cookie(response): admin_unit = getattr(g, "manage_admin_unit", None) diff --git a/project/services/admin.py b/project/services/admin.py index 5a130eb..37eb4c5 100644 --- a/project/services/admin.py +++ b/project/services/admin.py @@ -1,7 +1,7 @@ -from sqlalchemy import exists, func +from sqlalchemy import exists, func, update from project import db -from project.models import Settings +from project.models import Settings, User def upsert_settings(): @@ -17,3 +17,8 @@ def has_tos(): return db.session.scalar( exists().where(func.coalesce(Settings.tos, "") != "").select() ) + + +def reset_tos_accepted_for_users(): + db.session.execute(update(User).values(tos_accepted_at=None)) + db.session.commit() diff --git a/project/services/user.py b/project/services/user.py index d13611d..fd86a7b 100644 --- a/project/services/user.py +++ b/project/services/user.py @@ -122,3 +122,7 @@ def is_user_admin_member(user: User) -> bool: ).first() is not None ) + + +def set_user_accepted_tos(user: User): + user.tos_accepted_at = datetime.datetime.utcnow() diff --git a/project/templates/admin/reset_tos_accepted.html b/project/templates/admin/reset_tos_accepted.html new file mode 100644 index 0000000..6149a40 --- /dev/null +++ b/project/templates/admin/reset_tos_accepted.html @@ -0,0 +1,16 @@ +{% extends "layout.html" %} +{% from "_macros.html" import render_field_with_errors, render_field %} +{%- block title -%} +{{ _('Reset acceptance of terms of service and privacy') }} +{%- endblock -%} +{% block content %} + +

{{ _('Reset acceptance of terms of service and privacy') }}

+ +
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.reset_for_users, ri="checkbox") }} + {{ render_field(form.submit) }} +
+ +{% endblock %} diff --git a/project/templates/admin/settings.html b/project/templates/admin/settings.html index 00887e4..286681c 100644 --- a/project/templates/admin/settings.html +++ b/project/templates/admin/settings.html @@ -24,4 +24,8 @@ {{ render_field(form.submit) }} + + {% endblock %} \ No newline at end of file diff --git a/project/templates/admin/users.html b/project/templates/admin/users.html index db4f6c8..fafe959 100644 --- a/project/templates/admin/users.html +++ b/project/templates/admin/users.html @@ -19,6 +19,7 @@ {{ _('Email') }} created_at confirmed_at + tos_accepted_at deletion_requested_at @@ -29,6 +30,7 @@ {{ user.email }} {% if user.created_at %}{{ user.created_at | dateformat }}{% endif %} {% if user.confirmed_at %}{{ user.confirmed_at | dateformat }}{% endif %} + {% if user.tos_accepted_at %}{{ user.tos_accepted_at | dateformat }}{% endif %} {% if user.deletion_requested_at %}{{ user.deletion_requested_at | dateformat }}{% endif %} {{ _('Edit') }} @@ -42,4 +44,9 @@
{{ render_pagination(pagination) }}
+ + + {% endblock %} \ No newline at end of file diff --git a/project/templates/user/accept_tos.html b/project/templates/user/accept_tos.html new file mode 100644 index 0000000..54201a9 --- /dev/null +++ b/project/templates/user/accept_tos.html @@ -0,0 +1,22 @@ +{% extends "layout.html" %} +{% from "_macros.html" import render_field %} + +{% block content %} + +

{{ _('Confirmation required') }}

+
+
+ {{ form.hidden_tag() }} + +
+
+ {{ form.accept_tos(class="form-check-input")|safe }} + {{ form.accept_tos.label(class="form-check-label") }} +
+
+ + {{ render_field(form.submit) }} +
+
+ +{% endblock %} diff --git a/project/translations/de/LC_MESSAGES/messages.mo b/project/translations/de/LC_MESSAGES/messages.mo index 21fa5e7..e0a7927 100644 Binary files a/project/translations/de/LC_MESSAGES/messages.mo and b/project/translations/de/LC_MESSAGES/messages.mo differ diff --git a/project/translations/de/LC_MESSAGES/messages.po b/project/translations/de/LC_MESSAGES/messages.po index 7f3a608..4e3b2c7 100644 --- a/project/translations/de/LC_MESSAGES/messages.po +++ b/project/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-05-09 22:22+0200\n" +"POT-Creation-Date: 2023-05-11 11:19+0200\n" "PO-Revision-Date: 2020-06-07 18:51+0200\n" "Last-Translator: FULL NAME \n" "Language: de\n" @@ -230,21 +230,29 @@ msgstr "Startseite" msgid "Save" msgstr "Speichern" -#: project/forms/admin.py:21 project/forms/admin_unit_member.py:14 +#: project/forms/admin.py:22 +msgid "Reset for all users" +msgstr "Für alle Nutzer zurücksetzen" + +#: project/forms/admin.py:24 +msgid "Reset" +msgstr "Zurücksetzen" + +#: project/forms/admin.py:28 project/forms/admin_unit_member.py:14 #: project/forms/admin_unit_member.py:34 msgid "Roles" msgstr "Rollen" -#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:29 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "Nutzer aktualisieren" -#: project/forms/admin.py:26 project/templates/admin/delete_user.html:6 +#: project/forms/admin.py:33 project/templates/admin/delete_user.html:6 msgid "Delete user" msgstr "Nutzer löschen" -#: project/forms/admin.py:27 project/forms/admin_unit.py:53 +#: project/forms/admin.py:34 project/forms/admin_unit.py:53 #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 @@ -256,11 +264,11 @@ msgstr "Nutzer löschen" msgid "Email" msgstr "Email" -#: project/forms/admin.py:32 +#: project/forms/admin.py:39 msgid "Incoming reference requests allowed" msgstr "Eingehende Empfehlungsanfragen erlauben" -#: project/forms/admin.py:33 +#: project/forms/admin.py:40 msgid "" "If set, other organizations can ask this organization to reference their " "event." @@ -268,56 +276,56 @@ msgstr "" "Wenn gesetzt, können andere Organisationen diese Organisation bitten, " "deren Veranstaltungen zu empfehlen." -#: project/forms/admin.py:39 +#: project/forms/admin.py:46 msgid "Suggestions enabled" msgstr "Vorschläge aktiv" -#: project/forms/admin.py:40 +#: project/forms/admin.py:47 msgid "If set, the organization can work with suggestions." msgstr "Wenn gesetzt, kann die Organisation mit Vorschlägen arbeiten." -#: project/forms/admin.py:44 +#: project/forms/admin.py:51 msgid "Create other organizations" msgstr "Andere Organisationen erstellen" -#: project/forms/admin.py:45 +#: project/forms/admin.py:52 msgid "If set, members of the organization can create other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "erstellen." -#: project/forms/admin.py:51 +#: project/forms/admin.py:58 msgid "Invite other organizations" msgstr "Andere Organisationen einladen" -#: project/forms/admin.py:52 +#: project/forms/admin.py:59 msgid "If set, members of the organization can invite other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "einladen." -#: project/forms/admin.py:58 +#: project/forms/admin.py:65 msgid "Verify other organizations" msgstr "Andere Organisationen verifizieren" -#: project/forms/admin.py:59 +#: project/forms/admin.py:66 msgid "If set, members of the organization can verify other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "verifizieren." -#: project/forms/admin.py:64 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:71 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "Organisation aktualisieren" -#: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/forms/admin.py:75 project/templates/admin/delete_admin_unit.html:6 #: project/templates/admin_unit/request_deletion.html:6 #: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "Organisation löschen" -#: project/forms/admin.py:69 project/forms/admin_unit.py:34 +#: project/forms/admin.py:76 project/forms/admin_unit.py:34 #: project/forms/admin_unit.py:142 project/forms/admin_unit.py:147 #: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/event.py:114 project/forms/event_place.py:30 @@ -332,27 +340,27 @@ msgstr "Organisation löschen" msgid "Name" msgstr "Name" -#: project/forms/admin.py:73 project/forms/admin.py:80 +#: project/forms/admin.py:80 project/forms/admin.py:87 msgid "Recipient" msgstr "Empfänger" -#: project/forms/admin.py:75 +#: project/forms/admin.py:82 msgid "Send test mail synchronously" msgstr "Test-Mail synchron senden" -#: project/forms/admin.py:82 project/forms/admin.py:88 +#: project/forms/admin.py:89 project/forms/admin.py:95 msgid "Test recipient" msgstr "Test-Empfänger" -#: project/forms/admin.py:83 +#: project/forms/admin.py:90 msgid "All users with enabled newsletter setting" msgstr "Alle Nutzer mit aktiviertem Newsletter" -#: project/forms/admin.py:89 +#: project/forms/admin.py:96 msgid "Message" msgstr "Nachricht" -#: project/forms/admin.py:90 +#: project/forms/admin.py:97 msgid "Send newsletter" msgstr "Newsletter senden" @@ -561,9 +569,8 @@ msgstr "" #, python-format msgid "I read and accept %(privacy_open)sPrivacy%(privacy_close)s." msgstr "" -"Ich habe die " -"%(privacy_open)sDatenschutzerklärung%(privacy_close)s gelesen und " -"akzeptiere diese." +"Ich habe die %(privacy_open)sDatenschutzerklärung%(privacy_close)s " +"gelesen und akzeptiere diese." #: project/forms/common.py:95 msgid "0 (Little relevant)" @@ -1301,6 +1308,10 @@ msgstr "Erlauben" msgid "Deny" msgstr "Ablehnen" +#: project/forms/security.py:79 +msgid "Confirm" +msgstr "Bestätigen" + #: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:93 @@ -1397,7 +1408,7 @@ msgstr "Merkzettel" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 #: project/templates/admin/admin_units.html:36 -#: project/templates/admin/users.html:34 +#: project/templates/admin/users.html:36 #: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 @@ -1785,7 +1796,7 @@ msgid "View" msgstr "Anzeigen" #: project/templates/admin/admin_units.html:37 -#: project/templates/admin/users.html:35 +#: project/templates/admin/users.html:37 #: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 @@ -1802,7 +1813,7 @@ msgstr "Löschen" msgid "User" msgstr "Nutzer" -#: project/templates/admin/email.html:47 project/views/admin.py:144 +#: project/templates/admin/email.html:47 project/views/admin.py:165 msgid "Mail sent successfully" msgstr "Mail erfolgreich gesendet" @@ -1818,6 +1829,13 @@ msgstr "Test-Mail asynchron senden" msgid "Mails sent successfully" msgstr "Mails erfolgreich gesendet" +#: project/templates/admin/reset_tos_accepted.html:4 +#: project/templates/admin/reset_tos_accepted.html:8 +#: project/templates/admin/settings.html:28 +#: project/templates/admin/users.html:48 +msgid "Reset acceptance of terms of service and privacy" +msgstr "Akzeptanz der Nutzungsbedingungen und des Datenschutzes zurücksetzen" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 @@ -2366,6 +2384,10 @@ msgstr "Du hast noch keinen Account? Kein Problem!" msgid "Register for free" msgstr "Kostenlos registrieren" +#: project/templates/user/accept_tos.html:6 +msgid "Confirmation required" +msgstr "Bestätigung erforderlich" + #: project/templates/user/request_deletion.html:8 msgid "" "The account is not deleted immediately. After a period of time, the " @@ -2394,43 +2416,43 @@ msgstr "Optionale Details" msgid "Preview" msgstr "Vorschau" -#: project/views/admin.py:63 +#: project/views/admin.py:64 msgid "Organization successfully updated" msgstr "Organisation erfolgreich aktualisiert" -#: project/views/admin.py:85 project/views/admin_unit.py:187 +#: project/views/admin.py:86 project/views/admin_unit.py:187 #: project/views/admin_unit.py:220 project/views/manage.py:316 msgid "Entered name does not match organization name" msgstr "Der eingegebene Name entspricht nicht dem Namen der Organisation" -#: project/views/admin.py:89 +#: project/views/admin.py:90 msgid "Organization successfully deleted" msgstr "Organisation erfolgreich gelöscht" -#: project/views/admin.py:113 project/views/manage.py:486 -#: project/views/user.py:41 +#: project/views/admin.py:134 project/views/manage.py:486 +#: project/views/user.py:63 msgid "Settings successfully updated" msgstr "Einstellungen erfolgreich aktualisiert" -#: project/views/admin.py:133 +#: project/views/admin.py:154 #, python-format msgid "Test mail from %(site_name)s" msgstr "Test-Mail von %(site_name)s" -#: project/views/admin.py:162 +#: project/views/admin.py:183 #, python-format msgid "Newsletter from %(site_name)s" msgstr "Newsletter von %(site_name)s" -#: project/views/admin.py:212 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "Nutzer erfolgreich aktualisiert" -#: project/views/admin.py:232 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "Die eingegebene Email passt nicht zur Email des Nutzers" -#: project/views/admin.py:236 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "Nutzer erfolgreich gelöscht" @@ -2660,7 +2682,7 @@ msgstr "" "Ob alle zukünftigen Empfehlungsanfragen von %(admin_unit_name)s " "automatisch verifiziert werden sollen." -#: project/views/user.py:85 +#: project/views/user.py:107 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." @@ -2668,11 +2690,11 @@ msgstr "" "Du bist Administrator von mindestens einer Organisation. Beende deine " "Mitgliedschaft, um deinen Account zu löschen." -#: project/views/user.py:92 project/views/user.py:119 +#: project/views/user.py:114 project/views/user.py:141 msgid "Entered email does not match your email" msgstr "Die eingegebene Email entspricht nicht deiner Email" -#: project/views/user.py:138 +#: project/views/user.py:160 msgid "User deletion requested" msgstr "Löschung des Nutzers beantragt" diff --git a/project/translations/en/LC_MESSAGES/messages.mo b/project/translations/en/LC_MESSAGES/messages.mo index febbb7c..846c438 100644 Binary files a/project/translations/en/LC_MESSAGES/messages.mo and b/project/translations/en/LC_MESSAGES/messages.mo differ diff --git a/project/translations/en/LC_MESSAGES/messages.po b/project/translations/en/LC_MESSAGES/messages.po index c6467e6..e508d62 100644 --- a/project/translations/en/LC_MESSAGES/messages.po +++ b/project/translations/en/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-05-09 22:22+0200\n" +"POT-Creation-Date: 2023-05-11 11:19+0200\n" "PO-Revision-Date: 2021-04-30 15:04+0200\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -230,21 +230,29 @@ msgstr "" msgid "Save" msgstr "" -#: project/forms/admin.py:21 project/forms/admin_unit_member.py:14 +#: project/forms/admin.py:22 +msgid "Reset for all users" +msgstr "" + +#: project/forms/admin.py:24 +msgid "Reset" +msgstr "" + +#: project/forms/admin.py:28 project/forms/admin_unit_member.py:14 #: project/forms/admin_unit_member.py:34 msgid "Roles" msgstr "" -#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:29 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "" -#: project/forms/admin.py:26 project/templates/admin/delete_user.html:6 +#: project/forms/admin.py:33 project/templates/admin/delete_user.html:6 msgid "Delete user" msgstr "" -#: project/forms/admin.py:27 project/forms/admin_unit.py:53 +#: project/forms/admin.py:34 project/forms/admin_unit.py:53 #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 @@ -256,60 +264,60 @@ msgstr "" msgid "Email" msgstr "" -#: project/forms/admin.py:32 +#: project/forms/admin.py:39 msgid "Incoming reference requests allowed" msgstr "" -#: project/forms/admin.py:33 +#: project/forms/admin.py:40 msgid "" "If set, other organizations can ask this organization to reference their " "event." msgstr "" -#: project/forms/admin.py:39 +#: project/forms/admin.py:46 msgid "Suggestions enabled" msgstr "" -#: project/forms/admin.py:40 +#: project/forms/admin.py:47 msgid "If set, the organization can work with suggestions." msgstr "" -#: project/forms/admin.py:44 +#: project/forms/admin.py:51 msgid "Create other organizations" msgstr "" -#: project/forms/admin.py:45 +#: project/forms/admin.py:52 msgid "If set, members of the organization can create other organizations." msgstr "" -#: project/forms/admin.py:51 +#: project/forms/admin.py:58 msgid "Invite other organizations" msgstr "" -#: project/forms/admin.py:52 +#: project/forms/admin.py:59 msgid "If set, members of the organization can invite other organizations." msgstr "" -#: project/forms/admin.py:58 +#: project/forms/admin.py:65 msgid "Verify other organizations" msgstr "" -#: project/forms/admin.py:59 +#: project/forms/admin.py:66 msgid "If set, members of the organization can verify other organizations." msgstr "" -#: project/forms/admin.py:64 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:71 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "" -#: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/forms/admin.py:75 project/templates/admin/delete_admin_unit.html:6 #: project/templates/admin_unit/request_deletion.html:6 #: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "" -#: project/forms/admin.py:69 project/forms/admin_unit.py:34 +#: project/forms/admin.py:76 project/forms/admin_unit.py:34 #: project/forms/admin_unit.py:142 project/forms/admin_unit.py:147 #: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/event.py:114 project/forms/event_place.py:30 @@ -324,27 +332,27 @@ msgstr "" msgid "Name" msgstr "" -#: project/forms/admin.py:73 project/forms/admin.py:80 +#: project/forms/admin.py:80 project/forms/admin.py:87 msgid "Recipient" msgstr "" -#: project/forms/admin.py:75 +#: project/forms/admin.py:82 msgid "Send test mail synchronously" msgstr "" -#: project/forms/admin.py:82 project/forms/admin.py:88 +#: project/forms/admin.py:89 project/forms/admin.py:95 msgid "Test recipient" msgstr "" -#: project/forms/admin.py:83 +#: project/forms/admin.py:90 msgid "All users with enabled newsletter setting" msgstr "" -#: project/forms/admin.py:89 +#: project/forms/admin.py:96 msgid "Message" msgstr "" -#: project/forms/admin.py:90 +#: project/forms/admin.py:97 msgid "Send newsletter" msgstr "" @@ -1251,6 +1259,10 @@ msgstr "" msgid "Deny" msgstr "" +#: project/forms/security.py:79 +msgid "Confirm" +msgstr "" + #: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:93 @@ -1345,7 +1357,7 @@ msgstr "" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 #: project/templates/admin/admin_units.html:36 -#: project/templates/admin/users.html:34 +#: project/templates/admin/users.html:36 #: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 @@ -1733,7 +1745,7 @@ msgid "View" msgstr "" #: project/templates/admin/admin_units.html:37 -#: project/templates/admin/users.html:35 +#: project/templates/admin/users.html:37 #: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 @@ -1750,7 +1762,7 @@ msgstr "" msgid "User" msgstr "" -#: project/templates/admin/email.html:47 project/views/admin.py:144 +#: project/templates/admin/email.html:47 project/views/admin.py:165 msgid "Mail sent successfully" msgstr "" @@ -1766,6 +1778,13 @@ msgstr "" msgid "Mails sent successfully" msgstr "" +#: project/templates/admin/reset_tos_accepted.html:4 +#: project/templates/admin/reset_tos_accepted.html:8 +#: project/templates/admin/settings.html:28 +#: project/templates/admin/users.html:48 +msgid "Reset acceptance of terms of service and privacy" +msgstr "" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 @@ -2301,6 +2320,10 @@ msgstr "" msgid "Register for free" msgstr "" +#: project/templates/user/accept_tos.html:6 +msgid "Confirmation required" +msgstr "" + #: project/templates/user/request_deletion.html:8 msgid "" "The account is not deleted immediately. After a period of time, the " @@ -2327,43 +2350,43 @@ msgstr "" msgid "Preview" msgstr "" -#: project/views/admin.py:63 +#: project/views/admin.py:64 msgid "Organization successfully updated" msgstr "" -#: project/views/admin.py:85 project/views/admin_unit.py:187 +#: project/views/admin.py:86 project/views/admin_unit.py:187 #: project/views/admin_unit.py:220 project/views/manage.py:316 msgid "Entered name does not match organization name" msgstr "" -#: project/views/admin.py:89 +#: project/views/admin.py:90 msgid "Organization successfully deleted" msgstr "" -#: project/views/admin.py:113 project/views/manage.py:486 -#: project/views/user.py:41 +#: project/views/admin.py:134 project/views/manage.py:486 +#: project/views/user.py:63 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:133 +#: project/views/admin.py:154 #, python-format msgid "Test mail from %(site_name)s" msgstr "" -#: project/views/admin.py:162 +#: project/views/admin.py:183 #, python-format msgid "Newsletter from %(site_name)s" msgstr "" -#: project/views/admin.py:212 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "" -#: project/views/admin.py:232 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "" -#: project/views/admin.py:236 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "" @@ -2584,17 +2607,17 @@ msgid "" "verified automatically." msgstr "" -#: project/views/user.py:85 +#: project/views/user.py:107 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." msgstr "" -#: project/views/user.py:92 project/views/user.py:119 +#: project/views/user.py:114 project/views/user.py:141 msgid "Entered email does not match your email" msgstr "" -#: project/views/user.py:138 +#: project/views/user.py:160 msgid "User deletion requested" msgstr "" diff --git a/project/views/admin.py b/project/views/admin.py index fde5905..f8afdb3 100644 --- a/project/views/admin.py +++ b/project/views/admin.py @@ -13,6 +13,7 @@ from project.forms.admin import ( AdminTestEmailForm, DeleteAdminUnitForm, DeleteUserForm, + ResetTosAceptedForm, UpdateAdminUnitForm, UpdateUserForm, ) @@ -99,6 +100,26 @@ def admin_admin_unit_delete(id): ) +@app.route("/admin/reset-tos-accepted", methods=("GET", "POST")) +@roles_required("admin") +def admin_reset_tos_accepted(): + from project.services.admin import reset_tos_accepted_for_users + + form = ResetTosAceptedForm() + + if form.validate_on_submit(): + try: + reset_tos_accepted_for_users() + return redirect(url_for("admin")) + except SQLAlchemyError as e: # pragma: no cover + db.session.rollback() + flash(handleSqlError(e), "danger") + else: + flash_errors(form) + + return render_template("admin/reset_tos_accepted.html", form=form) + + @app.route("/admin/settings", methods=("GET", "POST")) @roles_required("admin") def admin_settings(): diff --git a/project/views/user.py b/project/views/user.py index bd82a2a..2041ffb 100644 --- a/project/views/user.py +++ b/project/views/user.py @@ -3,16 +3,18 @@ import datetime from flask import flash, redirect, render_template, url_for from flask_babel import gettext from flask_security import auth_required, current_user +from flask_security.utils import get_post_login_redirect from sqlalchemy.exc import SQLAlchemyError from project import app, db +from project.forms.security import AcceptTosForm from project.forms.user import ( CancelUserDeletionForm, NotificationForm, RequestUserDeletionForm, ) from project.models import AdminUnitInvitation, User -from project.services.user import is_user_admin_member +from project.services.user import is_user_admin_member, set_user_accepted_tos from project.views.utils import ( flash_errors, get_invitation_access_result, @@ -28,6 +30,26 @@ def profile(): return render_template("profile.html") +@app.route("/user/accept-tos", methods=("GET", "POST")) +@auth_required() +def user_accept_tos(): + form = AcceptTosForm() + + if current_user.tos_accepted_at: # pragma: no cover + return redirect(get_post_login_redirect()) + + if form.validate_on_submit(): + try: + set_user_accepted_tos(current_user) + db.session.commit() + return redirect(get_post_login_redirect()) + except SQLAlchemyError as e: # pragma: no cover + db.session.rollback() + flash(handleSqlError(e), "danger") + + return render_template("user/accept_tos.html", form=form) + + @app.route("/user/notifications", methods=("GET", "POST")) @auth_required() def user_notifications(): diff --git a/tests/api/test_event.py b/tests/api/test_event.py index 6af16c4..7a380c6 100644 --- a/tests/api/test_event.py +++ b/tests/api/test_event.py @@ -2,6 +2,8 @@ import base64 import pytest +from tests.seeder import Seeder + def test_read(client, app, db, seeder, utils): user_id, admin_unit_id = seeder.setup_base() @@ -412,7 +414,7 @@ def test_put_invalidDateFormat(client, seeder, utils, app): utils.assert_response_unprocessable_entity(response) -def test_put_startAfterEnd(client, seeder, utils, app): +def test_put_startAfterEnd(client, seeder: Seeder, utils, app): user_id, admin_unit_id = seeder.setup_api_access() event_id = seeder.create_event(admin_unit_id) place_id = seeder.upsert_default_event_place(admin_unit_id) diff --git a/tests/seeder.py b/tests/seeder.py index f599a28..586c7b5 100644 --- a/tests/seeder.py +++ b/tests/seeder.py @@ -34,6 +34,7 @@ class Seeder(object): password="MeinPasswortIstDasBeste", admin=False, confirm=True, + tos_accepted=True, ): from flask_security.confirmable import confirm_user @@ -41,6 +42,7 @@ class Seeder(object): add_admin_roles_to_user, create_user, find_user_by_email, + set_user_accepted_tos, ) with self._app.app_context(): @@ -52,6 +54,9 @@ class Seeder(object): if confirm: confirm_user(user) + if tos_accepted: + set_user_accepted_tos(user) + if admin: add_admin_roles_to_user(email) diff --git a/tests/views/test_admin.py b/tests/views/test_admin.py index 29c48f1..528e3e0 100644 --- a/tests/views/test_admin.py +++ b/tests/views/test_admin.py @@ -1,5 +1,8 @@ import pytest +from tests.seeder import Seeder +from tests.utils import UtilActions + def test_normal_user(client, seeder, utils): seeder.create_user() @@ -285,3 +288,23 @@ def test_admin_unit_delete(client, seeder, utils, app, db, mocker, db_error, non admin_unit = db.session.get(AdminUnit, admin_unit_id) assert admin_unit is None + + +def test_admin_reset_tos_accepted(client, app, db, seeder: Seeder, utils: UtilActions): + seeder.setup_base(admin=True) + + response = utils.get_endpoint_ok("admin_reset_tos_accepted") + response = utils.post_form( + response.request.url, + response, + { + "reset_for_users": "y", + "submit": "Reset", + }, + ) + utils.assert_response_redirect(response, "admin") + + with app.app_context(): + from project.models.user import User + + assert len(User.query.filter(User.tos_accepted_at.isnot(None)).all()) == 0 diff --git a/tests/views/test_manage.py b/tests/views/test_manage.py index 37c480d..2cf9016 100644 --- a/tests/views/test_manage.py +++ b/tests/views/test_manage.py @@ -4,7 +4,7 @@ from tests.seeder import Seeder from tests.utils import UtilActions -def test_index_noCookie(client, seeder, utils): +def test_index_noCookie(client, seeder: Seeder, utils): user_id, admin_unit_id = seeder.setup_base() response = utils.get_endpoint("manage") diff --git a/tests/views/test_user.py b/tests/views/test_user.py index e2d0fae..93092ca 100644 --- a/tests/views/test_user.py +++ b/tests/views/test_user.py @@ -1,6 +1,7 @@ import pytest from tests.seeder import Seeder +from tests.utils import UtilActions def test_profile(client, seeder, utils): @@ -245,3 +246,28 @@ def test_user_cancel_deletion( user = db.session.get(User, user_id) assert user.deletion_requested_at is None + + +def test_user_accept_tos(client, app, db, seeder: Seeder, utils: UtilActions): + seeder.setup_base() + + with app.app_context(): + from project.services.admin import reset_tos_accepted_for_users + + reset_tos_accepted_for_users() + + response = utils.get_endpoint("profile") + utils.assert_response_redirect( + response, "user_accept_tos", next="http://localhost/profile" + ) + + response = utils.get_endpoint_ok("user_accept_tos", next="/profile") + response = utils.post_form( + response.request.url, + response, + { + "accept_tos": "y", + "submit": "Confirm", + }, + ) + utils.assert_response_redirect(response, "profile")