From cc86c0061f5623728d6ca4ede6f3629190253025 Mon Sep 17 00:00:00 2001 From: christianbeeznst Date: Sat, 16 Mar 2024 21:15:48 -0500 Subject: [PATCH] Internal: Add session expiry redirect and custom exception handling --- assets/vue/components/Login.vue | 19 +++++++++- .../components/layout/TopbarNotLoggedIn.vue | 5 ++- public/main/inc/lib/api.lib.php | 3 +- src/CoreBundle/Component/Utils/ChamiloApi.php | 17 +++++++++ .../EventListener/ExceptionListener.php | 37 ++++++++++++++----- .../Exception/NotAllowedException.php | 16 ++++++++ .../Resources/views/Exception/error.html.twig | 12 +++--- 7 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 src/CoreBundle/Exception/NotAllowedException.php diff --git a/assets/vue/components/Login.vue b/assets/vue/components/Login.vue index 45cdc02e1f..19c5afdafd 100644 --- a/assets/vue/components/Login.vue +++ b/assets/vue/components/Login.vue @@ -115,7 +115,12 @@ async function performLogin() { if (!store.getters["security/hasError"]) { securityStore.user = store.state["security/user"] - if (typeof redirect !== "undefined") { + // Check if 'redirect' is an absolute URL + if (typeof redirect !== "undefined" && isValidHttpUrl(redirect.toString())) { + // If it's an absolute URL, redirect directly + window.location.href = redirect.toString() + } else if (typeof redirect !== "undefined") { + // If 'redirect' is a relative path, use 'router.push' to navigate await router.push({ path: redirect.toString() }) } else { if (responseData.load_terms) { @@ -126,4 +131,16 @@ async function performLogin() { } } } + +function isValidHttpUrl(string) { + let url + + try { + url = new URL(string) + } catch (_) { + return false + } + + return url.protocol === "http:" || url.protocol === "https:" +} diff --git a/assets/vue/components/layout/TopbarNotLoggedIn.vue b/assets/vue/components/layout/TopbarNotLoggedIn.vue index 832ed5f946..aaeae76837 100644 --- a/assets/vue/components/layout/TopbarNotLoggedIn.vue +++ b/assets/vue/components/layout/TopbarNotLoggedIn.vue @@ -37,13 +37,14 @@ function setLanguage(event) { window.location.href = newUrl.fullPath } -const languageItems = window.languages.map((language) => ({ +const languages = window.languages || [{ originalName: "English", isocode: "en" }] +const languageItems = languages.map((language) => ({ label: language.originalName, isoCode: language.isocode, command: setLanguage, })) -const currentLanguage = window.languages.find((language) => document.querySelector("html").lang === language.isocode) +const currentLanguage = languages.find((language) => document.querySelector("html").lang === language.isocode) const menuItems = computed(() => [ { diff --git a/public/main/inc/lib/api.lib.php b/public/main/inc/lib/api.lib.php index 3a9f4a1c9a..e9420646cc 100644 --- a/public/main/inc/lib/api.lib.php +++ b/public/main/inc/lib/api.lib.php @@ -9,6 +9,7 @@ use Chamilo\CoreBundle\Entity\Session as SessionEntity; use Chamilo\CoreBundle\Entity\SettingsCurrent; use Chamilo\CoreBundle\Entity\User; use Chamilo\CoreBundle\Entity\UserCourseCategory; +use Chamilo\CoreBundle\Exception\NotAllowedException; use Chamilo\CoreBundle\Framework\Container; use Chamilo\CourseBundle\Entity\CGroup; use Chamilo\CourseBundle\Entity\CLp; @@ -3516,7 +3517,7 @@ function api_not_allowed( $message = null, $responseCode = 0 ): never { - throw new Exception('You are not allowed'); + throw new NotAllowedException($message ?: 'You are not allowed', $responseCode); } /** diff --git a/src/CoreBundle/Component/Utils/ChamiloApi.php b/src/CoreBundle/Component/Utils/ChamiloApi.php index 5cc2e5f2bc..07dc90a5b1 100644 --- a/src/CoreBundle/Component/Utils/ChamiloApi.php +++ b/src/CoreBundle/Component/Utils/ChamiloApi.php @@ -428,4 +428,21 @@ class ChamiloApi instance.buildmarkersrolls(instance, instance.controls, instance.layers, instance.media); "; } + + /** + * Performs a redirection to the specified URL. + * + * This method sends a direct HTTP Location header to the client, + * causing the browser to navigate to the specified URL. It should be + * used with caution and only in scenarios where Symfony's standard + * response handling is not applicable. The method terminates script + * execution after sending the header. + */ + public static function redirectTo(string $url): void + { + if (!empty($url)) { + header("Location: $url"); + exit; + } + } } diff --git a/src/CoreBundle/EventListener/ExceptionListener.php b/src/CoreBundle/EventListener/ExceptionListener.php index ef993386a7..f424051cee 100644 --- a/src/CoreBundle/EventListener/ExceptionListener.php +++ b/src/CoreBundle/EventListener/ExceptionListener.php @@ -6,34 +6,53 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\EventListener; +use Chamilo\CoreBundle\Component\Utils\ChamiloApi; +use Chamilo\CoreBundle\Exception\NotAllowedException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Twig\Environment; class ExceptionListener { protected Environment $twig; + protected TokenStorageInterface $tokenStorage; + protected UrlGeneratorInterface $router; - public function __construct(Environment $twig) + public function __construct(Environment $twig, TokenStorageInterface $tokenStorage, UrlGeneratorInterface $router) { $this->twig = $twig; + $this->tokenStorage = $tokenStorage; + $this->router = $router; } public function onKernelException(ExceptionEvent $event): void { + // You get the exception object from the received event + $exception = $event->getThrowable(); + $request = $event->getRequest(); + + if ($exception instanceof AccessDeniedException || $exception instanceof NotAllowedException) { + if (null === $this->tokenStorage->getToken()) { + $currentUrl = $request->getUri(); + $parsedUrl = parse_url($currentUrl); + $baseUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + $path = rtrim($parsedUrl['path'], '/') ?: ''; + $query = $parsedUrl['query'] ?? ''; + $redirectUrl = $baseUrl . $path . ($query ? '?' . $query : ''); + + $loginUrl = $this->router->generate('login', ['redirect' => $redirectUrl], UrlGeneratorInterface::ABSOLUTE_URL); + ChamiloApi::redirectTo($loginUrl); + } + } + if (isset($_SERVER['APP_ENV']) && \in_array($_SERVER['APP_ENV'], ['dev', 'test'], true)) { return; } - // You get the exception object from the received event - $exception = $event->getThrowable(); - /*$message = sprintf( - 'My Error says: %s with code: %s', - $exception->getMessage(), - $exception->getCode() - );*/ - $message = $this->twig->render( '@ChamiloCore/Exception/error.html.twig', [ diff --git a/src/CoreBundle/Exception/NotAllowedException.php b/src/CoreBundle/Exception/NotAllowedException.php new file mode 100644 index 0000000000..1dbdf3a6dc --- /dev/null +++ b/src/CoreBundle/Exception/NotAllowedException.php @@ -0,0 +1,16 @@ +
-
-
-

Error

-

{{ exception.message }}

-
+
+

{{ 'Oops! Something went wrong.'|trans }}

+
+

{{ exception.message }}

+
+

{{ 'If the problem persists, please contact support.'|trans }}

{% endblock %}