1.3.0: PDF-Zweig SIP-Erreichbarkeit, Token in der URL nur noch fuer den APK-Download [deploy]
All checks were successful
Deploy netdiag / deploy (push) Successful in 14s
All checks were successful
Deploy netdiag / deploy (push) Successful in 14s
PDF-Zweig netdiagPdfSip()
- Eigener Zweig, weil die Aussage sonst untergeht: in einer flachen
Schluessel/Wert-Liste sieht "401 Unauthorized" wie ein Fehler aus, ist aber
der Beweis fuer eine erreichbare Instanz, die nur Zugangsdaten will. Erst
Befund im Klartext, dann eine Zeile je Transportweg.
- Der Grenzhinweis steht mit im Dokument: gemessen ist die Erreichbarkeit,
NICHT die Sprachqualitaet.
- Feldnamen-Falle wieder aufgetreten: 'port' gehoert in beiden Feldtabellen
laengst dem SNMP-Werkzeug ("Switch-Port"). Im Kunden-PDF haette gestanden
"Switch-Port: 5060" - deshalb 'zielPort'.
WLAN-Kanalanalyse: Warnungen nicht mehr abschneiden
- dol_trunc(..., 160) kappte genau die Begruendung ("... In dicht besiedelter
Umgebung meist ein Fehle..."). Eine Warnung ohne ihren Grund ist im
Kundendokument wertlos; MultiCell bricht ohnehin um.
Token in der Adresszeile
- netdiag_api_read_token() nimmt ?jwt= nur noch an, wenn der Endpunkt es
ausdruecklich erlaubt. Einzige Stelle: update.php?download=1.
- Grund: ein Token in der URL steht in jedem Zugriffs- und Proxy-Log und gilt
sieben Tage fuer die GESAMTE Kunden-API.
- Warum die Ausnahme bleibt: die App-Fassungen im Feld bauen die
Download-Adresse mit dem Token darin. Sofort schliessen hiesse, genau die
Geraete vom Update auszusperren, die die neue APK brauchen. Zu entfernen,
sobald Eddy den Rollout bestaetigt (Hinweis steht im Code).
- pdf.php nimmt ab sofort ausschliesslich den Authorization-Header.
Gegen die Testinstanz gemessen: ?jwt= liefert bei orders/customers/protocols/
pdf/update-Version jetzt 401, mit Bearer weiterhin 200.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a0dc0750cb
commit
087f37afa6
7 changed files with 230 additions and 12 deletions
51
ChangeLog.md
51
ChangeLog.md
|
|
@ -1,5 +1,56 @@
|
|||
# Changelog NetDiag
|
||||
|
||||
## 1.3.0 — 2026-08-19
|
||||
|
||||
### Neues Werkzeug: SIP-Erreichbarkeit (Telefonie)
|
||||
|
||||
Die App fragt Telefonanlage oder Provider mit einer OPTIONS-Anfrage (RFC 3261) —
|
||||
ohne Anruf, ohne Zugangsdaten, ohne dass irgendwo ein Telefon klingelt. Modulseitig
|
||||
kommt der PDF-Zweig dazu.
|
||||
|
||||
- `netdiagPdfSip()` in `lib/netdiag_pdf.lib.php`: Ziel, Port und Statuszeile in der
|
||||
Kopfzeile, darunter der Befund im Klartext, dann eine Zeile je Transportweg.
|
||||
**Warum ein eigener Zweig:** in einer flachen Schlüssel/Wert-Liste sieht „401
|
||||
Unauthorized" wie ein Fehler aus. Tatsächlich ist es der Beweis, dass die Instanz
|
||||
erreichbar ist und antwortet — sie will nur Zugangsdaten sehen. Diese Unterscheidung
|
||||
ist im Kundendokument der Unterschied zwischen „Anlage tot" und „Zugangsdaten prüfen".
|
||||
- Der Grenzhinweis steht mit im PDF: gemessen ist die **Erreichbarkeit**, NICHT die
|
||||
Sprachqualität. RTP-Jitter ohne aufgebautes Gespräch zu behaupten wäre eine
|
||||
Falschaussage.
|
||||
- `netdiagToolName()`: `sipcheck` → „SIP-Erreichbarkeit".
|
||||
- `netdiagKundenfelder()` um die Felder des Werkzeugs ergänzt (`befund`,
|
||||
`statuszeile`, `antwortWeg`, `anmeldungNoetig`, `versuche`, `unterstuetzteMethoden`,
|
||||
`zielIp`, `zielHerkunft`, `zielPort`, `laufzeitMs`).
|
||||
**Achtung, dabei aufgefallen:** der naheliegende Schlüssel `port` war bereits vom
|
||||
SNMP-Werkzeug als „Switch-Port" belegt. Ein SIP-Port unter diesem Namen stünde im
|
||||
Kundendokument falsch beschriftet — deshalb `zielPort`.
|
||||
|
||||
### WLAN-Kanalanalyse: Warnungen werden nicht mehr abgeschnitten
|
||||
|
||||
`netdiagPdfWifiKanal()` kürzte jede Warnung nach 160 Zeichen (`dol_trunc`). Betroffen
|
||||
war genau die Begründung: „… blockiert damit praktisch 2 der 3 störungsfreien Kanäle.
|
||||
In dicht besiedelter Umgebung meist ein Fehle…". Eine Warnung ohne ihren Grund ist im
|
||||
Kundendokument wertlos; MultiCell bricht ohnehin sauber um.
|
||||
|
||||
### Sicherheit: Token in der Adresszeile nur noch für den APK-Download
|
||||
|
||||
`netdiag_api_read_token()` nimmt `?jwt=` nicht mehr allgemein an, sondern nur noch,
|
||||
wenn der Endpunkt es ausdrücklich erlaubt (`netdiag_api_authenticate($db, true)`).
|
||||
Einzige Stelle: `update.php?download=1`.
|
||||
|
||||
Warum überhaupt: ein Token in der URL landet in Zugriffs- und Proxy-Logs und gilt
|
||||
sieben Tage für die **gesamte** Kunden-API. Vorher hätte damit jeder, der ein Log
|
||||
liest, Aufträge, Kunden und Protokolle abrufen können.
|
||||
|
||||
Warum die eine Ausnahme bleibt: die im Feld installierten App-Fassungen bauen die
|
||||
Download-Adresse mit dem Token darin. Wird der Zweig sofort geschlossen, kommt kein
|
||||
altes Gerät mehr an die neue APK — und die neue APK ist genau die, die den Zweig
|
||||
überflüssig macht. **Zu entfernen, sobald der Rollout bestätigt ist** (Hinweis steht
|
||||
im Code an beiden Stellen).
|
||||
|
||||
`pdf.php` nimmt ab sofort ausschließlich den `Authorization: Bearer`-Header.
|
||||
|
||||
|
||||
## 1.2.0 — 2026-08-17
|
||||
|
||||
### Anmeldung nur noch über AWL-Auth — der eigene JWT-Pfad ist weg
|
||||
|
|
|
|||
|
|
@ -198,11 +198,24 @@ function netdiag_api_error($message, $httpstatus = 400)
|
|||
|
||||
|
||||
/**
|
||||
* Token aus Request lesen (Authorization-Header oder ?jwt=).
|
||||
* Token aus Request lesen.
|
||||
*
|
||||
* Regelfall ist ausschliesslich der `Authorization: Bearer`-Header. Ein Token in
|
||||
* der Adresszeile landet in Zugriffs- und Proxy-Logs und im Verlauf jedes
|
||||
* Zwischensystems — bei einem Langzeit-Token (Standard 7 Tage) ist das ein
|
||||
* Nachschluessel zur gesamten Kunden-API.
|
||||
*
|
||||
* Genau eine Ausnahme, bewusst und befristet: der APK-Download in `update.php`.
|
||||
* Die im Feld installierten Fassungen rufen ihn mit `?jwt=` auf. Wer den Zweig
|
||||
* jetzt schon entfernt, sperrt diese Geraete vom Update aus — und die einzige
|
||||
* Quelle der neuen APK ist genau dieser Endpunkt. Deshalb bleibt er, bis Eddy
|
||||
* den Rollout bestaetigt; er gilt dann nur noch fuer eine einzige, lesende
|
||||
* Datei-Auslieferung statt fuer die ganze API.
|
||||
*
|
||||
* @param bool $allowQuery Query-Parameter `?jwt=` zusaetzlich zulassen
|
||||
* @return string Token oder leerer String
|
||||
*/
|
||||
function netdiag_api_read_token()
|
||||
function netdiag_api_read_token($allowQuery = false)
|
||||
{
|
||||
$auth = '';
|
||||
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||
|
|
@ -218,7 +231,7 @@ function netdiag_api_read_token()
|
|||
if (stripos($auth, 'Bearer ') === 0) {
|
||||
return trim(substr($auth, 7));
|
||||
}
|
||||
if (!empty($_GET['jwt'])) {
|
||||
if ($allowQuery && !empty($_GET['jwt'])) {
|
||||
return (string) $_GET['jwt'];
|
||||
}
|
||||
return '';
|
||||
|
|
@ -234,11 +247,12 @@ function netdiag_api_read_token()
|
|||
* sobald alle Geräte einmal neu angemeldet sind (siehe ROADMAP_UMSETZUNG L5).
|
||||
*
|
||||
* @param DoliDB $db Datenbank-Handler
|
||||
* @param bool $allowQuery Token auch aus `?jwt=` annehmen (nur APK-Download)
|
||||
* @return User Geladenes Benutzer-Objekt
|
||||
*/
|
||||
function netdiag_api_authenticate($db)
|
||||
function netdiag_api_authenticate($db, $allowQuery = false)
|
||||
{
|
||||
$token = netdiag_api_read_token();
|
||||
$token = netdiag_api_read_token($allowQuery);
|
||||
if (empty($token)) {
|
||||
netdiag_api_error('Kein Token übermittelt', 401);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@
|
|||
/**
|
||||
* \file netdiag/api/pdf.php
|
||||
* \ingroup netdiag
|
||||
* \brief API-Endpunkt: Protokoll-PDF streamen (GET ?id=&jwt=).
|
||||
* \brief API-Endpunkt: Protokoll-PDF streamen (GET ?id=).
|
||||
*
|
||||
* Authentifizierung ausschliesslich per `Authorization: Bearer`-Header. Ein
|
||||
* Token in der Adresszeile stuende in jedem Zugriffs- und Proxy-Log; der
|
||||
* Aufrufer holt das PDF deshalb per fetch und verarbeitet es als Blob.
|
||||
*/
|
||||
|
||||
require_once __DIR__.'/netdiag_api.lib.php';
|
||||
|
|
|
|||
|
|
@ -36,7 +36,18 @@ netdiag_api_bootstrap();
|
|||
* @var DoliDB $db
|
||||
*/
|
||||
|
||||
$user = netdiag_api_authenticate($db);
|
||||
/*
|
||||
* Einzige Stelle im Modul, die ein Token noch aus der Adresszeile annimmt
|
||||
* (`?jwt=`) — und nur fuer den Datei-Download. Grund: die im Feld installierten
|
||||
* App-Fassungen bauen die Download-Adresse mit dem Token darin. Wird der Zweig
|
||||
* jetzt geschlossen, kommt kein altes Geraet mehr an die neue APK, die genau
|
||||
* diese Abhaengigkeit aufloest. Die Versionsabfrage (ohne ?download=) laeuft
|
||||
* bereits ueber den normalen fetch-Weg der App und braucht ihn nicht.
|
||||
*
|
||||
* Zu entfernen, sobald Eddy den Rollout der Fassung >= 20260819 bestaetigt:
|
||||
* Parameter streichen -> `netdiag_api_authenticate($db)`.
|
||||
*/
|
||||
$user = netdiag_api_authenticate($db, isset($_GET['download']));
|
||||
|
||||
$registry = 'https://git.data-it-solution.de';
|
||||
$owner = 'data-it';
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class modNetDiag extends DolibarrModules
|
|||
$this->editor_url = '';
|
||||
$this->editor_squarred_logo = '';
|
||||
|
||||
$this->version = '1.2.0';
|
||||
$this->version = '1.3.0';
|
||||
|
||||
// Konstanten-Name in llx_const für Modul-Status
|
||||
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
|
||||
|
|
|
|||
|
|
@ -366,6 +366,22 @@ function netdiagKundenfelder()
|
|||
'mitNeuerAdresse' => array('Mit neuer IP-Adresse', ''),
|
||||
'andereOffenePorts' => array('Gleiche Adresse, andere offene Ports', ''),
|
||||
'unveraendert' => array('Unverändert', ''),
|
||||
// SIP-Erreichbarkeit (Telefonie). Der Befund ist die eigentliche Aussage:
|
||||
// „401 Anmeldung verlangt" ist ein ERREICHBARER Anschluss, nicht der
|
||||
// Ausfall, als den ihn eine nackte Ampel erscheinen ließe.
|
||||
'befund' => array('Befund', ''),
|
||||
'statuszeile' => array('Antwort der Gegenstelle', ''),
|
||||
'antwortWeg' => array('Antwort über', ''),
|
||||
'anmeldungNoetig' => array('Anmeldung verlangt', ''),
|
||||
'versuche' => array('Versuche je Transportweg', ''),
|
||||
'unterstuetzteMethoden' => array('Unterstützte SIP-Methoden', ''),
|
||||
'zielIp' => array('Adresse des Ziels', ''),
|
||||
'zielHerkunft' => array('Herkunft des Ziels', ''),
|
||||
// NICHT 'port' — der Schlüssel gehört hier bereits dem SNMP-Werkzeug
|
||||
// („Switch-Port"); ein SIP-Port unter diesem Namen stünde falsch beschriftet
|
||||
// im Kundendokument.
|
||||
'zielPort' => array('Port', ''),
|
||||
'laufzeitMs' => array('Laufzeit', 'ms'),
|
||||
// Netz/DHCP
|
||||
'server' => array('DHCP-Server', ''),
|
||||
'lease' => array('Lease-Dauer', 's'),
|
||||
|
|
@ -661,6 +677,7 @@ function netdiagToolName($tool)
|
|||
'wifitrack' => 'WLAN-Empfang',
|
||||
'dnscheck' => 'DNS-Prüfung',
|
||||
'geraetediff' => 'Geräte-Vergleich',
|
||||
'sipcheck' => 'SIP-Erreichbarkeit',
|
||||
// nicht mehr vorhandene Werkzeuge, aber in Altprotokollen enthalten
|
||||
'dhcpcheck' => 'DHCP-Prüfung',
|
||||
'wifiscan' => 'WLAN-Scan',
|
||||
|
|
|
|||
|
|
@ -224,6 +224,8 @@ function netdiagGeneratePdf($db, $protocol, $outputlangs)
|
|||
netdiagPdfDns($pdf, $m->result);
|
||||
} elseif ($m->tool === 'geraetediff') {
|
||||
netdiagPdfGeraeteDiff($pdf, $m->result);
|
||||
} elseif ($m->tool === 'sipcheck') {
|
||||
netdiagPdfSip($pdf, $m->result);
|
||||
} else {
|
||||
$pdf->MultiCell(180, 5, netdiagPdfFlattenResult($m->result), 1, 'L');
|
||||
}
|
||||
|
|
@ -831,7 +833,15 @@ function netdiagPdfWifiKanal($pdf, $json)
|
|||
|
||||
if (!empty($data['warnungen']) && is_array($data['warnungen'])) {
|
||||
foreach ($data['warnungen'] as $w) {
|
||||
$pdf->MultiCell(180, 5, '! '.dol_trunc(netdiagPdfText($w), 160), 1, 'L');
|
||||
/*
|
||||
* Ungekürzt. Vorher stand hier dol_trunc(..., 160) — und genau die
|
||||
* Begründung fiel weg: „…blockiert damit 2 der 3 störungsfreien
|
||||
* Kanäle. In dicht besiedelter Umgebung meist ein Fehle…". Die
|
||||
* Warnung ohne ihren Grund ist im Kundendokument wertlos, und
|
||||
* MultiCell bricht ohnehin sauber um.
|
||||
*/
|
||||
netdiagPdfEnsureSpace($pdf, 10);
|
||||
$pdf->MultiCell(180, 5, '! '.netdiagPdfText(netdiagKundentext((string) $w)), 1, 'L');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -861,3 +871,114 @@ function netdiagPdfWifiKanal($pdf, $json)
|
|||
$pdf->Cell(25, 5, (string) ($n['rssi'] ?? '').' dBm', 1, 1, 'R');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SIP-Erreichbarkeit ins PDF schreiben.
|
||||
*
|
||||
* Eigener Zweig, weil die entscheidende Aussage sonst untergeht: „401
|
||||
* Unauthorized" sieht in einer flachen Schlüssel/Wert-Liste wie ein Fehler
|
||||
* aus, ist aber der Beweis für einen erreichbaren Anschluss. Deshalb steht
|
||||
* hier der Klartext-Befund zuerst und in eigener Zeile — er ist das, was der
|
||||
* Kunde und der nächste Techniker lesen.
|
||||
*
|
||||
* @param TCPDF $pdf PDF-Objekt (Cursor steht direkt hinter der Titelzeile)
|
||||
* @param string $json result-JSON der Messung
|
||||
* @return void
|
||||
*/
|
||||
function netdiagPdfSip($pdf, $json)
|
||||
{
|
||||
$data = json_decode((string) $json, true);
|
||||
if (!is_array($data)) {
|
||||
$pdf->MultiCell(180, 5, netdiagPdfFlattenResult($json), 1, 'L');
|
||||
return;
|
||||
}
|
||||
|
||||
// Kopfzeile: Ziel, Port, Antwort
|
||||
$sum = array();
|
||||
if (!empty($data['ziel'])) {
|
||||
$teil = 'Ziel: '.$data['ziel'];
|
||||
if (!empty($data['zielIp']) && $data['zielIp'] !== $data['ziel']) {
|
||||
$teil .= ' ('.$data['zielIp'].')';
|
||||
}
|
||||
$sum[] = $teil;
|
||||
}
|
||||
if (!empty($data['zielPort'])) {
|
||||
$sum[] = 'Port '.$data['zielPort'];
|
||||
}
|
||||
if (!empty($data['statuszeile'])) {
|
||||
$sum[] = 'Antwort: '.$data['statuszeile'];
|
||||
} else {
|
||||
$sum[] = 'Antwort: keine';
|
||||
}
|
||||
if (!empty($data['antwortWeg'])) {
|
||||
$sum[] = 'über '.strtoupper((string) $data['antwortWeg']);
|
||||
}
|
||||
if (!empty($sum)) {
|
||||
$pdf->MultiCell(180, 5, netdiagPdfText(implode(' | ', $sum)), 1, 'L');
|
||||
}
|
||||
|
||||
// Der Befund im Klartext — die eigentliche Aussage der Messung
|
||||
if (!empty($data['befund'])) {
|
||||
netdiagPdfEnsureSpace($pdf, 10);
|
||||
$pdf->MultiCell(180, 5, netdiagPdfText(netdiagKundentext((string) $data['befund'])), 1, 'L');
|
||||
}
|
||||
|
||||
// Je Transportweg eine Zeile: erst damit ist belegbar, ob UDP still blieb
|
||||
// und nur TCP antwortete (Firewall) oder ob gar nichts kam.
|
||||
$versuche = (isset($data['versuche']) && is_array($data['versuche'])) ? $data['versuche'] : array();
|
||||
if (!empty($versuche)) {
|
||||
netdiagPdfEnsureSpace($pdf, 10);
|
||||
$pdf->SetFont('', 'B', 8);
|
||||
$pdf->Cell(180, 5, 'Versuche je Transportweg', 1, 1, 'L');
|
||||
$pdf->SetFont('', '', 8);
|
||||
foreach ($versuche as $v) {
|
||||
if (!is_array($v)) {
|
||||
continue;
|
||||
}
|
||||
$zeile = (string) ($v['transport'] ?? '?');
|
||||
if (!empty($v['antwort'])) {
|
||||
$zeile .= ': '.$v['antwort'];
|
||||
} elseif (!empty($v['hinweis'])) {
|
||||
$zeile .= ': '.$v['hinweis'];
|
||||
} else {
|
||||
$zeile .= ': keine Antwort';
|
||||
}
|
||||
if (isset($v['laufzeitMs']) && $v['laufzeitMs'] !== null && $v['laufzeitMs'] !== '') {
|
||||
$zeile .= ' ('.$v['laufzeitMs'].' ms)';
|
||||
}
|
||||
netdiagPdfEnsureSpace($pdf, 5, function ($p) {
|
||||
$p->SetFont('', 'B', 8);
|
||||
$p->Cell(180, 5, 'Versuche je Transportweg — Fortsetzung', 1, 1, 'L');
|
||||
$p->SetFont('', '', 8);
|
||||
});
|
||||
$pdf->MultiCell(180, 5, netdiagPdfText(netdiagKundentext($zeile)), 1, 'L');
|
||||
}
|
||||
}
|
||||
|
||||
// Gegenstelle und beherrschte Methoden — Beleg dafür, WAS dort antwortet
|
||||
$rest = array();
|
||||
if (!empty($data['gegenstelle'])) {
|
||||
$rest[] = 'Gegenstelle: '.$data['gegenstelle'];
|
||||
}
|
||||
if (!empty($data['unterstuetzteMethoden'])) {
|
||||
$rest[] = 'Unterstützte Methoden: '.$data['unterstuetzteMethoden'];
|
||||
}
|
||||
if (!empty($data['zielHerkunft'])) {
|
||||
$rest[] = 'Ziel: '.$data['zielHerkunft'];
|
||||
}
|
||||
if (!empty($rest)) {
|
||||
netdiagPdfEnsureSpace($pdf, 8);
|
||||
$pdf->MultiCell(180, 5, netdiagPdfText(netdiagKundentext(implode(' | ', $rest))), 1, 'L');
|
||||
}
|
||||
|
||||
// Grenze der Aussage zum Schluss — im Kundendokument unverzichtbar:
|
||||
// gemessen ist die Erreichbarkeit, NICHT die Sprachqualität.
|
||||
foreach (array('hinweis', 'verfahren') as $feld) {
|
||||
if (!empty($data[$feld])) {
|
||||
netdiagPdfEnsureSpace($pdf, 8);
|
||||
$pdf->SetFont('', 'I', 7);
|
||||
$pdf->MultiCell(180, 4, netdiagPdfText(netdiagKundentext((string) $data[$feld])), 1, 'L');
|
||||
$pdf->SetFont('', '', 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue