From a1ab16d54ce6f2977cab9b18ba79b83859de16db Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Wed, 8 Jul 2026 16:37:25 +0200 Subject: [PATCH] Fase 2: read-only user role with per-category view toggles Adds a third account type 'user' alongside super/admin: no create/edit/delete rights on qr codes, view access to dynamic/static lists gated per-account by two admin-controlled toggles (can_view_static, can_view_dynamic), and always full visibility into the dashboard/reports regardless of those toggles. - New columns can_view_static/can_view_dynamic on users (migrations/003) - Users class + form_users.php: 'user' type option with the two toggles - Access control: dynamic_qrcode.php/static_qrcode.php/bulk_action.php reject all mutations for type=user; dynamic_qrcodes.php/static_qrcodes.php enforce the view toggle and show all codes (no owner scoping, since 'user' owns none) - Sidebar and list tables hide add/edit/delete/bulk UI for the read-only role - index.php dashboard stats are unscoped for both 'super' and 'user' Co-Authored-By: Claude Sonnet 5 --- db/init.sql | 2 ++ db/migrations/003_user_role.sql | 22 +++++++++++++++++++ src/authenticate.php | 2 ++ src/bulk_action.php | 17 +++++++++++++- src/dynamic_qrcode.php | 6 +++++ src/dynamic_qrcodes.php | 11 +++++++++- src/forms/form_users.php | 39 +++++++++++++++++++++++++++++++++ src/forms/table_dynamic.php | 15 ++++++++++--- src/forms/table_static.php | 15 ++++++++++--- src/includes/sidebar.php | 10 +++++++++ src/index.php | 14 +++++++----- src/lib/Users/Users.php | 6 ++++- src/login.php | 2 ++ src/static_qrcode.php | 6 +++++ src/static_qrcodes.php | 11 +++++++++- 15 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 db/migrations/003_user_role.sql diff --git a/db/init.sql b/db/init.sql index 4f5d28b..80e0ded 100644 --- a/db/init.sql +++ b/db/init.sql @@ -16,6 +16,8 @@ CREATE TABLE IF NOT EXISTS `users` ( `type` varchar(10) NOT NULL, `must_change_password` tinyint(1) NOT NULL DEFAULT 0, `password_changed_at` datetime DEFAULT NULL, + `can_view_static` tinyint(1) NOT NULL DEFAULT 0, + `can_view_dynamic` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0 ; diff --git a/db/migrations/003_user_role.sql b/db/migrations/003_user_role.sql new file mode 100644 index 0000000..db063bb --- /dev/null +++ b/db/migrations/003_user_role.sql @@ -0,0 +1,22 @@ +-- Fase 2: read-only 'user' rol met twee zichtbaarheids-toggles. +-- type='user' vereist geen schemawijziging (varchar(10), geen enum-constraint). + +SET @db := DATABASE(); + +SET @col_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'users' AND COLUMN_NAME = 'can_view_static' +); +SET @sql := IF(@col_exists = 0, + 'ALTER TABLE `users` ADD COLUMN `can_view_static` TINYINT(1) NOT NULL DEFAULT 0', + 'SELECT 1'); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +SET @col_exists := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'users' AND COLUMN_NAME = 'can_view_dynamic' +); +SET @sql := IF(@col_exists = 0, + 'ALTER TABLE `users` ADD COLUMN `can_view_dynamic` TINYINT(1) NOT NULL DEFAULT 0', + 'SELECT 1'); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; diff --git a/src/authenticate.php b/src/authenticate.php index 5e65f78..59ca016 100644 --- a/src/authenticate.php +++ b/src/authenticate.php @@ -40,6 +40,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') $_SESSION['user_id'] = $row['id']; $_SESSION['username'] = $row['username']; $_SESSION['must_change_password'] = !empty($row['must_change_password']); + $_SESSION['can_view_static'] = !empty($row['can_view_static']); + $_SESSION['can_view_dynamic'] = !empty($row['can_view_dynamic']); $_SESSION['last_activity'] = time(); audit_log('login_success'); diff --git a/src/bulk_action.php b/src/bulk_action.php index 7aa5e3e..8e4041e 100644 --- a/src/bulk_action.php +++ b/src/bulk_action.php @@ -35,9 +35,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { exit(); } + if ($_SESSION['type'] === 'user') { + $view_flag = $type === 'dynamic' ? 'can_view_dynamic' : 'can_view_static'; + if (empty($_SESSION[$view_flag] ?? null)) { + http_response_code(403); + echo json_encode(['data' => 'Not allowed to view this qr code type.', 'status' => 403]); + exit(); + } + } + foreach ($params as $param) { $db->where('id', $param); - if ($_SESSION['type'] !== 'super') { + if ($_SESSION['type'] === 'admin') { $db->where('id_owner', $_SESSION['user_id']); $db->orWhere('id_owner', NULL, 'IS'); } @@ -69,6 +78,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ]); exit(); } else if($json["action"] == "delete") { + if ($_SESSION['type'] === 'user') { + http_response_code(403); + echo json_encode(['data' => 'The "user" role is read-only.', 'status' => 403]); + exit(); + } + $params = $json['params']; if (isset($json['type']) && in_array($json['type'], $allowed_types, true)) { diff --git a/src/dynamic_qrcode.php b/src/dynamic_qrcode.php index 5e846f6..f8e2783 100644 --- a/src/dynamic_qrcode.php +++ b/src/dynamic_qrcode.php @@ -3,6 +3,12 @@ require_once 'includes/bootstrap.php'; require_once BASE_PATH.'/includes/auth_validate.php'; require_once BASE_PATH . '/lib/DynamicQrcode/DynamicQrcode.php'; +if ($_SESSION['type'] === 'user') { + $_SESSION['failure'] = 'The "user" role is read-only and cannot create, edit or delete qr codes.'; + header('Location: index.php'); + exit; +} + $dynamic_qrcode_instance = new DynamicQrcode(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { diff --git a/src/dynamic_qrcodes.php b/src/dynamic_qrcodes.php index 58375a3..bfb8962 100644 --- a/src/dynamic_qrcodes.php +++ b/src/dynamic_qrcodes.php @@ -3,6 +3,12 @@ require_once 'includes/bootstrap.php'; require_once BASE_PATH . '/includes/auth_validate.php'; require_once BASE_PATH . '/lib/DynamicQrcode/DynamicQrcode.php'; +if ($_SESSION['type'] === 'user' && empty($_SESSION['can_view_dynamic'] ?? null)) { + $_SESSION['failure'] = 'You are not allowed to view dynamic qr codes.'; + header('Location: index.php'); + exit; +} + $db = getDbInstance(); $dynamic_qrcode = new DynamicQrcode(); @@ -12,7 +18,8 @@ require_once BASE_PATH . '/includes/search_order.php'; $page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 1; $db->pageLimit = 15; -if($_SESSION['type'] !== 'super') { +// 'user' ziet, net als 'super', alle codes (heeft zelf geen eigen codes om op te scopen). +if($_SESSION['type'] === 'admin') { $db->where("id_owner", $_SESSION['user_id']); $db->orWhere ("id_owner", NULL, 'IS'); } @@ -49,6 +56,7 @@ $total_pages = $db->totalPages;

Dynamic Qr codes

+
+ diff --git a/src/forms/form_users.php b/src/forms/form_users.php index e0d2aa1..d65c272 100644 --- a/src/forms/form_users.php +++ b/src/forms/form_users.php @@ -38,8 +38,47 @@ + +
+ +
+ +
+ +
+
+ > + +
+
+ > + +
+ Alleen van toepassing op het type 'User'. Reports/statistieken zijn voor 'User' altijd zichtbaar. +
+
+ + + diff --git a/src/forms/table_dynamic.php b/src/forms/table_dynamic.php index 608600a..054297b 100644 --- a/src/forms/table_dynamic.php +++ b/src/forms/table_dynamic.php @@ -1,4 +1,6 @@ +
+ +
+ + @@ -42,7 +47,9 @@ + + @@ -97,6 +104,7 @@ + +
ID Owner Filename
- + - + " > - +