Version 2.8.15: Probe der Strecke, Grund aus _fail, RFB 3.3

This commit is contained in:
alientim committed 2026-09-13 12:04:50 +02:00
1 parent f3b312046f
commit 270ba455a8
5 files changed
+123 -29

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm"
version = "2.8.14"
version = "2.8.15"
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
requires-python = ">=3.11"
dependencies = [
+20
View File
@@ -138,6 +138,26 @@ def init_app(app) -> None: # type: ignore[no-untyped-def]
def screen_socket(ws, device_id: int): # type: ignore[no-untyped-def]
_strecke(ws, device_id)
@sock.route("/ws/probe")
def probe_socket(ws): # type: ignore[no-untyped-def]
"""Sagt "da", und mehr nicht.
Wozu: bricht eine Bildschirmsitzung ab, gibt es zwei sehr verschiedene
Ursachen -- der VNC-Server mag nicht, oder die WebSocket-Verbindung
kommt gar nicht erst durch (ein Proxy zwischen Browser und TESM, der
"Upgrade" nicht weiterreicht, ist der haeufigste Fall). Von aussen
sehen beide gleich aus: ein Abbruch ohne Grund. Diese Probe trennt
sie, ohne dass jemand ein Protokoll lesen muss.
"""
if not current_user.is_authenticated:
ws.close(1008, "Nicht angemeldet.")
return
try:
ws.send("da")
ws.receive(timeout=2)
except Exception: # noqa: BLE001 - der Browser ist weg, das ist keine Stoerung
pass
def _strecke(ws, device_id: int) -> None: # type: ignore[no-untyped-def]
"""Bytes hin, Bytes her -- und sonst nichts.
+22 -1
View File
@@ -207,7 +207,28 @@ def sicherheitsverfahren(host: str, port: int = STANDARD_PORT) -> tuple[str, tup
# Wir antworten mit 3.8 -- der Fassung, die auch der Browser
# spricht. Ein Server, der mehr kann, faellt darauf zurueck und
# nennt trotzdem seine Verfahren.
sock.sendall(b"RFB 003.008" + bytes([10]))
# Antworten wird mit *seiner* Fassung, hoechstens 3.8. Wer einem
# 3.3-Server mit 3.8 antwortet, bekommt keine Liste, sondern eine
# 32-Bit-Zahl -- und liest sie als "keine Verfahren".
eigene = "003.003" if fassung == "003.003" else "003.008"
sock.sendall(b"RFB " + eigene.encode("ascii") + bytes([10]))
if eigene == "003.003":
# RFB 3.3: der Server bestimmt allein und schickt eine
# 32-Bit-Zahl. Das ist kein Sonderfall aus der Vorzeit --
# RealVNC faellt darauf zurueck, sobald keine Anmeldung
# verlangt wird.
rohwert = b""
while len(rohwert) < 4:
stueck = sock.recv(4 - len(rohwert))
if not stueck:
break
rohwert += stueck
if len(rohwert) < 4:
return fassung, ()
bestimmt = int.from_bytes(rohwert, "big")
return fassung, () if bestimmt == 0 else (bestimmt,)
anzahl = sock.recv(1)
if not anzahl:
return fassung, ()
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm-core"
version = "2.8.6"
version = "2.8.7"
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
requires-python = ">=3.11"
dependencies = [
@@ -23,13 +23,38 @@ import RFB from "../vendor/novnc/core/rfb.js";
// der angemeldete Weg der bessere.
const OHNE_ZUGANGSDATEN = new Set([1]);
// Der Eingriff sitzt in einer privaten Methode der mitgelieferten Bibliothek.
// Das ist bewusst hier und nicht in der Bibliothek selbst: eine neue Fassung
// von noVNC laesst sich dann austauschen, ohne eine Aenderung nachzupflegen --
// faellt die Methode weg, greift die Vorauswahl nicht mehr, und die Sitzung
// laeuft wieder wie ohne sie. Kein stiller Bruch.
// Drei Eingriffe in private Methoden der mitgelieferten Bibliothek. Bewusst
// hier und nicht in der Bibliothek selbst: eine neue Fassung von noVNC laesst
// sich dann austauschen, ohne eine Aenderung nachzupflegen -- faellt eine
// Methode weg, greift der jeweilige Eingriff nicht mehr, und die Sitzung
// laeuft wieder wie ohne ihn. Kein stiller Bruch.
const istUnterstuetzt = RFB.prototype._isSupportedSecurityType;
// Den *Grund* eines Fehlschlags kennt nur ``_fail`` -- das Ereignis
// "disconnect" traegt ihn nicht, und der Weg ueber die Protokollausgabe
// funktioniert nicht: die Bibliothek bindet ``console.error`` beim Laden, also
// bevor eine Seite etwas daran aendern koennte. (Genau daran ist der erste
// Versuch gescheitert -- die Meldung blieb leer.)
let letzterGrund = "";
const originalFail = RFB.prototype._fail;
RFB.prototype._fail = function (details) {
letzterGrund = String(details);
return originalFail.call(this, details);
};
// Und der Abschluss der Leitung selbst: der Schliesscode sagt, ob TESM
// abgewiesen hat (1008), ob es ein Serverfehler war (1011) -- oder ob die
// Verbindung nie zustande kam (1006).
let letzterCode = 0;
const originalSocketClose = RFB.prototype._socketClose;
RFB.prototype._socketClose = function (ereignis) {
letzterCode = (ereignis && ereignis.code) || 0;
if (ereignis && ereignis.reason) {
letzterGrund = letzterGrund || String(ereignis.reason);
}
return originalSocketClose.call(this, ereignis);
};
const wurzel = document.getElementById("screen");
if (wurzel) {
starten(wurzel);
@@ -54,32 +79,16 @@ function starten(wurzel) {
);
let rfb = null;
let letzterFehler = "";
function melden(text, art = "") {
zeile.textContent = text;
zeile.className = `screen__status${art ? " screen__status--" + art : ""}`;
}
// noVNC meldet den *Grund* eines Fehlschlags ausschliesslich ins Protokoll
// der Entwicklerkonsole -- das Ereignis "disconnect" trägt ihn nicht. Ohne
// diesen Griff bliebe der Seite nur "Verbindung abgebrochen", und damit
// steht der Anwender genau dort, wo er ohne die ganze Anzeige auch stuende.
// Darum wird die Fehlerausgabe waehrend der Sitzung mitgelesen -- und
// hinterher wieder losgelassen.
const echteAusgabe = console.error.bind(console);
console.error = (...teile) => {
letzterFehler = teile.map((teil) => String(teil)).join(" ");
echteAusgabe(...teile);
};
window.addEventListener("beforeunload", () => {
console.error = echteAusgabe;
});
function grund() {
const teile = [];
if (letzterFehler) {
teile.push(letzterFehler.replace(/^Failed (when|while) \w+: /, ""));
if (letzterGrund) {
teile.push(letzterGrund.replace(/^Failed (when|while) \w+: /, ""));
}
if (rfb && rfb._rfbAuthScheme > 0) {
teile.push(`gewähltes Verfahren: ${rfb._rfbAuthScheme}`);
@@ -87,9 +96,35 @@ function starten(wurzel) {
return teile.join(" · ");
}
// Steht die WebSocket-Strecke zu TESM überhaupt? Zwei sehr verschiedene
// Ursachen sehen von aussen gleich aus: der VNC-Server mag nicht, oder die
// Verbindung kommt nie zustande, weil ein Proxy zwischen Browser und TESM
// "Upgrade" nicht durchreicht. Diese Probe trennt sie.
function streckeTesten() {
return new Promise((fertig) => {
let beantwortet = false;
const pruefling = new WebSocket(`${schema}//${window.location.host}/ws/probe`);
const schliessen = (ergebnis) => {
if (beantwortet) return;
beantwortet = true;
try {
pruefling.close();
} catch (fehler) {
/* schon zu */
}
fertig(ergebnis);
};
pruefling.onmessage = () => schliessen(true);
pruefling.onerror = () => schliessen(false);
pruefling.onclose = () => schliessen(false);
window.setTimeout(() => schliessen(false), 4000);
});
}
function verbinden(zugang) {
melden("Verbindung wird aufgebaut …");
letzterFehler = "";
letzterGrund = "";
letzterCode = 0;
// Vorauswahl nur, wenn wir nichts anzubieten haben: dann zaehlt der Weg,
// der ohne Zugangsdaten auskommt -- sofern der Server ihn anbietet.
@@ -136,10 +171,28 @@ function starten(wurzel) {
melden(
erklaerung
? `Verbindung abgebrochen – ${erklaerung}`
: "Verbindung abgebrochen, ohne dass der Client einen Grund nennt. " +
"Antwortet der VNC-Server noch?",
: "Verbindung abgebrochen. Wird geprüft, woran es liegt …",
"fehler",
);
// 1006 heisst: die Leitung ging zu, ohne dass die Gegenseite einen
// Grund geschickt hat -- typisch, wenn die WebSocket-Verbindung gar
// nicht erst zustande kam. Dann sagt die Probe, wer schweigt.
if (letzterCode === 1006 || !erklaerung) {
streckeTesten().then((steht) => {
melden(
steht
? `${erklaerung || "Verbindung abgebrochen"} – die WebSocket-Strecke zu ` +
"TESM selbst steht (Probe erfolgreich), der Abbruch kommt also von " +
"der VNC-Seite."
: "Die WebSocket-Verbindung zu TESM kommt nicht zustande – auch eine " +
"einfache Probe scheitert. Ein Proxy zwischen Browser und TESM muss " +
"die Kopfzeilen Upgrade und Connection durchreichen (nginx: " +
"location /ws/ mit proxy_set_header Upgrade $http_upgrade). Dasselbe " +
"gilt für die Browser-Konsole.",
"fehler",
);
});
}
}
rfb = null;
});