185 lines
15 KiB
PHP
185 lines
15 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Rechte-Katalog: Alle Berechtigungen an einer Stelle, gruppiert, mit Beschreibung und Standardwert.
|
||
*
|
||
* - 'agent' : Standardwert für Mitarbeiter ohne eigene Rolle (und für Rechte, die in einer älteren Rolle noch fehlen). Rechte, die bis zur
|
||
* Einführung des Katalogs jeder Mitarbeiter hatte, stehen auf true; neue Adminportal-Bereiche auf false.
|
||
* - 'area' : Bereich des Adminportals („Co-Admin“): Wer so ein Recht hat, sieht das Adminportal im Menü.
|
||
* - Echte Administratoren haben immer alle Rechte (has_permission()).
|
||
* Bewusst nicht delegierbar (immer nur echte Administratoren): Einstellungen, Backup, System-Update, Plugins, API-Schlüssel, Webhooks, osTicket-Import,
|
||
* Sicherheitsprüfung, Passkeys ein-/ausschalten und die Datenschutz-Löschfunktionen.
|
||
*/
|
||
function permission_catalog(): array
|
||
{
|
||
$p = fn(string $label, string $hint, bool $agent, bool $area = false) => ['label' => $label, 'hint' => $hint, 'agent' => $agent, 'area' => $area];
|
||
return [
|
||
'tickets' => ['title' => 'Tickets bearbeiten', 'items' => [
|
||
'reply_ticket' => $p('Tickets beantworten', 'Antworten, Antworten einplanen, Anhänge aus E-Mails nachladen', true),
|
||
'post_note' => $p('Interne Notizen schreiben', 'Notizen, die nur das Team sieht', true),
|
||
'close_ticket' => $p('Tickets schließen/lösen', 'Status auf Gelöst oder Geschlossen setzen, als Spam markieren', true),
|
||
'assign_ticket' => $p('Tickets zuweisen (Mitarbeiter/Team)', 'Bearbeiter oder Team ändern', true),
|
||
'transfer_ticket' => $p('Tickets übertragen (Abteilung ändern)', 'Ticket in eine andere Abteilung verschieben', true),
|
||
'create_ticket' => $p('Tickets im Namen von Kunden erstellen', 'Neues Ticket anlegen', true),
|
||
'merge_split_ticket' => $p('Tickets zusammenführen und teilen', 'Doppelte Tickets vereinen, Nachrichten abspalten', true),
|
||
'link_tickets' => $p('Tickets verknüpfen', 'Verwandt, doppelt oder blockiert', true),
|
||
'delete_ticket' => $p('Tickets löschen', 'Endgültig löschen, auch per Sammelaktion', false),
|
||
]],
|
||
'customers' => ['title' => 'Kunden und Sperren', 'items' => [
|
||
'view_customers' => $p('Kundenprofile ansehen', 'Profil eines Kunden mit allen seinen Tickets', true),
|
||
'manage_customers' => $p('Kundenverzeichnis verwalten', 'Alle Kunden-Konten ansehen und Organisationen zuordnen', false, true),
|
||
'manage_organizations' => $p('Organisationen verwalten', 'Mehrere Kunden zu einer Firma bündeln', false, true),
|
||
'manage_banlist' => $p('Sperrliste und Absender sperren', 'Adressen und Domains blockieren', true),
|
||
]],
|
||
'reports' => ['title' => 'Auswertung und Export', 'items' => [
|
||
'view_reports' => $p('Berichte einsehen', 'Übersicht, Berichte, Bericht-Baukasten (nur eigene Abteilungen)', true),
|
||
'schedule_reports' => $p('Berichte per E-Mail versenden', 'Zeitpläne im Bericht-Baukasten (externe Empfänger nur Administratoren)', true),
|
||
'export_tickets' => $p('Tickets als CSV exportieren', 'Ticketliste für Excel herunterladen', false),
|
||
'view_time_entries' => $p('Zeiterfassung einsehen', 'Auswertung der erfassten Zeiten', false, true),
|
||
'view_audit_log' => $p('Admin-Protokoll einsehen', 'Wer hat was geändert', false, true),
|
||
]],
|
||
'mail' => ['title' => 'E-Mail', 'items' => [
|
||
'fetch_mail' => $p('E-Mails jetzt abrufen', 'Den Abruf per Knopf auslösen', true),
|
||
'manage_email_addresses' => $p('E-Mail-Adressen und Postfächer verwalten', 'Absender, Eingang, SMTP und IMAP je Adresse', false, true),
|
||
'manage_email_templates' => $p('E-Mail-Vorlagen verwalten', 'Texte der automatischen Mails', false, true),
|
||
'view_mail_queue' => $p('Mail-Warteschlange verwalten', 'Nicht zugestellte Mails ansehen, erneut senden, löschen', false, true),
|
||
]],
|
||
'content' => ['title' => 'Inhalte und Vorlagen', 'items' => [
|
||
'manage_canned' => $p('Textbausteine verwalten', 'Vorlagen für Antworten', false, true),
|
||
'manage_ticket_templates' => $p('Ticket-Vorlagen verwalten', 'Vorgefüllte Tickets für Mitarbeiter und Kunden', false, true),
|
||
'manage_kb' => $p('Wissensdatenbank verwalten', 'Artikel und Kategorien', false, true),
|
||
'manage_assistant' => $p('Digitalen Assistenten verwalten', 'Antworten, Fragen-Log, Einstellungen', false, true),
|
||
'manage_site_pages' => $p('Seiten im Kundenportal verwalten', 'Impressum, AGB und weitere Seiten', false, true),
|
||
'manage_languages' => $p('Sprachen verwalten', 'Sprachpakete und Standardsprache', false, true),
|
||
'manage_status_page' => $p('Statusseite verwalten', 'Dienste und Störungsmeldungen', false, true),
|
||
'share_views' => $p('Ansichten mit dem Team teilen', 'Gespeicherte Filter für alle sichtbar machen', true),
|
||
]],
|
||
'setup' => ['title' => 'Einrichtung', 'items' => [
|
||
'manage_departments' => $p('Abteilungen verwalten', 'Abteilungen, Formulare, automatische Zuweisung', false, true),
|
||
'manage_priorities' => $p('Prioritäten / SLA verwalten', 'Prioritäten und SLA-Pläne', false, true),
|
||
'manage_help_topics' => $p('Hilfethemen verwalten', 'Auswahl für Kunden beim Ticket-Erstellen', false, true),
|
||
'manage_custom_fields' => $p('Ticket-Felder und Listen verwalten', 'Zusatzfelder und Auswahllisten', false, true),
|
||
'manage_automation' => $p('Ticket-Regeln und wiederkehrende Tickets verwalten', 'Automatische Aktionen bei neuen Tickets, regelmäßige Tickets', false, true),
|
||
'manage_escalations' => $p('Eskalationsregeln verwalten', 'Aktionen bei überfälligen oder liegengebliebenen Tickets', false, true),
|
||
'manage_business_hours' => $p('Geschäftszeiten verwalten', 'Zeitfenster und Feiertage für die SLA', false, true),
|
||
'manage_problems' => $p('Problemmanagement', 'Probleme erfassen und mit Tickets verknüpfen', false, true),
|
||
]],
|
||
'team' => ['title' => 'Team und Sicherheit', 'items' => [
|
||
'manage_staff' => $p('Mitarbeiter verwalten', 'Konten, Sperre aufheben, Überall abmelden, Passkeys entfernen', false, true),
|
||
'manage_teams' => $p('Teams verwalten', 'Gruppen von Mitarbeitern', false, true),
|
||
'manage_roles' => $p('Rollen verwalten', 'Rechte vergeben', false, true),
|
||
'manage_availability' => $p('Status und Schichten der Kollegen ändern', 'Status anderer Mitarbeiter setzen, automatisches Offline einstellen', false, true),
|
||
'view_login_guard' => $p('Anmelde-Wächter ansehen', 'Anmeldeprotokoll und gesperrte Konten; Einstellungen bleiben Administratoren', false, true),
|
||
'view_help' => $p('Hilfe und Handbuch', 'Hilfe-Seite mit dem Handbuch (PDF)', false, true),
|
||
]],
|
||
];
|
||
}
|
||
|
||
/** Alle Rechte als schlüssel => Beschriftung (Reihenfolge wie im Katalog). */
|
||
function permission_labels(): array
|
||
{
|
||
$out = [];
|
||
foreach (permission_catalog() as $g) foreach ($g['items'] as $k => $i) $out[$k] = $i['label'];
|
||
return $out;
|
||
}
|
||
|
||
/** Rechte für Adminportal-Bereiche („Co-Admin“): schlüssel => Beschriftung. */
|
||
function admin_section_permissions(): array
|
||
{
|
||
$out = [];
|
||
foreach (permission_catalog() as $g) foreach ($g['items'] as $k => $i) if ($i['area']) $out[$k] = $i['label'];
|
||
return $out;
|
||
}
|
||
|
||
/** Standardrechte: Administrator alles, Mitarbeiter die Standardwerte des Katalogs. */
|
||
function default_permissions(string $role): array
|
||
{
|
||
$out = [];
|
||
foreach (permission_catalog() as $g) foreach ($g['items'] as $k => $i) $out[$k] = $role === 'admin' ? true : (bool) $i['agent'];
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Vorlagen für Rollen: Schlüssel => [Name, Beschreibung, Rechte, als Rolle anlegen?].
|
||
* Die Vorlagen mit „true“ werden bei der Installation und beim Update als echte Rollen angelegt (siehe permission_seed_default_roles()); sie lassen sich danach
|
||
* wie jede andere Rolle ändern oder löschen. „Mitarbeiter“ ist nur eine Vorlage im Formular, weil das die Standardrechte ohne Rolle sind.
|
||
*/
|
||
function permission_presets(): array
|
||
{
|
||
$all = array_keys(permission_labels());
|
||
$on = fn(array $keys) => array_fill_keys($keys, true) + array_fill_keys($all, false);
|
||
$agent = array_keys(array_filter(default_permissions('agent')));
|
||
$lead = array_merge($agent, ['delete_ticket', 'export_tickets', 'view_time_entries', 'manage_canned', 'manage_kb', 'manage_teams', 'manage_availability']);
|
||
return [
|
||
'mitarbeiter' => ['Mitarbeiter', 'Die Standardrechte ohne Rolle: Tickets bearbeiten, Berichte der eigenen Abteilungen.', $on($agent), false],
|
||
'co_admin' => ['Co-Administrator', 'Alle Rechte, die sich vergeben lassen. Nur Einstellungen, Backup, System-Update, Plugins, API-Schlüssel, Webhooks, Import und Sicherheitsprüfung bleiben echten Administratoren.', $on($all), true],
|
||
'support_manager' => ['Support-Manager', 'Leitet den Support: Tickets, Einrichtung (Abteilungen, Prioritäten, Themen, Regeln, Felder), Mitarbeiter, Teams, Protokolle und Mail-Warteschlange.', $on(array_merge($lead, [
|
||
'manage_departments', 'manage_priorities', 'manage_help_topics', 'manage_custom_fields', 'manage_automation', 'manage_escalations', 'manage_business_hours', 'manage_problems',
|
||
'manage_ticket_templates', 'manage_staff', 'view_audit_log', 'view_login_guard', 'view_mail_queue', 'manage_customers', 'manage_organizations', 'manage_email_templates', 'view_help'])), true],
|
||
'it' => ['IT / Technik', 'Betrieb und Technik: Tickets bearbeiten, dazu Postfächer und E-Mail-Adressen, Mail-Warteschlange, Anmelde-Wächter, Statusseite, Regeln, Eskalationen, Protokoll und Hilfe.', $on(array_merge($agent, [
|
||
'manage_email_addresses', 'view_mail_queue', 'view_login_guard', 'manage_status_page', 'manage_automation', 'manage_escalations', 'view_audit_log', 'manage_problems', 'manage_custom_fields', 'view_help'])), true],
|
||
'teamleitung' => ['Teamleitung', 'Führt ein Team: wie Mitarbeiter, dazu Löschen, Export, Zeiterfassung, Textbausteine, Wissensdatenbank, Teams und Schichten.', $on($lead), true],
|
||
'support_2' => ['Support 2nd Level', 'Erfahrene Mitarbeiter: Tickets bearbeiten, Probleme verwalten, Export und Zeiterfassung.', $on(array_merge($agent, ['manage_problems', 'export_tickets', 'view_time_entries'])), true],
|
||
'support_1' => ['Support 1st Level', 'Einstieg: antworten, Notizen, zuweisen, Tickets anlegen und verknüpfen, E-Mails abrufen. Kein Schließen, Verschieben, Zusammenführen oder Sperren.', $on(['reply_ticket', 'post_note', 'assign_ticket', 'create_ticket', 'link_tickets', 'view_customers', 'fetch_mail']), true],
|
||
'redaktion' => ['Redaktion (Wissen und Inhalte)', 'Pflegt Inhalte: Wissensdatenbank, digitaler Assistent, Textbausteine, Ticket- und E-Mail-Vorlagen, Seiten und Sprachen.', $on(['reply_ticket', 'post_note', 'view_customers', 'manage_kb', 'manage_assistant', 'manage_canned', 'manage_ticket_templates', 'manage_email_templates', 'manage_site_pages', 'manage_languages']), true],
|
||
'auswertung' => ['Auswertung / Controlling', 'Zahlen und Berichte: Berichte (auch per E-Mail), Export, Zeiterfassung, Protokoll, Ansichten teilen. Kann keine Tickets bearbeiten.', $on(['view_reports', 'schedule_reports', 'export_tickets', 'view_time_entries', 'view_audit_log', 'share_views', 'view_customers']), true],
|
||
'teamverwaltung' => ['Teamverwaltung / Schichtplanung', 'Verwaltet Personal: Mitarbeiter, Teams, Status und Schichten, Geschäftszeiten, Anmelde-Wächter ansehen.', $on(['view_reports', 'manage_staff', 'manage_teams', 'manage_availability', 'manage_business_hours', 'view_login_guard', 'view_audit_log']), true],
|
||
'aushilfe' => ['Aushilfe / Praktikant', 'Nur das Nötigste: antworten und Notizen schreiben, Kundenprofile ansehen.', $on(['reply_ticket', 'post_note', 'view_customers']), true],
|
||
'nur_lesen' => ['Nur lesen', 'Tickets, Kundenprofile und Berichte ansehen, nichts ändern.', $on(['view_customers', 'view_reports']), true],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Legt die vordefinierten Rollen an, die es noch nicht gibt (Vergleich über den Namen, Groß-/Kleinschreibung egal). Vorhandene Rollen werden nie verändert.
|
||
* @return list<string> Namen der neu angelegten Rollen
|
||
*/
|
||
function permission_seed_default_roles(PDO $pdo): array
|
||
{
|
||
$created = [];
|
||
$exists = $pdo->prepare('SELECT COUNT(*) FROM roles WHERE LOWER(name) = LOWER(?)');
|
||
$now = date('Y-m-d H:i:s');
|
||
foreach (permission_presets() as [$name, $desc, $perms, $seed]) {
|
||
if (!$seed) continue;
|
||
$exists->execute([$name]);
|
||
if ((int) $exists->fetchColumn() > 0) continue;
|
||
$pdo->prepare('INSERT INTO roles (name, permissions, is_active, notes, created_at, updated_at) VALUES (?,?,1,?,?,?)')
|
||
->execute([$name, json_encode($perms), 'Vordefinierte Rolle – frei änderbar. ' . $desc, $now, $now]);
|
||
$created[] = $name;
|
||
}
|
||
return $created;
|
||
}
|
||
|
||
/** Namen der vordefinierten Rollen, die auf dieser Installation fehlen. @return list<string> */
|
||
function permission_missing_default_roles(PDO $pdo): array
|
||
{
|
||
$have = array_map('mb_strtolower', $pdo->query('SELECT name FROM roles')->fetchAll(PDO::FETCH_COLUMN));
|
||
$missing = [];
|
||
foreach (permission_presets() as [$name, , , $seed]) if ($seed && !in_array(mb_strtolower($name), $have, true)) $missing[] = $name;
|
||
return $missing;
|
||
}
|
||
|
||
/**
|
||
* Rechte, die es schon vor dem Katalog gab. Fehlt eines davon in einer gespeicherten Rolle, gilt wie bisher „nein“; bei allen neueren Rechten gilt
|
||
* der Standardwert des Katalogs (so behalten bestehende Rollen, was bisher für alle offen war, und neue Adminbereiche bleiben aus).
|
||
*/
|
||
function permission_legacy_keys(): array
|
||
{
|
||
return ['reply_ticket', 'close_ticket', 'delete_ticket', 'transfer_ticket', 'post_note', 'assign_ticket', 'manage_canned', 'view_reports', 'manage_kb', 'create_ticket',
|
||
'manage_departments', 'manage_priorities', 'manage_help_topics', 'manage_email_addresses', 'manage_email_templates', 'manage_organizations', 'manage_teams', 'manage_roles',
|
||
'manage_staff', 'view_audit_log', 'manage_escalations', 'view_time_entries', 'manage_business_hours', 'manage_assistant'];
|
||
}
|
||
|
||
/** Wirksame Rechte einer gespeicherten Rolle (so, wie has_permission() sie auswertet): fehlende alte Rechte = nein, fehlende neuere = Standardwert. */
|
||
function permission_effective(array $rolePerms): array
|
||
{
|
||
$defaults = default_permissions('agent');
|
||
$legacy = permission_legacy_keys();
|
||
$out = [];
|
||
foreach (array_keys($defaults) as $k) {
|
||
$out[$k] = array_key_exists($k, $rolePerms) ? (bool) $rolePerms[$k] : (!in_array($k, $legacy, true) && $defaults[$k]);
|
||
}
|
||
return $out;
|
||
}
|
||
|