From e6911ce24ab14514f21075dc6599a8c07282993f Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 12 Nov 2021 09:36:54 +0200 Subject: [PATCH] Improved form validation JSON responses to contain list of failed fields with their error messages --- CHANGELOG.md | 1 + .../Grav/Common/Data/ValidationException.php | 23 +++++++++++++++---- .../Traits/ControllerResponseTrait.php | 6 ++++- .../RequestHandler/Middlewares/Exceptions.php | 6 ++++- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5babb09b91..fb744b44d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Added `route` and `request` to `onPagesInitialized` event * Improved page cloning, added method `Page::initialize()` * Improved `FlexObject::getChanges()`: return changed lists and arrays as whole instead of just changed keys/values + * Improved form validation JSON responses to contain list of failed fields with their error messages 3. [](#bugfix) * Fixed path traversal vulnerability when using `bin/grav server` * Fixed unescaped error messages in JSON error responses diff --git a/system/src/Grav/Common/Data/ValidationException.php b/system/src/Grav/Common/Data/ValidationException.php index 2d94ab8160..be6a674d70 100644 --- a/system/src/Grav/Common/Data/ValidationException.php +++ b/system/src/Grav/Common/Data/ValidationException.php @@ -10,16 +10,18 @@ namespace Grav\Common\Data; use Grav\Common\Grav; +use JsonSerializable; use RuntimeException; /** * Class ValidationException * @package Grav\Common\Data */ -class ValidationException extends RuntimeException +class ValidationException extends RuntimeException implements JsonSerializable { /** @var array */ protected $messages = []; + protected $escape = true; /** * @param array $messages @@ -32,21 +34,34 @@ public function setMessages(array $messages = []) $language = Grav::instance()['language']; $this->message = $language->translate('GRAV.FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message; - foreach ($messages as $variable => &$list) { + foreach ($messages as $list) { $list = array_unique($list); foreach ($list as $message) { - $this->message .= "
$message"; + $this->message .= '
' . htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } } return $this; } + public function setSimpleMessage(bool $escape = true): void + { + $first = reset($this->messages); + $message = reset($first); + + $this->message = $escape ? htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $message; + } + /** * @return array */ - public function getMessages() + public function getMessages(): array { return $this->messages; } + + public function jsonSerialize(): array + { + return ['validation' => $this->messages]; + } } diff --git a/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php index 5b4863573f..afa08aa42e 100644 --- a/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php +++ b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php @@ -19,6 +19,7 @@ use Grav\Framework\Psr7\Response; use Grav\Framework\RequestHandler\Exception\RequestException; use Grav\Framework\Route\Route; +use JsonSerializable; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; @@ -209,6 +210,9 @@ protected function getErrorJson(Throwable $e): array } else { $message = htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8'); } + + $extra = $e instanceof JsonSerializable ? $e->jsonSerialize() : []; + $response = [ 'code' => $code, 'status' => 'error', @@ -216,7 +220,7 @@ protected function getErrorJson(Throwable $e): array 'error' => [ 'code' => $code, 'message' => $message - ] + ] + $extra ]; /** @var Debugger $debugger */ diff --git a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php index e09f1b2575..9eadf72e3f 100644 --- a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php +++ b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php @@ -16,6 +16,7 @@ use Grav\Common\Grav; use Grav\Framework\Psr7\Response; use JsonException; +use JsonSerializable; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -46,6 +47,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } else { $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8'); } + + $extra = $exception instanceof JsonSerializable ? $exception->jsonSerialize() : []; + $response = [ 'code' => $code, 'status' => 'error', @@ -53,7 +57,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface 'error' => [ 'code' => $code, 'message' => $message, - ] + ] + $extra ]; /** @var Debugger $debugger */