Add a @require_admin decorator

It can be used to protect specific endpoints with ADMIN_PASS
(a password that is stored unencrypted in the settings)
The decorator has no effect if ADMIN_PASS is an empty string (default value)
This commit is contained in:
0livd 2017-05-04 17:55:40 +02:00
parent e3da3b3b7f
commit a5856a3b05
7 changed files with 64 additions and 2 deletions

View file

@ -8,3 +8,5 @@ SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "tralala" SECRET_KEY = "tralala"
MAIL_DEFAULT_SENDER = ("Budget manager", "budget@notmyidea.org") MAIL_DEFAULT_SENDER = ("Budget manager", "budget@notmyidea.org")
ADMIN_PASS = ""

View file

@ -83,6 +83,11 @@ class AuthenticationForm(FlaskForm):
submit = SubmitField(_("Get in")) submit = SubmitField(_("Get in"))
class AdminAuthenticationForm(FlaskForm):
admin_pass = PasswordField(_("Admin password"), validators=[Required()])
submit = SubmitField(_("Get in"))
class PasswordReminder(FlaskForm): class PasswordReminder(FlaskForm):
id = StringField(_("Project identifier"), validators=[Required()]) id = StringField(_("Project identifier"), validators=[Required()])
submit = SubmitField(_("Send me the code by email")) 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>{{ _("?") }} to") }} <a href="{{ url_for(".create_project", project_id=create_project) }}">{{ _("create it") }}</a>{{ _("?") }}
</p> </p>
{% endif %} {% endif %}
{% if admin %}
<form class="form-horizontal" method="POST" accept-charset="utf-8">
{{ forms.authenticate_admin(form) }}
</form>
{% else %}
<form class="form-horizontal" method="POST" accept-charset="utf-8"> <form class="form-horizontal" method="POST" accept-charset="utf-8">
{{ forms.authenticate(form) }} {{ forms.authenticate(form) }}
</form> </form>
{% endif %}
{% endblock %} {% endblock %}

View file

@ -45,6 +45,16 @@
{% endmacro %} {% endmacro %}
{% macro authenticate_admin(form) %}
{% include "display_errors.html" %}
{{ form.hidden_tag() }}
{{ input(form.admin_pass) }}
{{ submit(form.submit) }}
{% endmacro %}
{% macro create_project(form, home=False) %} {% macro create_project(form, home=False) %}
{% include "display_errors.html" %} {% include "display_errors.html" %}

View file

@ -41,6 +41,10 @@ msgstr "Email"
msgid "Project identifier" msgid "Project identifier"
msgstr "Identifiant du projet" msgstr "Identifiant du projet"
#: forms.py:87
msgid "Admin password"
msgstr "Mot de passe administrateur"
#: forms.py:87 templates/send_invites.html:5 #: forms.py:87 templates/send_invites.html:5
msgid "Create the project" msgid "Create the project"
msgstr "Créer le projet" msgstr "Créer le projet"
@ -158,6 +162,10 @@ msgstr "remboursements"
msgid "Export file format" msgid "Export file format"
msgstr "Format du fichier d'export" 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 #: web.py:95
msgid "This private code is not the right one" msgid "This private code is not the right one"
msgstr "Le code que vous avez entré n'est pas correct" 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 from smtplib import SMTPRecipientsRefused
import werkzeug import werkzeug
from sqlalchemy import orm from sqlalchemy import orm
from functools import wraps
# local modules # local modules
from models import db, Project, Person, Bill from models import db, Project, Person, Bill
from forms import AuthenticationForm, EditProjectForm, InviteForm, \ from forms import AdminAuthenticationForm, AuthenticationForm, EditProjectForm, \
MemberForm, PasswordReminder, ProjectForm, get_billform_for, \ InviteForm, MemberForm, PasswordReminder, ProjectForm, get_billform_for, \
ExportForm ExportForm
from utils import Redirect303, list_of_dicts2json, list_of_dicts2csv from utils import Redirect303, list_of_dicts2json, list_of_dicts2csv
@ -28,6 +29,19 @@ main = Blueprint("main", __name__)
mail = Mail() mail = Mail()
def require_admin(f):
"""Require admin permissions for @require_admin decorated endpoints.
Has no effect if ADMIN_PASS is empty (default value)
"""
@wraps(f)
def admin_auth(*args, **kws):
admin_pass = session.get('admin_pass', '')
if not admin_pass == current_app.config['ADMIN_PASS']:
raise Redirect303(url_for('.authenticate_admin', goto=request.path))
return f(*args, **kws)
return admin_auth
@main.url_defaults @main.url_defaults
def add_project_id(endpoint, values): def add_project_id(endpoint, values):
"""Add the project id to the url calls if it is expected. """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)) url_for(".authenticate", project_id=project_id))
@main.route("/authenticate_admin", methods=["GET", "POST"])
def authenticate_admin():
"""Admin authentication"""
form = AdminAuthenticationForm()
goto = request.args.get('goto', url_for('.home'))
if request.method == "POST":
if form.validate():
if form.admin_pass.data == current_app.config['ADMIN_PASS']:
session['admin_pass'] = form.admin_pass.data
session.update()
return redirect(goto)
else:
msg = _("This admin password is not the right one")
form.errors['admin_pass'] = [msg]
return render_template("authenticate.html", form=form, admin=True)
@main.route("/authenticate", methods=["GET", "POST"]) @main.route("/authenticate", methods=["GET", "POST"])
def authenticate(project_id=None): def authenticate(project_id=None):
"""Authentication form""" """Authentication form"""