From db982572aaf0ac474d2778c3dcd1aac33581feb3 Mon Sep 17 00:00:00 2001 From: Baptiste Jonglez Date: Wed, 14 Jul 2021 16:07:52 +0200 Subject: [PATCH] History: also ask for private code to confirm deletion This is the same idea as deleting a project: deleting history is also a major destructive action. We reuse the same form as for project deletion to ask for the private code and provide CSRF validation. --- ihatemoney/forms.py | 12 +++++++++++- ihatemoney/templates/forms.html | 22 ++++++++++++++++++++++ ihatemoney/templates/history.html | 6 ++---- ihatemoney/web.py | 30 +++++++++++++++++++----------- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index de2d4f1b..4abc86d3 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -221,7 +221,17 @@ class ProjectForm(EditProjectForm): raise ValidationError(Markup(message)) -class DeleteProjectForm(FlaskForm): +class DestructiveActionProjectForm(FlaskForm): + """Used for any important "delete" action linked to a project: + + - delete project itself + - delete history + - delete IP addresses in history + - possibly others in the future + + It asks the user to enter the private code to confirm deletion. + """ + password = PasswordField( _("Private code"), description=_("Enter private code to confirm deletion"), diff --git a/ihatemoney/templates/forms.html b/ihatemoney/templates/forms.html index 7c2fdeb1..eb3321a0 100644 --- a/ihatemoney/templates/forms.html +++ b/ihatemoney/templates/forms.html @@ -125,6 +125,28 @@ {% endmacro %} +{% macro delete_project_history(form) %} + + {% include "display_errors.html" %} + {{ form.hidden_tag() }} + {{ input(form.password, inline=True) }} +
+ +
+ +{% endmacro %} + +{% macro delete_ip_addresses(form) %} + + {% include "display_errors.html" %} + {{ form.hidden_tag() }} + {{ input(form.password) }} +
+ +
+ +{% endmacro %} + {% macro add_bill(form, edit=False, title=True) %}
diff --git a/ihatemoney/templates/history.html b/ihatemoney/templates/history.html index 576ba336..d9136324 100644 --- a/ihatemoney/templates/history.html +++ b/ihatemoney/templates/history.html @@ -55,8 +55,7 @@ @@ -76,8 +75,7 @@ diff --git a/ihatemoney/web.py b/ihatemoney/web.py index 2bddc296..ca452ed1 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -39,7 +39,7 @@ from werkzeug.security import check_password_hash, generate_password_hash from ihatemoney.forms import ( AdminAuthenticationForm, AuthenticationForm, - DeleteProjectForm, + DestructiveActionProjectForm, EditProjectForm, EmptyForm, InviteForm, @@ -402,7 +402,7 @@ def reset_password(): @main.route("//edit", methods=["GET", "POST"]) def edit_project(): edit_form = EditProjectForm(id=g.project.id) - delete_form = DeleteProjectForm(id=g.project.id) + delete_form = DestructiveActionProjectForm(id=g.project.id) import_form = UploadForm() # Import form if import_form.validate_on_submit(): @@ -517,7 +517,7 @@ def import_project(file, project): @main.route("//delete", methods=["POST"]) def delete_project(): - form = DeleteProjectForm(id=g.project.id) + form = DestructiveActionProjectForm(id=g.project.id) if form.validate(): g.project.remove_project() flash(_("Project successfully deleted")) @@ -799,11 +799,11 @@ def settle_bill(): @main.route("//history") def history(): """Query for the version entries associated with this project.""" - csrf_form = EmptyForm() history = get_history(g.project, human_readable_names=True) any_ip_addresses = any(event["ip"] for event in history) + delete_form = DestructiveActionProjectForm() return render_template( "history.html", current_view="history", @@ -812,33 +812,40 @@ def history(): LoggingMode=LoggingMode, OperationType=Operation, current_log_pref=g.project.logging_preference, - csrf_form=csrf_form, + delete_form=delete_form, ) @main.route("//erase_history", methods=["POST"]) def erase_history(): """Erase all history entries associated with this project.""" - # Used for CSRF validation - form = EmptyForm() + form = DestructiveActionProjectForm(id=g.project.id) if not form.validate(): - flash(_("CSRF Token: The CSRF token is invalid."), category="danger") + flash( + _("Error deleting project history: wrong private code or wrong CSRF token"), + category="danger", + ) return redirect(url_for(".history")) for query in get_history_queries(g.project): query.delete(synchronize_session="fetch") db.session.commit() + flash(_("Deleted project history.")) return redirect(url_for(".history")) @main.route("//strip_ip_addresses", methods=["POST"]) def strip_ip_addresses(): """Strip ip addresses from history entries associated with this project.""" - # Used for CSRF validation - form = EmptyForm() + form = DestructiveActionProjectForm(id=g.project.id) if not form.validate(): - flash(_("CSRF Token: The CSRF token is invalid."), category="danger") + flash( + _( + "Error deleting recorded IP addresses: wrong private code or wrong CSRF token" + ), + category="danger", + ) return redirect(url_for(".history")) for query in get_history_queries(g.project): @@ -846,6 +853,7 @@ def strip_ip_addresses(): version_object.transaction.remote_addr = None db.session.commit() + flash(_("Deleted recorded IP addresses in project history.")) return redirect(url_for(".history"))