From d9ae72f4f365a08cbfa364f5ade8e68572efc49e Mon Sep 17 00:00:00 2001 From: 0livd <0livd@users.noreply.github.com> Date: Wed, 28 Jun 2017 23:41:05 +0200 Subject: [PATCH] Update to a more flexible admin authentication We do not rely on the ADMIN_PASSWORD being defined or not to ask for admin permissions. Instead we now have a flexible admin auth that can be conditionnaly bypassed --- CHANGELOG.rst | 4 ++++ budget/default_settings.py | 4 +++- budget/run.py | 13 +++++++++++++ budget/templates/home.html | 6 +++--- budget/tests/tests.py | 2 ++ budget/web.py | 32 ++++++++++++++++++++------------ docs/installation.rst | 37 ++++++++++++++++++++----------------- 7 files changed, 65 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 33e1de58..6d42010e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,7 +11,11 @@ This document describes changes between each past release. - **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) - **BREAKING CHANGE** Turn the WSGI file into a python module, renamed from budget/ihatemoney.wsgi to budget/wsgi.py. Please update your Apache configuration! - Changed the recommended gunicorn configuration to use the wsgi module as an entrypoint +- **BREAKING CHANGE** The default value of ``ADMIN_PASSWORD`` has changed. If you have a custom settings file which set ``ADMIN_PASSWORD`` to an empty string (""), the application will use the default admin password until you update your settings. +### Added + +- Add a new setting to allow public project creation (ALLOW_PUBLIC_PROJECT_CREATION) ### Removed diff --git a/budget/default_settings.py b/budget/default_settings.py index 15fe9cdd..69a3b4ae 100644 --- a/budget/default_settings.py +++ b/budget/default_settings.py @@ -11,4 +11,6 @@ MAIL_DEFAULT_SENDER = ("Budget manager", "budget@notmyidea.org") ACTIVATE_DEMO_PROJECT = True -ADMIN_PASSWORD = "" +ADMIN_PASSWORD = "pbkdf2:sha256:50000$jc3isZTD$b3be8d04ed5c2c1ac89d5eb777facc94adaee48d473c9620f1e0cb73f3dcfa11" + +ALLOW_PUBLIC_PROJECT_CREATION = True diff --git a/budget/run.py b/budget/run.py index 5e65c905..8144707f 100644 --- a/budget/run.py +++ b/budget/run.py @@ -71,6 +71,19 @@ def configure(): UserWarning ) + if not app.config['ADMIN_PASSWORD']: + app.config['ADMIN_PASSWORD'] = default_settings.ADMIN_PASSWORD + # Since 2.0 + warnings.warn( + "The way Ihatemoney handles admin authentication has changed. You seem to be using " + + "an empty ADMIN_PASSWORD which is not supported anymore. Your ADMIN_PASWWORD has been" + + " automatically set to the default password to let you access your admin endpoints." + + " However this password is not secure and must be changed in your settings file. 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", + UserWarning + ) + configure() diff --git a/budget/templates/home.html b/budget/templates/home.html index 9bfe4671..a628eccc 100644 --- a/budget/templates/home.html +++ b/budget/templates/home.html @@ -28,9 +28,7 @@
- {% if is_admin_mode_enabled %} - ...{{ _("or create a new one") }} - {% else %} + {% if is_public_project_creation_allowed %}
...{{ _("or create a new one") }} @@ -40,6 +38,8 @@
+ {% else %} + ...{{ _("or create a new one") }} {% endif %} diff --git a/budget/tests/tests.py b/budget/tests/tests.py index 386920f5..0da05ed1 100644 --- a/budget/tests/tests.py +++ b/budget/tests/tests.py @@ -378,6 +378,8 @@ class BudgetTestCase(TestCase): def test_admin_authentication(self): run.app.config['ADMIN_PASSWORD'] = generate_password_hash("pass") + # Disable public project creation so we have an admin endpoint to test + run.app.config['ALLOW_PUBLIC_PROJECT_CREATION'] = False # 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 ba771247..70715998 100644 --- a/budget/web.py +++ b/budget/web.py @@ -34,17 +34,26 @@ main = Blueprint("main", __name__) mail = Mail() -def requires_admin(f): +def requires_admin(bypass=None): """Require admin permissions for @requires_admin decorated endpoints. Has no effect if ADMIN_PASSWORD is empty (default value) + The bypass variable is optionnal and used to conditionnaly bypass the admin authentication + It expects a tuple containing the name of an application setting and its expected value + e.g. if you use @require_admin(bypass=("ALLOW_PUBLIC_PROJECT_CREATION", True)) + Admin authentication will be bypassed when ALLOW_PUBLIC_PROJECT_CREATION is set to True """ - @wraps(f) - def admin_auth(*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 + def check_admin(f): + @wraps(f) + def admin_auth(*args, **kws): + is_admin_auth_bypassed = False + if bypass is not None and current_app.config.get(bypass[0]) == bypass[1]: + is_admin_auth_bypassed = True + is_admin = session.get('is_admin') + if is_admin or is_admin_auth_bypassed: + return f(*args, **kws) + raise Redirect303(url_for('.admin', goto=request.path)) + return admin_auth + return check_admin @main.url_defaults @@ -157,18 +166,17 @@ 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_public_project_creation_allowed = current_app.config['ALLOW_PUBLIC_PROJECT_CREATION'] 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, + is_public_project_creation_allowed=is_public_project_creation_allowed, auth_form=auth_form, session=session) @main.route("/create", methods=["GET", "POST"]) -@requires_admin +@requires_admin(bypass=("ALLOW_PUBLIC_PROJECT_CREATION", True)) def create_project(): form = ProjectForm() if request.method == "GET" and 'project_id' in request.values: diff --git a/docs/installation.rst b/docs/installation.rst index 3cd143d0..c0900129 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -72,23 +72,26 @@ 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. | -+----------------------------+---------------------------+----------------------------------------------------------------------------------------+ -| | ``""`` | 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*. | -+----------------------------+---------------------------+----------------------------------------------------------------------------------------+ ++------------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| 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. | ++------------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| | | Hashed password to access protected endpoints. The default password is ``adminpass``. | +| ADMIN_PASSWORD | ``"pbkdf2:sha256:50.."`` | **This needs to be changed**. | +| | | To generate the proper password HASH, use ``./budget/manage.py generate_password_hash``| +| | | and copy its output into the value of *ADMIN_PASSWORD*. | ++------------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| ALLOW_PUBLIC_PROJECT_CREATION| ``True`` | If set to `True`, everyone can create a project without entering the admin password | ++------------------------------+---------------------------+----------------------------------------------------------------------------------------+ .. _`the SQLAlechemy documentation`: http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls