Skip to content

Commit

Permalink
Change Console\UpdateConfigController to validade params
Browse files Browse the repository at this point in the history
Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
  • Loading branch information
MauricioFauth committed Mar 29, 2024
1 parent 0e7ea9c commit 2c32a6c
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 30 deletions.
11 changes: 6 additions & 5 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -875,11 +875,6 @@
<code><![CDATA[$request]]></code>
</UnusedParam>
</file>
<file src="src/Controllers/Console/UpdateConfigController.php">
<PossiblyUnusedMethod>
<code><![CDATA[__construct]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/Controllers/Database/CentralColumns/PopulateColumnsController.php">
<MixedArgument>
<code><![CDATA[$request->getParsedBodyParam('selectedTable')]]></code>
Expand Down Expand Up @@ -12454,6 +12449,12 @@
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
</DeprecatedMethod>
</file>
<file src="tests/unit/Controllers/Console/UpdateConfigControllerTest.php">
<PossiblyUnusedMethod>
<code><![CDATA[invalidParamsProvider]]></code>
<code><![CDATA[validParamsProvider]]></code>
</PossiblyUnusedMethod>
</file>
<file src="tests/unit/Controllers/Database/EventsControllerTest.php">
<DeprecatedMethod>
<code><![CDATA[Config::getInstance()]]></code>
Expand Down
8 changes: 2 additions & 6 deletions resources/js/src/modules/console/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,15 @@ export default class Config {
* @param {boolean|string|number} value
*/
function setConfigValue (key: string, value: boolean|number|string): void {
// Updating value in local storage.
const serialized = JSON.stringify(value);
localStorage.setItem('Console/' + key, serialized);

$.ajax({
url: 'index.php?route=/console/update-config',
type: 'POST',
dataType: 'json',
data: {
'ajax_request': true,
key: key,
server: CommonParams.get('server'),
value: serialized,
key: key,
value: value,
},
success: function (data) {
if (data.success !== true) {
Expand Down
87 changes: 68 additions & 19 deletions src/Controllers/Console/UpdateConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@

namespace PhpMyAdmin\Controllers\Console;

use InvalidArgumentException;
use PhpMyAdmin\Config;
use PhpMyAdmin\Controllers\AbstractController;
use PhpMyAdmin\Http\ServerRequest;
use PhpMyAdmin\Message;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\Template;

use function __;
use function in_array;
use function is_string;
use function json_decode;
use function is_numeric;

final class UpdateConfigController extends AbstractController
{
Expand All @@ -24,33 +25,81 @@ public function __construct(ResponseRenderer $response, Template $template, priv

public function __invoke(ServerRequest $request): void
{
$validKeys = [
'StartHistory',
'AlwaysExpand',
'CurrentQuery',
'EnterExecutes',
'DarkTheme',
'Mode',
'Height',
'GroupQueries',
'OrderBy',
'Order',
];
$key = $request->getParsedBodyParam('key');
$value = $request->getParsedBodyParam('value');
if (! in_array($key, $validKeys, true) || ! is_string($value)) {
try {
$key = $this->parseKeyParam($request->getParsedBodyParam('key'));
$value = $this->parseValueParam($key, $request->getParsedBodyParam('value'));
} catch (InvalidArgumentException $exception) {
$this->response->setRequestStatus(false);
$this->response->addJSON(['message' => Message::error()]);
$this->response->addJSON(['message' => Message::error($exception->getMessage())]);

return;
}

$result = $this->config->setUserValue(null, 'Console/' . $key, json_decode($value));
$result = $this->config->setUserValue(null, 'Console/' . $key, $value);
if ($result === true) {
return;
}

$this->response->setRequestStatus(false);
$this->response->addJSON(['message' => $result]);
}

/** @psalm-return 'StartHistory'|'AlwaysExpand'|'CurrentQuery'|'EnterExecutes'|'DarkTheme'|'Mode'|'Height'|'GroupQueries'|'OrderBy'|'Order' */
private function parseKeyParam(mixed $key): string
{
if (
! in_array($key, [
'StartHistory',
'AlwaysExpand',
'CurrentQuery',
'EnterExecutes',
'DarkTheme',
'Mode',
'Height',
'GroupQueries',
'OrderBy',
'Order',
], true)
) {
throw new InvalidArgumentException(__('Unexpected parameter value.'));
}

return $key;
}

/** @psalm-param 'StartHistory'|'AlwaysExpand'|'CurrentQuery'|'EnterExecutes'|'DarkTheme'|'Mode'|'Height'|'GroupQueries'|'OrderBy'|'Order' $key */
private function parseValueParam(string $key, mixed $value): bool|int|string
{
if (
in_array($key, [
'StartHistory',
'AlwaysExpand',
'CurrentQuery',
'EnterExecutes',
'DarkTheme',
'GroupQueries',
], true)
&& in_array($value, ['true', 'false'], true)
) {
return $value === 'true';
}

if ($key === 'Mode' && in_array($value, ['show', 'collapse', 'info'], true)) {
return $value;
}

if ($key === 'Height' && is_numeric($value) && $value > 0) {
return (int) $value;
}

if ($key === 'OrderBy' && in_array($value, ['exec', 'time', 'count'], true)) {
return $value;
}

if ($key === 'Order' && in_array($value, ['asc', 'desc'], true)) {
return $value;
}

throw new InvalidArgumentException(__('Unexpected parameter value.'));
}
}
142 changes: 142 additions & 0 deletions tests/unit/Controllers/Console/UpdateConfigControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

declare(strict_types=1);

namespace PhpMyAdmin\Tests\Controllers\Console;

use PhpMyAdmin\Config;
use PhpMyAdmin\Controllers\Console\UpdateConfigController;
use PhpMyAdmin\Http\Factory\ServerRequestFactory;
use PhpMyAdmin\Message;
use PhpMyAdmin\Template;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Tests\Stubs\ResponseRenderer;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;

#[CoversClass(UpdateConfigController::class)]
final class UpdateConfigControllerTest extends AbstractTestCase
{
#[DataProvider('validParamsProvider')]
public function testValidParams(string $key, string $value, bool|int|string $expected): void
{
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withParsedBody(['key' => $key, 'value' => $value]);

$config = new Config();
$responseRenderer = new ResponseRenderer();
$controller = new UpdateConfigController($responseRenderer, new Template($config), $config);
$controller($request);

self::assertSame($expected, $config->settings['Console'][$key]);
self::assertSame([], $responseRenderer->getJSONResult());
self::assertTrue($responseRenderer->hasSuccessState(), 'Should be a successful response.');
}

/** @return iterable<array{string, string, bool|int|string}> */
public static function validParamsProvider(): iterable
{
yield ['StartHistory', 'true', true];
yield ['StartHistory', 'false', false];
yield ['AlwaysExpand', 'true', true];
yield ['AlwaysExpand', 'false', false];
yield ['CurrentQuery', 'true', true];
yield ['CurrentQuery', 'false', false];
yield ['EnterExecutes', 'true', true];
yield ['EnterExecutes', 'false', false];
yield ['DarkTheme', 'true', true];
yield ['DarkTheme', 'false', false];
yield ['Mode', 'show', 'show'];
yield ['Mode', 'collapse', 'collapse'];
yield ['Mode', 'info', 'info'];
yield ['Height', '1', 1];
yield ['Height', '92', 92];
yield ['GroupQueries', 'true', true];
yield ['GroupQueries', 'false', false];
yield ['OrderBy', 'exec', 'exec'];
yield ['OrderBy', 'time', 'time'];
yield ['OrderBy', 'count', 'count'];
yield ['Order', 'asc', 'asc'];
yield ['Order', 'desc', 'desc'];
}

/**
* @param string|string[] $key
* @param string|string[] $value
*/
#[DataProvider('invalidParamsProvider')]
public function testInvalidParams(array|string $key, array|string $value): void
{
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withParsedBody(['key' => $key, 'value' => $value]);

$config = new Config();
$responseRenderer = new ResponseRenderer();
$controller = new UpdateConfigController($responseRenderer, new Template($config), $config);
$controller($request);

self::assertSame(
['message' => Message::error('Unexpected parameter value.')->getDisplay()],
$responseRenderer->getJSONResult(),
);
self::assertFalse($responseRenderer->hasSuccessState(), 'Should be a failed response.');
}

/** @return iterable<array{string|string[], string|string[]}> */
public static function invalidParamsProvider(): iterable
{
yield ['StartHistory', ''];
yield ['StartHistory', 'invalid'];
yield ['StartHistory', ['invalid']];
yield ['AlwaysExpand', ''];
yield ['AlwaysExpand', 'invalid'];
yield ['AlwaysExpand', ['invalid']];
yield ['CurrentQuery', ''];
yield ['CurrentQuery', 'invalid'];
yield ['CurrentQuery', ['invalid']];
yield ['EnterExecutes', ''];
yield ['EnterExecutes', 'invalid'];
yield ['EnterExecutes', ['invalid']];
yield ['DarkTheme', ''];
yield ['DarkTheme', 'invalid'];
yield ['DarkTheme', ['invalid']];
yield ['Mode', ''];
yield ['Mode', 'invalid'];
yield ['Mode', ['invalid']];
yield ['Height', ''];
yield ['Height', 'invalid'];
yield ['Height', ['invalid']];
yield ['Height', '0'];
yield ['Height', '-1'];
yield ['GroupQueries', ''];
yield ['GroupQueries', 'invalid'];
yield ['GroupQueries', ['invalid']];
yield ['OrderBy', ''];
yield ['OrderBy', 'invalid'];
yield ['OrderBy', ['invalid']];
yield ['Order', ''];
yield ['Order', 'invalid'];
yield ['Order', ['invalid']];
yield ['', 'invalid'];
yield ['invalid', 'invalid'];
yield [['invalid'], 'invalid'];
}

public function testFailedConfigSaving(): void
{
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withParsedBody(['key' => 'StartHistory', 'value' => 'true']);

$config = self::createStub(Config::class);
$config->method('setUserValue')->willReturn(Message::error('Could not save configuration'));
$responseRenderer = new ResponseRenderer();
$controller = new UpdateConfigController($responseRenderer, new Template($config), $config);
$controller($request);

self::assertSame(
['message' => Message::error('Could not save configuration')->getDisplay()],
$responseRenderer->getJSONResult(),
);
self::assertFalse($responseRenderer->hasSuccessState(), 'Should be a failed response.');
}
}

0 comments on commit 2c32a6c

Please sign in to comment.