178 lines
5.9 KiB
PHP
178 lines
5.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Auth;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use App\Auth\Csrf;
|
|
use App\Auth\PasswordHasher;
|
|
use App\Auth\PasswordResetService;
|
|
use App\Auth\SessionManager;
|
|
use App\Controllers\PasswordResetController;
|
|
use App\Core\Config;
|
|
use App\Core\Database;
|
|
use App\Core\Request;
|
|
use App\Tests\Support\FakeDatabase;
|
|
use App\Tests\Support\SpyMailer;
|
|
|
|
/**
|
|
* Sous-classe de test : injecte session test, FakeDatabase et SpyMailer dans le
|
|
* controleur de reinitialisation.
|
|
*/
|
|
final class TestPasswordResetController extends PasswordResetController
|
|
{
|
|
public function __construct(
|
|
Request $request,
|
|
Config $config,
|
|
Database $database,
|
|
private readonly SessionManager $testSession,
|
|
private readonly FakeDatabase $fakeDb,
|
|
private readonly SpyMailer $spyMailer,
|
|
) {
|
|
parent::__construct($request, $config, $database);
|
|
}
|
|
|
|
protected function sessionManager(): SessionManager
|
|
{
|
|
return $this->testSession;
|
|
}
|
|
|
|
protected function resetService(): PasswordResetService
|
|
{
|
|
return new PasswordResetService($this->fakeDb, $this->config, new PasswordHasher($this->config), $this->spyMailer);
|
|
}
|
|
}
|
|
|
|
final class PasswordResetControllerTest extends TestCase
|
|
{
|
|
/** @var list<string> */
|
|
private array $touchedKeys = [];
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->setEnv('PASSWORD_RESET_TTL', '3600');
|
|
$this->setEnv('APP_URL_ADMIN', 'https://admin.wakdo.test');
|
|
$this->setEnv('ARGON2_MEMORY_COST', '1024');
|
|
$this->setEnv('ARGON2_TIME_COST', '1');
|
|
$this->setEnv('ARGON2_THREADS', '1');
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
foreach ($this->touchedKeys as $key) {
|
|
putenv($key);
|
|
}
|
|
$this->touchedKeys = [];
|
|
}
|
|
|
|
private function setEnv(string $key, string $value): void
|
|
{
|
|
$this->touchedKeys[] = $key;
|
|
putenv($key . '=' . $value);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string> $form
|
|
*/
|
|
private function post(array $form, string $path): Request
|
|
{
|
|
return new Request(
|
|
'POST',
|
|
$path,
|
|
[],
|
|
['content-type' => 'application/x-www-form-urlencoded'],
|
|
http_build_query($form),
|
|
'203.0.113.5',
|
|
);
|
|
}
|
|
|
|
private function controller(
|
|
Request $request,
|
|
SessionManager $session,
|
|
FakeDatabase $db,
|
|
SpyMailer $mailer,
|
|
): TestPasswordResetController {
|
|
return new TestPasswordResetController($request, new Config(), new Database(new Config()), $session, $db, $mailer);
|
|
}
|
|
|
|
public function testShowRequestRendersCsrfField(): void
|
|
{
|
|
$session = new SessionManager(new Config(), true);
|
|
$request = new Request('GET', '/forgot_password', [], [], '', '203.0.113.5');
|
|
|
|
$response = $this->controller($request, $session, new FakeDatabase(), new SpyMailer())->showRequest();
|
|
|
|
self::assertSame(200, $response->status());
|
|
self::assertStringContainsString('name="_csrf"', $response->body());
|
|
}
|
|
|
|
public function testSubmitRequestRejectsInvalidCsrf(): void
|
|
{
|
|
$session = new SessionManager(new Config(), true);
|
|
Csrf::token($session);
|
|
$mailer = new SpyMailer();
|
|
|
|
$request = $this->post(['_csrf' => 'wrong', 'email' => 'admin@wakdo.local'], '/forgot_password');
|
|
$response = $this->controller($request, $session, new FakeDatabase(), $mailer)->submitRequest();
|
|
|
|
self::assertSame(403, $response->status());
|
|
self::assertSame([], $mailer->sent);
|
|
}
|
|
|
|
public function testSubmitRequestUnknownEmailIsNeutralAndSilent(): void
|
|
{
|
|
$session = new SessionManager(new Config(), true);
|
|
$token = Csrf::token($session);
|
|
$db = new FakeDatabase();
|
|
$db->emailLookupRow = null;
|
|
$mailer = new SpyMailer();
|
|
|
|
$request = $this->post(['_csrf' => $token, 'email' => 'ghost@wakdo.local'], '/forgot_password');
|
|
$response = $this->controller($request, $session, $db, $mailer)->submitRequest();
|
|
|
|
self::assertSame(200, $response->status());
|
|
self::assertStringContainsString('Si un compte', $response->body());
|
|
self::assertSame([], $mailer->sent);
|
|
// Anti-enumeration : un leurre (UPDATE no-op sur id = 0) aligne le profil
|
|
// d'ecritures sur le chemin email-connu ; rien n'est persiste.
|
|
self::assertCount(1, $db->writes);
|
|
self::assertStringContainsString('WHERE id = 0', $db->writes[0]['sql']);
|
|
}
|
|
|
|
public function testSubmitConfirmPasswordMismatchRendersError(): void
|
|
{
|
|
$session = new SessionManager(new Config(), true);
|
|
$token = Csrf::token($session);
|
|
|
|
$request = $this->post([
|
|
'_csrf' => $token,
|
|
'token' => 'raw-token',
|
|
'password' => 'longenough1',
|
|
'password_confirm' => 'different01',
|
|
], '/reset_password');
|
|
$response = $this->controller($request, $session, new FakeDatabase(), new SpyMailer())->submitConfirm();
|
|
|
|
self::assertSame(200, $response->status());
|
|
self::assertStringContainsString('ne correspondent pas', $response->body());
|
|
}
|
|
|
|
public function testSubmitConfirmValidTokenRedirectsToLogin(): void
|
|
{
|
|
$session = new SessionManager(new Config(), true);
|
|
$token = Csrf::token($session);
|
|
$db = new FakeDatabase();
|
|
$db->resetUserRow = ['id' => 7, 'role_id' => 3, 'password_reset_token_hash' => hash('sha256', 'raw-token')];
|
|
|
|
$request = $this->post([
|
|
'_csrf' => $token,
|
|
'token' => 'raw-token',
|
|
'password' => 'brandnewpassword',
|
|
'password_confirm' => 'brandnewpassword',
|
|
], '/reset_password');
|
|
$response = $this->controller($request, $session, $db, new SpyMailer())->submitConfirm();
|
|
|
|
self::assertSame(302, $response->status());
|
|
self::assertSame('/login?reset=ok', $response->header('Location'));
|
|
}
|
|
}
|