232 Tests, unter anderem: - Slug-Erzeugung, Kollisionsbehandlung und Validierung - URL-Validierung inklusive IDN, Prozentkodierung und abgelehnter Protokolle; Punycode gegengeprueft gegen idn_to_ascii - QR-Erzeugung in allen Groessen, mit Logo und Level-H-Automatik - Weiterleitung gegen eine echte SQLite-Datenbank samt Domain- Aufloesung ueber den Host-Header - Update einer bestehenden Installation auf mehrere Domains - Angriffsseitig: gefaelschte Proxy-Header, Host-Header-Injektion, SVG mit Skripten, XXE und externen Referenzen
90 lines
2.6 KiB
PHP
90 lines
2.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests;
|
|
|
|
use App\Support\CheckToken;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class CheckTokenTest extends TestCase
|
|
{
|
|
private const SECRET = 'a4f1c0d2e3b4a5968778695a4b3c2d1e0f1a2b3c4d5e6f708192a3b4c5d6e7f8';
|
|
|
|
public function testFreshTokenIsAccepted(): void
|
|
{
|
|
$token = CheckToken::create(self::SECRET);
|
|
|
|
self::assertTrue(CheckToken::isValid($token, self::SECRET));
|
|
}
|
|
|
|
public function testTokenOfAnotherInstallationIsRejected(): void
|
|
{
|
|
// Genau das beweist die Pruefung: nur wer das Geheimnis kennt,
|
|
// kann ein gueltiges Token erzeugen.
|
|
$token = CheckToken::create('ein-anderes-geheimnis');
|
|
|
|
self::assertFalse(CheckToken::isValid($token, self::SECRET));
|
|
}
|
|
|
|
public function testExpiredTokenIsRejected(): void
|
|
{
|
|
$now = 1_800_000_000;
|
|
$token = CheckToken::create(self::SECRET, $now);
|
|
|
|
self::assertTrue(CheckToken::isValid($token, self::SECRET, $now + CheckToken::LIFETIME - 1));
|
|
self::assertFalse(CheckToken::isValid($token, self::SECRET, $now + CheckToken::LIFETIME + 1));
|
|
}
|
|
|
|
public function testTokenFromTheFutureIsRejected(): void
|
|
{
|
|
$now = 1_800_000_000;
|
|
$token = CheckToken::create(self::SECRET, $now + 3600);
|
|
|
|
self::assertFalse(CheckToken::isValid($token, self::SECRET, $now));
|
|
}
|
|
|
|
/**
|
|
* @return array<string,array{0:string}>
|
|
*/
|
|
public static function malformedProvider(): array
|
|
{
|
|
return [
|
|
'leer' => [''],
|
|
'ohne Punkt' => ['1800000000'],
|
|
'ohne Signatur' => ['1800000000.'],
|
|
'Text statt Zeitstempel' => ['abc.def'],
|
|
'nur Signatur' => ['.abcdef'],
|
|
'negativer Zeitstempel' => ['-100.abcdef'],
|
|
'zu viele Teile' => ['1800000000.abc.def'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider malformedProvider
|
|
*/
|
|
public function testMalformedTokensAreRejected(string $token): void
|
|
{
|
|
self::assertFalse(CheckToken::isValid($token, self::SECRET));
|
|
}
|
|
|
|
public function testSignatureIsNotGuessable(): void
|
|
{
|
|
$now = 1_800_000_000;
|
|
$valid = CheckToken::create(self::SECRET, $now);
|
|
|
|
// Signatur um ein Zeichen veraendert.
|
|
$tampered = substr($valid, 0, -1) . (str_ends_with($valid, 'a') ? 'b' : 'a');
|
|
|
|
self::assertNotSame($valid, $tampered);
|
|
self::assertFalse(CheckToken::isValid($tampered, self::SECRET, $now));
|
|
}
|
|
|
|
public function testTokenContainsNoSecret(): void
|
|
{
|
|
$token = CheckToken::create(self::SECRET);
|
|
|
|
self::assertStringNotContainsString(self::SECRET, $token);
|
|
}
|
|
}
|