How-To: eine Aktion hinter einen zweiten Faktor stellen
Kontext
Ihr Plugin bietet eine Aktion, die etwas Unumkehrbares tut — ein Geheimnis schreiben, eine Verbindung entfernen, einen Datenbestand löschen — und die Projektrolle allein soll dafür nicht genügen: die Sitzung soll ihren zweiten Faktor in dieser Sitzung bewiesen haben.
Lösungsüberblick
Sie benutzen genau zwei Methoden von helper_plugin_wkidentity_contract:
| Methode | Wofür |
|---|---|
satisfies() | Reine Abfrage. Kein Umleiten, kein Schreiben in die Sitzung. Damit zeichnen Sie den Bildschirm. |
require() | Durchsetzung. Leitet in die Zusatzprüfung um, wenn die Sitzung sie noch erfüllen kann. |
Dazu kommt eine Bauform, die in dieser Suite jedes Paket gleich löst: eine eigene do=-Aktion, die nichts anderes tut, als require() zu rufen — die Step-up-Brücke. Sie ist nötig, weil require() umleitet und eine Umleitung nur in einer Phase funktioniert, in der noch nichts ausgegeben wurde.
Die Aufteilung der Verantwortung ist dabei fest: Ihr Plugin entscheidet, wer was darf. wkidentity beantwortet nur, was diese Sitzung bewiesen hat. Berechtigung und Nachweisgüte bleiben getrennt.
Umsetzung
1. Die Aktion beanspruchen
require() muss aus ACTION_ACT_PREPROCESS gerufen werden, nicht aus TPL_ACT_UNKNOWN. Der Grund ist gemessen und nicht theoretisch: TPL_ACT_UNKNOWN feuert aus tpl_content() heraus, also mitten in einer Seite, die das Template bereits schreibt. Ein send_redirect() von dort leitet nicht um, sondern löst E_WARNING: Cannot modify header information aus und wird übersprungen — und zwar auf genau dem Pfad, der am meisten zählt: der richtige Code wurde angenommen, der Nachweis vermerkt, und die Umleitung scheiterte still.
public function register(EventHandler $controller) { $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'claimStepUp'); $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'renderStepUp'); } public function claimStepUp(Event $event): void { if ($event->data !== 'wkexample_stepup') { return; } $event->preventDefault(); $event->stopPropagation(); $this->refusal = $this->decide(); // die ganze Entscheidung, siehe unten }
2. Die Entscheidung treffen
/** Kategorie => geforderte Nachweisgüte. */ private const CATEGORIES = [ 'secret-write' => 'MFA_ANY', 'remove' => 'MFA_ANY', ]; /** * @return string|null Sprachschlüssel einer Absage, oder null, wenn die * Anfrage bereits umgeleitet wurde. */ private function decide(): ?string { global $INPUT, $ID; $category = $INPUT->str('category'); if (!isset(self::CATEGORIES[$category])) { return 'stepup_bad_category'; } $identity = plugin_load('helper', 'wkidentity_contract'); if ($identity === null) { // wkidentity ist nicht installiert. Es gibt nichts zu beweisen; die // Aktion läuft unter Ihrer eigenen Rollenprüfung weiter. send_redirect($this->backUrl()); return null; } $scope = ['type' => 'adminAction', 'id' => 'wkexample:' . $category]; $target = ['type' => 'page', 'id' => (string) $ID, 'act' => 'wkexample']; if ($identity->require(self::CATEGORIES[$category], $scope, $target)) { // Schon erfüllt — nichts ist passiert, zurück an die Arbeit. send_redirect($this->backUrl()); return null; } // Hier steht nur, wer NIE erfüllen kann: kein passender Faktor hinterlegt. // Der Fall "noch nicht erfüllt, aber erfüllbar" kommt nie hier an — // require() hat dann bereits umgeleitet. return 'stepup_cannot'; }
3. Die ausführende Stelle noch einmal prüfen
Die Brücke ist der Weg zum Nachweis, nicht der Nachweis selbst. Die Stelle, die tatsächlich schreibt, fragt erneut — mit satisfies(), also ohne Nebenwirkung:
private function mayWriteSecret(string $category): bool { if (!$this->userHoldsRole('maintainer')) { // Ihre eigene Berechtigung, zuerst return false; } $identity = plugin_load('helper', 'wkidentity_contract'); if ($identity === null) { return true; // kein wkidentity: keine zusätzliche Forderung } return $identity->satisfies('MFA_ANY', ['type' => 'adminAction', 'id' => 'wkexample:' . $category]); }
4. Der Sackgasse einen Ausweg geben
Ein Konto ohne hinterlegten Faktor bleibt sonst stehen. wkrequest zeichnet genau diese Absage samt Auswegen — Faktor einrichten, ersatzweise Antrag stellen:
private function renderRefused(): void { $req = plugin_load('helper', 'wkrequest'); if ($req !== null) { echo $req->stepUpPanel([ 'requirement' => 'MFA_ANY', 'origin' => 'wkexample:secret-write', 'back' => $this->backUrl(), ]); return; } // wkrequest nicht installiert: eigene, schlichtere Absage. echo '<p>' . hsc($this->getLang('stepup_cannot')) . '</p>'; }
Anmerkungen
require()gibt bei Erfolgtruezurück und kehrt bei einer Umleitung nicht zurück. Einfalseheißt deshalb genau eine von zwei Sachen: die Forderung lauteteAUTHENTICATED(die wird hier nie hochgestuft — eine anonyme Sitzung anzumelden ist die Aufgabe von DokuWikis eigener ACL-Verweigerung), oder das Konto hat keinen Faktor, der die Forderung je erfüllen könnte. Behandeln Siefalseniemals als „abgelehnt, bitte erneut versuchen„.- Nach der Zusatzprüfung wird die Anfrage neu gestellt, nicht fortgesetzt. Es gibt keinen wiederaufgenommenen Aufruf, keinen gespeicherten Stapel. Ihr Code läuft von oben.
returnTargetist keine URL und nimmt keine an. Zulässig sind eine bereinigte Seiten-ID mittypepage(wahlweise mitact, einem Wort aus Kleinbuchstaben), sowietypeworkbenchmit einer bestehenden Contract-v1-Beitragskennung. Alles andere fällt auf$conf['start']zurück. Das ist beabsichtigt: das Paket, das die Authentifizierung besitzt, soll keine Adresse eines Fremden auswerten müssen.- Richtlinie kann Ihre Forderung erhöhen, nie senken. Die tatsächlich geprüfte Güte ist die strengere aus Ihrer Angabe und der für den Geltungsbereich hinterlegten Richtlinie. Eine Forderung ist eine Untergrenze, keine Obergrenze.
MFA_ANYist nur eine Forderung, nie ein erreichter Stand. Ein Konto hat genau ein hinterlegtes Verfahren; eine Sitzung, die überhaupt etwas bewiesen hat, hat immer ein bestimmtes bewiesen (TOTPoderMAIL_OTP).- Hinterlegt ist nicht bewiesen.
isEnrolled()ist eine Eigenschaft des Kontos ohne Zeitbezug.currentAssurance()ist eine Eigenschaft dieser Sitzung mit Ablaufzeitpunkt. Fragen Sie nie das eine, wenn Sie das andere meinen. - Eine defekte Richtliniendatei kostet nur den Beitrag der Richtlinie. Ihre eigene ausdrückliche Forderung bleibt unberührt und wird weiterhin durchgesetzt.
- Prüfen Sie die Fähigkeit, nicht die Klasse.
supports('identity.stepup')beantwortet die Frage, die Sie wirklich haben;class_exists()beantwortet eine andere.
Verwandte Themen
- API-Referenz — alle fünf Methoden des Identity Contract, Fähigkeitsnamen, Fehlerverhalten, Versionierungsregel; dazu die Tafel für die Sackgasse.
lib/plugins/wkidentity/docs/contract-v1.md— der Contract im Wortlaut.- Gebaute Vorbilder im Baum:
lib/plugins/wkdoado/StepUpBridge.php,lib/plugins/wkblog/StepUpBridge.php. - Fehlerbehebung — was zu tun ist, wenn die Umleitung auf der Startseite endet.
- Übersicht — der Einstieg mit der Routing-Karte.