diff --git a/libraries/classes/Common.php b/libraries/classes/Application.php similarity index 88% rename from libraries/classes/Common.php rename to libraries/classes/Application.php index 3ac02f01b1..c35c09a75c 100644 --- a/libraries/classes/Common.php +++ b/libraries/classes/Application.php @@ -48,10 +48,18 @@ use function trigger_error; use const CONFIG_FILE; use const E_USER_ERROR; -final class Common +final class Application { private static ServerRequest|null $request = null; + public static function init(): self + { + /** @var Application $application */ + $application = Core::getContainerBuilder()->get(self::class); + + return $application; + } + /** * Misc stuff and REQUIRED by ALL the scripts. * MUST be included by every script @@ -80,33 +88,28 @@ final class Common * - db connection * - authentication work */ - public static function run(bool $isSetupPage = false): void + public function run(bool $isSetupPage = false): void { - $GLOBALS['lang'] ??= null; - $GLOBALS['theme'] ??= null; - $GLOBALS['urlParams'] ??= null; - $GLOBALS['token_mismatch'] ??= null; + $container = Core::getContainerBuilder(); $request = self::getRequest(); $route = $request->getRoute(); $isMinimumCommon = $isSetupPage || $route === '/import-status' || $route === '/url' || $route === '/messages'; - $container = Core::getContainerBuilder(); - /** @var ErrorHandler $errorHandler */ $errorHandler = $container->get('error_handler'); $GLOBALS['errorHandler'] = $errorHandler; try { - self::checkRequiredPhpExtensions(); + $this->checkRequiredPhpExtensions(); } catch (MissingExtensionException $exception) { - echo self::getGenericError($exception->getMessage()); + echo $this->getGenericError($exception->getMessage()); return; } - self::configurePhpSettings(); + $this->configurePhpSettings(); /** @var Config $config */ $config = $container->get('config'); @@ -115,19 +118,19 @@ final class Common try { $config->loadAndCheck(CONFIG_FILE); } catch (ConfigException $exception) { - echo self::getGenericError($exception->getMessage()); + echo $this->getGenericError($exception->getMessage()); return; } - $request = self::updateUriScheme($config, $request); + $request = $this->updateUriScheme($config, $request); if ($route !== '/messages') { try { // Include session handling after the globals, to prevent overwriting. Session::setUp($config, $errorHandler); } catch (SessionHandlerException $exception) { - echo self::getGenericError($exception->getMessage()); + echo $this->getGenericError($exception->getMessage()); return; } @@ -147,10 +150,10 @@ final class Common $GLOBALS['urlParams'] = []; $container->setParameter('url_params', $GLOBALS['urlParams']); - self::setGotoAndBackGlobals($container, $config); - self::checkTokenRequestParam(); - self::setDatabaseAndTableFromRequest($container, $request); - self::setSQLQueryGlobalFromRequest($container, $request); + $this->setGotoAndBackGlobals($container, $config); + $this->checkTokenRequestParam(); + $this->setDatabaseAndTableFromRequest($container, $request); + $this->setSQLQueryGlobalFromRequest($container, $request); //$_REQUEST['set_theme'] // checked later in this file LABEL_theme_setup //$_REQUEST['server']; // checked later in this file @@ -172,21 +175,21 @@ final class Common $config->checkPermissions(); $config->checkErrors(); } catch (ConfigException $exception) { - echo self::getGenericError($exception->getMessage()); + echo $this->getGenericError($exception->getMessage()); return; } try { - self::checkServerConfiguration(); - self::checkRequest(); + $this->checkServerConfiguration(); + $this->checkRequest(); } catch (RuntimeException $exception) { - echo self::getGenericError($exception->getMessage()); + echo $this->getGenericError($exception->getMessage()); return; } - self::setCurrentServerGlobal($container, $config, $request->getParam('server')); + $this->setCurrentServerGlobal($container, $config, $request->getParam('server')); $GLOBALS['cfg'] = $config->settings; $settings = $config->getSettings(); @@ -206,7 +209,7 @@ final class Common } if ($isSetupPage) { - self::setupPageBootstrap($config); + $this->setupPageBootstrap($config); Routing::callSetupController($request); return; @@ -237,7 +240,7 @@ final class Common try { $authPlugin = $authPluginFactory->create(); } catch (AuthenticationPluginException $exception) { - echo self::getGenericError($exception->getMessage()); + echo $this->getGenericError($exception->getMessage()); return; } @@ -253,7 +256,7 @@ final class Common // phpcs:enable } - self::connectToDatabaseServer($GLOBALS['dbi'], $authPlugin, $currentServer); + $this->connectToDatabaseServer($GLOBALS['dbi'], $authPlugin, $currentServer); $authPlugin->rememberCredentials(); $authPlugin->checkTwoFactor(); @@ -261,7 +264,7 @@ final class Common Logging::logUser($config, $currentServer->user); if ($GLOBALS['dbi']->getVersion() < $settings->mysqlMinVersion['internal']) { - echo self::getGenericError(sprintf( + echo $this->getGenericError(sprintf( __('You should upgrade to %s %s or later.'), 'MySQL', $settings->mysqlMinVersion['human'], @@ -322,7 +325,7 @@ final class Common /** * Checks that required PHP extensions are there. */ - private static function checkRequiredPhpExtensions(): void + private function checkRequiredPhpExtensions(): void { /** * Warning about mbstring. @@ -374,7 +377,7 @@ final class Common /** * Applies changes to PHP configuration. */ - private static function configurePhpSettings(): void + private function configurePhpSettings(): void { /** * Set utf-8 encoding for PHP @@ -397,7 +400,7 @@ final class Common date_default_timezone_set(@date_default_timezone_get()); } - private static function setGotoAndBackGlobals(ContainerInterface $container, Config $config): void + private function setGotoAndBackGlobals(ContainerInterface $container, Config $config): void { $GLOBALS['back'] ??= null; $GLOBALS['urlParams'] ??= null; @@ -442,7 +445,7 @@ final class Common * GET Requests would never have token and therefore checking * mis-match does not make sense. */ - public static function checkTokenRequestParam(): void + public function checkTokenRequestParam(): void { $GLOBALS['token_mismatch'] = true; $GLOBALS['token_provided'] = false; @@ -478,7 +481,7 @@ final class Common Sanitize::removeRequestVars($allowList); } - private static function setDatabaseAndTableFromRequest(ContainerInterface $container, ServerRequest $request): void + private function setDatabaseAndTableFromRequest(ContainerInterface $container, ServerRequest $request): void { $GLOBALS['urlParams'] ??= null; @@ -500,7 +503,7 @@ final class Common /** * Check whether PHP configuration matches our needs. */ - private static function checkServerConfiguration(): void + private function checkServerConfiguration(): void { /** * As we try to handle charsets by ourself, mbstring overloads just @@ -533,7 +536,7 @@ final class Common /** * Checks request and fails with fatal error if something problematic is found */ - private static function checkRequest(): void + private function checkRequest(): void { if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) { throw new RuntimeException(__('GLOBALS overwrite attempt')); @@ -549,7 +552,7 @@ final class Common throw new RuntimeException(__('possible exploit')); } - private static function connectToDatabaseServer( + private function connectToDatabaseServer( DatabaseInterface $dbi, AuthenticationPlugin $auth, Server $currentServer, @@ -589,13 +592,13 @@ final class Common return self::$request; } - private static function setupPageBootstrap(Config $config): void + private function setupPageBootstrap(Config $config): void { // use default error handler restore_error_handler(); // Save current language in a cookie, since it was not set in Common::run(). - $config->setCookie('pma_lang', (string) $GLOBALS['lang']); + $config->setCookie('pma_lang', $GLOBALS['lang']); $config->set('is_setup', true); $GLOBALS['ConfigFile'] = new ConfigFile(); @@ -619,7 +622,7 @@ final class Common ob_start(); } - private static function setSQLQueryGlobalFromRequest(ContainerInterface $container, ServerRequest $request): void + private function setSQLQueryGlobalFromRequest(ContainerInterface $container, ServerRequest $request): void { $sqlQuery = ''; if ($request->isPost()) { @@ -634,7 +637,7 @@ final class Common $container->setParameter('sql_query', $sqlQuery); } - private static function setCurrentServerGlobal( + private function setCurrentServerGlobal( ContainerInterface $container, Config $config, mixed $serverParamFromRequest, @@ -646,7 +649,7 @@ final class Common $container->setParameter('url_params', $GLOBALS['urlParams']); } - private static function getGenericError(string $message): string + private function getGenericError(string $message): string { return (new Template())->render('error/generic', [ 'lang' => $GLOBALS['lang'] ?? 'en', @@ -655,7 +658,7 @@ final class Common ]); } - private static function updateUriScheme(Config $config, ServerRequest $request): ServerRequest + private function updateUriScheme(Config $config, ServerRequest $request): ServerRequest { $uriScheme = $config->isHttps() ? 'https' : 'http'; $uri = $request->getUri(); diff --git a/libraries/classes/DatabaseInterface.php b/libraries/classes/DatabaseInterface.php index f77587ccf6..a1822de576 100644 --- a/libraries/classes/DatabaseInterface.php +++ b/libraries/classes/DatabaseInterface.php @@ -225,7 +225,7 @@ class DatabaseInterface implements DbalInterface sprintf( 'SQL[%s?route=%s]: %0.3f(W:%d,C:%s,L:0x%02X) > %s', basename($_SERVER['SCRIPT_NAME']), - Common::getRequest()->getRoute(), + Application::getRequest()->getRoute(), $this->lastQueryExecutionTime, $this->getWarningCount($connectionType), $cacheAffectedRows ? 'y' : 'n', diff --git a/libraries/classes/DbTableExists.php b/libraries/classes/DbTableExists.php index b141e930a2..1aa9ff79cf 100644 --- a/libraries/classes/DbTableExists.php +++ b/libraries/classes/DbTableExists.php @@ -108,7 +108,7 @@ final class DbTableExists /** @var SqlController $controller */ $controller = Core::getContainerBuilder()->get(SqlController::class); - $controller(Common::getRequest()); + $controller(Application::getRequest()); ResponseRenderer::getInstance()->callExit(); } diff --git a/libraries/classes/Export.php b/libraries/classes/Export.php index f3bd74c1da..3ea5c91e88 100644 --- a/libraries/classes/Export.php +++ b/libraries/classes/Export.php @@ -1067,7 +1067,7 @@ class Export public function showPage(string $exportType): void { $GLOBALS['active_page'] ??= null; - $request = Common::getRequest(); + $request = Application::getRequest(); $container = Core::getContainerBuilder(); if ($exportType === 'server') { $GLOBALS['active_page'] = Url::getFromRoute('/server/export'); diff --git a/libraries/classes/Footer.php b/libraries/classes/Footer.php index 31578c3159..ad70d0b669 100644 --- a/libraries/classes/Footer.php +++ b/libraries/classes/Footer.php @@ -129,7 +129,7 @@ class Footer $GLOBALS['server'] ??= null; $params = []; - $params['route'] = Common::getRequest()->getRoute(); + $params['route'] = Application::getRequest()->getRoute(); if (isset($GLOBALS['db']) && strlen($GLOBALS['db']) > 0) { $params['db'] = $GLOBALS['db']; diff --git a/libraries/classes/Menu.php b/libraries/classes/Menu.php index 4f7a7b4b4e..4824398f4c 100644 --- a/libraries/classes/Menu.php +++ b/libraries/classes/Menu.php @@ -207,7 +207,7 @@ class Menu */ private function getTableTabs(): array { - $route = Common::getRequest()->getRoute(); + $route = Application::getRequest()->getRoute(); $isSystemSchema = Utilities::isSystemSchema($this->db); $tableIsView = $this->dbi->getTable($this->db, $this->table) @@ -319,7 +319,7 @@ class Menu */ private function getDbTabs(): array { - $route = Common::getRequest()->getRoute(); + $route = Application::getRequest()->getRoute(); $isSystemSchema = Utilities::isSystemSchema($this->db); $numTables = count($this->dbi->getTables($this->db)); @@ -434,7 +434,7 @@ class Menu */ private function getServerTabs(): array { - $route = Common::getRequest()->getRoute(); + $route = Application::getRequest()->getRoute(); $isSuperUser = $this->dbi->isSuperUser(); $isCreateOrGrantUser = $this->dbi->isGrantUser() || $this->dbi->isCreateUser(); diff --git a/libraries/classes/Plugins/Auth/AuthenticationCookie.php b/libraries/classes/Plugins/Auth/AuthenticationCookie.php index 51628beefc..c19810ed46 100644 --- a/libraries/classes/Plugins/Auth/AuthenticationCookie.php +++ b/libraries/classes/Plugins/Auth/AuthenticationCookie.php @@ -7,7 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Plugins\Auth; -use PhpMyAdmin\Common; +use PhpMyAdmin\Application; use PhpMyAdmin\Config; use PhpMyAdmin\Core; use PhpMyAdmin\Exceptions\SessionHandlerException; @@ -142,7 +142,7 @@ class AuthenticationCookie extends AuthenticationPlugin } $formParams = []; - $formParams['route'] = Common::getRequest()->getRoute(); + $formParams['route'] = Application::getRequest()->getRoute(); if (strlen($GLOBALS['db'])) { $formParams['db'] = $GLOBALS['db']; @@ -461,7 +461,7 @@ class AuthenticationCookie extends AuthenticationPlugin // any parameters to pass? $urlParams = []; - $urlParams['route'] = Common::getRequest()->getRoute(); + $urlParams['route'] = Application::getRequest()->getRoute(); if (strlen($GLOBALS['db']) > 0) { $urlParams['db'] = $GLOBALS['db']; diff --git a/libraries/classes/Plugins/TwoFactor/WebAuthn.php b/libraries/classes/Plugins/TwoFactor/WebAuthn.php index 6b4fc979c2..e4d0874932 100644 --- a/libraries/classes/Plugins/TwoFactor/WebAuthn.php +++ b/libraries/classes/Plugins/TwoFactor/WebAuthn.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Plugins\TwoFactor; -use PhpMyAdmin\Common; +use PhpMyAdmin\Application; use PhpMyAdmin\Http\ServerRequest; use PhpMyAdmin\Plugins\TwoFactorPlugin; use PhpMyAdmin\ResponseRenderer; @@ -75,7 +75,7 @@ class WebAuthn extends TwoFactorPlugin private function getRequest(): ServerRequest { if ($this->serverRequest === null) { - $this->serverRequest = Common::getRequest(); + $this->serverRequest = Application::getRequest(); } return $this->serverRequest; diff --git a/libraries/classes/Table/Indexes.php b/libraries/classes/Table/Indexes.php index f09263b0f0..b3c0c103b3 100644 --- a/libraries/classes/Table/Indexes.php +++ b/libraries/classes/Table/Indexes.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Table; -use PhpMyAdmin\Common; +use PhpMyAdmin\Application; use PhpMyAdmin\Controllers\Table\StructureController; use PhpMyAdmin\Core; use PhpMyAdmin\DatabaseInterface; @@ -96,7 +96,7 @@ final class Indexes } else { /** @var StructureController $controller */ $controller = Core::getContainerBuilder()->get(StructureController::class); - $controller(Common::getRequest()); + $controller(Application::getRequest()); } } else { $this->response->setRequestStatus(false); diff --git a/libraries/services.php b/libraries/services.php index fe28313e42..bf920494f7 100644 --- a/libraries/services.php +++ b/libraries/services.php @@ -3,6 +3,7 @@ declare(strict_types=1); use PhpMyAdmin\Advisory\Advisor; +use PhpMyAdmin\Application; use PhpMyAdmin\BrowseForeigners; use PhpMyAdmin\CheckUserPrivileges; use PhpMyAdmin\Config; @@ -62,6 +63,7 @@ return [ 'class' => Advisor::class, 'arguments' => ['$dbi' => '@dbi', '$expression' => '@expression_language'], ], + Application::class => ['class' => Application::class], 'browse_foreigners' => [ 'class' => BrowseForeigners::class, 'arguments' => ['@template', '@config'], diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 3ee15dd163..d94ff2d5cc 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -27,6 +27,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + escapeString @@ -207,35 +234,6 @@ $prevErrorHandler - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $collationConnection @@ -13074,6 +13072,14 @@ providerForTestRules + + + assertFalse + assertTrue + assertTrue + assertTrue + + $result @@ -13125,14 +13131,6 @@ $filesInfos - - - assertFalse - assertTrue - assertTrue - assertTrue - - diff --git a/public/index.php b/public/index.php index c65b83d1ba..783bf83545 100644 --- a/public/index.php +++ b/public/index.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use PhpMyAdmin\Common; +use PhpMyAdmin\Application; // phpcs:disable PSR1.Files.SideEffects if (! defined('ROOT_PATH')) { @@ -29,4 +29,4 @@ if (! @is_readable(AUTOLOAD_FILE)) { require AUTOLOAD_FILE; -Common::run(); +Application::init()->run(); diff --git a/public/setup/index.php b/public/setup/index.php index 1a2adccd60..45e7d488d2 100644 --- a/public/setup/index.php +++ b/public/setup/index.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use PhpMyAdmin\Common; +use PhpMyAdmin\Application; // phpcs:disable PSR1.Files.SideEffects if (! defined('ROOT_PATH')) { @@ -29,4 +29,4 @@ if (! @is_readable(AUTOLOAD_FILE)) { require AUTOLOAD_FILE; -Common::run(true); +Application::init()->run(true); diff --git a/test/classes/ApplicationTest.php b/test/classes/ApplicationTest.php new file mode 100644 index 0000000000..55b32538f8 --- /dev/null +++ b/test/classes/ApplicationTest.php @@ -0,0 +1,102 @@ +createMock(ContainerBuilder::class); + $container->expects($this->once())->method('get') + ->with($this->identicalTo(Application::class))->willReturn($application); + $GLOBALS['containerBuilder'] = $container; + $this->assertSame($application, Application::init()); + } + + public function testRunWithConfigError(): void + { + $GLOBALS['errorHandler'] = null; + $errorHandler = $this->createStub(ErrorHandler::class); + + $GLOBALS['config'] = null; + $config = $this->createMock(Config::class); + $config->expects($this->once())->method('loadAndCheck') + ->willThrowException(new ConfigException('Failed to load phpMyAdmin configuration.')); + + $container = $this->createStub(ContainerBuilder::class); + $container->method('get')->willReturnMap([ + ['error_handler', 1, $errorHandler], + ['config', 1, $config], + ]); + $GLOBALS['containerBuilder'] = $container; + + $request = $this->createStub(ServerRequest::class); + (new ReflectionProperty(Application::class, 'request'))->setValue($request); + + $expected = (new Template())->render('error/generic', [ + 'lang' => 'en', + 'dir' => 'ltr', + 'error_message' => 'Failed to load phpMyAdmin configuration.', + ]); + + $application = new Application(); + $application->run(); + + $output = $this->getActualOutputForAssertion(); + $this->assertSame($expected, $output); + $this->assertSame($config, $GLOBALS['config']); + $this->assertSame($errorHandler, $GLOBALS['errorHandler']); + + (new ReflectionProperty(Application::class, 'request'))->setValue(null); + } + + public function testCheckTokenRequestParam(): void + { + $application = new Application(); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $application->checkTokenRequestParam(); + $this->assertTrue($GLOBALS['token_mismatch']); + $this->assertFalse($GLOBALS['token_provided']); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $_POST['test'] = 'test'; + $application->checkTokenRequestParam(); + $this->assertTrue($GLOBALS['token_mismatch']); + $this->assertFalse($GLOBALS['token_provided']); + $this->assertArrayNotHasKey('test', $_POST); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $_POST['token'] = 'token'; + $_POST['test'] = 'test'; + $_SESSION[' PMA_token '] = 'mismatch'; + $application->checkTokenRequestParam(); + $this->assertTrue($GLOBALS['token_mismatch']); + $this->assertTrue($GLOBALS['token_provided']); + $this->assertArrayNotHasKey('test', $_POST); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $_POST['token'] = 'token'; + $_POST['test'] = 'test'; + $_SESSION[' PMA_token '] = 'token'; + $application->checkTokenRequestParam(); + $this->assertFalse($GLOBALS['token_mismatch']); + $this->assertTrue($GLOBALS['token_provided']); + $this->assertArrayHasKey('test', $_POST); + $this->assertEquals('test', $_POST['test']); + } +} diff --git a/test/classes/CommonTest.php b/test/classes/CommonTest.php deleted file mode 100644 index 6d55d810c9..0000000000 --- a/test/classes/CommonTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertTrue($GLOBALS['token_mismatch']); - $this->assertFalse($GLOBALS['token_provided']); - - $_SERVER['REQUEST_METHOD'] = 'POST'; - $_POST['test'] = 'test'; - Common::checkTokenRequestParam(); - $this->assertTrue($GLOBALS['token_mismatch']); - $this->assertFalse($GLOBALS['token_provided']); - $this->assertArrayNotHasKey('test', $_POST); - - $_SERVER['REQUEST_METHOD'] = 'POST'; - $_POST['token'] = 'token'; - $_POST['test'] = 'test'; - $_SESSION[' PMA_token '] = 'mismatch'; - Common::checkTokenRequestParam(); - $this->assertTrue($GLOBALS['token_mismatch']); - $this->assertTrue($GLOBALS['token_provided']); - $this->assertArrayNotHasKey('test', $_POST); - - $_SERVER['REQUEST_METHOD'] = 'POST'; - $_POST['token'] = 'token'; - $_POST['test'] = 'test'; - $_SESSION[' PMA_token '] = 'token'; - Common::checkTokenRequestParam(); - $this->assertFalse($GLOBALS['token_mismatch']); - $this->assertTrue($GLOBALS['token_provided']); - $this->assertArrayHasKey('test', $_POST); - $this->assertEquals('test', $_POST['test']); - } -}