feat(tokens): Master-Tokens nachtraeglich bearbeiten
Bisher erzwang jede Korrektur ein neues Token: ein Schreibfehler im Namen, ein
falsch gewaehlter Identitaetstyp, ein nachtraeglich gebrauchtes Recht. Das ist
nicht nur laestig, sondern gefaehrlich - der neue Wert muss auf alle Maschinen
verteilt werden, und am Ende laeuft das alte weiter, weil eine vergessen wurde.
AENDERBAR ist alles ausser dem Tokenwert selbst, der Nummer, dem Typ und dem
Anlagedatum: Name, Identitaetstyp, freie Identitaet, Projekt, Lizenzschluessel,
Umgebung, Ablaufdatum und die Rechte.
Der Wert bleibt unberuehrt, weil er es muss - er liegt nur als Hash vor und
steht auf fremden Maschinen. Ein "geaenderter" Wert waere ein neues Token mit
alter Nummer. Genau diese Zusage haelt ein eigener Testfall fest: derselbe rohe
Token muss nach der Aenderung weiter funktionieren.
- TokenManager::updateMasterToken() mit zwei Schranken: leerer Name und
leere Rechteliste werden abgewiesen. Ein Token ohne Rechte sieht gueltig
aus und laesst jeden Aufruf mit "unauthorized" scheitern, ohne dass der
Grund sichtbar waere.
- Was sich geaendert hat, geht ins Log. Eine stille Rechteaenderung an einem
Token ist genau das, was man spaeter rekonstruieren will.
- Dialog in der Token-Verwaltung, erreichbar ueber den Stift in der Zeile.
Zeigt Nummer und Hinweis, dass der Wert unveraendert bleibt.
- Haengen Sub-Tokens daran, sagt der Dialog, dass eine Einschraenkung NICHT
rueckwirkend auf sie durchschlaegt - sie haben ihre Rechte beim Ausstellen
bekommen. Wer die Kinder beschneiden will, widerruft und stellt neu aus.
$scopeChoices ist aus dem Anlegeformular in den Datenteil gewandert. Beide
Formulare lesen jetzt dieselbe Liste - eine an zwei Stellen gefuehrte Liste
haette genau den Fehler wiederholt, der eben erst behoben wurde: Die
Supervisor-Rechte fehlten monatelang im Formular, obwohl TokenManager sie kennt.
Testlauf 136 Faelle. Da die Bearbeitung bewusst keine API hat - ein Token soll
seine eigenen Rechte nicht erweitern koennen - fuehrt der Weg dorthin ueber
testctl.php?action=update_master_token, wie schon beim Werkbank-Token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
eeda6c911e
commit
57aa0bc5be
@@ -246,6 +246,90 @@ def _(ctx):
|
||||
erwarte_fehler(r, http=(401, 403))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Token nachtraeglich aendern
|
||||
#
|
||||
# Bis 2.7.0 ging das nicht: Ein Schreibfehler im Namen oder ein nachtraeglich
|
||||
# gebrauchtes Recht erzwang ein komplett neues Token - mit der Folge, dass der
|
||||
# neue Wert auf alle Maschinen verteilt werden muss und am Ende das alte
|
||||
# mitlaeuft, weil eine vergessen wurde.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@test('token', 'Aenderbares Token laesst sich anlegen')
|
||||
def _(ctx):
|
||||
r = ctx.testctl('workbench_token', project_slug='myapp', name='vorher')
|
||||
erwarte_erfolg(r)
|
||||
ctx['edit_token_id'] = r.feld('token_id')
|
||||
ctx['edit_token_raw'] = r.feld('raw_token')
|
||||
|
||||
|
||||
@test('token', 'Name und Rechte lassen sich nachtraeglich aendern')
|
||||
def _(ctx):
|
||||
r = ctx.testctl('update_master_token',
|
||||
token_id=ctx['edit_token_id'],
|
||||
name='nachher',
|
||||
owner_type='dev_agent',
|
||||
project_slug='myapp',
|
||||
environment='development',
|
||||
scopes=['bugtracker:read', 'supervisor:read'])
|
||||
erwarte_erfolg(r)
|
||||
erwarte(r.feld('token', 'name') == 'nachher',
|
||||
'Name nicht uebernommen: %r' % r.feld('token', 'name'))
|
||||
erwarte(r.feld('token', 'environment') == 'development',
|
||||
'Umgebung nicht uebernommen: %r' % r.feld('token', 'environment'))
|
||||
erwarte('supervisor:read' in (r.feld('token', 'scopes') or ''),
|
||||
'Rechte nicht uebernommen: %r' % r.feld('token', 'scopes'))
|
||||
|
||||
|
||||
@test('token', 'Der Tokenwert bleibt bei einer Aenderung derselbe')
|
||||
def _(ctx):
|
||||
"""
|
||||
Die eigentliche Zusage der Funktion. Waere der Wert danach ein anderer,
|
||||
muessten alle Maschinen neu bestueckt werden - dann koennte man auch
|
||||
gleich ein neues Token ausstellen, und die Bearbeitung waere sinnlos.
|
||||
"""
|
||||
r = ctx.client.get('/api/bugtracker/v1/projects', token=ctx['edit_token_raw'])
|
||||
erwarte_erfolg(r)
|
||||
|
||||
|
||||
@test('token', 'Entzogene Rechte greifen sofort')
|
||||
def _(ctx):
|
||||
"""
|
||||
Die Gegenprobe: Der Wert bleibt, die Rechte nicht. Das Token hatte
|
||||
bugtracker:manage und hat es nach der Aenderung nicht mehr.
|
||||
"""
|
||||
r = ctx.client.post('/api/bugtracker/v1/manage',
|
||||
query={'action': 'next'},
|
||||
token=ctx['edit_token_raw'],
|
||||
body={'limit': 1})
|
||||
erwarte_fehler(r, http=(401, 403))
|
||||
|
||||
|
||||
@test('token', 'Aenderung ohne Rechte wird abgewiesen')
|
||||
def _(ctx):
|
||||
"""
|
||||
Ein Token ohne jedes Recht sieht gueltig aus und kann nichts - der
|
||||
Aufrufer bekaeme "unauthorized" ohne erkennbaren Grund.
|
||||
"""
|
||||
r = ctx.testctl('update_master_token', token_id=ctx['edit_token_id'],
|
||||
name='ohne rechte', scopes=[])
|
||||
erwarte_fehler(r, http=400, code='invalid_update')
|
||||
|
||||
|
||||
@test('token', 'Aenderung ohne Namen wird abgewiesen')
|
||||
def _(ctx):
|
||||
r = ctx.testctl('update_master_token', token_id=ctx['edit_token_id'],
|
||||
name=' ', scopes=['bugtracker:read'])
|
||||
erwarte_fehler(r, http=400, code='invalid_update')
|
||||
|
||||
|
||||
@test('token', 'Aenderung an einem fehlenden Token antwortet 404')
|
||||
def _(ctx):
|
||||
r = ctx.testctl('update_master_token', token_id='tok_m_gibtsnicht',
|
||||
name='egal', scopes=['bugtracker:read'])
|
||||
erwarte_fehler(r, http=404, code='not_found')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Allgemeines Verhalten der Schnittstelle
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -235,6 +235,44 @@ switch ($action) {
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Master-Token nachtraeglich aendern.
|
||||
//
|
||||
// Der Weg dorthin ist die Oberflaeche - eine API gibt es dafuer bewusst
|
||||
// nicht, denn ein Token soll seine eigenen Rechte nicht erweitern koennen.
|
||||
// Damit waere die Aenderung aber ungeprueft geblieben, und gerade hier
|
||||
// zaehlt eine Zusage: dass der Tokenwert unveraendert bleibt. Sonst
|
||||
// muessten alle Maschinen neu bestueckt werden, und das Versprechen der
|
||||
// Funktion waere gebrochen.
|
||||
// ------------------------------------------------------------------
|
||||
case 'update_master_token': {
|
||||
$tokenId = (string)(Http::str('token_id') ?? '');
|
||||
if ($tokenId === '') {
|
||||
Http::fail(400, 'missing_token_id', 'token_id wird gebraucht.');
|
||||
}
|
||||
|
||||
$felder = Http::body();
|
||||
unset($felder['action'], $felder['token_id']);
|
||||
|
||||
try {
|
||||
$ok = (new TokenManager($db))->updateMasterToken($tokenId, $felder);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
Http::fail(400, 'invalid_update', $e->getMessage());
|
||||
}
|
||||
|
||||
if (!$ok) {
|
||||
Http::fail(404, 'not_found', sprintf('Master-Token "%s" existiert nicht.', $tokenId));
|
||||
}
|
||||
|
||||
$stmt = $db->prepare('SELECT token_id, name, project_slug, owner_type, owner_identity,
|
||||
scopes, environment, expires_at, type
|
||||
FROM dc_tokens WHERE token_id = :id');
|
||||
$stmt->execute([':id' => $tokenId]);
|
||||
|
||||
Http::ok(['token' => $stmt->fetch() ?: null]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Signierschluessel fuer den Updateservice. Er wird auf der Testinstanz
|
||||
// erzeugt und nie von der Produktion kopiert - ein Testlauf darf mit dem
|
||||
@@ -302,6 +340,6 @@ switch ($action) {
|
||||
default:
|
||||
Http::fail(400, 'unknown_action', sprintf('Unbekannte Aktion "%s".', $action), null, [
|
||||
'actions' => ['status', 'wipe', 'migrate', 'reset', 'workbench_token',
|
||||
'genkey', 'log', 'clearlog'],
|
||||
'update_master_token', 'genkey', 'log', 'clearlog'],
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user