Introduce honeypot for login/registering/password changing

This commit is contained in:
2024-07-10 12:24:41 +02:00
parent 7de2e00c94
commit 72cf5a3d5e
9 changed files with 132 additions and 17 deletions

View File

@ -33,6 +33,7 @@
{% endif %}
{% csrf_token %}
{% render_honeypot_field %}
<div>
<label for="{{ form.username.name }}">{{ form.username.label }}</label>

View File

@ -3,6 +3,7 @@
{% block content %}
<form method="post" action="">
{% csrf_token %}
{% render_honeypot_field %}
{{ form.as_p() }}
<input type="submit" value="{% trans %}Reset{% endtrans %}" />
</form>

View File

@ -23,6 +23,7 @@
{% else %}
<form action="{{ url('core:register') }}" method="post">
{% csrf_token %}
{% render_honeypot_field %}
{{ form }}
<input type="submit" value="{% trans %}Register{% endtrans %}" />
</form>

View File

@ -0,0 +1,58 @@
#
# Copyright 2024
# - Sli <antoine@bartuccio.fr>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from typing import Callable
import honeypot.templatetags.honeypot as honeypot_filters
from django.template.loader import render_to_string
from jinja2 import Environment, nodes
from jinja2.ext import Extension
from jinja2.parser import Parser
class HoneypotExtension(Extension):
"""
Wrapper around the honeypot extension tag
Known limitation: doesn't support arguments
Usage: {% render_honeypot_field %}
"""
tags = {"render_honeypot_field"}
def __init__(self, environment: Environment) -> None:
environment.globals["render_honeypot_field"] = (
honeypot_filters.render_honeypot_field
)
self.environment = environment
def parse(self, parser: Parser) -> nodes.Output:
lineno = parser.stream.expect("name:render_honeypot_field").lineno
call = self.call_method(
"_render",
[nodes.Name("render_honeypot_field", "load", lineno=lineno)],
lineno=lineno,
)
return nodes.Output([nodes.MarkSafe(call)])
def _render(self, render_honeypot_field: Callable[[str | None], str]):
return render_to_string("honeypot/honeypot_field.html", render_honeypot_field())

View File

@ -36,6 +36,7 @@ class TestUserRegistration:
@pytest.fixture()
def valid_payload(self):
return {
settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE,
"first_name": "this user does not exist (yet)",
"last_name": "this user does not exist (yet)",
"email": "i-dont-exist-yet@git.an",
@ -68,6 +69,13 @@ class TestUserRegistration:
assert response.status_code == 200
assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content)
def test_register_honeypot_fail(self, client, valid_payload):
payload = valid_payload | {
settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "random"
}
response = client.post(reverse("core:register"), payload)
assert response.status_code == 400
def test_register_user_form_fail_already_exists(self, client, valid_payload):
"""Should not register a user correctly if it already exists."""
# create the user, then try to create it again
@ -90,7 +98,11 @@ class TestUserLogin:
response = client.post(
reverse("core:login"),
{"username": user.username, "password": "wrong-password"},
{
"username": user.username,
"password": "wrong-password",
settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE,
},
)
assert response.status_code == 200
assert (
@ -98,12 +110,28 @@ class TestUserLogin:
"et votre mot de passe ne correspondent pas. Merci de réessayer.</p>"
) in str(response.content.decode())
def test_login_honeypot(self, client, user):
response = client.post(
reverse("core:login"),
{
"username": user.username,
"password": "wrong-password",
settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "incorrect",
},
)
assert response.status_code == 400
def test_login_success(self, client, user):
"""
Should login a user correctly
"""
response = client.post(
reverse("core:login"), {"username": user.username, "password": "plop"}
reverse("core:login"),
{
"username": user.username,
"password": "plop",
settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE,
},
)
assertRedirects(response, reverse("core:index"))

View File

@ -36,6 +36,7 @@ from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.template.response import TemplateResponse
from django.urls import reverse, reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
from django.views.generic import (
CreateView,
@ -46,6 +47,7 @@ from django.views.generic import (
)
from django.views.generic.dates import MonthMixin, YearMixin
from django.views.generic.edit import UpdateView
from honeypot.decorators import check_honeypot
from api.views.sas import all_pictures_of_user
from core.models import Gift, Preferences, SithFile, User
@ -69,6 +71,7 @@ from subscription.models import Subscription
from trombi.views import UserTrombiForm
@method_decorator(check_honeypot, name="post")
class SithLoginView(views.LoginView):
"""
The login View
@ -124,9 +127,10 @@ def password_root_change(request, user_id):
)
@method_decorator(check_honeypot, name="post")
class SithPasswordResetView(views.PasswordResetView):
"""
Allows someone to enter an email adresse for resetting password
Allows someone to enter an email address for resetting password
"""
template_name = "core/password_reset.jinja"
@ -153,12 +157,13 @@ class SithPasswordResetConfirmView(views.PasswordResetConfirmView):
class SithPasswordResetCompleteView(views.PasswordResetCompleteView):
"""
Confirm the password has sucessfully been reset
Confirm the password has successfully been reset
"""
template_name = "core/password_reset_complete.jinja"
@check_honeypot
def register(request):
context = {}
if request.method == "POST":