diff options
Diffstat (limited to 'budget')
| -rw-r--r-- | budget/default_settings.py | 2 | ||||
| -rw-r--r-- | budget/forms.py | 5 | ||||
| -rw-r--r-- | budget/templates/authenticate.html | 6 | ||||
| -rw-r--r-- | budget/templates/forms.html | 10 | ||||
| -rw-r--r-- | budget/templates/home.html | 4 | ||||
| -rw-r--r-- | budget/tests/tests.py | 21 | ||||
| -rw-r--r-- | budget/translations/fr/LC_MESSAGES/messages.mo | bin | 8040 -> 8226 bytes | |||
| -rw-r--r-- | budget/translations/fr/LC_MESSAGES/messages.po | 8 | ||||
| -rw-r--r-- | budget/web.py | 39 |
9 files changed, 93 insertions, 2 deletions
diff --git a/budget/default_settings.py b/budget/default_settings.py index 210b3f2..15fe9cd 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 f446475..06df743 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 98914d0..f241c48 100644 --- a/budget/templates/authenticate.html +++ b/budget/templates/authenticate.html @@ -7,7 +7,13 @@ to") }} <a href="{{ url_for(".create_project", project_id=create_project) }}">{{ _("create it") }}</a>{{ _("?") }} </p> {% endif %} +{% if admin_auth %} +<form class="form-horizontal" method="POST" accept-charset="utf-8"> + {{ forms.admin(form) }} +</form> +{% else %} <form class="form-horizontal" method="POST" accept-charset="utf-8"> {{ forms.authenticate(form) }} </form> +{% endif %} {% endblock %} diff --git a/budget/templates/forms.html b/budget/templates/forms.html index 01e5486..ffdd165 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 edbee61..c7a9d1e 100644 --- a/budget/templates/home.html +++ b/budget/templates/home.html @@ -28,6 +28,9 @@ </form> </div> <div class="col-3 offset-md-1"> + {% if is_admin_mode_enabled %} + <a href="{{ url_for(".create_project") }}">...{{ _("or create a new one") }}</a> + {% else %} <form id="creation-form" class="form-horizontal" action="{{ url_for(".create_project") }}" method="post"> <fieldset class="form-group"> <legend>...{{ _("or create a new one") }}</legend> @@ -37,6 +40,7 @@ <button class="btn" type="submit">{{ _("let's get started") }}</button> </div> </form> + {% endif %} </main> </div> {% endblock %} diff --git a/budget/tests/tests.py b/budget/tests/tests.py index e18e9c3..a1cedfa 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('<a href="/admin?goto=%2Fcreate">', resp.data.decode('utf-8')) + + # test right password + resp = self.app.post("/admin?goto=%2Fcreate", data={'admin_password': 'pass'}) + self.assertIn('<a href="/create">/create</a>', resp.data.decode('utf-8')) + + # test wrong password + resp = self.app.post("/admin?goto=%2Fcreate", data={'admin_password': 'wrong'}) + self.assertNotIn('<a href="/create">/create</a>', resp.data.decode('utf-8')) + + # test empty password + resp = self.app.post("/admin?goto=%2Fcreate", data={'admin_password': ''}) + self.assertNotIn('<a href="/create">/create</a>', 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 Binary files differindex 1794c62..c824b18 100644 --- a/budget/translations/fr/LC_MESSAGES/messages.mo +++ b/budget/translations/fr/LC_MESSAGES/messages.mo diff --git a/budget/translations/fr/LC_MESSAGES/messages.po b/budget/translations/fr/LC_MESSAGES/messages.po index 8bf347a..609846f 100644 --- a/budget/translations/fr/LC_MESSAGES/messages.po +++ b/budget/translations/fr/LC_MESSAGES/messages.po @@ -41,6 +41,10 @@ msgstr "Email" msgid "Project identifier" msgstr "Identifiant du projet" +#: forms.py:87 +msgid "Admin password" +msgstr "Mot de passe administrateur" + #: forms.py:87 templates/send_invites.html:5 msgid "Create the project" msgstr "Créer le projet" @@ -159,6 +163,10 @@ msgid "Export file format" msgstr "Format du fichier d'export" #: web.py:95 +msgid "This admin password is not the right one" +msgstr "Le mot de passe administrateur que vous avez entré n'est pas correct" + +#: web.py:95 msgid "This private code is not the right one" msgstr "Le code que vous avez entré n'est pas correct" diff --git a/budget/web.py b/budget/web.py index efb427c..3bfa73a 100644 --- a/budget/web.py +++ b/budget/web.py @@ -16,11 +16,12 @@ from flask_babel import get_locale, gettext as _ from smtplib import SMTPRecipientsRefused import werkzeug from sqlalchemy import orm +from functools import wraps # local modules from models import db, Project, Person, Bill -from forms import AuthenticationForm, EditProjectForm, InviteForm, \ - MemberForm, PasswordReminder, ProjectForm, get_billform_for, \ +from forms import AdminAuthenticationForm, AuthenticationForm, EditProjectForm, \ + InviteForm, MemberForm, PasswordReminder, ProjectForm, get_billform_for, \ ExportForm from utils import Redirect303, list_of_dicts2json, list_of_dicts2csv @@ -28,6 +29,19 @@ main = Blueprint("main", __name__) mail = Mail() +def requires_admin(f): + """Require admin permissions for @requires_admin decorated endpoints. + Has no effect if ADMIN_PASSWORD is empty (default value) + """ + @wraps(f) + def admin_auth(*args, **kws): + admin_password = session.get('admin_password', '') + if not admin_password == current_app.config['ADMIN_PASSWORD']: + raise Redirect303(url_for('.admin', goto=request.path)) + return f(*args, **kws) + return admin_auth + + @main.url_defaults def add_project_id(endpoint, values): """Add the project id to the url calls if it is expected. @@ -66,6 +80,23 @@ def pull_project(endpoint, values): url_for(".authenticate", project_id=project_id)) +@main.route("/admin", methods=["GET", "POST"]) +def admin(): + """Admin authentication""" + form = AdminAuthenticationForm() + goto = request.args.get('goto', url_for('.home')) + if request.method == "POST": + if form.validate(): + if form.admin_password.data == current_app.config['ADMIN_PASSWORD']: + session['admin_password'] = form.admin_password.data + session.update() + return redirect(goto) + else: + msg = _("This admin password is not the right one") + form.errors['admin_password'] = [msg] + return render_template("authenticate.html", form=form, admin_auth=True) + + @main.route("/authenticate", methods=["GET", "POST"]) def authenticate(project_id=None): """Authentication form""" @@ -121,14 +152,18 @@ def authenticate(project_id=None): def home(): project_form = ProjectForm() auth_form = AuthenticationForm() + # If ADMIN_PASSWORD is empty we consider that admin mode is disabled + is_admin_mode_enabled = bool(current_app.config['ADMIN_PASSWORD']) is_demo_project_activated = current_app.config['ACTIVATE_DEMO_PROJECT'] return render_template("home.html", project_form=project_form, is_demo_project_activated=is_demo_project_activated, + is_admin_mode_enabled=is_admin_mode_enabled, auth_form=auth_form, session=session) @main.route("/create", methods=["GET", "POST"]) +@requires_admin def create_project(): form = ProjectForm() if request.method == "GET" and 'project_id' in request.values: |
