From cbc59da916df9904de39e44282531a8f39dfc650 Mon Sep 17 00:00:00 2001 From: Philipp Metzner Date: Tue, 17 Feb 2026 11:34:59 +0100 Subject: [PATCH 1/5] List current logged-in HoO user as editable row in cms_users - make user visible (non-gray) - only for HoO users - enable user editing themselves (no 403 error when clicking row) --- include/cms_users.php | 7 ++++--- include/cms_users_edit.php | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/cms_users.php b/include/cms_users.php index bb625c9e..fa777de6 100644 --- a/include/cms_users.php +++ b/include/cms_users.php @@ -46,13 +46,14 @@ // Do not forget to specify :userGroupLevel and :user in the db call later // related to this trello card https://trello.com/c/KI47eGPI $cms_users_same_or_upper_level_query = ' - SELECT u.*, 0 AS visible, g.label AS usergroup, 1 AS preventdelete, 1 as disableifistrue + SELECT u.*, IF(u.id = :user, 1, 0) AS visible, g.label AS usergroup, 1 AS preventdelete, 1 as disableifistrue FROM cms_users AS u INNER JOIN cms_usergroups AS g ON g.id = u.cms_usergroups_id INNER JOIN cms_usergroups_camps AS uc ON uc.cms_usergroups_id = g.id INNER JOIN cms_usergroups_levels AS l ON l.id = g.userlevel - WHERE (l.level >= :userGroupLevel AND u.id != :user) - AND uc.camp_id IN ('.($_SESSION['camp']['id'] ?: 0).') + WHERE l.level >= :userGroupLevel + AND (u.id != :user OR :userGroupLevel = 100) + AND uc.camp_id IN ('.(intval($_SESSION['camp']['id']) ?: 0).') AND NOT (u.valid_lastday < CURDATE() AND UNIX_TIMESTAMP(u.valid_lastday) != 0) AND UNIX_TIMESTAMP(u.deleted) = 0 GROUP BY u.id diff --git a/include/cms_users_edit.php b/include/cms_users_edit.php index f576fa31..1c2f817c 100644 --- a/include/cms_users_edit.php +++ b/include/cms_users_edit.php @@ -114,7 +114,12 @@ FROM cms_usergroups AS ug LEFT OUTER JOIN cms_usergroups_levels AS ugl ON ugl.id=ug.userlevel WHERE ug.id = :id AND (NOT ug.deleted OR ug.deleted IS NULL)', ['id' => $data['cms_usergroups_id']]); - if (!$_SESSION['user']['is_admin'] && ($data && ($data['is_admin'] || ($_SESSION['organisation']['id'] != $requesteduser['organisation_id']) || ($_SESSION['usergroup']['userlevel'] <= $requesteduser['userlevel'])))) { + if (!$_SESSION['user']['is_admin'] + && $data + && $data['id'] != $_SESSION['user']['id'] + && ($data['is_admin'] + || $_SESSION['organisation']['id'] != $requesteduser['organisation_id'] + || $_SESSION['usergroup']['userlevel'] <= $requesteduser['userlevel'])) { throw new Exception('You do not have access to this user!', 403); } From 2479fb33c65bf4d68338293c03a35601dcc5a5ac Mon Sep 17 00:00:00 2001 From: Philipp Metzner Date: Tue, 17 Feb 2026 11:41:40 +0100 Subject: [PATCH 2/5] Avoid editing of same- or higher-level users --- include/cms_users.php | 4 ++-- include/cms_users_deactivated.php | 4 ++-- include/cms_users_expired.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/cms_users.php b/include/cms_users.php index fa777de6..2c41e5f8 100644 --- a/include/cms_users.php +++ b/include/cms_users.php @@ -28,7 +28,7 @@ // Execution of queries in cms_users_page.php $cms_users_lower_level_query = ' - SELECT u.*, NOT u.is_admin AS visible, g.label AS usergroup, 0 AS preventdelete, 0 as disableifistrue + SELECT u.*, NOT u.is_admin AS visible, g.label AS usergroup, 0 AS preventdelete, 0 as disableifistrue, 0 AS preventedit FROM cms_users AS u LEFT OUTER JOIN cms_usergroups AS g ON g.id = u.cms_usergroups_id LEFT OUTER JOIN cms_usergroups_camps AS uc ON uc.cms_usergroups_id = g.id @@ -46,7 +46,7 @@ // Do not forget to specify :userGroupLevel and :user in the db call later // related to this trello card https://trello.com/c/KI47eGPI $cms_users_same_or_upper_level_query = ' - SELECT u.*, IF(u.id = :user, 1, 0) AS visible, g.label AS usergroup, 1 AS preventdelete, 1 as disableifistrue + SELECT u.*, IF(u.id = :user, 1, 0) AS visible, g.label AS usergroup, 1 AS preventdelete, 1 as disableifistrue, IF(u.id = :user, 0, 1) AS preventedit FROM cms_users AS u INNER JOIN cms_usergroups AS g ON g.id = u.cms_usergroups_id INNER JOIN cms_usergroups_camps AS uc ON uc.cms_usergroups_id = g.id diff --git a/include/cms_users_deactivated.php b/include/cms_users_deactivated.php index ad95eb65..500713cc 100644 --- a/include/cms_users_deactivated.php +++ b/include/cms_users_deactivated.php @@ -24,7 +24,7 @@ ); // Execution of queries in cms_users_page.php - $cms_users_lower_level_query = 'SELECT u.id, u.naam, SUBSTR(u.email, 1, LENGTH(u.email)-LENGTH(".deleted.")-LENGTH(u.id)) AS email, u.valid_firstday, u.valid_lastday, NOT u.is_admin AS visible, g.label AS usergroup, 0 AS preventdelete, 1 as disableifistrue + $cms_users_lower_level_query = 'SELECT u.id, u.naam, SUBSTR(u.email, 1, LENGTH(u.email)-LENGTH(".deleted.")-LENGTH(u.id)) AS email, u.valid_firstday, u.valid_lastday, NOT u.is_admin AS visible, g.label AS usergroup, 0 AS preventdelete, 1 as disableifistrue, 0 as preventedit FROM cms_users AS u LEFT OUTER JOIN cms_usergroups AS g ON g.id = u.cms_usergroups_id LEFT OUTER JOIN cms_usergroups_camps AS uc ON uc.cms_usergroups_id = g.id @@ -40,7 +40,7 @@ // Do not forget to specify :usergroup and :user in the db call later $cms_users_same_level_query = ' - SELECT u.id, u.naam, SUBSTR(u.email, 1, LENGTH(u.email)-LENGTH(".deleted.")-LENGTH(u.id)) AS email, u.valid_firstday, u.valid_lastday, 0 AS visible, g.label AS usergroup, 1 AS preventdelete, 1 as disableifistrue + SELECT u.id, u.naam, SUBSTR(u.email, 1, LENGTH(u.email)-LENGTH(".deleted.")-LENGTH(u.id)) AS email, u.valid_firstday, u.valid_lastday, 0 AS visible, g.label AS usergroup, 1 AS preventdelete, 1 as disableifistrue, 1 AS preventedit FROM cms_users AS u LEFT OUTER JOIN cms_usergroups AS g ON g.id = u.cms_usergroups_id WHERE u.cms_usergroups_id = :usergroup diff --git a/include/cms_users_expired.php b/include/cms_users_expired.php index 8cfcb899..1514b4ea 100644 --- a/include/cms_users_expired.php +++ b/include/cms_users_expired.php @@ -29,7 +29,7 @@ ); // Execution of queries in cms_users_page.php - $cms_users_lower_level_query = 'SELECT u.*, NOT u.is_admin AS visible, g.label AS usergroup, 0 AS preventdelete, 1 as disableifistrue + $cms_users_lower_level_query = 'SELECT u.*, NOT u.is_admin AS visible, g.label AS usergroup, 0 AS preventdelete, 1 as disableifistrue, 0 AS preventedit FROM cms_users AS u LEFT OUTER JOIN cms_usergroups AS g ON g.id = u.cms_usergroups_id LEFT OUTER JOIN cms_usergroups_camps AS uc ON uc.cms_usergroups_id = g.id @@ -46,7 +46,7 @@ // Do not forget to specify :usergroup and :user in the db call later $cms_users_same_level_query = ' - SELECT u.*, 0 AS visible, g.label AS usergroup, 1 AS preventdelete, 1 as disableifistrue + SELECT u.*, 0 AS visible, g.label AS usergroup, 1 AS preventdelete, 1 as disableifistrue, 1 AS preventedit FROM cms_users AS u LEFT OUTER JOIN cms_usergroups AS g ON g.id = u.cms_usergroups_id WHERE u.cms_usergroups_id = :usergroup From 9884de37e3e19da7662329a61b0461ad498acc53 Mon Sep 17 00:00:00 2001 From: Philipp Metzner Date: Tue, 17 Feb 2026 12:24:02 +0100 Subject: [PATCH 3/5] List "Boxtribute God" as available usergroups only if current user is god Previously, when logged in as a HoO (same userlevel as Boxtribute God) and in organisation 1 (same organisation_id as Boxtribute God), it was possible to select "Boxtribute God" when editing a lower-level user. When logged-in as God, it should still be possible to create another God user. In this case we have to handle _SESSION['usergroup'] being unset. In the end, one could re-evaluate why God users are associated with an organisation at all, or for staging data, why the God user is in the same organisation (ID 1) as regular "partner users". --- include/cms_users_edit.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/include/cms_users_edit.php b/include/cms_users_edit.php index 1c2f817c..4e8aa683 100644 --- a/include/cms_users_edit.php +++ b/include/cms_users_edit.php @@ -135,11 +135,14 @@ // display admin role in the usergroup - only for user with admin roles // related to this trello card https://trello.com/c/YAF3Az4P $usergroups = db_array(' - SELECT ug.id AS value, ug.label - FROM cms_usergroups AS ug - LEFT OUTER JOIN cms_usergroups_levels AS ugl ON (ugl.id=ug.userlevel) - WHERE ug.organisation_id = :organisation_id AND (ugl.level < :userlevel OR :is_admin OR (ugl.level <= :userlevel AND 100 = :userlevel)) AND (NOT ug.deleted OR ug.deleted IS NULL) - ORDER BY ug.label', ['organisation_id' => $_SESSION['organisation']['id'], 'userlevel' => $_SESSION['usergroup']['userlevel'], 'is_admin' => $_SESSION['user']['is_admin']]); + SELECT ug.id AS value, ug.label + FROM cms_usergroups AS ug + LEFT OUTER JOIN cms_usergroups_levels AS ugl ON (ugl.id=ug.userlevel) + WHERE ug.organisation_id = :organisation_id + AND (:is_admin OR (ugl.level < :userlevel OR (ugl.level <= :userlevel AND 100 = :userlevel))) + AND (:is_admin OR ug.label != "Boxtribute God") + AND (NOT ug.deleted OR ug.deleted IS NULL) + ORDER BY ug.label', ['organisation_id' => $_SESSION['organisation']['id'], 'userlevel' => $_SESSION['usergroup']['userlevel'], 'is_admin' => $_SESSION['user']['is_admin']]); addfield('select', 'Select user group', 'cms_usergroups_id', ['required' => true, 'options' => $usergroups, 'testid' => 'user_group']); addfield('line'); From 450a7ba4595b6271c1b6fbeb631a2b58ea1627df Mon Sep 17 00:00:00 2001 From: Philipp Metzner Date: Tue, 17 Feb 2026 16:16:21 +0100 Subject: [PATCH 4/5] Prevent HoO user from downgrading their usergroup if they're the only HoO --- include/cms_users_edit.php | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/include/cms_users_edit.php b/include/cms_users_edit.php index 4e8aa683..a81c13ed 100644 --- a/include/cms_users_edit.php +++ b/include/cms_users_edit.php @@ -74,10 +74,37 @@ WHERE ug.id = :id AND (NOT ug.deleted OR ug.deleted IS NULL)', ['id' => $_POST['cms_usergroups_id'][0]]); $is_admin = $_SESSION['user']['is_admin']; $organisation_allowed = ($_SESSION['organisation']['id'] == $posteduser['organisation_id']); - // allow admins to create another admin account + // allow HoO to create another HoO account // related to this trello card https://trello.com/c/YAF3Az4P $userlevel_allowed = ($_SESSION['usergroup']['userlevel'] > $posteduser['userlevel']) || ($_SESSION['usergroup']['userlevel'] == $posteduser['userlevel'] && '100' == $_SESSION['usergroup']['userlevel']); + // Prevent HoO user from downgrading their usergroup if they're the only HoO + if (!$is_admin + && $_POST['id'] == $_SESSION['user']['id'] + && 100 == $_SESSION['usergroup']['userlevel'] + && $posteduser['userlevel'] < $_SESSION['usergroup']['userlevel']) { + // Count how many HoO users exist in this organization + $hoo_count = db_value( + ' + SELECT COUNT(DISTINCT u.id) + FROM cms_users AS u + LEFT JOIN cms_usergroups AS ug ON ug.id = u.cms_usergroups_id + LEFT JOIN cms_usergroups_levels AS ugl ON ugl.id = ug.userlevel + WHERE ug.organisation_id = :org_id + AND ugl.level = 100 + AND (NOT u.deleted OR u.deleted IS NULL) + AND (NOT ug.deleted OR ug.deleted IS NULL) + AND NOT (u.valid_lastday < CURDATE() AND UNIX_TIMESTAMP(u.valid_lastday) != 0)', + ['org_id' => $_SESSION['organisation']['id']] + ); + + // If this is the last HoO, prevent the change + if ($hoo_count <= 1) { + trigger_error('You cannot downgrade yourself. Your organisation must have at least one Head of Operations user.', E_USER_NOTICE); + redirect('?action=cms_users_edit&id='.$_POST['id'].'&origin='.$_POST['_origin'].'&warning=1&message=You cannot downgrade yourself. Your organisation must have at least one Head of Operations user.'); + } + } + if ($is_admin || ($organisation_allowed && $userlevel_allowed)) { $keys = ['naam', 'email', 'cms_usergroups_id', 'valid_firstday', 'valid_lastday']; $userId = db_transaction(function () use ($table, $keys, $userId) { From 97ccdd2468ee47443f5a9e142d48773f7e04e2c7 Mon Sep 17 00:00:00 2001 From: HaGuesto Date: Mon, 23 Mar 2026 20:35:41 +0100 Subject: [PATCH 5/5] HOO should not be able to set a date if they are the last HoO --- include/cms_users_edit.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/cms_users_edit.php b/include/cms_users_edit.php index a81c13ed..78f91869 100644 --- a/include/cms_users_edit.php +++ b/include/cms_users_edit.php @@ -81,8 +81,7 @@ // Prevent HoO user from downgrading their usergroup if they're the only HoO if (!$is_admin && $_POST['id'] == $_SESSION['user']['id'] - && 100 == $_SESSION['usergroup']['userlevel'] - && $posteduser['userlevel'] < $_SESSION['usergroup']['userlevel']) { + && 100 == $_SESSION['usergroup']['userlevel']) { // Count how many HoO users exist in this organization $hoo_count = db_value( ' @@ -100,8 +99,13 @@ // If this is the last HoO, prevent the change if ($hoo_count <= 1) { - trigger_error('You cannot downgrade yourself. Your organisation must have at least one Head of Operations user.', E_USER_NOTICE); - redirect('?action=cms_users_edit&id='.$_POST['id'].'&origin='.$_POST['_origin'].'&warning=1&message=You cannot downgrade yourself. Your organisation must have at least one Head of Operations user.'); + if ($posteduser['userlevel'] < $_SESSION['usergroup']['userlevel']) { + redirect('?action=cms_users_edit&id='.$_POST['id'].'&origin='.$_POST['_origin'].'&warning=1&message=You cannot downgrade yourself. Your organisation must have at least one Head of Operations user.'); + trigger_error('You cannot downgrade yourself. Your organisation must have at least one Head of Operations user.', E_USER_NOTICE); + } elseif (('' !== $_POST['valid_firstday']) || ('' !== $_POST['valid_lastday'])) { + redirect('?action=cms_users_edit&id='.$_POST['id'].'&origin='.$_POST['_origin'].'&warning=1&message=You cannot edit yourself. Your organisation must have at least one Head of Operations user.'); + trigger_error('You cannot edit yourself. Your organisation must have at least one Head of Operations user.', E_USER_NOTICE); + } } }