From 0c1d26966538a7c043eaf9d0d9da55078160e512 Mon Sep 17 00:00:00 2001 From: Deploymentcenter Bot Date: Sun, 30 Aug 2026 21:09:38 +0200 Subject: [PATCH] test(e2e): whoami absichern und die gemeldete Fassung festnageln Vier Faelle, die genau die Fehler wiederfinden wuerden, um die es in 2.7.1 ging: - whoami nennt Bindung und Rechte, und zwar dieselben, die provisioniert wurden - whoami gibt den Klartextwert des Tokens nicht heraus. Eine Auskunft ueber ein Token darf das Token nicht weiterreichen - whoami ohne Token antwortet 401 - Health meldet dieselbe Fassung, die changelog.json fuehrt. Das ist der Fall, der #346 nicht wiederkommen laesst: Ein fester Wert in der Konfiguration faellt sonst niemandem auf, weil die Datei gitignored ist Ausserdem: Wird der Shared Key an whoami geschickt, sagt der Endpunkt das jetzt (not_a_token) statt "Token unbekannt". Der Shared Key ist kein Token - er hat weder Bindung noch Rechteliste, und ohne den Hinweis sucht der Aufrufer den Fehler bei seinem Token. Co-Authored-By: Claude Opus 5 --- public/api/tokens/v1/whoami.php | 15 ++++++++++ tests/e2e/suite_kern.py | 53 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/public/api/tokens/v1/whoami.php b/public/api/tokens/v1/whoami.php index 36bf2e5..d4fefb3 100644 --- a/public/api/tokens/v1/whoami.php +++ b/public/api/tokens/v1/whoami.php @@ -22,6 +22,7 @@ declare(strict_types=1); require_once __DIR__ . '/../../../../src/bootstrap.php'; +use Deploymentcenter\Core\Config; use Deploymentcenter\Core\Db; use Deploymentcenter\Core\Http; use Deploymentcenter\Core\TokenManager; @@ -42,6 +43,20 @@ $manager = new TokenManager($db); $token = $manager->validateToken($presented, null); if ($token === null) { + // Der haeufigste Irrtum an dieser Stelle: Es wurde der Shared Key + // geschickt. Der ist kein Token - er hat weder Bindung noch Rechteliste, + // und die Frage "wer bin ich" hat fuer ihn keine Antwort. Ohne diesen + // Hinweis sucht der Aufrufer den Fehler bei seinem Token. + $sharedKey = (string)Config::get('security.shared_key', ''); + if ($sharedKey !== '' && hash_equals($sharedKey, $presented)) { + Http::fail( + 401, + 'not_a_token', + 'Das ist der Shared Key, kein Agenten-Token. Er hat keine Bindung und keine ' + . 'Rechteliste - dieser Endpunkt kann darueber nichts sagen.' + ); + } + Http::fail(401, 'invalid_token', 'Token unbekannt, widerrufen oder abgelaufen.'); } diff --git a/tests/e2e/suite_kern.py b/tests/e2e/suite_kern.py index 5cc4792..64f2955 100644 --- a/tests/e2e/suite_kern.py +++ b/tests/e2e/suite_kern.py @@ -46,6 +46,27 @@ def _(ctx): erwarte(zusatz, "Mit Token kommen keine zusaetzlichen Angaben: %s" % r.kurz(300)) +@test('kern', 'Health meldet die Fassung aus dem Changelog') +def _(ctx): + """ + Die Fassung stand frueher als feste Zeichenkette in config/config.php und + blieb dort auf 2.0.0 stehen, waehrend 2.7.0 lief. Zwei gemeldete Fehler + sind daran fehldiagnostiziert worden ("der Server ist zu alt"). Sie kommt + jetzt aus public/docs/changelog.json - und dieser Fall haelt beides + zusammen. + """ + import json + + pfad = PROJECT_DIR / 'public' / 'docs' / 'changelog.json' + erwartet = json.loads(pfad.read_text(encoding='utf-8'))['current'] + + r = ctx.client.get('/api/health', token=None) + erwarte_status(r, 200) + erwarte(r.feld('version') == erwartet, + "Server meldet %r, changelog.json fuehrt %r" + % (r.feld('version'), erwartet)) + + @test('kern', 'OpenAPI-Beschreibung ist gueltig') def _(ctx): """Die Beschreibung ist der Vertrag mit den SDKs - sie muss ladbar bleiben.""" @@ -221,6 +242,38 @@ def _(ctx): erwarte_fehler(r, http=(401, 403)) +@test('token', 'whoami nennt Bindung und Rechte des eigenen Tokens') +def _(ctx): + """ + Ohne diese Auskunft liesse sich ein Recht nur durch Ausprobieren + feststellen - und das hat Nebenwirkungen: eine Probe auf + bugtracker:manage uebernaehme ein Item, eine auf license:dev stellte + einen Schluessel aus. + """ + r = ctx.client.get('/api/tokens/v1/whoami', token=ctx['sub_token']) + erwarte_erfolg(r) + erwarte(r.feld('type') == 'sub', "type ist %r" % r.feld('type')) + erwarte(sorted(r.feld('scopes', default=[])) == sorted(ctx['sub_scopes']), + "whoami meldet %s, provisioniert wurde %s" + % (r.feld('scopes'), ctx['sub_scopes'])) + erwarte(isinstance(r.feld('is_workbench'), bool), + "is_workbench fehlt oder ist kein Wahrheitswert: %s" % r.kurz(300)) + + +@test('token', 'whoami liefert den Klartextwert nicht aus') +def _(ctx): + """Eine Auskunft ueber ein Token darf das Token nicht weiterreichen.""" + r = ctx.client.get('/api/tokens/v1/whoami', token=ctx['sub_token']) + erwarte(ctx['sub_token'] not in (r.body or ''), + "Der Tokenwert steht in der Antwort: %s" % r.kurz(300)) + + +@test('token', 'whoami ohne Token abgewiesen') +def _(ctx): + r = ctx.client.get('/api/tokens/v1/whoami', token=None) + erwarte_fehler(r, http=401) + + @test('token', 'Provisionierung ohne Master-Token abgewiesen') def _(ctx): r = ctx.client.post('/api/tokens/v1/provision', token=None,