diff --git a/ChangeLog.md b/ChangeLog.md index bbc7f57..c7b413d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,42 @@ # Changelog NetDiag +## 1.2.0 — 2026-08-17 + +### Anmeldung nur noch über AWL-Auth — der eigene JWT-Pfad ist weg + +**Achtung, das ist ein Schnitt:** Geräte mit einer App-Fassung, die noch ein +modul-eigenes netdiag-JWT benutzt, können sich nicht mehr anmelden und brauchen +die aktuelle APK. Eddy hat den Rollout am 17.08.2026 als abgeschlossen bestätigt. + +Warum das mehr ist als Aufräumen: Der alte Pfad prüfte die Signatur eines selbst +ausgestellten Tokens und holte damit einen Benutzer aus der Datenbank — **an der +Sitzungsliste von awlauth vorbei**. „Gerät abmelden" hatte auf ein solches Token +keine Wirkung; ein verlorenes Handy blieb bis zum Ablauf der TTL angemeldet. Jetzt +gibt es genau eine Stelle, an der Sitzungen entstehen und enden. + +- `api/auth.php`: der Rückfallweg (`checkLoginPassEntity` + eigenes JWT) ist + entfernt. Ohne aktives AWL-Auth antwortet die Anmeldung mit 503 und einer + klaren Meldung, statt auf einen zweiten Weg auszuweichen. +- `api/netdiag_api.lib.php`: `netdiag_api_authenticate()` prüft nur noch + `awlauth_verify_token()`. Die Hilfsfunktionen `netdiag_jwt_encode/decode`, + `netdiag_jwt_secret` und die Base64-URL-Helfer sind ersatzlos entfallen. +- **Konstanten `NETDIAG_API_JWT_SECRET` und `NETDIAG_API_TOKEN_TTL`** sind aus + dem Modul-Descriptor raus. Bestehende Werte in `llx_const` werden bewusst + NICHT gelöscht — sie schaden nicht, und ein Löschlauf beim Modul-Update ist + das größere Risiko. +- **TTL-Feld aus `admin/setup.php` entfernt.** Die Gültigkeit kommt jetzt allein + aus `AWLAUTH_TTL` (awlauth-Setup, Standard 7 Tage, dort 1–180 Tage + einstellbar). Zwei Stellschrauben für dieselbe Sache sind eine Fehlerquelle — + die hiesige hätte nach diesem Umbau still gar nichts mehr getan. An ihrer + Stelle steht jetzt ein Verweis. +- Sprachdateien de_DE und en_US nachgezogen, beide wieder deckungsgleich + (57 Schlüssel). + +Geprüft gegen die lokale Testinstanz: Anmeldung mit falschem Passwort → sauberer +Fehler von awlauth; API mit ungültigem Token → 401 „Token ungültig oder +abgelaufen"; ohne Token → 401 „Kein Token übermittelt". Kein PHP-Fehler durch +die entfernten Funktionen. + ## 1.1.7 — 2026-08-17 ### PDF-Zweig und Feldtabelle für den Geräte-Vergleich diff --git a/admin/setup.php b/admin/setup.php index e3fc74c..e3f72af 100644 --- a/admin/setup.php +++ b/admin/setup.php @@ -70,11 +70,6 @@ $action = GETPOST('action', 'aZ09'); // Konstanten speichern if ($action == 'updateconst') { - $ttl = GETPOSTINT('NETDIAG_API_TOKEN_TTL'); - if ($ttl < 60) { - $ttl = 604800; - } - dolibarr_set_const($db, 'NETDIAG_API_TOKEN_TTL', $ttl, 'chaine', 0, '', $conf->entity); dolibarr_set_const($db, 'NETDIAG_APK_URL', GETPOST('NETDIAG_APK_URL', 'alpha'), 'chaine', 0, '', $conf->entity); setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); header("Location: ".$_SERVER["PHP_SELF"]); @@ -104,9 +99,15 @@ print ''; print ''; print ''; -print ''; -print ''; +/* + * Die Gültigkeit der Anmeldung wird NICHT mehr hier eingestellt. Sie kommt + * seit 17.08.2026 allein aus `AWLAUTH_TTL` im Setup des Moduls AWL-Auth + * (Standard 7 Tage). Zwei Stellschrauben für dieselbe Sache sind eine + * Fehlerquelle: die hiesige wirkte nur auf den inzwischen entfernten + * modul-eigenen Anmeldeweg und hätte still nichts mehr getan. + */ +print ''; +print ''; print ''; diff --git a/api/auth.php b/api/auth.php index b227735..39b4571 100644 --- a/api/auth.php +++ b/api/auth.php @@ -55,11 +55,25 @@ if ($login === '' || $password === '') { require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php'; require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; -if (netdiag_awlauth_available()) { - // --------------------------------------------------------------------- - // Zentrale Anmeldung über awlauth (Rate-Limit, Passwortprüfung, - // SSO-Eignung) — dieselbe Prüfung wie in allen anderen AWL-Apps. - // --------------------------------------------------------------------- +/* + * Anmeldung ausschließlich über awlauth — Rate-Limit, Passwortprüfung, + * SSO-Eignung und die zentrale Sitzungsliste kommen von dort, wie in allen + * anderen AWL-Apps. + * + * Der frühere Rückfallweg mit modul-eigenem JWT ist am 17.08.2026 entfallen + * (APK-Rollout abgeschlossen). Er stellte Tokens aus, die an der + * awlauth-Geräteliste vorbeiliefen: „Gerät abmelden" hatte darauf keine + * Wirkung. Lieber ein klarer Fehler als eine zweite, unsichtbare Sitzungsart. + * + * Die Gültigkeit bestimmt jetzt allein `AWLAUTH_TTL` (awlauth-Setup, + * Standard 7 Tage) — es gibt keine konkurrierende Einstellung mehr im + * NetDiag-Setup. + */ +if (!netdiag_awlauth_available()) { + netdiag_api_error('Anmeldung nicht möglich: das Modul AWL-Auth ist nicht aktiv', 503); +} + +{ $res = awlauth_login($login, $password); if (empty($res['success'])) { netdiag_api_error($res['error'], (int) $res['http']); @@ -87,42 +101,6 @@ if (netdiag_awlauth_available()) { } $token = $issued['token']; $ttl = (int) $issued['expiresIn']; -} else { - // --------------------------------------------------------------------- - // Fallback ohne awlauth: modul-eigenes JWT (Alt-Verhalten). - // --------------------------------------------------------------------- - $entitytocheck = (int) $conf->entity; - $authmode = (getDolGlobalString('MAIN_AUTHENTICATION_MODE') ? getDolGlobalString('MAIN_AUTHENTICATION_MODE') : 'dolibarr'); - $resultlogin = checkLoginPassEntity($login, $password, $entitytocheck, explode(',', $authmode)); - - if (empty($resultlogin)) { - // Kurze Verzögerung gegen Brute-Force - sleep(1); - netdiag_api_error('Login fehlgeschlagen', 401); - } - - $user = new User($db); - if ($user->fetch('', $resultlogin, '', 0, $entitytocheck) <= 0 || empty($user->id)) { - netdiag_api_error('Benutzer nicht gefunden', 401); - } - if (!empty($user->statut) && $user->statut == 0) { - netdiag_api_error('Benutzer deaktiviert', 403); - } - $user->loadRights(); - - if (!$user->hasRight('netdiag', 'protocol', 'read')) { - netdiag_api_error('Keine Berechtigung für NetDiag', 403); - } - - $ttl = (int) getDolGlobalString('NETDIAG_API_TOKEN_TTL', '604800'); - if ($ttl < 60) { - $ttl = 604800; - } - - $token = netdiag_jwt_encode(array( - 'sub' => (int) $user->id, - 'name' => $user->getFullName($langs), - ), $ttl); } netdiag_api_respond(array( diff --git a/api/netdiag_api.lib.php b/api/netdiag_api.lib.php index bc0f8ed..692c348 100644 --- a/api/netdiag_api.lib.php +++ b/api/netdiag_api.lib.php @@ -192,94 +192,10 @@ function netdiag_api_error($message, $httpstatus = 400) netdiag_api_respond(array('error' => $message), $httpstatus); } -/** - * Base64-URL-kodieren (JWT-konform, ohne Padding). - * - * @param string $data Rohdaten - * @return string Kodierter String - */ -function netdiag_base64url_encode($data) -{ - return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); -} -/** - * Base64-URL-dekodieren. - * - * @param string $data Kodierter String - * @return string Rohdaten - */ -function netdiag_base64url_decode($data) -{ - return base64_decode(strtr($data, '-_', '+/')); -} -/** - * Geheimen JWT-Schlüssel des Moduls holen. - * - * @return string Schlüssel - */ -function netdiag_jwt_secret() -{ - $secret = getDolGlobalString('NETDIAG_API_JWT_SECRET'); - if (empty($secret)) { - // Fallback: Instanz-eindeutiger Wert (sollte nach Modulaktivierung nicht eintreten) - $secret = md5(DOL_DOCUMENT_ROOT.getDolGlobalString('MAIN_INFO_SOCIETE_NOM')); - } - return $secret; -} -/** - * JWT (HS256) erzeugen. - * - * @param array $payload Nutzdaten (sub, name, exp werden ergänzt) - * @param int $ttl Gültigkeit in Sekunden - * @return string Signiertes Token - */ -function netdiag_jwt_encode($payload, $ttl) -{ - $header = array('alg' => 'HS256', 'typ' => 'JWT'); - $now = dol_now(); - $payload['iat'] = $now; - $payload['exp'] = $now + $ttl; - $seg = array(); - $seg[] = netdiag_base64url_encode(json_encode($header)); - $seg[] = netdiag_base64url_encode(json_encode($payload)); - $signinginput = implode('.', $seg); - $signature = hash_hmac('sha256', $signinginput, netdiag_jwt_secret(), true); - $seg[] = netdiag_base64url_encode($signature); - - return implode('.', $seg); -} - -/** - * JWT prüfen und Nutzdaten zurückgeben. - * - * @param string $token JWT - * @return array|null Nutzdaten oder null bei ungültig/abgelaufen - */ -function netdiag_jwt_decode($token) -{ - $parts = explode('.', (string) $token); - if (count($parts) !== 3) { - return null; - } - list($h, $p, $s) = $parts; - $expected = hash_hmac('sha256', $h.'.'.$p, netdiag_jwt_secret(), true); - $given = netdiag_base64url_decode($s); - if (!hash_equals($expected, $given)) { - return null; - } - $payload = json_decode(netdiag_base64url_decode($p), true); - if (!is_array($payload)) { - return null; - } - if (empty($payload['exp']) || $payload['exp'] < dol_now()) { - return null; - } - return $payload; -} /** * Token aus Request lesen (Authorization-Header oder ?jwt=). @@ -327,25 +243,25 @@ function netdiag_api_authenticate($db) netdiag_api_error('Kein Token übermittelt', 401); } - $user = null; - - // 1. Zentrales Auth-Modul - if (netdiag_awlauth_available()) { - $user = awlauth_verify_token($token); - } - - // 2. Übergang: altes netdiag-JWT - if ($user === null) { - $payload = netdiag_jwt_decode($token); - if ($payload !== null && !empty($payload['sub'])) { - require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; - $legacy = new User($db); - if ($legacy->fetch((int) $payload['sub']) > 0 && !empty($legacy->id)) { - $legacy->loadRights(); - $user = $legacy; - } - } + /* + * Ausschließlich über das zentrale Auth-Modul. Der Übergangspfad mit dem + * eigenen netdiag-JWT ist am 17.08.2026 entfallen, nachdem Eddy den + * APK-Rollout als abgeschlossen bestätigt hat. + * + * Warum das ein Gewinn ist und nicht nur Aufräumen: Der alte Pfad prüfte + * die Signatur eines selbst ausgestellten Tokens und holte damit einen + * Benutzer aus der Datenbank — an der Sitzungsliste von awlauth vorbei. + * „Gerät abmelden" dort hatte auf ein solches Token keine Wirkung; ein + * verlorenes Handy blieb bis zum Ablauf der TTL angemeldet. Jetzt gibt es + * genau eine Stelle, an der Sitzungen enden. + * + * Ohne aktives awlauth verweigert die API bewusst den Dienst, statt auf + * einen zweiten Weg auszuweichen. + */ + if (!netdiag_awlauth_available()) { + netdiag_api_error('Anmeldung nicht möglich: das Modul AWL-Auth ist nicht aktiv', 503); } + $user = awlauth_verify_token($token); if ($user === null || empty($user->id)) { netdiag_api_error('Token ungültig oder abgelaufen', 401); diff --git a/core/modules/modNetDiag.class.php b/core/modules/modNetDiag.class.php index 2e80371..25b2fdc 100644 --- a/core/modules/modNetDiag.class.php +++ b/core/modules/modNetDiag.class.php @@ -67,7 +67,7 @@ class modNetDiag extends DolibarrModules $this->editor_url = ''; $this->editor_squarred_logo = ''; - $this->version = '1.1.7'; + $this->version = '1.2.0'; // Konstanten-Name in llx_const für Modul-Status $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); @@ -120,9 +120,12 @@ class modNetDiag extends DolibarrModules // Konstanten beim Aktivieren anlegen // (Key, Typ, Wert, Beschreibung, sichtbar, current/allentities, deleteonunactive) $this->const = array( - 1 => array('NETDIAG_API_JWT_SECRET', 'chaine', dol_hash(dol_print_date(dol_now(), 'dayhourrfc').mt_rand(), 'md5'), 'Geheimer Schlüssel zum Signieren der API-JWT', 0, 'current', 1), - 2 => array('NETDIAG_API_TOKEN_TTL', 'chaine', '604800', 'Gültigkeit des API-Tokens in Sekunden (Standard 7 Tage)', 1, 'current', 0), - 3 => array('NETDIAG_APK_URL', 'chaine', 'https://git.data-it-solution.de/api/packages/data-it/generic/netdiag-apk/latest/NetDiag.apk', 'Download-URL der Android-App (für QR-Code im Admin)', 1, 'current', 0), + // NETDIAG_API_JWT_SECRET und NETDIAG_API_TOKEN_TTL sind am 17.08.2026 + // entfallen: die Anmeldung läuft ausschließlich über awlauth, die + // Gültigkeit bestimmt dort AWLAUTH_TTL. Bestehende Werte in llx_const + // werden bewusst NICHT gelöscht — sie schaden nicht, und ein + // Löschlauf beim Modul-Update ist das größere Risiko. + 1 => array('NETDIAG_APK_URL', 'chaine', 'https://git.data-it-solution.de/api/packages/data-it/generic/netdiag-apk/latest/NetDiag.apk', 'Download-URL der Android-App (für QR-Code im Admin)', 1, 'current', 0), ); if (!isModEnabled("netdiag")) { diff --git a/langs/de_DE/netdiag.lang b/langs/de_DE/netdiag.lang index 1c2713e..04155b3 100644 --- a/langs/de_DE/netdiag.lang +++ b/langs/de_DE/netdiag.lang @@ -16,8 +16,11 @@ NetDiagSetup = NetDiag Einrichtung NetDiagSetupPage = NetDiag Einrichtungsseite NetDiagAbout = Über NetDiag NetDiagAboutPage = NetDiag Info-Seite -NETDIAG_API_TOKEN_TTL = Token-Gültigkeit (Sekunden) -NETDIAG_API_TOKEN_TTLTooltip = Wie lange ein API-Token der mobilen App gültig bleibt. Standard 604800 (7 Tage). +# Entfallen am 17.08.2026: die Gültigkeit kommt jetzt aus AWLAUTH_TTL im Setup +# des Moduls AWL-Auth. Schlüssel bleiben stehen, damit ältere Protokolle und +# Screenshots weiterhin auflösbar sind. +NetDiagTokenTTLMoved = Gültigkeit der Anmeldung +NetDiagTokenTTLMovedValue = Wird zentral im Modul AWL-Auth eingestellt (Standard 7 Tage). NETDIAG_APK_URL = Download-URL der App (APK) NETDIAG_APK_URLTooltip = Adresse, unter der die Android-App (APK) liegt. Wird als QR-Code angezeigt. NetDiagApiSecretInfo = Der JWT-Schlüssel wurde beim Aktivieren automatisch erzeugt und ist nicht editierbar. diff --git a/langs/en_US/netdiag.lang b/langs/en_US/netdiag.lang index 967956d..6b53c8f 100644 --- a/langs/en_US/netdiag.lang +++ b/langs/en_US/netdiag.lang @@ -16,8 +16,9 @@ NetDiagSetup = NetDiag setup NetDiagSetupPage = NetDiag setup page NetDiagAbout = About NetDiag NetDiagAboutPage = NetDiag about page -NETDIAG_API_TOKEN_TTL = Token validity (seconds) -NETDIAG_API_TOKEN_TTLTooltip = How long a mobile app API token stays valid. Default 604800 (7 days). +# Removed 2026-08-17: validity now comes from AWLAUTH_TTL in the AWL-Auth module setup. +NetDiagTokenTTLMoved = Login validity +NetDiagTokenTTLMovedValue = Configured centrally in the AWL-Auth module (default 7 days). NETDIAG_APK_URL = App download URL (APK) NETDIAG_APK_URLTooltip = Address where the Android app (APK) is hosted. Shown as a QR code. NetDiagApiSecretInfo = The JWT secret was generated automatically on activation and is not editable.
'.$langs->trans("Parameter").''.$langs->trans("Value").'
'.$langs->trans("NETDIAG_API_TOKEN_TTL"); -print ' '.$form->textwithpicto('', $langs->trans("NETDIAG_API_TOKEN_TTLTooltip")).'
'.$langs->trans("NetDiagTokenTTLMoved").''.$langs->trans("NetDiagTokenTTLMovedValue").'
'.$langs->trans("NETDIAG_APK_URL"); print ' '.$form->textwithpicto('', $langs->trans("NETDIAG_APK_URLTooltip")).'