From 9e7890e566920b831fa777f90cdb00097231d397 Mon Sep 17 00:00:00 2001 From: "Kristijan \\\"Fremen\\\" Velkovski" Date: Fri, 23 Feb 2024 23:24:25 +0000 Subject: [PATCH 1/3] Translated using Weblate (Macedonian) Currently translated at 28.5% (979 of 3427 strings) [ci skip] Translation: phpMyAdmin/5.2 Translate-URL: https://hosted.weblate.org/projects/phpmyadmin/5-2/mk/ Signed-off-by: "Kristijan \"Fremen\" Velkovski" --- po/mk.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/mk.po b/po/mk.po index 4b79b61c7e..0296dce133 100644 --- a/po/mk.po +++ b/po/mk.po @@ -4,7 +4,7 @@ msgstr "" "Project-Id-Version: phpMyAdmin 5.2.2-dev\n" "Report-Msgid-Bugs-To: translators@phpmyadmin.net\n" "POT-Creation-Date: 2023-11-12 15:23-0300\n" -"PO-Revision-Date: 2023-12-30 21:39+0000\n" +"PO-Revision-Date: 2024-02-24 02:49+0000\n" "Last-Translator: \"Kristijan \\\"Fremen\\\" Velkovski\" \n" "Language-Team: Macedonian \n" @@ -13,7 +13,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n==1 || n%10==1 ? 0 : 1;\n" -"X-Generator: Weblate 5.4-dev\n" +"X-Generator: Weblate 5.5-dev\n" #: libraries/advisory_rules_generic.php:9 msgid "Uptime below one day" @@ -6623,7 +6623,7 @@ msgstr "Полигон" #: libraries/classes/Controllers/JavaScriptMessagesController.php:482 #: templates/display/results/table.twig:174 msgid "Geometry" -msgstr "" +msgstr "Геометрија" #: libraries/classes/Controllers/JavaScriptMessagesController.php:483 #, fuzzy @@ -14845,11 +14845,11 @@ msgstr "Транформации на веб прелистувачот" #: templates/display/results/table.twig:179 msgid "Well Known Text" -msgstr "" +msgstr "Добро Познат Текст" #: templates/display/results/table.twig:183 msgid "Well Known Binary" -msgstr "" +msgstr "Добро Познат Бинарен" #: templates/display/results/table.twig:255 #: templates/sql/no_results_returned.twig:9 From 0048963013deb0dcb910f80ad1e98eb3e3267bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Thu, 29 Feb 2024 15:53:59 -0300 Subject: [PATCH 2/3] Fix case where tables from wrong db is loaded in navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurício Meneghini Fauth --- ChangeLog | 1 + libraries/classes/Navigation/Nodes/Node.php | 4 ++-- test/classes/Navigation/Nodes/NodeTest.php | 13 +++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index a3f463f057..25771e69fb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -68,6 +68,7 @@ phpMyAdmin - ChangeLog - issue #17363 Fix duplicate route parameter after logging in - issue #15670 Fix case where the data is truncated after changing a longtext column's collation - issue #18865 Fix missing text-nowrap for timestamps columns +- issue #19022 Fix case where tables from wrong database is loaded in navigation tree 5.2.1 (2023-02-07) - issue #17522 Fix case where the routes cache file is invalid diff --git a/libraries/classes/Navigation/Nodes/Node.php b/libraries/classes/Navigation/Nodes/Node.php index e20d7f25a1..3a0fa424d1 100644 --- a/libraries/classes/Navigation/Nodes/Node.php +++ b/libraries/classes/Navigation/Nodes/Node.php @@ -178,13 +178,13 @@ class Node { if ($realName) { foreach ($this->children as $child) { - if ($child->realName == $name) { + if ($child->realName === $name) { return $child; } } } else { foreach ($this->children as $child) { - if ($child->name == $name && $child->isNew === false) { + if ($child->name === $name && $child->isNew === false) { return $child; } } diff --git a/test/classes/Navigation/Nodes/NodeTest.php b/test/classes/Navigation/Nodes/NodeTest.php index 1978139743..241c08dc6c 100644 --- a/test/classes/Navigation/Nodes/NodeTest.php +++ b/test/classes/Navigation/Nodes/NodeTest.php @@ -76,6 +76,19 @@ class NodeTest extends AbstractTestCase ); } + public function testGetChild(): void + { + $parent = NodeFactory::getInstance('Node', 'parent'); + $childOne = NodeFactory::getInstance('Node', '0'); + $childTwo = NodeFactory::getInstance('Node', '00'); + $parent->addChild($childOne); + $parent->addChild($childTwo); + self::assertSame($childTwo, $parent->getChild('00')); + self::assertSame($childOne, $parent->getChild('0')); + self::assertSame($childTwo, $parent->getChild('00', true)); + self::assertSame($childOne, $parent->getChild('0', true)); + } + /** * SetUp for hasChildren */ From 78335aac87362b8af5e199fe3a91cbdc378f5921 Mon Sep 17 00:00:00 2001 From: Luca Camillo Date: Sat, 2 Mar 2024 23:31:03 +0100 Subject: [PATCH 3/3] Add role based auth for MySQL 8.x and compatibles (#18814) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add role based auth for mysql 8.x or compatible like mariadb/aws rds aurora mysql Signed-off-by: Luca Camillo * Using cache and check server version Signed-off-by: Luca Camillo * implement tests for getCurrentRolesAndHost / getCurrentRoles functions Signed-off-by: Luca Camillo * fix role checks on mariadb Signed-off-by: Luca Camillo * Simplify code + ignore NONE as result of CURRENT_ROLE() Signed-off-by: Luca Camillo * Fix tests Signed-off-by: Maurício Meneghini Fauth Fixes #18782 --------- Signed-off-by: Luca Camillo Signed-off-by: Maurício Meneghini Fauth Co-authored-by: Maurício Meneghini Fauth --- libraries/classes/DatabaseInterface.php | 84 +++++++++++++++++++++++++ libraries/classes/Query/Generator.php | 16 ++++- libraries/classes/Util.php | 1 + phpstan-baseline.neon | 25 ++++++++ psalm-baseline.xml | 27 +++++--- test/classes/DatabaseInterfaceTest.php | 68 ++++++++++++++++++++ test/classes/Stubs/DbiDummy.php | 8 ++- 7 files changed, 218 insertions(+), 11 deletions(-) diff --git a/libraries/classes/DatabaseInterface.php b/libraries/classes/DatabaseInterface.php index 744bd05eda..19ae952307 100644 --- a/libraries/classes/DatabaseInterface.php +++ b/libraries/classes/DatabaseInterface.php @@ -50,6 +50,7 @@ use function openlog; use function reset; use function sprintf; use function str_contains; +use function str_replace; use function str_starts_with; use function stripos; use function strlen; @@ -121,6 +122,9 @@ class DatabaseInterface implements DbalInterface /** @var array Current user and host cache */ private $currentUser; + /** @var array>|null Current role and host cache */ + private $currentRoleAndHost = null; + /** @var string|null lower_case_table_names value cache */ private $lowerCaseTableNames = null; @@ -1707,6 +1711,38 @@ class DatabaseInterface implements DbalInterface return '@'; } + /** + * gets the current role with host. Role maybe multiple separated by comma + * Support start from MySQL 8.x / MariaDB 10.0.5 + * + * @see https://dev.mysql.com/doc/refman/8.0/en/roles.html + * @see https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_current-role + * @see https://mariadb.com/kb/en/mariadb-1005-release-notes/#newly-implemented-features + * @see https://mariadb.com/kb/en/roles_overview/ + * + * @return array> the current roles i.e. array of role@host + */ + public function getCurrentRoles(): array + { + if (($this->isMariaDB() && $this->getVersion() < 100500) || $this->getVersion() < 80000) { + return []; + } + + if (SessionCache::has('mysql_cur_role')) { + return SessionCache::get('mysql_cur_role'); + } + + $role = $this->fetchValue('SELECT CURRENT_ROLE();'); + if ($role === false || $role === null || $role === 'NONE') { + return []; + } + + $role = array_map('trim', explode(',', str_replace('`', '', $role))); + SessionCache::set('mysql_cur_role', $role); + + return $role; + } + public function isSuperUser(): bool { if (SessionCache::has('is_superuser')) { @@ -1766,6 +1802,21 @@ class DatabaseInterface implements DbalInterface $hasGrantPrivilege = (bool) $result->numRows(); } + if (! $hasGrantPrivilege) { + foreach ($this->getCurrentRolesAndHost() as [$role, $roleHost]) { + $query = QueryGenerator::getInformationSchemaDataForGranteeRequest($role, $roleHost ?? ''); + $result = $this->tryQuery($query); + + if ($result) { + $hasGrantPrivilege = (bool) $result->numRows(); + } + + if ($hasGrantPrivilege) { + break; + } + } + } + SessionCache::set('is_grantuser', $hasGrantPrivilege); return $hasGrantPrivilege; @@ -1808,6 +1859,21 @@ class DatabaseInterface implements DbalInterface $hasCreatePrivilege = (bool) $result->numRows(); } + if (! $hasCreatePrivilege) { + foreach ($this->getCurrentRolesAndHost() as [$role, $roleHost]) { + $query = QueryGenerator::getInformationSchemaDataForCreateRequest($role, $roleHost ?? ''); + $result = $this->tryQuery($query); + + if ($result) { + $hasCreatePrivilege = (bool) $result->numRows(); + } + + if ($hasCreatePrivilege) { + break; + } + } + } + SessionCache::set('is_createuser', $hasCreatePrivilege); return $hasCreatePrivilege; @@ -1838,6 +1904,24 @@ class DatabaseInterface implements DbalInterface return $this->currentUser; } + /** + * Get the current role and host. + * + * @return array> array of role and hostname + */ + public function getCurrentRolesAndHost(): array + { + if ($this->currentRoleAndHost === null) { + $roles = $this->getCurrentRoles(); + + $this->currentRoleAndHost = array_map(static function (string $role) { + return explode('@', $role); + }, $roles); + } + + return $this->currentRoleAndHost; + } + /** * Returns value for lower_case_table_names variable * diff --git a/libraries/classes/Query/Generator.php b/libraries/classes/Query/Generator.php index d14c59fbe4..e0d4a0b542 100644 --- a/libraries/classes/Query/Generator.php +++ b/libraries/classes/Query/Generator.php @@ -223,13 +223,20 @@ class Generator public static function getInformationSchemaDataForCreateRequest(string $user, string $host): string { + // second part of query is for MariaDB that not show roles inside INFORMATION_SCHEMA db return 'SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES` ' . "WHERE `PRIVILEGE_TYPE` = 'CREATE USER' AND " - . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; + . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE`" + . ' UNION ' + . 'SELECT 1 FROM mysql.user ' + . "WHERE `create_user_priv` = 'Y' COLLATE utf8mb4_general_ci AND " + . "'" . $user . "' LIKE `User` AND '' LIKE `Host`" + . ' LIMIT 1'; } public static function getInformationSchemaDataForGranteeRequest(string $user, string $host): string { + // second part of query is for MariaDB that not show roles inside INFORMATION_SCHEMA db return 'SELECT 1 FROM (' . 'SELECT `GRANTEE`, `IS_GRANTABLE` FROM ' . '`INFORMATION_SCHEMA`.`COLUMN_PRIVILEGES` UNION ' @@ -240,7 +247,12 @@ class Generator . 'SELECT `GRANTEE`, `IS_GRANTABLE` FROM ' . '`INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t ' . "WHERE `IS_GRANTABLE` = 'YES' AND " - . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` LIMIT 1"; + . "'''" . $user . "''@''" . $host . "''' LIKE `GRANTEE` " + . ' UNION ' + . 'SELECT 1 FROM mysql.user ' + . "WHERE `create_user_priv` = 'Y' COLLATE utf8mb4_general_ci AND " + . "'" . $user . "' LIKE `User` AND '' LIKE `Host`" + . ' LIMIT 1'; } public static function getInformationSchemaForeignKeyConstraintsRequest( diff --git a/libraries/classes/Util.php b/libraries/classes/Util.php index 3c66198d1e..0dbdaed742 100644 --- a/libraries/classes/Util.php +++ b/libraries/classes/Util.php @@ -1266,6 +1266,7 @@ class Util SessionCache::remove('is_createuser'); SessionCache::remove('is_grantuser'); SessionCache::remove('mysql_cur_user'); + SessionCache::remove('mysql_cur_role'); } /** diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a685f4e3cf..b34d41b182 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2675,6 +2675,16 @@ parameters: count: 1 path: libraries/classes/DatabaseInterface.php + - + message: "#^Method PhpMyAdmin\\\\DatabaseInterface\\:\\:getCurrentRoles\\(\\) should return array\\\\> but returns array\\\\.$#" + count: 1 + path: libraries/classes/DatabaseInterface.php + + - + message: "#^Method PhpMyAdmin\\\\DatabaseInterface\\:\\:getCurrentRoles\\(\\) should return array\\\\> but returns mixed\\.$#" + count: 1 + path: libraries/classes/DatabaseInterface.php + - message: "#^Method PhpMyAdmin\\\\DatabaseInterface\\:\\:getCurrentUser\\(\\) should return string but returns mixed\\.$#" count: 1 @@ -2750,6 +2760,11 @@ parameters: count: 2 path: libraries/classes/DatabaseInterface.php + - + message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(array\\\\)\\: mixed\\)\\|null, Closure\\(string\\)\\: non\\-empty\\-array\\ given\\.$#" + count: 1 + path: libraries/classes/DatabaseInterface.php + - message: "#^Parameter \\#1 \\$eachTables of static method PhpMyAdmin\\\\Query\\\\Compatibility\\:\\:getISCompatForGetTablesFull\\(\\) expects array, array\\|false given\\.$#" count: 1 @@ -2795,6 +2810,11 @@ parameters: count: 1 path: libraries/classes/DatabaseInterface.php + - + message: "#^Variable \\$roleHost on left side of \\?\\? always exists and is not nullable\\.$#" + count: 2 + path: libraries/classes/DatabaseInterface.php + - message: "#^Method PhpMyAdmin\\\\Dbal\\\\DbalInterface\\:\\:connect\\(\\) has parameter \\$server with no value type specified in iterable type array\\.$#" count: 1 @@ -9725,6 +9745,11 @@ parameters: count: 1 path: test/classes/Database/TriggersTest.php + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertSame\\(\\) with array\\ and array\\\\> will always evaluate to false\\.$#" + count: 1 + path: test/classes/DatabaseInterfaceTest.php + - message: "#^Method PhpMyAdmin\\\\Tests\\\\DatabaseInterfaceTest\\:\\:currentUserData\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index c4686e02fa..2838a68906 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -5727,12 +5727,15 @@ - + $this->extension === null + '' + '' $result_target[] + $row['Data_free'] $row['Data_length'] @@ -5741,7 +5744,8 @@ $row['Max_data_length'] $row['Rows'] - + + $role $this->extension->getProtoInfo($this->links[$link]) @@ -5895,8 +5899,9 @@ $trigger['TRIGGER_NAME'] $warningsCount - + array + array<int, array<int, string>> string @@ -5910,7 +5915,8 @@ $this->links[$link]->warning_count - + + SessionCache::get('mysql_cur_role') SessionCache::get('mysql_cur_user') reset($columns) @@ -5918,8 +5924,9 @@ $this->fetchResult($sql, null, 'Field', $link) string[] - + $user + SessionCache::get('mysql_cur_role') SessionCache::get('mysql_cur_user') @@ -5944,6 +5951,10 @@ $databases[$database_name]['SCHEMA_TABLES'] $databases[$database_name]['SCHEMA_TABLE_ROWS'] + + $roleHost + $roleHost + @@ -15307,16 +15318,18 @@ - + + $value $value $value $value - + array array array array + mixed[] diff --git a/test/classes/DatabaseInterfaceTest.php b/test/classes/DatabaseInterfaceTest.php index 4133fe1a42..44890314c2 100644 --- a/test/classes/DatabaseInterfaceTest.php +++ b/test/classes/DatabaseInterfaceTest.php @@ -113,6 +113,74 @@ class DatabaseInterfaceTest extends AbstractTestCase ]; } + /** + * Tests for DBI::getCurrentRole() method. + * + * @param string[][]|false $value + * @param string[] $string + * @param string[][] $expected + * + * @dataProvider currentRolesData + */ + public function testGetCurrentRoles( + string $version, + bool $isRoleSupported, + $value, + array $string, + array $expected + ): void { + $this->dbi->setVersion(['@@version' => $version]); + + SessionCache::remove('mysql_cur_role'); + + if ($isRoleSupported) { + $this->dummyDbi->addResult('SELECT CURRENT_ROLE();', $value); + } + + self::assertSame($expected, $this->dbi->getCurrentRolesAndHost()); + + self::assertSame($string, $this->dbi->getCurrentRoles()); + + $this->assertAllQueriesConsumed(); + } + + /** + * Data provider for getCurrentRole() tests. + * + * @return mixed[] + */ + public static function currentRolesData(): array + { + return [ + ['10.4.99-MariaDB', false, false, [], []], + ['5.7.35 - MySQL Community Server (GPL)', false, false, [], []], + [ + '8.0.0 - MySQL Community Server - GPL', + true, + [['`role`@`localhost`']], + ['role@localhost'], + [['role', 'localhost']], + ], + [ + '8.0.0 - MySQL Community Server - GPL', + true, + [['`role`@`localhost`, `role2`@`localhost`']], + ['role@localhost', 'role2@localhost'], + [['role', 'localhost'], ['role2', 'localhost']], + ], + ['8.0.0 - MySQL Community Server - GPL', true, [['@`localhost`']], ['@localhost'], [['', 'localhost']]], + ['10.5.0-MariaDB', true, [['`role`@`localhost`']], ['role@localhost'], [['role', 'localhost']]], + [ + '10.5.0-MariaDB', + true, + [['`role`@`localhost`, `role2`@`localhost`']], + ['role@localhost', 'role2@localhost'], + [['role', 'localhost'], ['role2', 'localhost']], + ], + ['10.5.0-MariaDB', true, [['@`localhost`']], ['@localhost'], [['', 'localhost']]], + ]; + } + /** * Tests for DBI::getColumnMapFromSql() method. */ diff --git a/test/classes/Stubs/DbiDummy.php b/test/classes/Stubs/DbiDummy.php index 35e65d60a7..90bcbb6970 100644 --- a/test/classes/Stubs/DbiDummy.php +++ b/test/classes/Stubs/DbiDummy.php @@ -582,7 +582,9 @@ class DbiDummy implements DbiExtension [ 'query' => 'SELECT 1 FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES`' . " WHERE `PRIVILEGE_TYPE` = 'CREATE USER'" - . " AND '''pma_test''@''localhost''' LIKE `GRANTEE` LIMIT 1", + . " AND '''pma_test''@''localhost''' LIKE `GRANTEE`" + . " UNION SELECT 1 FROM mysql.user WHERE `create_user_priv` = 'Y' COLLATE utf8mb4_general_ci" + . " AND 'pma_test' LIKE `User` AND '' LIKE `Host` LIMIT 1", 'result' => [['1']], ], [ @@ -595,7 +597,9 @@ class DbiDummy implements DbiExtension . ' UNION SELECT `GRANTEE`, `IS_GRANTABLE`' . ' FROM `INFORMATION_SCHEMA`.`USER_PRIVILEGES`) t' . " WHERE `IS_GRANTABLE` = 'YES'" - . " AND '''pma_test''@''localhost''' LIKE `GRANTEE` LIMIT 1", + . " AND '''pma_test''@''localhost''' LIKE `GRANTEE`" + . " UNION SELECT 1 FROM mysql.user WHERE `create_user_priv` = 'Y' COLLATE utf8mb4_general_ci" + . " AND 'pma_test' LIKE `User` AND '' LIKE `Host` LIMIT 1", 'result' => [['1']], ], [