From ea8eda35a7bd831964c38b38cc9a5b19bcb44d6a Mon Sep 17 00:00:00 2001 From: 0livd <0livd@users.noreply.github.com> Date: Thu, 18 May 2017 10:48:09 +0100 Subject: [PATCH] Public project creation and admin permissions (#210) * Add a @requires_admin decorator It can be used to protect specific endpoints with ADMIN_PASSWORD (a password that is stored unencrypted in the settings) The decorator has no effect if ADMIN_PASSWORD is an empty string (default value) * Require admin permissions to access create project endpoint When ADMIN_PASSWORD is not empty, project creation form on the home page will be replaced by a link to the create project endpoint so one is able to enter the admin password before filling the form --- budget/default_settings.py | 2 + budget/forms.py | 5 +++ budget/templates/authenticate.html | 6 +++ budget/templates/forms.html | 10 +++++ budget/templates/home.html | 4 ++ budget/tests/tests.py | 21 ++++++++++ .../translations/fr/LC_MESSAGES/messages.mo | Bin 8040 -> 8226 bytes .../translations/fr/LC_MESSAGES/messages.po | 8 ++++ budget/web.py | 39 +++++++++++++++++- 9 files changed, 93 insertions(+), 2 deletions(-) diff --git a/budget/default_settings.py b/budget/default_settings.py index 210b3f20..15fe9cdd 100644 --- a/budget/default_settings.py +++ b/budget/default_settings.py @@ -10,3 +10,5 @@ SECRET_KEY = "tralala" MAIL_DEFAULT_SENDER = ("Budget manager", "budget@notmyidea.org") ACTIVATE_DEMO_PROJECT = True + +ADMIN_PASSWORD = "" diff --git a/budget/forms.py b/budget/forms.py index f4464751..06df7430 100644 --- a/budget/forms.py +++ b/budget/forms.py @@ -83,6 +83,11 @@ class AuthenticationForm(FlaskForm): submit = SubmitField(_("Get in")) +class AdminAuthenticationForm(FlaskForm): + admin_password = PasswordField(_("Admin password"), validators=[Required()]) + submit = SubmitField(_("Get in")) + + class PasswordReminder(FlaskForm): id = StringField(_("Project identifier"), validators=[Required()]) submit = SubmitField(_("Send me the code by email")) diff --git a/budget/templates/authenticate.html b/budget/templates/authenticate.html index 98914d09..f241c487 100644 --- a/budget/templates/authenticate.html +++ b/budget/templates/authenticate.html @@ -7,7 +7,13 @@ to") }} {{ _("create it") }}{{ _("?") }}

{% endif %} +{% if admin_auth %} +
+ {{ forms.admin(form) }} +
+{% else %}
{{ forms.authenticate(form) }}
+{% endif %} {% endblock %} diff --git a/budget/templates/forms.html b/budget/templates/forms.html index 01e54867..ffdd165b 100644 --- a/budget/templates/forms.html +++ b/budget/templates/forms.html @@ -45,6 +45,16 @@ {% endmacro %} +{% macro admin(form) %} + + {% include "display_errors.html" %} + + {{ form.hidden_tag() }} + {{ input(form.admin_password) }} + {{ submit(form.submit) }} + +{% endmacro %} + {% macro create_project(form, home=False) %} {% include "display_errors.html" %} diff --git a/budget/templates/home.html b/budget/templates/home.html index edbee61a..c7a9d1e8 100644 --- a/budget/templates/home.html +++ b/budget/templates/home.html @@ -28,6 +28,9 @@
+ {% if is_admin_mode_enabled %} + ...{{ _("or create a new one") }} + {% else %}
...{{ _("or create a new one") }} @@ -37,6 +40,7 @@
+ {% endif %} {% endblock %} diff --git a/budget/tests/tests.py b/budget/tests/tests.py index e18e9c32..a1cedfad 100644 --- a/budget/tests/tests.py +++ b/budget/tests/tests.py @@ -44,6 +44,8 @@ class TestCase(unittest.TestCase): # clean after testing models.db.session.remove() models.db.drop_all() + # reconfigure app with default settings + run.configure() def login(self, project, password=None, test_client=None): password = password or project @@ -373,6 +375,25 @@ class BudgetTestCase(TestCase): c.get("/exit") self.assertNotIn('raclette', session) + def test_admin_authentication(self): + run.app.config['ADMIN_PASSWORD'] = "pass" + + # test the redirection to the authentication page when trying to access admin endpoints + resp = self.app.get("/create") + self.assertIn('', resp.data.decode('utf-8')) + + # test right password + resp = self.app.post("/admin?goto=%2Fcreate", data={'admin_password': 'pass'}) + self.assertIn('/create', resp.data.decode('utf-8')) + + # test wrong password + resp = self.app.post("/admin?goto=%2Fcreate", data={'admin_password': 'wrong'}) + self.assertNotIn('/create', resp.data.decode('utf-8')) + + # test empty password + resp = self.app.post("/admin?goto=%2Fcreate", data={'admin_password': ''}) + self.assertNotIn('/create', resp.data.decode('utf-8')) + def test_manage_bills(self): self.post_project("raclette") diff --git a/budget/translations/fr/LC_MESSAGES/messages.mo b/budget/translations/fr/LC_MESSAGES/messages.mo index 1794c62c0829ef2d705a2b38c02038c453eede41..c824b18aeafe0f26a114e334bc629711589a1e0d 100644 GIT binary patch delta 2109 zcmZ|Pe@K;A9LMo*dgtwjsre(9*SV)(*R0I2ZLJutTvAc4tu@Lv1jTFJh8lXW+ZyXh z$Z(8NVOG#D%8`{NGDf?ccwHL9yX6)$ydOpF^#<54Dq9n2NulLj4dGkzv;!iHz zqaoC1^%OO5fJ9Jx8Y(G6sP;14&3C#CweVjVY&QOdimYEq0whsbg7mSasEOA*Yf%f> zglyWjVhDG-=e?LiJ%Ku+>sW$!P?1UEp~jnwip=Y%ao$BD;9D&Pg>nNHVjS6&9YQWU z!%d&v_pbdODzuM~{Ip?I(y5c`AylLaQIRY~MPvagA|Il1pdK^y{kKz4NcN&SUP1E4 z{y^>I8R{1;Oaf>JGf@M*h4Ziy6}dLl!uO#1^&pph$4v|Q85zn3P#b=VLFTt)db0@| zhk7s>HBbpEREtqNTa7yNYUEvOKqXhZt9QHS-{O4QZ(tiH@onn8?Wm1)qK>2+eSJ2) z6g<3*8t^eHIns%%LN(jD0&h^?giG*M65(^)f))52d1Ph0JO`^Vj2)<*pFthXRn*4% z^NGKbYmf%*{2A)^Aiyg0VlgV&)*$(0n^9-qj>_gQQK9Wc?dWUNI2TY8^kF&vf@PS` zZnVG;Q1i4F5PyaC2o0s!i(1eC5(ImK%IZ{RE5;lgIeTX%Y5{9e3#djdpdR%(#$0d3x8&36=)GpCCvXu?aViGD<#)qT_oAEE}z@jABz&yKHah&FngYg$@9ZfdSe%ozP? z%G8?ww0Y4MudylS#WqH~=IDlvF|VmHk~oky7)X9)&fLyzS?4Fkyt>H97!hye-Dpd! hxh58gHz!`pemhvOGU7Eb!2k5=>A(dVbBf=mu=tD+@)mVxVMQ9>n zS|XX$7Y%J8rIz#}k-bQtG%6|59F>(xzVxZzUvFqU&;2{+JokOhbzSG&+jh%9Pv5aI zgI_NG9`~K#`(*amn-7 zGh5lsRCW>j68Qrf*YIJ!!F9}*O!rhKn#bI?nB%#G?cdcL z$ILr}31l{NCgxVor%zef=b9UqF_|rAGJS+u;8`Z4mzdMNg_(FqHNJ;AjGt8Fzi_?x z@-HUv)h4@v+nCB8<{ZVl0Hf2u{^^ZvKOoVTTF%e znF@Z$RN^30p#f&^|7HSdGDKZPlkqC1GV3UovauT9$&2s*K8-~96_e>PX0LyxPr2ZdL)B=t z^gEf~=dqi2aTV8AxdYZ}PJTpoaV&_rgAB> z=g(JeU;@~}1hAb6U