Files
opensourcepos/tests/Controllers/LoginTest.php
T
jekkos 9eaa2f34f3 chore: strip advisory IDs from code comments and changelog (#4720)
Per the project's policy of treating security advisory IDs as
secret-like, remove the identifiers embedded in source/test comments
and CHANGELOG entries. Each keeps its human-readable description (and
PR number where present), so traceability is preserved. No logic changes.
2026-09-30 15:49:18 +02:00

159 lines
4.5 KiB
PHP

<?php
namespace Tests\Controllers;
use CodeIgniter\Config\Services;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
/**
* Test suite for the Login controller, including the CI Throttler
* mitigation for brute-force/credential-stuffing.
*/
class LoginTest extends CIUnitTestCase
{
use DatabaseTestTrait;
use FeatureTestTrait;
protected $migrate = true;
protected $migrateOnce = true;
protected $refresh = false;
protected $namespace = null;
private array $usedKeys = [];
protected function setUp(): void
{
parent::setUp();
Services::session()->destroy();
$this->clearThrottleState();
}
protected function tearDown(): void
{
$this->clearThrottleState();
parent::tearDown();
}
private function clearThrottleState(): void
{
$throttler = Services::throttler();
foreach ($this->usedKeys as $key) {
$throttler->remove($key);
}
$this->usedKeys = [];
Services::resetSingle('throttler');
}
private function trackThrottleKeys(string $username = ''): void
{
$this->usedKeys[] = 'login-ip-' . Services::request()->getIPAddress();
if ($username !== '') {
$this->usedKeys[] = 'login-user-' . strtolower($username);
}
}
/**
* Login::index() redirects to 'login' and runs a migration when the
* app's migration state isn't current, bypassing credential checks
* entirely. Skip credential-dependent assertions in that case so this
* test only exercises login logic when the DB is actually current
* (throttling itself is verified independently and runs before this
* branch, so it's unaffected).
*/
private function skipIfMigrationRequired(\CodeIgniter\Test\TestResponse $response): void
{
$redirectUrl = $response->getRedirectUrl();
if ($redirectUrl !== null && str_ends_with(rtrim(strtolower($redirectUrl), '/'), '/login')) {
$this->markTestSkipped('App migration state is not current in this environment; skipping credential-path assertions.');
}
}
public function testValidCredentialsLogsIn(): void
{
$response = $this->post('/login', [
'username' => 'admin',
'password' => 'pointofsale',
]);
$this->trackThrottleKeys('admin');
$this->skipIfMigrationRequired($response);
$response->assertRedirectTo('home');
}
public function testInvalidCredentialsShowsError(): void
{
$response = $this->post('/login', [
'username' => 'admin',
'password' => 'wrongpassword',
]);
$this->trackThrottleKeys('admin');
$this->skipIfMigrationRequired($response);
$response->assertStatus(200);
$response->assertSee(lang('Login.invalid_username_and_password'));
}
public function testSixthFailedAttemptIsThrottled(): void
{
for ($i = 0; $i < 5; $i++) {
$this->post('/login', [
'username' => 'wronguser',
'password' => 'wrongpassword',
]);
$this->trackThrottleKeys('wronguser');
}
$response = $this->post('/login', [
'username' => 'wronguser',
'password' => 'wrongpassword',
]);
$this->trackThrottleKeys('wronguser');
$response->assertStatus(429);
$result = json_decode($response->getJSON(), true);
$this->assertFalse($result['success']);
$this->assertSame(lang('Login.too_many_attempts'), $result['message']);
}
public function testCorrectLoginStillWorksUnderThrottleCapacity(): void
{
for ($i = 0; $i < 3; $i++) {
$this->post('/login', [
'username' => 'admin',
'password' => 'wrongpassword',
]);
$this->trackThrottleKeys('admin');
}
$response = $this->post('/login', [
'username' => 'admin',
'password' => 'pointofsale',
]);
$this->trackThrottleKeys('admin');
$this->skipIfMigrationRequired($response);
$response->assertRedirectTo('home');
}
public function testGetRequestToLoginIsNeverThrottled(): void
{
for ($i = 0; $i < 10; $i++) {
$response = $this->get('/login');
$this->skipIfMigrationRequired($response);
$response->assertStatus(200);
}
}
}