Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional support for a simple CAPTCHA. #844

Merged
merged 4 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

`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
--------------------------
Expand Down
4 changes: 4 additions & 0 deletions ihatemoney/conf-templates/ihatemoney.cfg.j2
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ ACTIVATE_ADMIN_DASHBOARD = False
# Enable secure cookies. Requires HTTPS. Disable if you run your ihatemoney
# service over plain HTTP.
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
1 change: 1 addition & 0 deletions ihatemoney/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@
"uk",
"zh_Hans",
]
ENABLE_CAPTCHA = False
13 changes: 13 additions & 0 deletions ihatemoney/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,19 @@ def validate_id(form, field):
)
raise ValidationError(Markup(message))

@classmethod
def enable_captcha(cls):
captchaField = StringField(
_("Which is a real currency: Euro or Petro dollar?"),
validators=[DataRequired()],
)
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):
"""Used for any important "delete" action linked to a project:
Expand Down
5 changes: 4 additions & 1 deletion ihatemoney/templates/forms.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
{{ input(form.name) }}
{{ input(form.password) }}
{{ input(form.contact_email) }}
{% if config['ENABLE_CAPTCHA'] %}
{{ input(form.captcha) }}
{% endif %}
{{ input(form.default_currency) }}
{% if not home %}
{{ submit(form.submit, home=True) }}
Expand Down Expand Up @@ -171,7 +174,7 @@
</div>
</div>
</div>

<details class="mb-3">
<summary class="mb-2">{{ _("More options") }}</summary>
{% if g.project.default_currency != "XXX" %}
Expand Down
1 change: 1 addition & 0 deletions ihatemoney/tests/common/ihatemoney_testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class BaseTestCase(TestCase):
SQLALCHEMY_DATABASE_URI = os.environ.get(
"TESTING_SQLALCHEMY_DATABASE_URI", "sqlite://"
)
ENABLE_CAPTCHA = False

def create_app(self):
# Pass the test object as a configuration.
Expand Down
45 changes: 45 additions & 0 deletions ihatemoney/tests/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def test_default_configuration(self):
self.assertTrue(self.app.config["ACTIVATE_DEMO_PROJECT"])
self.assertTrue(self.app.config["ALLOW_PUBLIC_PROJECT_CREATION"])
self.assertFalse(self.app.config["ACTIVATE_ADMIN_DASHBOARD"])
self.assertFalse(self.app.config["ENABLE_CAPTCHA"])

def test_env_var_configuration_file(self):
"""Test that settings are loaded from a configuration file specified
Expand Down Expand Up @@ -241,6 +242,50 @@ def test_invitation_email_failure(self):
)


class CaptchaTestCase(IhatemoneyTestCase):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why when put in this file it doesn't have side effects, because it had some when I put it in the budget_test.py file. Any idea?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact… no. I put it there just because I felt it was the right place, and it just worked.

ENABLE_CAPTCHA = True

def test_project_creation_with_captcha(self):
with self.app.test_client() as c:
c.post(
"/create",
data={
"name": "raclette party",
"id": "raclette",
"password": "party",
"contact_email": "raclette@notmyidea.org",
"default_currency": "USD",
},
)
assert len(models.Project.query.all()) == 0

c.post(
"/create",
data={
"name": "raclette party",
"id": "raclette",
"password": "party",
"contact_email": "raclette@notmyidea.org",
"default_currency": "USD",
"captcha": "nope",
},
)
assert len(models.Project.query.all()) == 0

c.post(
"/create",
data={
"name": "raclette party",
"id": "raclette",
"password": "party",
"contact_email": "raclette@notmyidea.org",
"default_currency": "USD",
"captcha": "euro",
},
)
assert len(models.Project.query.all()) == 1


class TestCurrencyConverter(unittest.TestCase):
converter = CurrencyConverter()
mock_data = {"USD": 1, "EUR": 0.8, "CAD": 1.2, CurrencyConverter.no_currency: 1}
Expand Down
11 changes: 9 additions & 2 deletions ihatemoney/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,16 @@ def authenticate(project_id=None):
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)
def home():
project_form = ProjectForm()
project_form = get_project_form()
auth_form = AuthenticationForm()
is_demo_project_activated = current_app.config["ACTIVATE_DEMO_PROJECT"]
is_public_project_creation_allowed = current_app.config[
Expand All @@ -287,7 +294,7 @@ def mobile():
@main.route("/create", methods=["GET", "POST"])
@requires_admin(bypass=("ALLOW_PUBLIC_PROJECT_CREATION", True))
def create_project():
form = ProjectForm()
form = get_project_form()
if request.method == "GET" and "project_id" in request.values:
form.name.data = request.values["project_id"]

Expand Down