Merge branch 'QA_5_2'

Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
This commit is contained in:
Maurício Meneghini Fauth 2024-03-03 00:46:10 -03:00
commit a82c7fcb6f
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
9 changed files with 246 additions and 4 deletions

View File

@ -78,6 +78,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

View File

@ -6715,6 +6715,16 @@ parameters:
count: 1
path: src/DatabaseInterface.php
-
message: "#^Method PhpMyAdmin\\\\DatabaseInterface\\:\\:getCurrentRoles\\(\\) should return array\\<int, array\\<int, string\\>\\> but returns array\\<int, string\\>\\.$#"
count: 1
path: src/DatabaseInterface.php
-
message: "#^Method PhpMyAdmin\\\\DatabaseInterface\\:\\:getCurrentRoles\\(\\) should return array\\<int, array\\<int, string\\>\\> but returns mixed\\.$#"
count: 1
path: src/DatabaseInterface.php
-
message: "#^Method PhpMyAdmin\\\\DatabaseInterface\\:\\:getCurrentUser\\(\\) should return string but returns mixed\\.$#"
count: 1
@ -6745,6 +6755,11 @@ parameters:
count: 1
path: src/DatabaseInterface.php
-
message: "#^Only booleans are allowed in an if condition, PhpMyAdmin\\\\Dbal\\\\ResultInterface\\|false given\\.$#"
count: 2
path: src/DatabaseInterface.php
-
message: "#^Only booleans are allowed in an if condition, int\\|false given\\.$#"
count: 2
@ -6755,6 +6770,11 @@ parameters:
count: 1
path: src/DatabaseInterface.php
-
message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(array\\<int, string\\>\\)\\: mixed\\)\\|null, Closure\\(string\\)\\: non\\-empty\\-array\\<int, string\\> given\\.$#"
count: 1
path: src/DatabaseInterface.php
-
message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(string\\)\\: non\\-empty\\-string given\\.$#"
count: 1
@ -6805,6 +6825,11 @@ parameters:
count: 1
path: src/DatabaseInterface.php
-
message: "#^Variable \\$roleHost on left side of \\?\\? always exists and is not nullable\\.$#"
count: 2
path: src/DatabaseInterface.php
-
message: "#^Method PhpMyAdmin\\\\Dbal\\\\MysqliResult\\:\\:fetchAllAssoc\\(\\) should return array\\<int, array\\<string\\|null\\>\\> but returns array\\.$#"
count: 1
@ -15905,6 +15930,16 @@ parameters:
count: 1
path: tests/unit/DatabaseInterfaceTest.php
-
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertSame\\(\\) with array\\<string\\> and array\\<int, array\\<int, string\\>\\> will always evaluate to false\\.$#"
count: 1
path: tests/unit/DatabaseInterfaceTest.php
-
message: "#^Parameter \\#2 \\$result of method PhpMyAdmin\\\\Tests\\\\Stubs\\\\DbiDummy\\:\\:addResult\\(\\) expects array\\<int, non\\-empty\\-array\\<int, float\\|int\\|string\\|null\\>\\>\\|bool, array\\<array\\<string\\>\\>\\|false given\\.$#"
count: 1
path: tests/unit/DatabaseInterfaceTest.php
-
message: "#^Property PhpMyAdmin\\\\Config\\:\\:\\$selectedServer \\(array\\{host\\: string, port\\: string, socket\\: string, ssl\\: bool, ssl_key\\: string\\|null, ssl_cert\\: string\\|null, ssl_ca\\: string\\|null, ssl_ca_path\\: string\\|null, \\.\\.\\.\\}\\) does not accept array\\{host\\: string, port\\: string, socket\\: string, ssl\\: bool, ssl_key\\: string\\|null, ssl_cert\\: string\\|null, ssl_ca\\: string\\|null, ssl_ca_path\\: string\\|null, \\.\\.\\.\\}\\.$#"
count: 1

View File

@ -4911,6 +4911,15 @@
<DeprecatedProperty>
<code><![CDATA[Routing::$route]]></code>
</DeprecatedProperty>
<DocblockTypeContradiction>
<code><![CDATA['']]></code>
<code><![CDATA['']]></code>
</DocblockTypeContradiction>
<InvalidArgument>
<code><![CDATA[static function (string $role) {
return explode('@', $role);
}]]></code>
</InvalidArgument>
<InvalidOperand>
<code><![CDATA[$row['Data_free']]]></code>
<code><![CDATA[$row['Data_length']]]></code>
@ -4919,6 +4928,9 @@
<code><![CDATA[$row['Max_data_length']]]></code>
<code><![CDATA[$row['Rows']]]></code>
</InvalidOperand>
<InvalidReturnStatement>
<code><![CDATA[$role]]></code>
</InvalidReturnStatement>
<MixedArgument>
<code><![CDATA[$a]]></code>
<code><![CDATA[$b]]></code>
@ -4953,6 +4965,7 @@
<code><![CDATA[$this->versionString]]></code>
</MixedAssignment>
<MixedInferredReturnType>
<code><![CDATA[array<int, array<int, string>>]]></code>
<code><![CDATA[mixed[]]]></code>
<code><![CDATA[mixed[]]]></code>
<code><![CDATA[string]]></code>
@ -4963,6 +4976,7 @@
</MixedOperand>
<MixedReturnStatement>
<code><![CDATA[$resultRows]]></code>
<code><![CDATA[SessionCache::get('mysql_cur_role')]]></code>
<code><![CDATA[SessionCache::get('mysql_cur_user')]]></code>
<code><![CDATA[reset($columns)]]></code>
<code><![CDATA[reset($columns)]]></code>
@ -5029,6 +5043,10 @@
<code><![CDATA[bool]]></code>
<code><![CDATA[bool]]></code>
</PossiblyUnusedReturnValue>
<RedundantConditionGivenDocblockType>
<code><![CDATA[$roleHost]]></code>
<code><![CDATA[$roleHost]]></code>
</RedundantConditionGivenDocblockType>
<RiskyTruthyFalsyComparison>
<code><![CDATA[$index->isUnique()]]></code>
<code><![CDATA[$limitCount]]></code>
@ -13357,6 +13375,9 @@
</PossiblyUnusedMethod>
</file>
<file src="tests/unit/DatabaseInterfaceTest.php">
<ArgumentTypeCoercion>
<code><![CDATA[$value]]></code>
</ArgumentTypeCoercion>
<DeprecatedMethod>
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[Config::getInstance()]]></code>
@ -13369,6 +13390,7 @@
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
</DeprecatedMethod>
<PossiblyUnusedMethod>
<code><![CDATA[currentRolesData]]></code>
<code><![CDATA[currentUserData]]></code>
<code><![CDATA[errorData]]></code>
<code><![CDATA[isAmazonRdsData]]></code>

View File

@ -51,6 +51,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 strnatcasecmp;
@ -110,6 +111,9 @@ class DatabaseInterface implements DbalInterface
/** @var array<int, string>|null */
private array|null $currentUserAndHost = null;
/** @var array<int, array<int, string>>|null Current role and host cache */
private array|null $currentRoleAndHost = null;
/**
* @var int|null lower_case_table_names value cache
* @psalm-var 0|1|2|null
@ -1430,6 +1434,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<int, array<int, string>> 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')) {
@ -1478,6 +1514,21 @@ class DatabaseInterface implements DbalInterface
$query = QueryGenerator::getInformationSchemaDataForGranteeRequest($user, $host);
$hasGrantPrivilege = (bool) $this->fetchValue($query);
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;
@ -1514,6 +1565,21 @@ class DatabaseInterface implements DbalInterface
$query = QueryGenerator::getInformationSchemaDataForCreateRequest($user, $host);
$hasCreatePrivilege = (bool) $this->fetchValue($query);
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;
@ -1548,6 +1614,24 @@ class DatabaseInterface implements DbalInterface
return $this->currentUserAndHost;
}
/**
* Get the current role and host.
*
* @return array<int, array<int, string>> 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
*

View File

@ -240,13 +240,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 '
@ -257,7 +264,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(

View File

@ -839,6 +839,7 @@ class Util
SessionCache::remove('is_createuser');
SessionCache::remove('is_grantuser');
SessionCache::remove('mysql_cur_user');
SessionCache::remove('mysql_cur_role');
}
/**

View File

@ -101,6 +101,75 @@ 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,
array|false $value,
array $string,
array $expected,
): void {
$dbiDummy = $this->createDbiDummy();
$dbi = $this->createDatabaseInterface($dbiDummy);
$dbi->setVersion(['@@version' => $version]);
SessionCache::remove('mysql_cur_role');
if ($isRoleSupported) {
$dbiDummy->addResult('SELECT CURRENT_ROLE();', $value);
}
self::assertSame($expected, $dbi->getCurrentRolesAndHost());
self::assertSame($string, $dbi->getCurrentRoles());
$dbiDummy->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::getSystemDatabase() method.
*/

View File

@ -96,6 +96,20 @@ final class NodeTest extends AbstractTestCase
self::assertSame($child, $parent->getChild('child real name', true));
}
public function testGetChildNodeWithEqualNumericName(): void
{
$config = new Config();
$parent = new Node($config, 'parent');
$childOne = new Node($config, '0');
$childTwo = new Node($config, '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));
}
public function testRemoveChildNode(): void
{
$config = new Config();

View File

@ -369,7 +369,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']],
],
[
@ -382,7 +384,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']],
],
[