From c895366684926922d8fb4c3ccacc9e9ad92d4a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sun, 13 Aug 2023 22:08:28 +0200 Subject: [PATCH] refactor: store user profile in g.user --- canaille/app/flask.py | 7 ++++++- canaille/backends/ldap/models.py | 3 +++ canaille/core/account.py | 14 +++++++++++--- canaille/core/models.py | 3 +++ tests/conftest.py | 3 +++ tests/core/test_account.py | 8 ++++---- tests/core/test_invitation.py | 5 +++++ tests/core/test_profile_edition.py | 6 ++++++ tests/core/test_profile_settings.py | 2 ++ tests/oidc/test_authorization_code_flow.py | 2 ++ 10 files changed, 45 insertions(+), 8 deletions(-) diff --git a/canaille/app/flask.py b/canaille/app/flask.py index 92b895a8..a12d7c20 100644 --- a/canaille/app/flask.py +++ b/canaille/app/flask.py @@ -6,6 +6,7 @@ from canaille.app import models from flask import abort from flask import current_app +from flask import g from flask import render_template from flask import request from flask import session @@ -14,12 +15,16 @@ def current_user(): + if "user" in g: + return g.user + for user_id in session.get("user_id", [])[::-1]: user = models.User.get(id=user_id) if user and ( not current_app.backend.has_account_lockability() or not user.locked ): - return user + g.user = user + return g.user session["user_id"].remove(user_id) diff --git a/canaille/backends/ldap/models.py b/canaille/backends/ldap/models.py index ff03cd1b..4cc0bedf 100644 --- a/canaille/backends/ldap/models.py +++ b/canaille/backends/ldap/models.py @@ -174,6 +174,9 @@ def save(self, *args, **kwargs): def load_permissions(self): conn = Backend.get().connection + self.permissions = set() + self.read = set() + self.write = set() for access_group_name, details in current_app.config["ACL"].items(): filter_ = self.acl_filter_to_ldap_filter(details.get("FILTER")) diff --git a/canaille/core/account.py b/canaille/core/account.py index b08c052c..3c790e76 100644 --- a/canaille/core/account.py +++ b/canaille/core/account.py @@ -26,6 +26,7 @@ from flask import Blueprint from flask import current_app from flask import flash +from flask import g from flask import redirect from flask import request from flask import send_file @@ -494,6 +495,8 @@ def profile_create(current_app, form): user.set_password(form["password1"].data) user.save() + user.load_permissions() + flash(_("User account creation succeed."), "success") return user @@ -568,6 +571,7 @@ def profile_edition_main_form_validation(user, edited_user, profile_form): edited_user.preferred_language = None edited_user.save() + g.user.reload() def profile_edition_emails_form(user, edited_user, has_smtp): @@ -611,10 +615,12 @@ def profile_edition_remove_email(user, edited_user, email): @bp.route("/profile/", methods=("GET", "POST")) @user_needed() def profile_edition(user, edited_user): - if not user.can_manage_users and not (user.can_edit_self and edited_user == user): + if not user.can_manage_users and not ( + user.can_edit_self and edited_user.id == user.id + ): abort(404) - menuitem = "profile" if edited_user == user else "users" + menuitem = "profile" if edited_user.id == user.id else "users" has_smtp = "SMTP" in current_app.config has_email_confirmation = current_app.config.get("EMAIL_CONFIRMATION") is True or ( current_app.config.get("EMAIL_CONFIRMATION") is None and has_smtp @@ -681,7 +687,9 @@ def profile_edition(user, edited_user): @bp.route("/profile//settings", methods=("GET", "POST")) @user_needed() def profile_settings(user, edited_user): - if not user.can_manage_users and not (user.can_edit_self and edited_user == user): + if not user.can_manage_users and not ( + user.can_edit_self and edited_user.id == user.id + ): abort(404) if ( diff --git a/canaille/core/models.py b/canaille/core/models.py index d634a713..77a92086 100644 --- a/canaille/core/models.py +++ b/canaille/core/models.py @@ -1,5 +1,6 @@ import datetime +from flask import g from flask import session @@ -15,6 +16,7 @@ def get_from_login(cls, login=None, **kwargs): raise NotImplementedError() def login(self): + g.user = self try: previous = ( session["user_id"] @@ -29,6 +31,7 @@ def login(self): def logout(self): try: session["user_id"].pop() + del g.user if not session["user_id"]: del session["user_id"] except (IndexError, KeyError): diff --git a/tests/conftest.py b/tests/conftest.py index 5d5f3f46..a1fa9ae0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -150,6 +150,7 @@ def user(app, backend): formatted_address="1235, somewhere", ) u.save() + u.load_permissions() yield u u.delete() @@ -164,6 +165,7 @@ def admin(app, backend): password="admin", ) u.save() + u.load_permissions() yield u u.delete() @@ -178,6 +180,7 @@ def moderator(app, backend): password="moderator", ) u.save() + u.load_permissions() yield u u.delete() diff --git a/tests/core/test_account.py b/tests/core/test_account.py index 63942d67..be24b2b6 100644 --- a/tests/core/test_account.py +++ b/tests/core/test_account.py @@ -2,22 +2,24 @@ from unittest import mock from canaille.app import models +from flask import g def test_index(testclient, user): res = testclient.get("/", status=302) assert res.location == "/login" - with testclient.session_transaction() as sess: - sess["user_id"] = [user.id] + g.user = user res = testclient.get("/", status=302) assert res.location == "/profile/user" testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = ["use_oidc"] + g.user.reload() res = testclient.get("/", status=302) assert res.location == "/consent/" testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = [] + g.user.reload() res = testclient.get("/", status=302) assert res.location == "/about" @@ -243,7 +245,6 @@ def test_user_deleted_in_session(testclient, backend): with testclient.session_transaction() as session: session["user_id"] = [u.id] - testclient.get("/profile/jake", status=200) u.delete() testclient.get("/profile/jake", status=404) @@ -425,7 +426,6 @@ def test_signin_locked_account(testclient, user): def test_account_locked_during_session(testclient, logged_user): - testclient.get("/profile/user/settings", status=200) logged_user.lock_date = datetime.datetime.now(datetime.timezone.utc) logged_user.save() testclient.get("/profile/user/settings", status=403) diff --git a/tests/core/test_invitation.py b/tests/core/test_invitation.py index 368c22e7..d66238ed 100644 --- a/tests/core/test_invitation.py +++ b/tests/core/test_invitation.py @@ -2,6 +2,7 @@ from canaille.app import models from canaille.core.account import Invitation +from flask import g def test_invitation(testclient, logged_admin, foo_group, smtpd): @@ -19,9 +20,11 @@ def test_invitation(testclient, logged_admin, foo_group, smtpd): url = res.pyquery(".copy-text")[0].value # logout + g.user = None with testclient.session_transaction() as sess: del sess["user_id"] + testclient.get("/logout") res = testclient.get(url, status=200) assert res.form["user_name"].value == "someone" @@ -68,6 +71,7 @@ def test_invitation_editable_user_name(testclient, logged_admin, foo_group, smtp url = res.pyquery(".copy-text")[0].value # logout + g.user = None with testclient.session_transaction() as sess: del sess["user_id"] @@ -114,6 +118,7 @@ def test_generate_link(testclient, logged_admin, foo_group, smtpd): url = res.pyquery(".copy-text")[0].value # logout + g.user = None with testclient.session_transaction() as sess: del sess["user_id"] diff --git a/tests/core/test_profile_edition.py b/tests/core/test_profile_edition.py index 1976dbf8..d19f3404 100644 --- a/tests/core/test_profile_edition.py +++ b/tests/core/test_profile_edition.py @@ -1,5 +1,6 @@ import pytest from canaille.core.populate import fake_users +from flask import g from webtest import Upload @@ -86,6 +87,7 @@ def test_user_list_search_only_allowed_fields( res.mustcontain(no=moderator.formatted_name[0]) testclient.app.config["ACL"]["DEFAULT"]["READ"].remove("user_name") + g.user.reload() form = res.forms["search"] form["query"] = "user" @@ -105,6 +107,7 @@ def test_edition_permission( testclient.get("/profile/user", status=404) testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = ["edit_self"] + g.user.reload() testclient.get("/profile/user", status=200) @@ -204,6 +207,7 @@ def test_field_permissions_none(testclient, logged_user): "PERMISSIONS": ["edit_self"], } + g.user.reload() res = testclient.get("/profile/user", status=200) form = res.forms["baseform"] assert "phone_numbers-0" not in form.fields @@ -230,6 +234,7 @@ def test_field_permissions_read(testclient, logged_user): "WRITE": [], "PERMISSIONS": ["edit_self"], } + g.user.reload() res = testclient.get("/profile/user", status=200) form = res.forms["baseform"] assert "phone_numbers-0" in form.fields @@ -256,6 +261,7 @@ def test_field_permissions_write(testclient, logged_user): "WRITE": ["phone_numbers"], "PERMISSIONS": ["edit_self"], } + g.user.reload() res = testclient.get("/profile/user", status=200) form = res.forms["baseform"] assert "phone_numbers-0" in form.fields diff --git a/tests/core/test_profile_settings.py b/tests/core/test_profile_settings.py index d6782693..745eebd6 100644 --- a/tests/core/test_profile_settings.py +++ b/tests/core/test_profile_settings.py @@ -2,6 +2,7 @@ from unittest import mock from canaille.app import models +from flask import g def test_edition( @@ -300,6 +301,7 @@ def test_edition_permission( testclient.get("/profile/user/settings", status=404) testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = ["edit_self"] + g.user.reload() testclient.get("/profile/user/settings", status=200) diff --git a/tests/oidc/test_authorization_code_flow.py b/tests/oidc/test_authorization_code_flow.py index e2b02bb8..f1ed8ab9 100644 --- a/tests/oidc/test_authorization_code_flow.py +++ b/tests/oidc/test_authorization_code_flow.py @@ -7,6 +7,7 @@ from authlib.oauth2.rfc7636 import create_s256_code_challenge from canaille.app import models from canaille.oidc.oauth import setup_oauth +from flask import g from werkzeug.security import gen_salt from . import client_credentials @@ -234,6 +235,7 @@ def test_logout_login(testclient, logged_user, client): ) res = res.form.submit(name="answer", value="logout", status=302) + g.user = None res = res.follow(status=200) res.form["login"] = logged_user.user_name[0]