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)
This commit is contained in:
0livd 2017-05-04 17:55:40 +02:00
parent 091553be56
commit 5880a1a3a2
7 changed files with 64 additions and 2 deletions

View file

@ -10,3 +10,5 @@ SECRET_KEY = "tralala"
MAIL_DEFAULT_SENDER = ("Budget manager", "budget@notmyidea.org")
ACTIVATE_DEMO_PROJECT = True
ADMIN_PASSWORD = ""

View file

@ -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"))

View file

@ -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 %}

View file

@ -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" %}

View file

@ -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"
@ -158,6 +162,10 @@ msgstr "remboursements"
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"

View file

@ -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"""