Add optional support for a simple CAPTCHA.

This commit is contained in:
Alexis Métaireau 2021-10-06 00:25:42 +02:00 committed by Glandos
parent e626a1cbea
commit 0fff98eea0
9 changed files with 40 additions and 4 deletions

View file

@ -155,6 +155,11 @@ Note: this setting is actually interpreted by Flask-Babel, see the
.. _Flask-Babel guide for formatting dates: https://pythonhosted.org/Flask-Babel/#formatting-dates .. _Flask-Babel guide for formatting dates: https://pythonhosted.org/Flask-Babel/#formatting-dates
`ENABLE_CAPTCHA`
---------------
It is possible to add a simple captcha in order to filter out spammer bots on the form creation.
In order to do so, you just have to set `ENABLE_CAPTCHA = True`.
Configuring emails sending Configuring emails sending
-------------------------- --------------------------

View file

@ -42,3 +42,7 @@ ACTIVATE_ADMIN_DASHBOARD = False
# Enable secure cookies. Requires HTTPS. Disable if you run your ihatemoney # Enable secure cookies. Requires HTTPS. Disable if you run your ihatemoney
# service over plain HTTP. # service over plain HTTP.
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
# You can activate an optional CAPTCHA if you want to. It can be helpful
# to filter spammer bots.
# ENABLE_CAPTCHA = True

View file

@ -32,3 +32,4 @@ SUPPORTED_LANGUAGES = [
"uk", "uk",
"zh_Hans", "zh_Hans",
] ]
ENABLE_CAPTCHA = False

View file

@ -3,12 +3,13 @@ from re import match
from types import SimpleNamespace from types import SimpleNamespace
import email_validator import email_validator
from flask import request from flask import current_app, request
from flask_babel import lazy_gettext as _ from flask_babel import lazy_gettext as _
from flask_wtf.file import FileAllowed, FileField, FileRequired from flask_wtf.file import FileAllowed, FileField, FileRequired
from flask_wtf.form import FlaskForm from flask_wtf.form import FlaskForm
from markupsafe import Markup from markupsafe import Markup
from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.security import check_password_hash, generate_password_hash
from wtforms.fields import Field
from wtforms.fields.core import Label, SelectField, SelectMultipleField from wtforms.fields.core import Label, SelectField, SelectMultipleField
from wtforms.fields.html5 import DateField, DecimalField, URLField from wtforms.fields.html5 import DateField, DecimalField, URLField
from wtforms.fields.simple import BooleanField, PasswordField, StringField, SubmitField from wtforms.fields.simple import BooleanField, PasswordField, StringField, SubmitField
@ -221,6 +222,16 @@ class ProjectForm(EditProjectForm):
) )
raise ValidationError(Markup(message)) raise ValidationError(Markup(message))
@classmethod
def enable_captcha(cls):
captchaField = StringField(_("Which is a real currency: Euro or Petro dollar?"))
setattr(cls, "captcha", captchaField)
def validate_captcha(form, field):
if not field.data.lower() == _("euro"):
message = _("Please, validate the captcha to proceed.")
raise ValidationError(Markup(message))
class DestructiveActionProjectForm(FlaskForm): class DestructiveActionProjectForm(FlaskForm):
"""Used for any important "delete" action linked to a project: """Used for any important "delete" action linked to a project:

View file

@ -75,6 +75,9 @@
{{ input(form.name) }} {{ input(form.name) }}
{{ input(form.password) }} {{ input(form.password) }}
{{ input(form.contact_email) }} {{ input(form.contact_email) }}
{% if config['ENABLE_CAPTCHA'] %}
{{ input(form.captcha) }}
{% endif %}
{{ input(form.default_currency) }} {{ input(form.default_currency) }}
{% if not home %} {% if not home %}
{{ submit(form.submit, home=True) }} {{ submit(form.submit, home=True) }}
@ -171,7 +174,7 @@
</div> </div>
</div> </div>
</div> </div>
<details class="mb-3"> <details class="mb-3">
<summary class="mb-2">{{ _("More options") }}</summary> <summary class="mb-2">{{ _("More options") }}</summary>
{% if g.project.default_currency != "XXX" %} {% if g.project.default_currency != "XXX" %}

View file

@ -17,6 +17,8 @@ from ihatemoney.tests.common.ihatemoney_testcase import IhatemoneyTestCase
from ihatemoney.versioning import LoggingMode from ihatemoney.versioning import LoggingMode
class BudgetTestCase(IhatemoneyTestCase): class BudgetTestCase(IhatemoneyTestCase):
def test_notifications(self): def test_notifications(self):
"""Test that the notifications are sent, and that email addresses """Test that the notifications are sent, and that email addresses
@ -310,6 +312,7 @@ class BudgetTestCase(IhatemoneyTestCase):
# project removed # project removed
self.assertEqual(len(models.Project.query.all()), 0) self.assertEqual(len(models.Project.query.all()), 0)
def test_bill_placeholder(self): def test_bill_placeholder(self):
self.post_project("raclette") self.post_project("raclette")
self.login("raclette") self.login("raclette")

View file

@ -15,6 +15,7 @@ class BaseTestCase(TestCase):
SQLALCHEMY_DATABASE_URI = os.environ.get( SQLALCHEMY_DATABASE_URI = os.environ.get(
"TESTING_SQLALCHEMY_DATABASE_URI", "sqlite://" "TESTING_SQLALCHEMY_DATABASE_URI", "sqlite://"
) )
ENABLE_CAPTCHA = False
def create_app(self): def create_app(self):
# Pass the test object as a configuration. # Pass the test object as a configuration.

View file

@ -31,6 +31,7 @@ class ConfigurationTestCase(BaseTestCase):
self.assertTrue(self.app.config["ACTIVATE_DEMO_PROJECT"]) self.assertTrue(self.app.config["ACTIVATE_DEMO_PROJECT"])
self.assertTrue(self.app.config["ALLOW_PUBLIC_PROJECT_CREATION"]) self.assertTrue(self.app.config["ALLOW_PUBLIC_PROJECT_CREATION"])
self.assertFalse(self.app.config["ACTIVATE_ADMIN_DASHBOARD"]) self.assertFalse(self.app.config["ACTIVATE_ADMIN_DASHBOARD"])
self.assertFalse(self.app.config["ENABLE_CAPTCHA"])
def test_env_var_configuration_file(self): def test_env_var_configuration_file(self):
"""Test that settings are loaded from a configuration file specified """Test that settings are loaded from a configuration file specified

View file

@ -260,9 +260,16 @@ def authenticate(project_id=None):
return render_template("authenticate.html", form=form) return render_template("authenticate.html", form=form)
def get_project_form():
if current_app.config.get("ENABLE_CAPTCHA", False):
ProjectForm.enable_captcha()
return ProjectForm()
@main.route("/", strict_slashes=False) @main.route("/", strict_slashes=False)
def home(): def home():
project_form = ProjectForm() project_form = get_project_form()
auth_form = AuthenticationForm() auth_form = AuthenticationForm()
is_demo_project_activated = current_app.config["ACTIVATE_DEMO_PROJECT"] is_demo_project_activated = current_app.config["ACTIVATE_DEMO_PROJECT"]
is_public_project_creation_allowed = current_app.config[ is_public_project_creation_allowed = current_app.config[
@ -287,7 +294,7 @@ def mobile():
@main.route("/create", methods=["GET", "POST"]) @main.route("/create", methods=["GET", "POST"])
@requires_admin(bypass=("ALLOW_PUBLIC_PROJECT_CREATION", True)) @requires_admin(bypass=("ALLOW_PUBLIC_PROJECT_CREATION", True))
def create_project(): def create_project():
form = ProjectForm() form = get_project_form()
if request.method == "GET" and "project_id" in request.values: if request.method == "GET" and "project_id" in request.values:
form.name.data = request.values["project_id"] form.name.data = request.values["project_id"]