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 1/7] 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 From 62dd103b30d602e566c0675095d2a43b76fe000f Mon Sep 17 00:00:00 2001 From: 0livd <0livd@users.noreply.github.com> Date: Wed, 28 Jun 2017 23:50:45 +0200 Subject: [PATCH 2/7] Admin can access every projects --- CHANGELOG.rst | 1 + budget/tests/tests.py | 7 +++++++ budget/web.py | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6d42010e..e938db04 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,7 @@ This document describes changes between each past release. ### Added - Add a new setting to allow public project creation (ALLOW_PUBLIC_PROJECT_CREATION) +- With admin credentials, one can access every project ### Removed diff --git a/budget/tests/tests.py b/budget/tests/tests.py index 0da05ed1..d49f3b5c 100644 --- a/budget/tests/tests.py +++ b/budget/tests/tests.py @@ -376,6 +376,13 @@ class BudgetTestCase(TestCase): c.get("/exit") self.assertNotIn('raclette', session) + # test that whith admin credentials, one can access every project + run.app.config['ADMIN_PASSWORD'] = generate_password_hash("pass") + with run.app.test_client() as c: + resp = c.post("/admin?goto=%2Fraclette", data={'admin_password': 'pass'}) + self.assertNotIn("Authentication", resp.data.decode('utf-8')) + self.assertTrue(session['is_admin']) + 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 diff --git a/budget/web.py b/budget/web.py index 70715998..ecb6f7e8 100644 --- a/budget/web.py +++ b/budget/web.py @@ -72,6 +72,7 @@ def add_project_id(endpoint, values): def pull_project(endpoint, values): """When a request contains a project_id value, transform it directly into a project by checking the credentials are stored in session. + With admin credentials, one can access every project. If not, redirect the user to an authentication form """ @@ -85,7 +86,8 @@ def pull_project(endpoint, values): if not project: raise Redirect303(url_for(".create_project", project_id=project_id)) - if project.id in session and session[project.id] == project.password: + is_admin = session.get('is_admin') + if project.id in session and session[project.id] == project.password or is_admin: # add project into kwargs and call the original function g.project = project else: From d8107d449ddd601f38913478f571522cf5ec949d Mon Sep 17 00:00:00 2001 From: 0livd <0livd@users.noreply.github.com> Date: Thu, 29 Jun 2017 17:55:12 +0200 Subject: [PATCH 3/7] Add delete and edit project actions in the dashboard The dashboard is deactivated by default and is only accessible by admins when activated A new ACTIVATE_DASHBOARD setting is introduced --- CHANGELOG.rst | 3 +++ budget/default_settings.py | 2 ++ budget/static/css/main.css | 23 ++++++++++++++++++ budget/templates/dashboard.html | 11 +++++++-- budget/tests/tests.py | 12 +++++++-- .../translations/fr/LC_MESSAGES/messages.mo | Bin 8425 -> 8537 bytes .../translations/fr/LC_MESSAGES/messages.po | 4 +++ budget/web.py | 7 ++++-- docs/installation.rst | 5 +++- 9 files changed, 60 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e938db04..6ae16805 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,11 +12,14 @@ This document describes changes between each past release. - **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. +- **BREAKING CHANGE** Admin privileges are required to access the dashboard ### Added - Add a new setting to allow public project creation (ALLOW_PUBLIC_PROJECT_CREATION) - With admin credentials, one can access every project +- Add delete and edit project actions in the dashboard +- Add a new setting to activate the dashboard (ACTIVATE_DASHBOARD) ### Removed diff --git a/budget/default_settings.py b/budget/default_settings.py index 69a3b4ae..f05c7798 100644 --- a/budget/default_settings.py +++ b/budget/default_settings.py @@ -14,3 +14,5 @@ ACTIVATE_DEMO_PROJECT = True ADMIN_PASSWORD = "pbkdf2:sha256:50000$jc3isZTD$b3be8d04ed5c2c1ac89d5eb777facc94adaee48d473c9620f1e0cb73f3dcfa11" ALLOW_PUBLIC_PROJECT_CREATION = True + +ACTIVATE_DASHBOARD = False diff --git a/budget/static/css/main.css b/budget/static/css/main.css index 54a00081..aedb2d15 100644 --- a/budget/static/css/main.css +++ b/budget/static/css/main.css @@ -169,6 +169,29 @@ footer{ background: url('../images/edit.png') no-repeat right; } +.project-actions { + padding-top: 10px; + text-align: center; +} + +.project-actions > .delete, .project-actions > .edit { + font-size: 0px; + display: block; + width: 16px; + height: 16px; + margin: 2px; + margin-left: 5px; + float: left; +} + +.project-actions > .delete{ + background: url('../images/delete.png') no-repeat right; +} + +.project-actions > .edit{ + background: url('../images/edit.png') no-repeat right; +} + .balance .balance-value{ text-align:right; } diff --git a/budget/templates/dashboard.html b/budget/templates/dashboard.html index 3f50915a..35a845b8 100644 --- a/budget/templates/dashboard.html +++ b/budget/templates/dashboard.html @@ -1,8 +1,8 @@ {% extends "layout.html" %} {% block content %} - +{% if is_dashboard_activated %} - + {% for project in projects|sort(attribute='name') %} @@ -13,9 +13,16 @@ {% endif %} + {% endfor %}
{{ _("Project") }}{{ _("Number of members") }}{{ _("Number of bills") }}{{_("Newest bill")}}{{_("Oldest bill")}}
{{ _("Project") }}{{ _("Number of members") }}{{ _("Number of bills") }}{{_("Newest bill")}}{{_("Oldest bill")}}{{_("Actions")}}
{{ project.name }}{{ project.members | count }}{{ project.get_bills().count() }} + {{ _('edit') }} + {{ _('delete') }} +
+{% else %} +
{{ _("The Dashboard is currently deactivated.") }}
+{% endif %} {% endblock %} diff --git a/budget/tests/tests.py b/budget/tests/tests.py index d49f3b5c..11b3a0b7 100644 --- a/budget/tests/tests.py +++ b/budget/tests/tests.py @@ -607,8 +607,16 @@ class BudgetTestCase(TestCase): self.assertIn("Invalid email address", resp.data.decode('utf-8')) def test_dashboard(self): - response = self.app.get("/dashboard") - self.assertEqual(response.status_code, 200) + # test that the dashboard is deactivated by default + resp = self.app.post("/admin?goto=%2Fdashboard", data={'admin_password': 'adminpass'}, + follow_redirects=True) + self.assertIn('
', resp.data.decode('utf-8')) + + # test access to the dashboard when it is activated + run.app.config['ACTIVATE_DASHBOARD'] = True + resp = self.app.post("/admin?goto=%2Fdashboard", data={'admin_password': 'adminpass'}, + follow_redirects=True) + self.assertIn('ProjectNumber of members', resp.data.decode('utf-8')) def test_settle_page(self): self.post_project("raclette") diff --git a/budget/translations/fr/LC_MESSAGES/messages.mo b/budget/translations/fr/LC_MESSAGES/messages.mo index 210852b0cf2b263ee5f84967ecea82579ef5145b..9797791b9e9f1c94edaf925590955854024f7c0f 100644 GIT binary patch delta 2102 zcmYM!e@N7K9LMpu`J-K4S!TayeNCsWEo)i|;YM9qtfD9ek$+TY_azVfQQjF^*|E_a zY-}*r1)E4+L0Yj2q^PK9#B51F2}~J9tX2jVMT>}t^?c4u?qsIKjn7uTAMzbYR_*=g%uurlRL^1&-oktw_JLUveuf!XjNGyXs3Df(FkFEn zu);Hp>C_u>q*=^%Qb?sCj-#;^wUDEjhLM)b}t&swk{ool#O}q!E-~q3_3&&Ewj>GXGPQ@2E5y!J#Jy(ncI2X0h zO4N=wB9}$@`51R#2J>471+DNTs$;iT??J7&AGNb5=*Rz2q4v{R5y?We7ohshMn$k3 z>1vh8Wi|Y$UlZy`TQH{3?xmoWAIBU#gZuFg>PWufDGjt6)h~hLu>-Z!KX4pALJgcm zcodOI=wJz|eKq#uW(;7(2;$#Bp^XEZhNHi0>Ef>u<5N|w((1IRPB*t@?5wZrwO9aW(Q z*pAxScc`r2g_>Z$*Zw;yNl$z2xAAA*+lQzP#F|KePbvJ03UL<>!Fxy_dw`nwm1okJ z!3Fq`pjb9e!a`KPCCFu~_|Z{S;Y{3&ickX8?-~-JnBAhFfu5owki^TRD$i(1GQ9LxOHOhG%|g$m6+ z)J_k1?dMSgT}3VME^4RGP-p%IHL#y|Oi7l9ss~W_E3g#9_%$ZH_FTdg)6R-0XyO3s z^;v>?pbi~uM-BWJDn}lmB9_d-%cC}uXu^g`x2q|p_*`QI1;T7M#FWr hjvI+O^lWgetKAwNbwU>oM&3Po;h&5_!}98a4umL6kqXVt4lore;zx!SM4F_g z7^svW9!af5iGLVkVv!^S8i^n!C>lmVkk!)Xi!Zdi`+jETopp?<%-n)|J&KElr3ptc^;>xr?_uVd z#k@C{P3A8P6kO%i%H_<&t2l`3s`k&>tiGQOJjNkB&3^1|xp{9WM{pDq=uBqCkJ441 zcDsQuu+jWwy+R_~#&q0U)xTyUKF+M24 z^lN9fbSY~|?Yjzzd@BcYCqLv7W=j@%D+8@y`mN@*T+hDzgc8=QL&|?aZF7U{<(_32ZZyi9H;~eSD6ms^<^t zEQazF6Znf9$fZnX*Y=kG6gFzGjb%&a4&I^u8E@zBOn`0nDQ98^a|Y(IjW017+QMXJ zCo}Fr4&b-U_-C1Ze=q@c))aC`y4c8APfewf=`fgC;Yeo1W0(P^Gb?+D`MhQ`6D+LS zmoSHOS=GLkt9`G#nFUO-@1t3JQXwf`$;-Ke`jn5EiNC5m%mi?hl2K0cdj45GZ@Q)w zSLx@Lts29dIg!awJ2T$p(#(8*qutUzjY;t% zbWY0?%;9^csxMFoul#qvywT?FWWoJ z3RW`%ZRdFIVKR4y3Gi>GUxS-sX=MVLOjDE@Y%qVBt&r3_%dB)!9`G$@pmj`u+nAMp z$?W+-X5b%~Lw3HZ*ZUUpdcGpCo%2!GF$iXM1>5nfH^#i mnTg+ICfvyE(Qm-*PhE8I=1)Q-Ss~+S+mgq diff --git a/budget/translations/fr/LC_MESSAGES/messages.po b/budget/translations/fr/LC_MESSAGES/messages.po index 0f3339ef..eec04676 100644 --- a/budget/translations/fr/LC_MESSAGES/messages.po +++ b/budget/translations/fr/LC_MESSAGES/messages.po @@ -271,6 +271,10 @@ msgstr "Facture la plus récente" msgid "Oldest bill" msgstr "Facture la plus ancienne" +#: templates/dashboard.html:25 +msgid "The Dashboard is currently deactivated." +msgstr "La page d'administration est actuellement désactivée." + #: templates/edit_project.html:6 templates/list_bills.html:24 msgid "you sure?" msgstr "c'est sûr ?" diff --git a/budget/web.py b/budget/web.py index ecb6f7e8..0d00cd11 100644 --- a/budget/web.py +++ b/budget/web.py @@ -294,7 +294,7 @@ def delete_project(): g.project.remove_project() flash(_('Project successfully deleted')) - return redirect(url_for(".home")) + return redirect(request.headers.get('Referer') or url_for('.home')) @main.route("/exit") @@ -507,5 +507,8 @@ def settle_bill(): @main.route("/dashboard") +@requires_admin() def dashboard(): - return render_template("dashboard.html", projects=Project.query.all()) + is_dashboard_activated = current_app.config['ACTIVATE_DASHBOARD'] + return render_template("dashboard.html", projects=Project.query.all(), + is_dashboard_activated=is_dashboard_activated) diff --git a/docs/installation.rst b/docs/installation.rst index c0900129..7c881cf0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -86,12 +86,15 @@ properly. | 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**. | +| | | **This needs to be changed** when you disable public project creation or activate the | +| ADMIN_PASSWORD | ``"pbkdf2:sha256:50.."`` | dashboard. | | | | 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 | +------------------------------+---------------------------+----------------------------------------------------------------------------------------+ +| ACTIVATE_DASHBOARD | ``False`` | If set to `True`, the dashboard will become accessible entering the admin password | ++------------------------------+---------------------------+----------------------------------------------------------------------------------------+ .. _`the SQLAlechemy documentation`: http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls From 87c32e6edc5017beade734cc0d9e06b5fcbacb50 Mon Sep 17 00:00:00 2001 From: 0livd <0livd@users.noreply.github.com> Date: Thu, 29 Jun 2017 18:05:25 +0200 Subject: [PATCH 4/7] Add a link to the dashboard in the nav bar It is only showed when ACTIVATE_DASHBOARD is set to True --- CHANGELOG.rst | 1 + budget/templates/layout.html | 3 +++ budget/web.py | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6ae16805..c913cd5c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,7 @@ This document describes changes between each past release. - With admin credentials, one can access every project - Add delete and edit project actions in the dashboard - Add a new setting to activate the dashboard (ACTIVATE_DASHBOARD) +- Add a link to the dashboard in the navigation bar when it is activated ### Removed diff --git a/budget/templates/layout.html b/budget/templates/layout.html index 6ecff413..bb4153d0 100644 --- a/budget/templates/layout.html +++ b/budget/templates/layout.html @@ -70,6 +70,9 @@ {% endif %} + {% if g.is_dashboard_activated %} + + {% endif %}
diff --git a/budget/web.py b/budget/web.py index 0d00cd11..e97857d4 100644 --- a/budget/web.py +++ b/budget/web.py @@ -68,6 +68,14 @@ def add_project_id(endpoint, values): values['project_id'] = g.project.id +@main.url_value_preprocessor +def set_is_dashboard_activated(endpoint, values): + """Set is_dashboard_activated application wide + so this variable can be used in the layout template + """ + g.is_dashboard_activated = current_app.config["ACTIVATE_DASHBOARD"] + + @main.url_value_preprocessor def pull_project(endpoint, values): """When a request contains a project_id value, transform it directly From 59c94fe07ebbf85c712003af4efeca778d661959 Mon Sep 17 00:00:00 2001 From: 0livd <0livd@users.noreply.github.com> Date: Mon, 3 Jul 2017 23:23:29 +0200 Subject: [PATCH 5/7] Add a breaking changes section in the CHANGELOG --- CHANGELOG.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c913cd5c..b762ac5e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,13 +6,15 @@ This document describes changes between each past release. 2.0 (unreleased) ---------------- +### Breaking Changes + +- 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) +- Turn the WSGI file into a python module, renamed from budget/ihatemoney.wsgi to budget/wsgi.py. Please update your Apache configuration! +- Admin privileges are required to access the dashboard + ### 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) -- **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. -- **BREAKING CHANGE** Admin privileges are required to access the dashboard ### Added From 503bbff0c9ee4ef5e1076fc3df8b795ea24686e3 Mon Sep 17 00:00:00 2001 From: 0livd <0livd@users.noreply.github.com> Date: Mon, 3 Jul 2017 23:28:26 +0200 Subject: [PATCH 6/7] Rename ACTIVATE_DASHBOARD to ACTIVATE_ADMIN_DASHBOARD --- CHANGELOG.rst | 2 +- budget/default_settings.py | 2 +- budget/templates/dashboard.html | 2 +- budget/tests/tests.py | 2 +- budget/web.py | 4 ++-- docs/installation.rst | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b762ac5e..d2e10ca7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,7 +21,7 @@ This document describes changes between each past release. - Add a new setting to allow public project creation (ALLOW_PUBLIC_PROJECT_CREATION) - With admin credentials, one can access every project - Add delete and edit project actions in the dashboard -- Add a new setting to activate the dashboard (ACTIVATE_DASHBOARD) +- Add a new setting to activate the dashboard (ACTIVATE_ADMIN_DASHBOARD) - Add a link to the dashboard in the navigation bar when it is activated ### Removed diff --git a/budget/default_settings.py b/budget/default_settings.py index f05c7798..f17651bc 100644 --- a/budget/default_settings.py +++ b/budget/default_settings.py @@ -15,4 +15,4 @@ ADMIN_PASSWORD = "pbkdf2:sha256:50000$jc3isZTD$b3be8d04ed5c2c1ac89d5eb777facc94a ALLOW_PUBLIC_PROJECT_CREATION = True -ACTIVATE_DASHBOARD = False +ACTIVATE_ADMIN_DASHBOARD = False diff --git a/budget/templates/dashboard.html b/budget/templates/dashboard.html index 35a845b8..231462b3 100644 --- a/budget/templates/dashboard.html +++ b/budget/templates/dashboard.html @@ -1,6 +1,6 @@ {% extends "layout.html" %} {% block content %} -{% if is_dashboard_activated %} +{% if is_admin_dashboard_activated %} {% for project in projects|sort(attribute='name') %} diff --git a/budget/tests/tests.py b/budget/tests/tests.py index 11b3a0b7..bb265316 100644 --- a/budget/tests/tests.py +++ b/budget/tests/tests.py @@ -613,7 +613,7 @@ class BudgetTestCase(TestCase): self.assertIn('
', resp.data.decode('utf-8')) # test access to the dashboard when it is activated - run.app.config['ACTIVATE_DASHBOARD'] = True + run.app.config['ACTIVATE_ADMIN_DASHBOARD'] = True resp = self.app.post("/admin?goto=%2Fdashboard", data={'admin_password': 'adminpass'}, follow_redirects=True) self.assertIn('
{{ _("Project") }}{{ _("Number of members") }}{{ _("Number of bills") }}{{_("Newest bill")}}{{_("Oldest bill")}}{{_("Actions")}}
ProjectNumber of members', resp.data.decode('utf-8')) diff --git a/budget/web.py b/budget/web.py index e97857d4..95909158 100644 --- a/budget/web.py +++ b/budget/web.py @@ -517,6 +517,6 @@ def settle_bill(): @main.route("/dashboard") @requires_admin() def dashboard(): - is_dashboard_activated = current_app.config['ACTIVATE_DASHBOARD'] + is_admin_dashboard_activated = current_app.config['ACTIVATE_ADMIN_DASHBOARD'] return render_template("dashboard.html", projects=Project.query.all(), - is_dashboard_activated=is_dashboard_activated) + is_admin_dashboard_activated=is_admin_dashboard_activated) diff --git a/docs/installation.rst b/docs/installation.rst index 7c881cf0..59e658e7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -93,7 +93,7 @@ properly. +------------------------------+---------------------------+----------------------------------------------------------------------------------------+ | ALLOW_PUBLIC_PROJECT_CREATION| ``True`` | If set to `True`, everyone can create a project without entering the admin password | +------------------------------+---------------------------+----------------------------------------------------------------------------------------+ -| ACTIVATE_DASHBOARD | ``False`` | If set to `True`, the dashboard will become accessible entering the admin password | +| ACTIVATE_ADMIN_DASHBOARD | ``False`` | If set to `True`, the dashboard will become accessible entering the admin password | +------------------------------+---------------------------+----------------------------------------------------------------------------------------+ .. _`the SQLAlechemy documentation`: http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls From b2ca05991071b4e63ee8e8f32f236b5f417f4cb5 Mon Sep 17 00:00:00 2001 From: 0livd <0livd@users.noreply.github.com> Date: Mon, 3 Jul 2017 23:36:00 +0200 Subject: [PATCH 7/7] Revert to an empty default ADMIN_PASSWORD When ADMIN_PASSWORD is left empty, all administrative tasks are not available --- .gitignore | 1 + budget/default_settings.py | 2 +- budget/run.py | 13 ------------- budget/templates/admin.html | 12 ++++++++++++ budget/templates/authenticate.html | 6 ------ budget/templates/layout.html | 2 +- budget/tests/tests.py | 1 + .../translations/fr/LC_MESSAGES/messages.mo | Bin 8537 -> 8665 bytes .../translations/fr/LC_MESSAGES/messages.po | 4 ++++ budget/web.py | 16 +++++++++++----- docs/installation.rst | 7 ++++--- 11 files changed, 35 insertions(+), 29 deletions(-) create mode 100644 budget/templates/admin.html diff --git a/.gitignore b/.gitignore index cb78e40b..f1083404 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dist docs/_build/ .tox dist +.cache/ diff --git a/budget/default_settings.py b/budget/default_settings.py index f17651bc..e656ece2 100644 --- a/budget/default_settings.py +++ b/budget/default_settings.py @@ -11,7 +11,7 @@ MAIL_DEFAULT_SENDER = ("Budget manager", "budget@notmyidea.org") ACTIVATE_DEMO_PROJECT = True -ADMIN_PASSWORD = "pbkdf2:sha256:50000$jc3isZTD$b3be8d04ed5c2c1ac89d5eb777facc94adaee48d473c9620f1e0cb73f3dcfa11" +ADMIN_PASSWORD = "" ALLOW_PUBLIC_PROJECT_CREATION = True diff --git a/budget/run.py b/budget/run.py index 8144707f..5e65c905 100644 --- a/budget/run.py +++ b/budget/run.py @@ -71,19 +71,6 @@ 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/admin.html b/budget/templates/admin.html new file mode 100644 index 00000000..95fe68b2 --- /dev/null +++ b/budget/templates/admin.html @@ -0,0 +1,12 @@ +{% extends "layout.html" %} +{% block content %} +

Authentication

+ +{% if is_admin_auth_enabled %} +
+ {{ forms.admin(form) }} +
+{% else %} +
{{ _("Administration tasks are currently not activated.") }}
+{% endif %} +{% endblock %} diff --git a/budget/templates/authenticate.html b/budget/templates/authenticate.html index f241c487..98914d09 100644 --- a/budget/templates/authenticate.html +++ b/budget/templates/authenticate.html @@ -7,13 +7,7 @@ to") }} {{ _("create it") }}{{ _("?") }}

{% endif %} -{% if admin_auth %} -
- {{ forms.admin(form) }} -
-{% else %}
{{ forms.authenticate(form) }}
-{% endif %} {% endblock %} diff --git a/budget/templates/layout.html b/budget/templates/layout.html index bb4153d0..07edb0c1 100644 --- a/budget/templates/layout.html +++ b/budget/templates/layout.html @@ -70,7 +70,7 @@ {% endif %} - {% if g.is_dashboard_activated %} + {% if g.show_admin_dashboard_link %} {% endif %} diff --git a/budget/tests/tests.py b/budget/tests/tests.py index bb265316..040936f3 100644 --- a/budget/tests/tests.py +++ b/budget/tests/tests.py @@ -614,6 +614,7 @@ class BudgetTestCase(TestCase): # test access to the dashboard when it is activated run.app.config['ACTIVATE_ADMIN_DASHBOARD'] = True + run.app.config['ADMIN_PASSWORD'] = generate_password_hash("adminpass") resp = self.app.post("/admin?goto=%2Fdashboard", data={'admin_password': 'adminpass'}, follow_redirects=True) self.assertIn('
ProjectNumber of members', resp.data.decode('utf-8')) diff --git a/budget/translations/fr/LC_MESSAGES/messages.mo b/budget/translations/fr/LC_MESSAGES/messages.mo index 9797791b9e9f1c94edaf925590955854024f7c0f..5e2cc5fe97e46349a3afec5869b0226f6aa461b6 100644 GIT binary patch delta 2116 zcmY+^YiLwQ7{>9p8sl1%7^7QlwrS3axvDWLcx$biG~!*Pm`a1#HCb^rZftg=Mr#*o z+N#YLQ47)vk_1Hk;3bH7tEsKl1}(IOTCc$yjY6w}*rN6Ff5<>FoSfgxoH;Y|zB6Yp zmRu}NT+2w>X8f(?{|){hc318HzjHm!UZ;8DqmS^&0gIOvM5mgkvxhKR~@# zhk5uhYN0Ji#I_r`>=3sC=C?KqO5t793Lm37rf^ezAZo>hsK|;j1t+6YJ{uK4xoeN0 z`mID|Y~fq9W~1 zItJhn)VQUnj4VJ8SG)G@*h#%LmHd}b_=b(D!w$^Hd89)FeS*3873SaxR75vXd#*4Q zX)kVCU?wUfMK~BoV>MQ|=Vy_%+7(m)chbrKn-uQTpcMBZ-_nn4w*{Oz_%`+7I3BA} z3*3b|8z+&VtOJMPU#Lt4*cW9e7d7rg%)&C%_>~C?>bM-WqSdIwqITJUjAa|$^RH16 zHlZTkgL?m4RAfJ)PW|tw32wXgd#FSDpKH(N<;{G*i2@3W;1~yBC|*XT*ylIvhB?SP zmXDhFEoUie0W*-~*+LB93Y>tAsQ&H9WxsOMmUZHAe1K#sVL`qnb)17r)%&OcSD-Sm z9+le9F%P$*GIAU>@XyF)w_N)ZRH{?iAI`7!Lmk3wS09hc*c43G_dl0{QZpZwvbCr) z@fm6%EvU>KLiM|WoImTqY88ne^@~@A3Sb&Hjk5?xVKpj)J5dX7MJ_wdZ2AuALPc7LYM+D}Xf|qrAEF}t2(|a?kTqH(>aexA`YHGP5{{w$4kp%9 z2$CPY*o=zoAZp@MsL!Y!^};>$FprJaz*A9YWicvapE{eco%(n9E;iC>9sYu4IER-R z%C=>Z{~`+eY0!7xfyzKKqiK(_P!Z;%4%;|X24ftQ znwv6QjtXo6YMgL(qHBT}4J9-*;5+yuYK6&O*F-^7il^g9EJrP9Ba%hifjZrXaTvCt z&cNTO_x^SDq+r)V(ohTW&!?c3WTC#_A@0Fg)E-Vk4WNEHL{+GXSD_~S618<(Q5k7Q zjdO0xk)CbERm&sMNGx6(ibraqUOW_A7V|>2VXv~Twl*A%SJ!*dnz$FLj7L_6;^C^I o)>XdWQoNbrm>2)Psd5Qc<%M1zBvupMvMDprEmqVz&7Yp~0^BLrNdN!< delta 2011 zcmYM!duWzb90%~ToR{fR(`lM5=cU2eZK60UY_^;oO7P@oXhY0o_Ao;Kzr@@ zxcctmv)1=g-?Kwf`~S0Ncqx-rKjT~;Wh+OHD5Z|qa}3+)R$0UhaVJOe9**Yn%1(|_ zU(KX?%`G*XO83rPi3IjnSQ;T#O=&Zzu?9EnHjiFcoLB*Y~ehn zeHqVi72COdwD>=*(Br_a;CPvoffllr_w#bDV|LWboZaWlPQPLn*v~}dENAjhKF-NH z<$f2ft-Q%>U>h&v`(wpFAwHlX9;V|g1C=MZQ2iG!;8o5k3w)HxjrFvd(!<%@!9;3+ ziO@-A-1BVa5a*Tgr!oDmt|??i^O$70sj{7(DNCyR4=_7?nAy<^W`JjzoxQ|l{U&CD ztyTMbOp@-d+K=%y@9k-31GP00;2MQDm=N#d1w280$|+{ze=6%H3@)I7f>N3}g>#sG zOX({2`N>hO;I&-EM5vePcaS1fD@PSF&~Hox>bzqK=`hY>6BCJr)TP`_S9zprU&n;_ z70TDLkx9NSRed)TseMeO4>1wxXG7lqa|%g@p$;|+naM=tMy6v2usBG6Q|fEbs?r zr@u32ex4b)(L0tTo5|GMnfuE*pPhW3y;b`RVX9?ka}_diJM;Q1Wgb|`7H(q({+h{= zQ%uB$IQTf7eY};ou~Db>yqAl4kRB-$3^Iq;v6T-si@)fXE)6-O9%hHznJoW=iOd1! z|KWSCWxZLGbT!J$vYE+&51Fj~mgaeiXP|AXzUpDO-0D=hI;R@lW%)XRjp zk8^p1Sx`gE;6GV2lil+Z-nhS;(u*Lb{oS^f0gOjyn``V;6H~dzk_H pnIt;NO#C-9;izeYXWGOpbOtlfT^sig-@EhE5r-Og_DvYm_#aUPxxD}Y diff --git a/budget/translations/fr/LC_MESSAGES/messages.po b/budget/translations/fr/LC_MESSAGES/messages.po index eec04676..09b5af70 100644 --- a/budget/translations/fr/LC_MESSAGES/messages.po +++ b/budget/translations/fr/LC_MESSAGES/messages.po @@ -247,6 +247,10 @@ msgstr "le créer" msgid "?" msgstr " ?" +#: templates/authenticate.html:7 +msgid "Administration tasks are currently not activated." +msgstr "Les tâches d'administration sont actuellement désactivées." + #: templates/create_project.html:4 msgid "Create a new project" msgstr "Créer un nouveau projet" diff --git a/budget/web.py b/budget/web.py index 95909158..ea49a2e2 100644 --- a/budget/web.py +++ b/budget/web.py @@ -69,11 +69,13 @@ def add_project_id(endpoint, values): @main.url_value_preprocessor -def set_is_dashboard_activated(endpoint, values): - """Set is_dashboard_activated application wide +def set_show_admin_dashboard_link(endpoint, values): + """Set show_admin_dashboard_link application wide so this variable can be used in the layout template """ - g.is_dashboard_activated = current_app.config["ACTIVATE_DASHBOARD"] + + g.show_admin_dashboard_link = (current_app.config["ACTIVATE_ADMIN_DASHBOARD"] and + current_app.config["ADMIN_PASSWORD"]) @main.url_value_preprocessor @@ -106,9 +108,12 @@ def pull_project(endpoint, values): @main.route("/admin", methods=["GET", "POST"]) def admin(): - """Admin authentication""" + """Admin authentication + When ADMIN_PASSWORD is empty, admin authentication is deactivated + """ form = AdminAuthenticationForm() goto = request.args.get('goto', url_for('.home')) + is_admin_auth_enabled = bool(current_app.config['ADMIN_PASSWORD']) if request.method == "POST": if form.validate(): if check_password_hash(current_app.config['ADMIN_PASSWORD'], form.admin_password.data): @@ -118,7 +123,8 @@ def admin(): 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) + return render_template("admin.html", form=form, + is_admin_auth_enabled=is_admin_auth_enabled) @main.route("/authenticate", methods=["GET", "POST"]) diff --git a/docs/installation.rst b/docs/installation.rst index 59e658e7..610a844b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -85,15 +85,16 @@ properly. +------------------------------+---------------------------+----------------------------------------------------------------------------------------+ | 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``. | -| | | **This needs to be changed** when you disable public project creation or activate the | -| ADMIN_PASSWORD | ``"pbkdf2:sha256:50.."`` | dashboard. | +| | | Hashed password to access protected endpoints. When left empty, all administrative | +| ADMIN_PASSWORD | ``""`` | tasks are disabled. | | | | 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 | +| | | If set to `False`, a non empty ADMIN_PASSWORD needs to be set | +------------------------------+---------------------------+----------------------------------------------------------------------------------------+ | ACTIVATE_ADMIN_DASHBOARD | ``False`` | If set to `True`, the dashboard will become accessible entering the admin password | +| | | If set to `True`, a non empty ADMIN_PASSWORD needs to be set | +------------------------------+---------------------------+----------------------------------------------------------------------------------------+ .. _`the SQLAlechemy documentation`: http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls