From 09da83977986cbf943ec0090528d299ae126895d Mon Sep 17 00:00:00 2001 From: M_Viper Date: Thu, 5 Nov 2026 21:21:31 +0000 Subject: [PATCH] Upload via GUI (452 Dateien) --- DeskHub-Webseite/api/staff/v1/roles.php | 20 ++++-- DeskHub-Webseite/api/staff/v1/status.php | 4 +- .../api/staff/v1/ticket_rules.php | 4 +- DeskHub-Webseite/assets/js/passkey.js | 70 +++++++++++++++++++ 4 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 DeskHub-Webseite/assets/js/passkey.js diff --git a/DeskHub-Webseite/api/staff/v1/roles.php b/DeskHub-Webseite/api/staff/v1/roles.php index 2bb4e4f9..82a38c6b 100644 --- a/DeskHub-Webseite/api/staff/v1/roles.php +++ b/DeskHub-Webseite/api/staff/v1/roles.php @@ -9,10 +9,7 @@ $pdo = get_db(); // Ticket-Rechte + alle Adminportal-Bereichsrechte (admin_section_permissions(), includes/functions.php) – // zusammen ergeben sie das vollständige Set, das über eine benutzerdefinierte Rolle vergeben werden kann. -$rolePermissionKeys = array_merge([ - 'reply_ticket', 'close_ticket', 'delete_ticket', 'transfer_ticket', 'post_note', - 'assign_ticket', 'manage_canned', 'view_reports', 'manage_kb', 'create_ticket', -], array_keys(admin_section_permissions())); +$rolePermissionKeys = array_keys(permission_labels()); if ($_SERVER['REQUEST_METHOD'] === 'GET') { $roles = $pdo->query( @@ -21,11 +18,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { api_json([ 'permissionKeys' => $rolePermissionKeys, + 'permissionLabels' => permission_labels(), + 'permissionGroups' => array_values(array_map(fn($g) => ['title' => $g['title'], 'keys' => array_keys($g['items'])], permission_catalog())), 'defaultAgentPermissions' => default_permissions('agent'), 'items' => array_map(fn(array $r) => [ 'id' => (int) $r['id'], 'name' => $r['name'], 'permissions' => json_decode($r['permissions'] ?? '{}', true) ?? [], + 'effectivePermissions' => permission_effective(json_decode($r['permissions'] ?? '{}', true) ?? []), 'staffCount' => (int) $r['staff_count'], 'isActive' => (bool) ($r['is_active'] ?? 1), 'notes' => $r['notes'] ?? null, @@ -44,9 +44,19 @@ if ($action === 'save') { if ($name === '') api_error(422, 'NAME_REQUIRED', 'Bitte einen Namen angeben.'); $incoming = (array) ($body['permissions'] ?? []); + // Rechte, die eine ältere App-Version nicht kennt (und deshalb nicht mitschickt), bleiben unverändert bzw. bekommen den Standardwert + $existingPerms = default_permissions('agent'); + if ($id) { + $ep = $pdo->prepare('SELECT permissions FROM roles WHERE id = ?'); + $ep->execute([$id]); + $existingPerms = array_merge($existingPerms, json_decode((string) $ep->fetchColumn(), true) ?: []); + } $perms = []; foreach ($rolePermissionKeys as $p) { - $perms[$p] = !empty($incoming[$p]); + $want = array_key_exists($p, $incoming) ? !empty($incoming[$p]) : !empty($existingPerms[$p]); + // Wer kein echter Administrator ist, kann keine Rechte vergeben, die er selbst nicht hat + if ($staff['role'] !== 'admin' && $want && !has_permission($staff, $p)) $want = !empty($existingPerms[$p]) && $id > 0; + $perms[$p] = $want; } $json = json_encode($perms); $dup = $pdo->prepare('SELECT COUNT(*) FROM roles WHERE LOWER(name) = LOWER(?) AND id <> ?'); diff --git a/DeskHub-Webseite/api/staff/v1/status.php b/DeskHub-Webseite/api/staff/v1/status.php index 15ef98cb..04a15f19 100644 --- a/DeskHub-Webseite/api/staff/v1/status.php +++ b/DeskHub-Webseite/api/staff/v1/status.php @@ -17,7 +17,7 @@ $staff = api_require_staff(); $pdo = get_db(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { - api_require_admin($staff); + api_require_admin_or_permission($staff, 'manage_status_page'); $b = api_request_body(); $action = (string) ($b['action'] ?? ''); if ($action === 'set_component') { @@ -39,4 +39,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { api_error(422, 'UNKNOWN_ACTION', 'Unbekannte Aktion.'); } } -api_json(['status' => status_summary($pdo)] + ['canManage' => $staff['role'] === 'admin']); +api_json(['status' => status_summary($pdo)] + ['canManage' => has_permission($staff, 'manage_status_page')]); diff --git a/DeskHub-Webseite/api/staff/v1/ticket_rules.php b/DeskHub-Webseite/api/staff/v1/ticket_rules.php index 496f46ce..5ff112c2 100644 --- a/DeskHub-Webseite/api/staff/v1/ticket_rules.php +++ b/DeskHub-Webseite/api/staff/v1/ticket_rules.php @@ -12,8 +12,8 @@ require_once __DIR__ . '/common.php'; api_require_method('GET', 'POST'); $staff = api_require_staff(); -if ($staff['role'] !== 'admin') { - api_error(403, 'ADMIN_REQUIRED', 'Nur Administratoren können Ticket-Regeln verwalten.'); +if (!has_permission($staff, 'manage_automation')) { + api_error(403, 'FORBIDDEN', 'Für Ticket-Regeln fehlt die erforderliche Berechtigung.'); } $pdo = get_db(); diff --git a/DeskHub-Webseite/assets/js/passkey.js b/DeskHub-Webseite/assets/js/passkey.js new file mode 100644 index 00000000..073f4833 --- /dev/null +++ b/DeskHub-Webseite/assets/js/passkey.js @@ -0,0 +1,70 @@ +/* Passkeys im Browser: Anmeldung (staff/login.php) und Registrierung (Profil → Sicherheit). Spricht mit /staff/passkey.php. */ +(function () { + 'use strict'; + function b64uToBuf(s) { + s = s.replace(/-/g, '+').replace(/_/g, '/'); + while (s.length % 4) s += '='; + var bin = atob(s), buf = new Uint8Array(bin.length); + for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i); + return buf.buffer; + } + function bufToB64u(buf) { + var b = new Uint8Array(buf), s = ''; + for (var i = 0; i < b.length; i++) s += String.fromCharCode(b[i]); + return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); + } + function post(csrf, fields) { + var fd = new FormData(); + fd.append('csrf_token', csrf); + Object.keys(fields).forEach(function (k) { fd.append(k, fields[k]); }); + return fetch('/staff/passkey.php', { method: 'POST', body: fd, credentials: 'same-origin' }).then(function (r) { + return r.json().catch(function () { return { ok: false, error: 'Unerwartete Antwort des Servers.' }; }); + }); + } + function supported() { + return !!(window.PublicKeyCredential && navigator.credentials && window.isSecureContext); + } + function friendly(e) { + if (e && (e.name === 'NotAllowedError' || e.name === 'AbortError')) return 'Abgebrochen oder abgelaufen. Bitte noch einmal versuchen.'; + if (e && e.name === 'InvalidStateError') return 'Dieses Gerät ist schon als Passkey registriert.'; + return (e && e.message) ? e.message : 'Der Passkey konnte nicht verwendet werden.'; + } + + window.DeskHubPasskey = { + supported: supported, + login: function (csrf) { + return post(csrf, { action: 'login_options' }).then(function (r) { + if (!r.ok) throw new Error(r.error); + var o = r.options; + return navigator.credentials.get({ publicKey: { challenge: b64uToBuf(o.challenge), rpId: o.rpId, timeout: o.timeout, userVerification: o.userVerification, allowCredentials: [] } }); + }).then(function (c) { + var rs = c.response; + return post(csrf, { action: 'login_verify', credential: JSON.stringify({ id: c.id, response: { + clientDataJSON: bufToB64u(rs.clientDataJSON), authenticatorData: bufToB64u(rs.authenticatorData), signature: bufToB64u(rs.signature), + userHandle: rs.userHandle ? bufToB64u(rs.userHandle) : '' + } }) }); + }).then(function (r) { + if (!r.ok) throw new Error(r.error); + return r; + }).catch(function (e) { throw new Error(friendly(e)); }); + }, + register: function (csrf, name, password) { + return post(csrf, { action: 'register_options', password: password }).then(function (r) { + if (!r.ok) throw new Error(r.error); + var o = r.options; + o.challenge = b64uToBuf(o.challenge); + o.user.id = b64uToBuf(o.user.id); + o.excludeCredentials = (o.excludeCredentials || []).map(function (c) { return { type: c.type, id: b64uToBuf(c.id) }; }); + return navigator.credentials.create({ publicKey: o }); + }).then(function (c) { + var rs = c.response; + return post(csrf, { action: 'register_verify', name: name, credential: JSON.stringify({ id: c.id, response: { + clientDataJSON: bufToB64u(rs.clientDataJSON), attestationObject: bufToB64u(rs.attestationObject) + } }) }); + }).then(function (r) { + if (!r.ok) throw new Error(r.error); + return r; + }).catch(function (e) { throw new Error(friendly(e)); }); + } + }; +})();