diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa0724d4..7e5514cc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,10 +3,12 @@ Changelog This document describes changes between each past release. -1.1 (unreleased) +2.0 (unreleased) ---------------- -- Nothing changed yet. +### Changed + +- **BREAKING CHANGE** Use a hashed ``ADMIN_PASSWORD`` instead of a clear text one, ``./budget/manage.py generate_password_hash`` can be used to generate a proper password HASH (#236) 1.0 (2017-06-20) diff --git a/budget/manage.py b/budget/manage.py index 94a21a27..f717fed5 100755 --- a/budget/manage.py +++ b/budget/manage.py @@ -1,15 +1,26 @@ #!/usr/bin/env python -from flask_script import Manager +from flask_script import Manager, Command from flask_migrate import Migrate, MigrateCommand +from werkzeug.security import generate_password_hash from run import app from models import db +from getpass import getpass + + +class GeneratePasswordHash(Command): + "Get password from user and hash it without printing it in clear text" + + def run(self): + password = getpass(prompt='Password: ') + print(generate_password_hash(password)) migrate = Migrate(app, db) manager = Manager(app) manager.add_command('db', MigrateCommand) +manager.add_command('generate_password_hash', GeneratePasswordHash) if __name__ == '__main__': diff --git a/budget/run.py b/budget/run.py index 00d43264..b576f729 100644 --- a/budget/run.py +++ b/budget/run.py @@ -52,6 +52,17 @@ def configure(): if not 'MAIL_DEFAULT_SENDER' in app.config: app.config['MAIL_DEFAULT_SENDER'] = DEFAULT_MAIL_SENDER + if "pbkdf2:sha256:" not in app.config['ADMIN_PASSWORD'] and app.config['ADMIN_PASSWORD']: + # Since 2.0 + warnings.warn( + "The way Ihatemoney stores your ADMIN_PASSWORD has changed. You are using an unhashed" + +" ADMIN_PASSWORD, which is not supported anymore and won't let you access your admin" + +" endpoints. Please use the command './budget/manage.py generate_password_hash'" + +" to generate a proper password HASH and copy the output to the value of" + +" ADMIN_PASSWORD in your settings file.", + UserWarning + ) + configure() diff --git a/budget/tests/tests.py b/budget/tests/tests.py index a1cedfad..16aaae9f 100644 --- a/budget/tests/tests.py +++ b/budget/tests/tests.py @@ -10,6 +10,7 @@ import json from collections import defaultdict import six +from werkzeug.security import generate_password_hash from flask import session # Unset configuration file env var if previously set @@ -376,7 +377,7 @@ class BudgetTestCase(TestCase): self.assertNotIn('raclette', session) def test_admin_authentication(self): - run.app.config['ADMIN_PASSWORD'] = "pass" + run.app.config['ADMIN_PASSWORD'] = generate_password_hash("pass") # test the redirection to the authentication page when trying to access admin endpoints resp = self.app.get("/create") diff --git a/budget/web.py b/budget/web.py index 3bfa73a1..ff157b64 100644 --- a/budget/web.py +++ b/budget/web.py @@ -13,6 +13,8 @@ from flask import Blueprint, current_app, flash, g, redirect, \ render_template, request, session, url_for, send_file from flask_mail import Mail, Message from flask_babel import get_locale, gettext as _ +from werkzeug.security import generate_password_hash, \ + check_password_hash from smtplib import SMTPRecipientsRefused import werkzeug from sqlalchemy import orm @@ -35,10 +37,10 @@ def requires_admin(f): """ @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) + is_admin = session.get('is_admin') + if is_admin or not current_app.config['ADMIN_PASSWORD']: + return f(*args, **kws) + raise Redirect303(url_for('.admin', goto=request.path)) return admin_auth @@ -87,8 +89,8 @@ def admin(): 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 + if check_password_hash(current_app.config['ADMIN_PASSWORD'], form.admin_password.data): + session['is_admin'] = True session.update() return redirect(goto) else: diff --git a/docs/installation.rst b/docs/installation.rst index 785ccc66..3cd143d0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -72,21 +72,23 @@ ihatemoney relies on a configuration file. If you run the application for the first time, you will need to take a few moments to configure the application properly. -+----------------------------+---------------------------+-----------------------------------------------------------------------------+ -| Setting name | Default | What does it do? | -+============================+===========================+=============================================================================+ -| SQLALCHEMY_DATABASE_URI | ``sqlite:///budget.db`` | Specifies the type of backend to use and its location. More information | -| | | on the format used can be found on `the SQLAlchemy documentation`. | -+----------------------------+---------------------------+-----------------------------------------------------------------------------+ -| SECRET_KEY | ``tralala`` | The secret key used to encrypt the cookies. **This needs to be changed**. | -+----------------------------+---------------------------+-----------------------------------------------------------------------------+ -| MAIL_DEFAULT_SENDER | ``("Budget manager", | A python tuple describing the name and email adress to use when sending | -| | "budget@notmyidea.org")`` | emails. | -+----------------------------+---------------------------+-----------------------------------------------------------------------------+ -| ACTIVATE_DEMO_PROJECT | ``True`` | If set to `True`, a demo project will be available on the frontpage. | -+----------------------------+---------------------------+-----------------------------------------------------------------------------+ -| ADMIN_PASSWORD | ``""`` | If not empty, the specified password must be entered to create new projects | -+----------------------------+---------------------------+-----------------------------------------------------------------------------+ ++----------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| Setting name | Default | What does it do? | ++============================+===========================+========================================================================================+ +| SQLALCHEMY_DATABASE_URI | ``sqlite:///budget.db`` | Specifies the type of backend to use and its location. More information | +| | | on the format used can be found on `the SQLAlchemy documentation`. | ++----------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| SECRET_KEY | ``tralala`` | The secret key used to encrypt the cookies. **This needs to be changed**. | ++----------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| MAIL_DEFAULT_SENDER | ``("Budget manager", | A python tuple describing the name and email adress to use when sending | +| | "budget@notmyidea.org")`` | emails. | ++----------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| ACTIVATE_DEMO_PROJECT | ``True`` | If set to `True`, a demo project will be available on the frontpage. | ++----------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| | ``""`` | If not empty, the specified password must be entered to create new projects. | +| ADMIN_PASSWORD | | To generate the proper password HASH, use ``./budget/manage.py generate_password_hash``| +| | | and copy its output into the value of *ADMIN_PASSWORD*. | ++----------------------------+---------------------------+----------------------------------------------------------------------------------------+ .. _`the SQLAlechemy documentation`: http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls