Sie befinden sich hier: start » de » Interne Dokumentation » DokuWiki-Erweiterungen (WvdS) » Erweiterungspunkte der WvdS-DW-Suite » How-To: eine Aktion hinter einen zweiten Faktor stellen

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 Erfolg true zurück und kehrt bei einer Umleitung nicht zurück. Ein false heißt deshalb genau eine von zwei Sachen: die Forderung lautete AUTHENTICATED (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 Sie false niemals 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.
  • returnTarget ist keine URL und nimmt keine an. Zulässig sind eine bereinigte Seiten-ID mit type page (wahlweise mit act, einem Wort aus Kleinbuchstaben), sowie type workbench mit 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_ANY ist 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 (TOTP oder MAIL_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.
de/wiki/dwe/sdk/howto.txt · Zuletzt geändert: von 0.0.0.0