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 @@
+ {% 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