Fix 2FA not getting checked

Signed-off-by: Maurício Meneghini Fauth <mauricio@mfauth.net>
This commit is contained in:
Maurício Meneghini Fauth 2024-08-25 17:13:53 -03:00
parent fdfe7a92e9
commit 7818399700
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
5 changed files with 124 additions and 15 deletions

View File

@ -10002,11 +10002,6 @@
<code><![CDATA[$_POST['u2f_registration_response']]]></code>
</PossiblyInvalidCast>
</file>
<file src="src/Plugins/TwoFactor/Simple.php">
<UnusedClass>
<code><![CDATA[Simple]]></code>
</UnusedClass>
</file>
<file src="src/Plugins/TwoFactorPlugin.php">
<DeprecatedMethod>
<code><![CDATA[Config::getInstance()]]></code>

View File

@ -26,7 +26,7 @@ class Simple extends TwoFactorPlugin
*/
public function check(ServerRequest $request): bool
{
return isset($_POST['2fa_confirm']);
return $request->hasBodyParam('2fa_confirm');
}
/**

View File

@ -204,7 +204,11 @@ class TwoFactor
return $this->backend->check($request);
}
if (! isset($_SESSION['two_factor_check']) || ! is_bool($_SESSION['two_factor_check'])) {
if (
! isset($_SESSION['two_factor_check'])
|| ! is_bool($_SESSION['two_factor_check'])
|| ! $_SESSION['two_factor_check']
) {
$_SESSION['two_factor_check'] = $this->backend->check($request);
}

View File

@ -4,6 +4,9 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Plugins;
use PhpMyAdmin\Config;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\ConfigStorage\RelationParameters;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Exceptions\AuthenticationFailure;
use PhpMyAdmin\Exceptions\ExitException;
@ -20,12 +23,21 @@ final class AuthenticationPluginTest extends AbstractTestCase
{
public function testCheckTwoFactor(): void
{
/** @psalm-suppress DeprecatedMethod */
$config = Config::getInstance();
/** @psalm-suppress InaccessibleProperty */
$config->config->debug->simple2fa = true;
$GLOBALS['lang'] = 'en';
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addResult('SHOW TABLES FROM `phpmyadmin`;', [['pma__userconfig'], ['Tables_in_phpmyadmin']]);
$dbiDummy->addSelectDb('phpmyadmin');
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
$dbiDummy->addResult(
// phpcs:ignore Generic.Files.LineLength.TooLong
"SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts FROM `db_pma`.`pma__userconfig` WHERE `username` = 'test_user'",
[['{"2fa":{"backend":"simple","settings":[]}}', '1724620722']],
['config_data', 'ts'],
);
$dbiDummy->addResult('SELECT CURRENT_USER();', [['test_user@localhost']]);
DatabaseInterface::$instance = $this->createDatabaseInterface($dbiDummy);
$object = new class extends AuthenticationPlugin {
public function showLoginForm(): Response|null
@ -46,6 +58,13 @@ final class AuthenticationPluginTest extends AbstractTestCase
$_SESSION['two_factor_check'] = false;
$relationParameters = RelationParameters::fromArray([
'user' => 'test_user',
'db' => 'db_pma',
'userconfigwork' => true,
'userconfig' => 'pma__userconfig',
]);
(new ReflectionProperty(Relation::class, 'cache'))->setValue(null, $relationParameters);
(new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null);
$responseRenderer = ResponseRenderer::getInstance();
$responseRenderer->setAjax(false);
@ -60,5 +79,67 @@ final class AuthenticationPluginTest extends AbstractTestCase
'You have enabled two factor authentication, please confirm your login.',
(string) $response->getBody(),
);
$dbiDummy->assertAllQueriesConsumed();
$dbiDummy->assertAllSelectsConsumed();
}
public function testCheckTwoFactorConfirmation(): void
{
/** @psalm-suppress DeprecatedMethod */
$config = Config::getInstance();
/** @psalm-suppress InaccessibleProperty */
$config->config->debug->simple2fa = true;
$GLOBALS['lang'] = 'en';
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addResult(
// phpcs:ignore Generic.Files.LineLength.TooLong
"SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts FROM `db_pma`.`pma__userconfig` WHERE `username` = 'test_user'",
[['{"2fa":{"backend":"simple","settings":[]}}', '1724620722']],
['config_data', 'ts'],
);
DatabaseInterface::$instance = $this->createDatabaseInterface($dbiDummy);
$object = new class extends AuthenticationPlugin {
public function showLoginForm(): Response|null
{
return null;
}
public function readCredentials(): bool
{
return false;
}
public function showFailure(AuthenticationFailure $failure): Response
{
throw new ExitException();
}
};
$_SESSION['two_factor_check'] = false;
$relationParameters = RelationParameters::fromArray([
'user' => 'test_user',
'db' => 'db_pma',
'userconfigwork' => true,
'userconfig' => 'pma__userconfig',
]);
(new ReflectionProperty(Relation::class, 'cache'))->setValue(null, $relationParameters);
(new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null);
$responseRenderer = ResponseRenderer::getInstance();
$responseRenderer->setAjax(false);
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withParsedBody(['2fa_confirm' => '1']);
$object->user = 'test_user';
$response = $object->checkTwoFactor($request);
self::assertNull($response);
$dbiDummy->assertAllQueriesConsumed();
$dbiDummy->assertAllSelectsConsumed();
}
}

View File

@ -11,8 +11,12 @@ use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\ConfigStorage\RelationParameters;
use PhpMyAdmin\Current;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Http\Factory\ServerRequestFactory;
use PhpMyAdmin\Http\ServerRequest;
use PhpMyAdmin\Plugins\TwoFactor\Application;
use PhpMyAdmin\Plugins\TwoFactor\Invalid;
use PhpMyAdmin\Plugins\TwoFactor\Key;
use PhpMyAdmin\Plugins\TwoFactor\Simple;
use PhpMyAdmin\Tests\Stubs\DbiDummy;
use PhpMyAdmin\TwoFactor;
use PHPUnit\Framework\Attributes\CoversClass;
@ -29,6 +33,10 @@ use function str_replace;
use const JSON_UNESCAPED_SLASHES;
#[CoversClass(TwoFactor::class)]
#[CoversClass(Application::class)]
#[CoversClass(Invalid::class)]
#[CoversClass(Key::class)]
#[CoversClass(Simple::class)]
class TwoFactorTest extends AbstractTestCase
{
protected DatabaseInterface $dbi;
@ -161,6 +169,9 @@ class TwoFactorTest extends AbstractTestCase
self::assertTrue($object->check($request));
self::assertSame('', $object->render($request));
$_SESSION['two_factor_check'] = false;
self::assertTrue($object->check($request));
$this->dummyDbi->assertAllQueriesConsumed();
$this->loadResultForConfig(['type' => 'db']);
@ -170,9 +181,24 @@ class TwoFactorTest extends AbstractTestCase
self::assertSame('', $object->setup($request));
}
public function testInvalidBackend(): void
{
$object = $this->getTwoFactorAndLoadConfig('user', ['type' => 'db', 'backend' => 'unknown-backend']);
$backend = $object->getBackend();
self::assertSame('invalid', $backend::$id);
self::assertSame('Invalid two-factor authentication', $backend::getName());
self::assertSame('Error fallback only!', $backend::getDescription());
$request = ServerRequestFactory::create()->createServerRequest('POST', 'https://example.com/');
self::assertFalse($object->check($request, true));
self::assertStringContainsString(
'The configured two factor authentication is not available, please install missing dependencies.',
$object->render($request),
);
self::assertSame('', $object->setup($request));
}
public function testSimple(): void
{
$request = new ServerRequest(self::createStub(ServerRequestInterface::class));
$config = Config::getInstance();
$config->config->debug->simple2fa = true;
$object = $this->getTwoFactorAndLoadConfig('user', ['type' => 'db', 'backend' => 'simple']);
@ -180,12 +206,15 @@ class TwoFactorTest extends AbstractTestCase
self::assertSame('simple', $backend::$id);
$config->config->debug->simple2fa = false;
unset($_POST['2fa_confirm']);
$serverRequestFactory = ServerRequestFactory::create();
$request = $serverRequestFactory->createServerRequest('POST', 'https://example.com/');
self::assertFalse($object->check($request, true));
$_POST['2fa_confirm'] = 1;
$request = $serverRequestFactory->createServerRequest('POST', 'https://example.com/')
->withParsedBody(['2fa_confirm' => '1']);
self::assertTrue($object->check($request, true));
unset($_POST['2fa_confirm']);
$request = $serverRequestFactory->createServerRequest('POST', 'https://example.com/');
/* Test rendering */
self::assertNotEquals('', $object->render($request));