All checks were successful
Deploy Eplan / deploy (push) Successful in 9s
Aus /dolibarr audit Report: - en_US: 5 fehlende Tab-/Placeholder-Keys ergänzt (Parity 100%) - LIKE-Wildcard-Escape: %, _ und \\ im User-Input werden maskiert bevor in LIKE '%..%'-Pattern eingebaut (sonst matched "100%" zu viel) Betrifft auftraege_listen (Z.75-77) und kunden_suchen (Z.124) - Rate-Limit: File-basiert nach KB #354 — max. 10 fehlgeschlagene Token- Checks pro IP in 15 Minuten, dann 429. Bei Erfolg wird Zähler resettet. Zähler liegen unter DOL_DATA_ROOT/eplan/loginattempts/<sha1(ip)>.json Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
2.8 KiB
PHP
99 lines
2.8 KiB
PHP
<?php
|
|
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
|
* GPL v3+
|
|
*
|
|
* Token-Handling für die Eplan-PWA-Anbindung.
|
|
* Kein JWT — ein simpler Shared-Secret-Token reicht, weil nur der
|
|
* ElektroPlan-Backend-Container den Endpoint anspricht (Server-zu-Server
|
|
* über HTTPS). Kein User-Kontext, keine Rotation per Request.
|
|
*/
|
|
|
|
/**
|
|
* Lazy-init: Token aus llx_const laden, falls leer frisch erzeugen.
|
|
* @return string 64-Zeichen hex Token
|
|
*/
|
|
function eplanTokenHolen()
|
|
{
|
|
global $db, $conf;
|
|
|
|
$token = getDolGlobalString('EPLAN_PWA_SECRET');
|
|
if (!empty($token)) {
|
|
return $token;
|
|
}
|
|
|
|
$token = bin2hex(random_bytes(32));
|
|
dolibarr_set_const($db, 'EPLAN_PWA_SECRET', $token, 'chaine', 0, 'PWA-Token', $conf->entity);
|
|
return $token;
|
|
}
|
|
|
|
/**
|
|
* Neuen Token erzeugen (überschreibt den alten).
|
|
* @return string neuer Token
|
|
*/
|
|
function eplanTokenRotieren()
|
|
{
|
|
global $db, $conf;
|
|
$token = bin2hex(random_bytes(32));
|
|
dolibarr_set_const($db, 'EPLAN_PWA_SECRET', $token, 'chaine', 0, 'PWA-Token', $conf->entity);
|
|
return $token;
|
|
}
|
|
|
|
/**
|
|
* Vergleicht einen übergebenen Token mit dem gespeicherten (timing-safe).
|
|
* @param string $eingang Token aus dem Request
|
|
* @return bool
|
|
*/
|
|
function eplanTokenPruefen($eingang)
|
|
{
|
|
if (empty($eingang)) return false;
|
|
$gespeichert = getDolGlobalString('EPLAN_PWA_SECRET');
|
|
if (empty($gespeichert)) return false;
|
|
return hash_equals($gespeichert, (string) $eingang);
|
|
}
|
|
|
|
/**
|
|
* Token aus Request holen: bevorzugt Header X-Eplan-Token, sonst Query-Param `token`.
|
|
* @return string|null
|
|
*/
|
|
function eplanTokenAusRequest()
|
|
{
|
|
$header = $_SERVER['HTTP_X_EPLAN_TOKEN'] ?? '';
|
|
if (!empty($header)) return $header;
|
|
return $_GET['token'] ?? $_POST['token'] ?? null;
|
|
}
|
|
|
|
/**
|
|
* File-basierter Brute-Force-Schutz (KB #354):
|
|
* Pro IP maximal 10 fehlgeschlagene Versuche in 15 Minuten, danach blockiert.
|
|
* Bei Erfolg wird der Zähler zurückgesetzt.
|
|
* @return bool true wenn Request erlaubt, false wenn geblockt
|
|
*/
|
|
function eplanRateLimit($erfolg = null)
|
|
{
|
|
global $conf;
|
|
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
|
$dir = DOL_DATA_ROOT.'/eplan/loginattempts';
|
|
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
|
$datei = $dir.'/'.sha1($ip).'.json';
|
|
$now = time();
|
|
$fenster = 900; // 15 Minuten
|
|
$limit = 10;
|
|
|
|
$daten = ['count' => 0, 'first' => $now];
|
|
if (file_exists($datei)) {
|
|
$daten = json_decode(file_get_contents($datei), true) ?: $daten;
|
|
if ($now - ($daten['first'] ?? 0) > $fenster) {
|
|
$daten = ['count' => 0, 'first' => $now];
|
|
}
|
|
}
|
|
|
|
if ($erfolg === true) {
|
|
@unlink($datei);
|
|
return true;
|
|
}
|
|
if ($erfolg === false) {
|
|
$daten['count']++;
|
|
@file_put_contents($datei, json_encode($daten));
|
|
}
|
|
return ($daten['count'] ?? 0) < $limit;
|
|
}
|