mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-10-02 15:45:09 -04:00
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.
159 lines
4.5 KiB
PHP
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);
|
|
}
|
|
}
|
|
}
|