OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

VaultExtensionWriteTest.php

4 KB
<?php

declare(strict_types=1);

use OpenNIT\Vault\TotpGenerator;

/**
 * Hält fest, was die Erweiterungs-Schnittstelle beim Schreiben zusichert:
 * Team-Einträge lassen sich mit dem Token bearbeiten, die Liste nennt das
 * Schreibrecht je Eintrag, und ein eingegebenes 2FA-Secret wird in eine
 * eindeutige Ablageform gebracht oder klar abgewiesen.
 */
return static function (): void {

    $wurzel = dirname(__DIR__);
    $quelle = (string)file_get_contents($wurzel . '/src/Controllers/VaultApiController.php');

    // ----------------------------------------------------------------
    // Schreibzugriff auf Team-Einträge ohne Web-Sitzung
    // ----------------------------------------------------------------

    test('Der Team-Schlüssel für Schreibzugriffe stammt aus dem Token, nicht aus der Web-Sitzung', function () use ($quelle) {
        // Bearer-Aufrufe der Erweiterung führen keine Web-Sitzung; ein Team-
        // Schlüssel aus der Sitzung wäre dort immer leer und jeder Team-Eintrag
        // fälschlich schreibgeschützt.
        assertSame(0, preg_match_all('/getTeamVaultKey\(/', $quelle));
        assertTrue(str_contains($quelle, 'decryptTeamVaultKeys($userId, $vmk)[$teamId]'));
    });

    test('Die Eintragsliste nennt das Schreibrecht je Eintrag', function () use ($quelle) {
        // Persönliche Einträge, Team-Einträge und die Zielliste nennen es.
        assertTrue(preg_match_all("/'can_write'\s*=>/", $quelle) >= 3);
    });

    test('Das Ablaufdatum übersteht ein Bearbeiten aus der Erweiterung', function () use ($quelle) {
        assertTrue(str_contains($quelle, "'expires_at'     => \$expiresAt"));
    });

    // ----------------------------------------------------------------
    // 2FA-Secret aus der Erweiterung
    // ----------------------------------------------------------------

    test('Ein Base32-Secret wird bereinigt und in Großbuchstaben abgelegt', function () {
        assertSame('JBSWY3DPEHPK3PXP', TotpGenerator::normalizeSecret('jbsw y3dp-ehpk 3pxp'));
        assertSame('JBSWY3DPEHPK3PXP', TotpGenerator::normalizeSecret("  JBSWY3DPEHPK3PXP====\n"));
    });

    test('Ein otpauth-Link liefert sein Secret', function () {
        $link = 'otpauth://totp/OpenNIT:max%40example.org?secret=jbswy3dpehpk3pxp&issuer=OpenNIT&period=30&digits=6&algorithm=SHA1';
        assertSame('JBSWY3DPEHPK3PXP', TotpGenerator::normalizeSecret($link));
        assertSame('JBSWY3DPEHPK3PXP', TotpGenerator::normalizeSecret('OTPAUTH://totp/Dienst?secret=JBSWY3DPEHPK3PXP'));
    });

    test('Leere Eingabe bleibt leer', function () {
        assertSame('', TotpGenerator::normalizeSecret(''));
        assertSame('', TotpGenerator::normalizeSecret("   \t"));
    });

    test('Unbrauchbare Eingaben werden abgewiesen', function () {
        // Zeichen außerhalb des Base32-Alphabets (0, 1, 8, 9, Umlaute)
        assertSame(null, TotpGenerator::normalizeSecret('JBSWY3DP0189'));
        assertSame(null, TotpGenerator::normalizeSecret('geheimß'));
        // otpauth ohne Secret, für HOTP oder mit Parametern, die der Generator nicht rechnet
        assertSame(null, TotpGenerator::normalizeSecret('otpauth://totp/Dienst?issuer=X'));
        assertSame(null, TotpGenerator::normalizeSecret('otpauth://hotp/Dienst?secret=JBSWY3DPEHPK3PXP'));
        assertSame(null, TotpGenerator::normalizeSecret('otpauth://totp/Dienst?secret=JBSWY3DPEHPK3PXP&algorithm=SHA256'));
        assertSame(null, TotpGenerator::normalizeSecret('otpauth://totp/Dienst?secret=JBSWY3DPEHPK3PXP&digits=8'));
        assertSame(null, TotpGenerator::normalizeSecret('otpauth://totp/Dienst?secret=JBSWY3DPEHPK3PXP&period=60'));
    });

    test('Das bereinigte Secret erzeugt denselben Code wie die Rohform', function () {
        assertSame(
            TotpGenerator::generate('JBSWY3DPEHPK3PXP'),
            TotpGenerator::generate((string)TotpGenerator::normalizeSecret('jbsw y3dp ehpk 3pxp'))
        );
    });
};