mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-05-01 18:52:23 +02:00
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.
This commit is contained in:
parent
969029a811
commit
db982572aa
4 changed files with 54 additions and 16 deletions
|
@ -221,7 +221,17 @@ class ProjectForm(EditProjectForm):
|
||||||
raise ValidationError(Markup(message))
|
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(
|
password = PasswordField(
|
||||||
_("Private code"),
|
_("Private code"),
|
||||||
description=_("Enter private code to confirm deletion"),
|
description=_("Enter private code to confirm deletion"),
|
||||||
|
|
|
@ -125,6 +125,28 @@
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro delete_project_history(form) %}
|
||||||
|
|
||||||
|
{% include "display_errors.html" %}
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ input(form.password, inline=True) }}
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-danger">{{ _("Confirm deletion") }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro delete_ip_addresses(form) %}
|
||||||
|
|
||||||
|
{% include "display_errors.html" %}
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ input(form.password) }}
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-danger">{{ _("Confirm deletion") }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro add_bill(form, edit=False, title=True) %}
|
{% macro add_bill(form, edit=False, title=True) %}
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|
|
@ -55,8 +55,7 @@
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ _("Close") }}</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ _("Close") }}</button>
|
||||||
<form action="{{ url_for(".strip_ip_addresses") }}" method="post">
|
<form action="{{ url_for(".strip_ip_addresses") }}" method="post">
|
||||||
{{ csrf_form.csrf_token }}
|
{{ forms.delete_ip_addresses(delete_form) }}
|
||||||
<input type="submit" class="btn btn-danger" value="{{ _("Confirm Delete") }}" name="{{ _("Confirm Delete") }}"/>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,8 +75,7 @@
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ _("Close") }}</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ _("Close") }}</button>
|
||||||
<form action="{{ url_for(".erase_history") }}" method="post">
|
<form action="{{ url_for(".erase_history") }}" method="post">
|
||||||
{{ csrf_form.csrf_token }}
|
{{ forms.delete_project_history(delete_form) }}
|
||||||
<input type="submit" class="btn btn-danger" value="{{ _("Confirm Delete") }}" name="{{ _("Confirm Delete") }}"/>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -39,7 +39,7 @@ from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
from ihatemoney.forms import (
|
from ihatemoney.forms import (
|
||||||
AdminAuthenticationForm,
|
AdminAuthenticationForm,
|
||||||
AuthenticationForm,
|
AuthenticationForm,
|
||||||
DeleteProjectForm,
|
DestructiveActionProjectForm,
|
||||||
EditProjectForm,
|
EditProjectForm,
|
||||||
EmptyForm,
|
EmptyForm,
|
||||||
InviteForm,
|
InviteForm,
|
||||||
|
@ -402,7 +402,7 @@ def reset_password():
|
||||||
@main.route("/<project_id>/edit", methods=["GET", "POST"])
|
@main.route("/<project_id>/edit", methods=["GET", "POST"])
|
||||||
def edit_project():
|
def edit_project():
|
||||||
edit_form = EditProjectForm(id=g.project.id)
|
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 = UploadForm()
|
||||||
# Import form
|
# Import form
|
||||||
if import_form.validate_on_submit():
|
if import_form.validate_on_submit():
|
||||||
|
@ -517,7 +517,7 @@ def import_project(file, project):
|
||||||
|
|
||||||
@main.route("/<project_id>/delete", methods=["POST"])
|
@main.route("/<project_id>/delete", methods=["POST"])
|
||||||
def delete_project():
|
def delete_project():
|
||||||
form = DeleteProjectForm(id=g.project.id)
|
form = DestructiveActionProjectForm(id=g.project.id)
|
||||||
if form.validate():
|
if form.validate():
|
||||||
g.project.remove_project()
|
g.project.remove_project()
|
||||||
flash(_("Project successfully deleted"))
|
flash(_("Project successfully deleted"))
|
||||||
|
@ -799,11 +799,11 @@ def settle_bill():
|
||||||
@main.route("/<project_id>/history")
|
@main.route("/<project_id>/history")
|
||||||
def history():
|
def history():
|
||||||
"""Query for the version entries associated with this project."""
|
"""Query for the version entries associated with this project."""
|
||||||
csrf_form = EmptyForm()
|
|
||||||
history = get_history(g.project, human_readable_names=True)
|
history = get_history(g.project, human_readable_names=True)
|
||||||
|
|
||||||
any_ip_addresses = any(event["ip"] for event in history)
|
any_ip_addresses = any(event["ip"] for event in history)
|
||||||
|
|
||||||
|
delete_form = DestructiveActionProjectForm()
|
||||||
return render_template(
|
return render_template(
|
||||||
"history.html",
|
"history.html",
|
||||||
current_view="history",
|
current_view="history",
|
||||||
|
@ -812,33 +812,40 @@ def history():
|
||||||
LoggingMode=LoggingMode,
|
LoggingMode=LoggingMode,
|
||||||
OperationType=Operation,
|
OperationType=Operation,
|
||||||
current_log_pref=g.project.logging_preference,
|
current_log_pref=g.project.logging_preference,
|
||||||
csrf_form=csrf_form,
|
delete_form=delete_form,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/<project_id>/erase_history", methods=["POST"])
|
@main.route("/<project_id>/erase_history", methods=["POST"])
|
||||||
def erase_history():
|
def erase_history():
|
||||||
"""Erase all history entries associated with this project."""
|
"""Erase all history entries associated with this project."""
|
||||||
# Used for CSRF validation
|
form = DestructiveActionProjectForm(id=g.project.id)
|
||||||
form = EmptyForm()
|
|
||||||
if not form.validate():
|
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"))
|
return redirect(url_for(".history"))
|
||||||
|
|
||||||
for query in get_history_queries(g.project):
|
for query in get_history_queries(g.project):
|
||||||
query.delete(synchronize_session="fetch")
|
query.delete(synchronize_session="fetch")
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
flash(_("Deleted project history."))
|
||||||
return redirect(url_for(".history"))
|
return redirect(url_for(".history"))
|
||||||
|
|
||||||
|
|
||||||
@main.route("/<project_id>/strip_ip_addresses", methods=["POST"])
|
@main.route("/<project_id>/strip_ip_addresses", methods=["POST"])
|
||||||
def strip_ip_addresses():
|
def strip_ip_addresses():
|
||||||
"""Strip ip addresses from history entries associated with this project."""
|
"""Strip ip addresses from history entries associated with this project."""
|
||||||
# Used for CSRF validation
|
form = DestructiveActionProjectForm(id=g.project.id)
|
||||||
form = EmptyForm()
|
|
||||||
if not form.validate():
|
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"))
|
return redirect(url_for(".history"))
|
||||||
|
|
||||||
for query in get_history_queries(g.project):
|
for query in get_history_queries(g.project):
|
||||||
|
@ -846,6 +853,7 @@ def strip_ip_addresses():
|
||||||
version_object.transaction.remote_addr = None
|
version_object.transaction.remote_addr = None
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
flash(_("Deleted recorded IP addresses in project history."))
|
||||||
return redirect(url_for(".history"))
|
return redirect(url_for(".history"))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue