From f990a83453aa8a70c8d47d4e012600251ddff7d6 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Thu, 30 Jan 2020 13:49:42 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B02060=20-=20Migrate=20error=20page=20to?= =?UTF-8?q?=20the=20Symfony=20framework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portal/config/services.yaml | 4 + .../src/EventListener/ExceptionListener.php | 155 ++++++++++++++++++ .../portal/src/Helper/ApplicationHelper.php | 115 ------------- .../portal/templates/errors/layout.html.twig | 80 ++++++--- .../vendor/composer/autoload_classmap.php | 1 + .../vendor/composer/autoload_static.php | 1 + 6 files changed, 217 insertions(+), 139 deletions(-) create mode 100644 datamodels/2.x/itop-portal-base/portal/src/EventListener/ExceptionListener.php diff --git a/datamodels/2.x/itop-portal-base/portal/config/services.yaml b/datamodels/2.x/itop-portal-base/portal/config/services.yaml index a343a9f4c..d43b9bd77 100644 --- a/datamodels/2.x/itop-portal-base/portal/config/services.yaml +++ b/datamodels/2.x/itop-portal-base/portal/config/services.yaml @@ -76,6 +76,10 @@ services: tags: [{ name: 'kernel.event_listener', event: 'kernel.request', priority: 300 }] Combodo\iTop\Portal\EventListener\CssFromSassCompiler: tags: [{ name: 'kernel.event_listener', event: 'kernel.request', priority: 200 }] + Combodo\iTop\Portal\EventListener\ExceptionListener: + tags: [{ name: 'kernel.event_listener', event: 'kernel.exception', priority: 500 }] + calls: + - [setContainer, ['@service_container']] # Add more service definitions when explicit configuration is needed # Please note that last definitions always *replace* previous ones diff --git a/datamodels/2.x/itop-portal-base/portal/src/EventListener/ExceptionListener.php b/datamodels/2.x/itop-portal-base/portal/src/EventListener/ExceptionListener.php new file mode 100644 index 000000000..0dd8638bd --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/src/EventListener/ExceptionListener.php @@ -0,0 +1,155 @@ + + * @package Combodo\iTop\Portal\EventListener + * @since 2.7.0 + */ +class ExceptionListener implements ContainerAwareInterface +{ + /** @var \Symfony\Component\DependencyInjection\ContainerInterface $container */ + private $oContainer; + + /** + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $oEvent + * + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function onKernelException(GetResponseForExceptionEvent $oEvent) + { + // Get the exception object from the received event + $oException = $oEvent->getException(); + + // Prepare / format exception data + $sErrorMessage = $oException->getMessage(); + // - For none HTTP exception, status code will be a generic 500 + $iStatusCode = ($oException instanceof HttpExceptionInterface) ? $oException->getStatusCode() : Response::HTTP_INTERNAL_SERVER_ERROR; + switch ($iStatusCode) + { + case 404: + $sErrorTitle = Dict::S('Error:HTTP:404'); + break; + default: + $sErrorTitle = Dict::S('Error:HTTP:500'); + break; + } + + // Prepare flatten exception + $oFlattenException = ($_SERVER['APP_DEBUG'] == 1) ? FlattenException::create($oException) : null; + // Remove APPROOT from file paths if in production (SF context) + if (!is_null($oFlattenException) && ($_SERVER['APP_ENV'] === 'prod')) + { + $oFlattenException->setFile($this->removeAppRootFromPath($oFlattenException->getFile())); + + $aTrace = $oFlattenException->getTrace(); + foreach ($aTrace as $iIdx => $aEntry) + { + $aTrace[$iIdx]['file'] = $this->removeAppRootFromPath($aEntry['file']); + } + $oFlattenException->setTrace($aTrace, $oFlattenException->getFile(), $oFlattenException->getLine()); + } + + // Log exception in iTop log + IssueLog::Error($sErrorTitle.': '.$sErrorMessage); + + // Prepare data for template + $aData = array( + 'exception' => $oFlattenException, + 'code' => $iStatusCode, + 'error_title' => $sErrorTitle, + 'error_message' => $sErrorMessage, + ); + + // Generate the response + if ($oEvent->getRequest()->isXmlHttpRequest()) + { + $oResponse = new JsonResponse($aData); + } + else + { + $oResponse = new Response(); + $oResponse->setContent($this->oContainer->get('twig')->render('itop-portal-base/portal/templates/errors/layout.html.twig', + $aData)); + } + $oResponse->setStatusCode($iStatusCode); + + // HttpExceptionInterface is a special type of exception that holds status code and header details + if ($oException instanceof HttpExceptionInterface) + { + $oResponse->headers->replace($oException->getHeaders()); + } + + // Send the modified response object to the event + $oEvent->setResponse($oResponse); + } + + /** + * Normalize a path by replacing '\' with '/' + * + * @param string $sInputPath + * + * @return string|string[] + */ + protected function normalizePath($sInputPath) + { + return str_replace('\\', '/', $sInputPath); + } + + /** + * Remove iTop's APPROOT path from the $sInputPath. Used to avoid "full path disclosure" vulnerabilities. + * + * @param string $sInputPath + * + * @return string + */ + protected function removeAppRootFromPath($sInputPath) + { + $sNormalizedAppRoot = $this->normalizePath(APPROOT); + $sNormalizedInputPath = $this->normalizePath($sInputPath); + return str_replace($sNormalizedAppRoot, '', $sNormalizedInputPath); + } + + /** + * @inheritDoc + */ + public function setContainer(ContainerInterface $oContainer = null) + { + $this->oContainer = $oContainer; + } +} \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php index 8a021cb02..79d3fcb4c 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php @@ -89,121 +89,6 @@ class ApplicationHelper } } - /** - * Registers an exception handler that will intercept controllers exceptions and display them in a nice template. - * Note : It is only active when $oApp['debug'] is false - * - * @param Application $oApp - * - * @todo - */ - public static function RegisterExceptionHandler(Application $oApp) - { - // Intercepting fatal errors and exceptions - ErrorHandler::register(); - ExceptionHandler::register(($oApp['debug'] === true)); - - // Intercepting manually aborted request - if (1 || !$oApp['debug']) - { - $oApp->error(function (Exception $oException /*, Request $oRequest*/) use ($oApp) { - $iErrorCode = ($oException instanceof HttpException) ? $oException->getStatusCode() : 500; - - $aData = array( - 'exception' => $oException, - 'code' => $iErrorCode, - 'error_title' => '', - 'error_message' => $oException->getMessage(), - ); - - switch ($iErrorCode) - { - case 404: - $aData['error_title'] = Dict::S('Error:HTTP:404'); - break; - default: - $aData['error_title'] = Dict::S('Error:HTTP:500'); - break; - } - - IssueLog::Error($aData['error_title'].' : '.$aData['error_message']); - - if ($oApp['request_stack']->getCurrentRequest()->isXmlHttpRequest()) - { - $oResponse = $oApp->json($aData, $iErrorCode); - } - else - { - // Preparing debug trace - $aSteps = array(); - foreach ($oException->getTrace() as $aStep) - { - // - Default file name - if (!isset($aStep['file'])) - { - $aStep['file'] = ''; - } - $aFileParts = explode('\\', $aStep['file']); - // - Default line number - if (!isset($aStep['line'])) - { - $aStep['line'] = 'unknown'; - } - // - Default class name - if (isset($aStep['class']) && isset($aStep['function']) && isset($aStep['type'])) - { - $aClassParts = explode('\\', $aStep['class']); - $sClassName = $aClassParts[count($aClassParts) - 1]; - $sClassFQ = $aStep['class']; - - $aArgsAsString = array(); - foreach ($aStep['args'] as $arg) - { - if (is_array($arg)) - { - $aArgsAsString[] = 'array(...)'; - } - elseif (is_object($arg)) - { - $aArgsAsString[] = 'object('.get_class($arg).')'; - } - else - { - $aArgsAsString[] = $arg; - } - } - - $sFunctionCall = $sClassName.$aStep['type'].$aStep['function'].'('.implode(', ', - $aArgsAsString).')'; - } - else - { - $sClassName = null; - $sClassFQ = null; - $sFunctionCall = null; - } - - $aSteps[] = array( - 'file_fq' => $aStep['file'], - 'file_name' => $aFileParts[count($aFileParts) - 1], - 'line' => $aStep['line'], - 'class_name' => $sClassName, - 'class_fq' => $sClassFQ, - 'function_call' => $sFunctionCall, - ); - } - - $aData['debug_trace_steps'] = $aSteps; - - $oResponse = $oApp['twig']->render('itop-portal-base/portal/templates/errors/layout.html.twig', - $aData); - } - - return $oResponse; - }); - } - } - /** * Loads the brick's security from the OQL queries to profiles arrays * diff --git a/datamodels/2.x/itop-portal-base/portal/templates/errors/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/errors/layout.html.twig index a9a54506c..676c9a263 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/errors/layout.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/errors/layout.html.twig @@ -2,10 +2,7 @@ {# Base error layout #} {% extends 'itop-portal-base/portal/templates/layout.html.twig' %} -{% block pNavigationWrapper %} -{% endblock %} - -{% block pMainWrapper %} +{% block pStyleinline %} - +{% endblock %} + +{% block pNavigationWrapper %} +{% endblock %} + +{% block pMainWrapper %}
{% if app['kernel'].debug == true %} -
-
    - {% for aStep in debug_trace_steps %} -
  1. - {% if aStep.function_call is not null %}at {{ aStep.function_call }}{% endif %} - in {{ aStep.file_name }} line {{ aStep.line }} -
  2. +
    + {# Note: The following is copied by the '@Twig/Exception/exception.html.twig' #} + {% set exception_as_array = exception.toarray %} + {% set _exceptions_with_user_code = [] %} + {% for i, e in exception_as_array %} + {% for trace in e.trace %} + {% if (trace.file is not empty) and ('/vendor/' not in trace.file) and ('/var/cache/' not in trace.file) and not loop.last %} + {% set _exceptions_with_user_code = _exceptions_with_user_code|merge([i]) %} + {% endif %} {% endfor %} -
+ {% endfor %} +

+ {% if exception_as_array|length > 1 %} + Exceptions {{ exception_as_array|length }} + {% else %} + Exception + {% endif %} +

+ +
+ {% for i, e in exception_as_array %} + {{ include('@Twig/Exception/traces.html.twig', { exception: e, index: loop.index, expand: i in _exceptions_with_user_code or (_exceptions_with_user_code is empty and loop.first) }, with_context = false) }} + {% endfor %} +
{% endif %}
+{% endblock %} + +{% block pPageLiveScripts %} + {{ include('@Twig/base_js.html.twig') }} {% endblock %} \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/vendor/composer/autoload_classmap.php b/datamodels/2.x/itop-portal-base/portal/vendor/composer/autoload_classmap.php index 6379e08a5..bac414a6d 100644 --- a/datamodels/2.x/itop-portal-base/portal/vendor/composer/autoload_classmap.php +++ b/datamodels/2.x/itop-portal-base/portal/vendor/composer/autoload_classmap.php @@ -51,6 +51,7 @@ return array( 'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetPluginPropertyClass' => $baseDir . '/src/EventListener/ApplicationContextSetPluginPropertyClass.php', 'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetUrlMakerClass' => $baseDir . '/src/EventListener/ApplicationContextSetUrlMakerClass.php', 'Combodo\\iTop\\Portal\\EventListener\\CssFromSassCompiler' => $baseDir . '/src/EventListener/CssFromSassCompiler.php', + 'Combodo\\iTop\\Portal\\EventListener\\ExceptionListener' => $baseDir . '/src/EventListener/ExceptionListener.php', 'Combodo\\iTop\\Portal\\EventListener\\UserProvider' => $baseDir . '/src/EventListener/UserProvider.php', 'Combodo\\iTop\\Portal\\Form\\ObjectFormManager' => $baseDir . '/src/Form/ObjectFormManager.php', 'Combodo\\iTop\\Portal\\Form\\PasswordFormManager' => $baseDir . '/src/Form/PasswordFormManager.php', diff --git a/datamodels/2.x/itop-portal-base/portal/vendor/composer/autoload_static.php b/datamodels/2.x/itop-portal-base/portal/vendor/composer/autoload_static.php index 81aaa7455..5a7d4e2c3 100644 --- a/datamodels/2.x/itop-portal-base/portal/vendor/composer/autoload_static.php +++ b/datamodels/2.x/itop-portal-base/portal/vendor/composer/autoload_static.php @@ -71,6 +71,7 @@ class ComposerStaticInitdf408f3f8ea034d298269cdf7647358b 'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetPluginPropertyClass' => __DIR__ . '/../..' . '/src/EventListener/ApplicationContextSetPluginPropertyClass.php', 'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetUrlMakerClass' => __DIR__ . '/../..' . '/src/EventListener/ApplicationContextSetUrlMakerClass.php', 'Combodo\\iTop\\Portal\\EventListener\\CssFromSassCompiler' => __DIR__ . '/../..' . '/src/EventListener/CssFromSassCompiler.php', + 'Combodo\\iTop\\Portal\\EventListener\\ExceptionListener' => __DIR__ . '/../..' . '/src/EventListener/ExceptionListener.php', 'Combodo\\iTop\\Portal\\EventListener\\UserProvider' => __DIR__ . '/../..' . '/src/EventListener/UserProvider.php', 'Combodo\\iTop\\Portal\\Form\\ObjectFormManager' => __DIR__ . '/../..' . '/src/Form/ObjectFormManager.php', 'Combodo\\iTop\\Portal\\Form\\PasswordFormManager' => __DIR__ . '/../..' . '/src/Form/PasswordFormManager.php',