Upload via GUI (360 Dateien)

This commit is contained in:
2026-10-30 21:49:58 +00:00
parent aed0db39ba
commit 91ae4659b5
10 changed files with 711 additions and 9 deletions
+112 -1
View File
@@ -34,13 +34,15 @@ function plugin_known_permissions(): array
'api.endpoints' => 'Eigene Endpunkte in der Mitarbeiter-API',
'api.public' => 'Öffentliche Webhook-Adresse (zum Beispiel für Discord oder Telegram); das Plugin prüft die Echtheit der Anfragen selbst',
'db.migrations' => 'Eigene Datenbank-Tabellen anlegen',
'auth.customer' => 'Kunden über einen externen Dienst anmelden und Kundenkonten anlegen (zum Beispiel authentik): Knopf auf der Anmeldeseite des Kundenportals. ACHTUNG: Das Plugin entscheidet selbst, wer sich anmelden darf und ob Konten entstehen.',
'auth.login' => 'Mitarbeiter über einen externen Dienst anmelden (zum Beispiel authentik): Knopf auf der Anmeldeseite und eigene Anmelde-Adresse. ACHTUNG: Das Plugin entscheidet selbst, wer sich anmelden darf.',
];
}
/** Gemeinsamer Zustand (Hooks, Menüs, aktuell ausgeführtes Plugin). */
function &plugin_state(): array
{
static $state = ['hooks' => [], 'filters' => [], 'menus' => [], 'api' => [], 'webhooks' => [], 'perms' => [], 'loaded' => [], 'current' => null, 'booted' => false, 'shutdown' => false];
static $state = ['hooks' => [], 'filters' => [], 'menus' => [], 'api' => [], 'webhooks' => [], 'logins' => [], 'login_loggedin' => [], 'customer_logins' => [], 'perms' => [], 'loaded' => [], 'current' => null, 'booted' => false, 'shutdown' => false];
return $state;
}
@@ -128,6 +130,8 @@ function plugin_filter_permission(string $hook): ?string
{
if ($hook === 'ticket.view.sidebar' || $hook === 'staff.page.top') return 'ui.sidebar';
if (str_starts_with($hook, 'ticket.')) return 'hooks.tickets';
if ($hook === 'staff.login.buttons') return 'auth.login'; // Knöpfe auf der Mitarbeiter-Anmeldeseite
if ($hook === 'customer.login.buttons') return 'auth.customer'; // Knöpfe auf der Kunden-Anmeldeseite
if ($hook === 'backup.list.rows') return 'ui.menu'; // wer einen Reiter unter „Backup“ anbieten darf, darf auch die Liste der Backups ergänzen
return null;
}
@@ -182,6 +186,37 @@ function plugin_register_webhook(string $endpoint, callable $handler): void
$state['webhooks'][$slug][$endpoint] = $handler;
}
/**
* Registriert eine Anmelde-Adresse für Mitarbeiter: GET /staff/plugin_login.php?p=<slug>&e=<name>. Gedacht für externe Anmeldedienste (OpenID Connect, SAML …).
* Der Handler erhält ['query' => $_GET, 'ip' => …] (die Sitzung $_SESSION steht zur Verfügung) und gibt eines davon zurück:
* ['redirect' => 'https://…'] weiter zum Anmeldedienst (nur https, oder http zu localhost)
* ['staff_id' => 12] dieser Mitarbeiter ist geprüft: DeskHub meldet ihn an (nur aktive Konten, mit Eintrag im Admin-Protokoll)
* ['error' => 'Text'] zurück zur Anmeldeseite mit dieser Meldung
* Den Link zum Starten baut plugin_login_url(). Wer anmelden darf, entscheidet allein das Plugin (Berechtigung auth.login).
*
* Mit $whenLoggedIn = true läuft die Adresse auch für bereits angemeldete Mitarbeiter (zum Beispiel, um ein Konto beim Anmeldedienst zu verknüpfen).
* Der Handler bekommt dann zusätzlich ['staff' => <angemeldeter Mitarbeiter>] und darf nur zurückgeben:
* ['redirect' => 'https://…'] weiter zum Anmeldedienst
* ['local' => '/staff/…', 'notice' => 'Text'] zurück in den Mitarbeiterbereich, mit Hinweis
* ['error' => 'Text', 'back' => '/staff/…'] zurück mit Fehlermeldung
* Ein anderer Mitarbeiter kann so nie angemeldet werden (staff_id wird dann abgelehnt).
*/
function plugin_register_login(string $endpoint, callable $handler, bool $whenLoggedIn = false): void
{
$state = &plugin_state();
$slug = $state['current'];
if ($slug === null || !preg_match('/^[a-z0-9_-]{1,40}$/', $endpoint)) return;
if (!plugin_permission_allowed('auth.login')) return;
$state['logins'][$slug][$endpoint] = $handler;
$state['login_loggedin'][$slug][$endpoint] = $whenLoggedIn;
}
/** Adresse (ohne Domain) einer mit plugin_register_login() angemeldeten Anmelde-Adresse. */
function plugin_login_url(string $slug, string $endpoint): string
{
return '/staff/plugin_login.php?p=' . rawurlencode($slug) . '&e=' . rawurlencode($endpoint);
}
function plugin_register_api(string $endpoint, callable $handler, ?string $permission = null): void
{
if (!plugin_permission_allowed('api.endpoints')) return;
@@ -983,3 +1018,79 @@ function catalog_thumb(string $kind, array $c): string
}
return '<div aria-hidden="true" style="' . $box . 'display:flex;align-items:center;justify-content:center;font-weight:700;color:var(--muted,#64748b);font-size:1.3rem">' . e(mb_strtoupper(mb_substr((string) $c['name'], 0, 1))) . '</div>';
}
// ── Anmeldung von Kunden über Plugins (Berechtigung auth.customer) ──────────────────────────────────────────────────────────────
/**
* Registriert eine Anmelde-Adresse für Kunden: GET /plugin_login.php?p=<slug>&e=<name> (öffentlich, Kundenportal).
* Der Handler erhält ['query' => $_GET, 'ip' => …] und gibt zurück:
* ['redirect' => 'https://…'] weiter zum Anmeldedienst (nur https, oder http zu localhost)
* ['customer' => ['email' => …, 'name' => …, 'create' => true|false, 'via' => 'Dienst']]
* diese Person ist vom Dienst bestätigt: DeskHub meldet das Kundenkonto mit dieser
* E-Mail-Adresse an; gibt es keines und 'create' ist true, wird es angelegt
* ['error' => 'Text'] zurück zur Kunden-Anmeldeseite mit dieser Meldung
* Die Rückkehr vom Dienst darf auch über eine mit plugin_register_login() angemeldete Adresse kommen, wenn das Plugin beide Berechtigungen hat.
* Das Plugin trägt die Verantwortung, dass die E-Mail-Adresse wirklich bestätigt ist.
*/
function plugin_register_customer_login(string $endpoint, callable $handler): void
{
$state = &plugin_state();
$slug = $state['current'];
if ($slug === null || !preg_match('/^[a-z0-9_-]{1,40}$/', $endpoint)) return;
if (!plugin_permission_allowed('auth.customer')) return;
$state['customer_logins'][$slug][$endpoint] = $handler;
}
/** Adresse (ohne Domain) einer mit plugin_register_customer_login() angemeldeten Kunden-Anmelde-Adresse. */
function plugin_customer_login_url(string $slug, string $endpoint): string
{
return '/plugin_login.php?p=' . rawurlencode($slug) . '&e=' . rawurlencode($endpoint);
}
/** Gilt die Weiterleitung zu einem Anmeldedienst? Nur https (oder http zu localhost zum Testen), keine Zeichen < Leerzeichen. */
function plugin_login_external_url_ok(string $url): bool
{
if ($url === '' || strlen($url) > 4000 || preg_match('/[\x00-\x20]/', $url)) return false;
$p = parse_url($url);
if (!$p || empty($p['host'])) return false;
$scheme = $p['scheme'] ?? '';
return $scheme === 'https' || ($scheme === 'http' && in_array($p['host'], ['localhost', '127.0.0.1', '[::1]'], true));
}
/**
* Meldet einen vom Plugin bestätigten Kunden an (und legt das Konto an, wenn erlaubt). Endet immer mit einer Weiterleitung.
* Ein Plugin ohne Berechtigung auth.customer kommt hier nicht durch.
* @param array{email?:string,name?:string,create?:bool,via?:string} $c
*/
function plugin_customer_login_finish(string $slug, array $c): never
{
$fail = function (string $message): never {
flash('error', $message);
redirect('/login.php');
};
if (!in_array('auth.customer', plugin_state()['perms'][$slug] ?? [], true)) $fail('Diese Anmeldung ist nicht erlaubt.');
$email = strtolower(trim((string) ($c['email'] ?? '')));
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > 150) $fail('Die E-Mail-Adresse ist ungültig.');
if (is_banned($email)) $fail('Mit dieser E-Mail-Adresse ist keine Anmeldung möglich.');
$pdo = get_db();
$st = $pdo->prepare('SELECT * FROM users WHERE LOWER(email) = ?');
$st->execute([$email]);
$user = $st->fetch();
$created = false;
$via = mb_substr((string) ($c['via'] ?? $slug), 0, 60);
if (!$user) {
if (empty($c['create'])) $fail('Für diese E-Mail-Adresse gibt es kein Kundenkonto.');
$name = trim((string) preg_replace('/\s+/u', ' ', strip_tags((string) ($c['name'] ?? ''))));
if ($name === '') $name = ucfirst(explode('@', $email)[0]);
$name = mb_substr($name, 0, 120);
// Ein Passwort gibt es nicht: Wer eines möchte, nutzt „Passwort vergessen“
$pdo->prepare('INSERT INTO users (name, email, password_hash, created_at) VALUES (?,?,?,?)')
->execute([$name, $email, password_hash(bin2hex(random_bytes(24)), PASSWORD_DEFAULT), db_now()]);
$user = ['id' => (int) $pdo->lastInsertId(), 'name' => $name, 'email' => $email];
audit_log(null, 'Kundenkonto über ' . $via . ' angelegt: ' . mb_substr($email, 0, 120));
$created = true;
}
customer_login($user);
if ($created) flash('success', 'Willkommen! Ihr Konto wurde erstellt.');
redirect('/home.php');
}