From b1a94536b4b185bcaaedc7fac11b9bdf0ab3a52c Mon Sep 17 00:00:00 2001 From: lukmzig Date: Thu, 21 Aug 2025 15:47:18 +0200 Subject: [PATCH 1/5] close write session --- config/event_subscribers.yaml | 4 ++ .../SessionCloseSubscriber.php | 51 +++++++++++++++++++ src/Security/Voter/AuthorizationVoter.php | 15 ++++-- .../Voter/ElementTypePermissionVoter.php | 4 ++ .../Voter/PublicAuthorizationVoter.php | 4 ++ src/Security/Voter/UserPasswordVoter.php | 7 ++- src/Security/Voter/UserPermissionVoter.php | 4 ++ src/Security/Voter/UserPerspectiveVoter.php | 4 ++ 8 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 src/EventSubscriber/SessionCloseSubscriber.php diff --git a/config/event_subscribers.yaml b/config/event_subscribers.yaml index cbfc3dcd0..01afb6065 100644 --- a/config/event_subscribers.yaml +++ b/config/event_subscribers.yaml @@ -13,6 +13,10 @@ services: tags: [ 'kernel.event_subscriber' ] arguments: ['%pimcore_studio_backend.url_prefix%'] + Pimcore\Bundle\StudioBackendBundle\EventSubscriber\SessionCloseSubscriber: + tags: [ 'kernel.event_subscriber' ] + arguments: ['%pimcore_studio_backend.url_prefix%'] + Pimcore\Bundle\StudioBackendBundle\EventSubscriber\ApiExceptionSubscriber: tags: [ 'kernel.event_subscriber' ] arguments: ["%kernel.environment%", '%pimcore_studio_backend.url_prefix%'] \ No newline at end of file diff --git a/src/EventSubscriber/SessionCloseSubscriber.php b/src/EventSubscriber/SessionCloseSubscriber.php new file mode 100644 index 000000000..e14da233e --- /dev/null +++ b/src/EventSubscriber/SessionCloseSubscriber.php @@ -0,0 +1,51 @@ + 'onLoginSuccess', + ]; + } + + public function onLoginSuccess(LoginSuccessEvent $event): void + { + $request = $event->getRequest(); + if (!$this->isStudioBackendPath($request->getPathInfo(), $this->urlPrefix)) { + return; + } + + if (session_status() === PHP_SESSION_ACTIVE) { + session_write_close(); + } + } +} \ No newline at end of file diff --git a/src/Security/Voter/AuthorizationVoter.php b/src/Security/Voter/AuthorizationVoter.php index 3b406a2a7..099d668ee 100644 --- a/src/Security/Voter/AuthorizationVoter.php +++ b/src/Security/Voter/AuthorizationVoter.php @@ -15,6 +15,7 @@ use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NoRequestException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotAuthorizedException; +use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; @@ -23,7 +24,13 @@ */ final class AuthorizationVoter extends Voter { - private const SUPPORTED_ATTRIBUTE = 'STUDIO_API'; + public function __construct( + private readonly SecurityServiceInterface $securityService + + ) { + } + + private const string SUPPORTED_ATTRIBUTE = 'STUDIO_API'; /** * {@inheritdoc} @@ -38,10 +45,10 @@ protected function supports(string $attribute, mixed $subject): bool */ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { - if ($attribute !== self::SUPPORTED_ATTRIBUTE) { - return false; + if ($this->securityService->isSessionWritable()) { + session_write_close(); } - return true; + return $attribute === self::SUPPORTED_ATTRIBUTE; } } diff --git a/src/Security/Voter/ElementTypePermissionVoter.php b/src/Security/Voter/ElementTypePermissionVoter.php index ce3fa4375..6d7acaec3 100644 --- a/src/Security/Voter/ElementTypePermissionVoter.php +++ b/src/Security/Voter/ElementTypePermissionVoter.php @@ -59,6 +59,10 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter { $elementType = $this->getElementTypeFromRequest(); + if ($this->securityService->isSessionWritable()) { + session_write_close(); + } + if (!$elementType || !array_key_exists($elementType, self::TYPE_TO_PERMISSION)) { return false; } diff --git a/src/Security/Voter/PublicAuthorizationVoter.php b/src/Security/Voter/PublicAuthorizationVoter.php index bf00b56a1..aad9b36c4 100644 --- a/src/Security/Voter/PublicAuthorizationVoter.php +++ b/src/Security/Voter/PublicAuthorizationVoter.php @@ -61,6 +61,10 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter $request = $this->getCurrentRequest($this->requestStack); $subjectName = $this->getSubjectName($subject); + if ($this->securityService->isSessionWritable()) { + session_write_close(); + } + return $this->voteOnRequest($request, $subjectName); } diff --git a/src/Security/Voter/UserPasswordVoter.php b/src/Security/Voter/UserPasswordVoter.php index 4ed0f5d60..f32bb2805 100644 --- a/src/Security/Voter/UserPasswordVoter.php +++ b/src/Security/Voter/UserPasswordVoter.php @@ -51,12 +51,17 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter $userId = $this->getUserIdFromRequest(); $currentUser = $this->securityService->getCurrentUser(); + + if ($this->securityService->isSessionWritable()) { + session_write_close(); + } + if ($userId === $currentUser->getId()) { // Allow user to update their own password return true; } - return $this->securityService->getCurrentUser()->isAllowed(UserPermissions::USER_MANAGEMENT->value); + return $currentUser->isAllowed(UserPermissions::USER_MANAGEMENT->value); } private function getUserIdFromRequest(): int diff --git a/src/Security/Voter/UserPermissionVoter.php b/src/Security/Voter/UserPermissionVoter.php index 5cc18be4a..a2523f542 100644 --- a/src/Security/Voter/UserPermissionVoter.php +++ b/src/Security/Voter/UserPermissionVoter.php @@ -58,6 +58,10 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter throw new ForbiddenException(sprintf('User does not have permission: %s', $attribute)); } + if ($this->securityService->isSessionWritable()) { + session_write_close(); + } + return true; } diff --git a/src/Security/Voter/UserPerspectiveVoter.php b/src/Security/Voter/UserPerspectiveVoter.php index 34f3e900a..4126299c9 100644 --- a/src/Security/Voter/UserPerspectiveVoter.php +++ b/src/Security/Voter/UserPerspectiveVoter.php @@ -59,6 +59,10 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter $this->userPerspectiveService->validatePerspectiveAccess($user, $this->getPerspectiveFromRequest()); + if ($this->securityService->isSessionWritable()) { + session_write_close(); + } + return true; } From fe84ca158ae85f811e51fd1edccea1421cdcb294 Mon Sep 17 00:00:00 2001 From: lukmzig <30526586+lukmzig@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:48:55 +0000 Subject: [PATCH 2/5] Apply php-cs-fixer changes --- src/EventSubscriber/SessionCloseSubscriber.php | 2 +- src/Security/Voter/AuthorizationVoter.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EventSubscriber/SessionCloseSubscriber.php b/src/EventSubscriber/SessionCloseSubscriber.php index e14da233e..8906d50e8 100644 --- a/src/EventSubscriber/SessionCloseSubscriber.php +++ b/src/EventSubscriber/SessionCloseSubscriber.php @@ -48,4 +48,4 @@ public function onLoginSuccess(LoginSuccessEvent $event): void session_write_close(); } } -} \ No newline at end of file +} diff --git a/src/Security/Voter/AuthorizationVoter.php b/src/Security/Voter/AuthorizationVoter.php index 099d668ee..ab2748ea5 100644 --- a/src/Security/Voter/AuthorizationVoter.php +++ b/src/Security/Voter/AuthorizationVoter.php @@ -29,7 +29,7 @@ public function __construct( ) { } - + private const string SUPPORTED_ATTRIBUTE = 'STUDIO_API'; /** From 993060c85fc33f160b92da8aaeb9edcc714dc9cb Mon Sep 17 00:00:00 2001 From: lukmzig Date: Fri, 22 Aug 2025 10:48:27 +0200 Subject: [PATCH 3/5] update session subscriber --- .../SessionCloseSubscriber.php | 25 +++++++++++++++++-- .../Authenticator/AdminTokenAuthenticator.php | 4 --- src/Security/Service/SecurityService.php | 5 ---- .../Service/SecurityServiceInterface.php | 2 -- src/Security/Voter/AuthorizationVoter.php | 4 --- .../Voter/ElementTypePermissionVoter.php | 4 --- .../Voter/PublicAuthorizationVoter.php | 4 --- src/Security/Voter/UserPasswordVoter.php | 4 --- src/Security/Voter/UserPermissionVoter.php | 4 --- src/Security/Voter/UserPerspectiveVoter.php | 4 --- 10 files changed, 23 insertions(+), 37 deletions(-) diff --git a/src/EventSubscriber/SessionCloseSubscriber.php b/src/EventSubscriber/SessionCloseSubscriber.php index 8906d50e8..aaf6c072b 100644 --- a/src/EventSubscriber/SessionCloseSubscriber.php +++ b/src/EventSubscriber/SessionCloseSubscriber.php @@ -16,6 +16,9 @@ use Pimcore\Bundle\StudioBackendBundle\Util\Trait\StudioBackendPathTrait; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; /** @@ -27,6 +30,7 @@ public function __construct( private string $urlPrefix, + private RequestStack $requestStack, ) { } @@ -34,6 +38,7 @@ public static function getSubscribedEvents(): array { return [ LoginSuccessEvent::class => 'onLoginSuccess', + KernelEvents::REQUEST => 'onKernelRequest', ]; } @@ -44,8 +49,24 @@ public function onLoginSuccess(LoginSuccessEvent $event): void return; } - if (session_status() === PHP_SESSION_ACTIVE) { - session_write_close(); + $this->closeSessionWrite(); + } + + public function onKernelRequest(RequestEvent $event): void + { + $request = $event->getRequest(); + if (!$event->isMainRequest() || !$this->isStudioBackendPath($request->getPathInfo(), $this->urlPrefix)) { + return; + } + + $this->closeSessionWrite(); + } + + private function closeSessionWrite(): void + { + $session = $this->requestStack->getSession(); + if ($session->isStarted()) { + $session->save(); } } } diff --git a/src/Security/Authenticator/AdminTokenAuthenticator.php b/src/Security/Authenticator/AdminTokenAuthenticator.php index a0f38042b..355d64920 100644 --- a/src/Security/Authenticator/AdminTokenAuthenticator.php +++ b/src/Security/Authenticator/AdminTokenAuthenticator.php @@ -90,10 +90,6 @@ public function onAuthenticationSuccess(Request $request, TokenInterface $token, ); } - if (session_status() === PHP_SESSION_ACTIVE) { - session_write_close(); - } - return null; } diff --git a/src/Security/Service/SecurityService.php b/src/Security/Service/SecurityService.php index 1f769117e..dbbdc7c32 100644 --- a/src/Security/Service/SecurityService.php +++ b/src/Security/Service/SecurityService.php @@ -109,9 +109,4 @@ public function getSpecialDataObjectPermissions( $permission ); } - - public function isSessionWritable(): bool - { - return session_status() === PHP_SESSION_ACTIVE; - } } diff --git a/src/Security/Service/SecurityServiceInterface.php b/src/Security/Service/SecurityServiceInterface.php index c72f6e4f8..5b18055a2 100644 --- a/src/Security/Service/SecurityServiceInterface.php +++ b/src/Security/Service/SecurityServiceInterface.php @@ -56,6 +56,4 @@ public function getSpecialDataObjectPermissions( UserInterface $user, string $permission ): array; - - public function isSessionWritable(): bool; } diff --git a/src/Security/Voter/AuthorizationVoter.php b/src/Security/Voter/AuthorizationVoter.php index ab2748ea5..91d00c3f2 100644 --- a/src/Security/Voter/AuthorizationVoter.php +++ b/src/Security/Voter/AuthorizationVoter.php @@ -45,10 +45,6 @@ protected function supports(string $attribute, mixed $subject): bool */ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { - if ($this->securityService->isSessionWritable()) { - session_write_close(); - } - return $attribute === self::SUPPORTED_ATTRIBUTE; } } diff --git a/src/Security/Voter/ElementTypePermissionVoter.php b/src/Security/Voter/ElementTypePermissionVoter.php index 6d7acaec3..ce3fa4375 100644 --- a/src/Security/Voter/ElementTypePermissionVoter.php +++ b/src/Security/Voter/ElementTypePermissionVoter.php @@ -59,10 +59,6 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter { $elementType = $this->getElementTypeFromRequest(); - if ($this->securityService->isSessionWritable()) { - session_write_close(); - } - if (!$elementType || !array_key_exists($elementType, self::TYPE_TO_PERMISSION)) { return false; } diff --git a/src/Security/Voter/PublicAuthorizationVoter.php b/src/Security/Voter/PublicAuthorizationVoter.php index aad9b36c4..bf00b56a1 100644 --- a/src/Security/Voter/PublicAuthorizationVoter.php +++ b/src/Security/Voter/PublicAuthorizationVoter.php @@ -61,10 +61,6 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter $request = $this->getCurrentRequest($this->requestStack); $subjectName = $this->getSubjectName($subject); - if ($this->securityService->isSessionWritable()) { - session_write_close(); - } - return $this->voteOnRequest($request, $subjectName); } diff --git a/src/Security/Voter/UserPasswordVoter.php b/src/Security/Voter/UserPasswordVoter.php index f32bb2805..b695bb625 100644 --- a/src/Security/Voter/UserPasswordVoter.php +++ b/src/Security/Voter/UserPasswordVoter.php @@ -52,10 +52,6 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter $currentUser = $this->securityService->getCurrentUser(); - if ($this->securityService->isSessionWritable()) { - session_write_close(); - } - if ($userId === $currentUser->getId()) { // Allow user to update their own password return true; diff --git a/src/Security/Voter/UserPermissionVoter.php b/src/Security/Voter/UserPermissionVoter.php index a2523f542..5cc18be4a 100644 --- a/src/Security/Voter/UserPermissionVoter.php +++ b/src/Security/Voter/UserPermissionVoter.php @@ -58,10 +58,6 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter throw new ForbiddenException(sprintf('User does not have permission: %s', $attribute)); } - if ($this->securityService->isSessionWritable()) { - session_write_close(); - } - return true; } diff --git a/src/Security/Voter/UserPerspectiveVoter.php b/src/Security/Voter/UserPerspectiveVoter.php index 4126299c9..34f3e900a 100644 --- a/src/Security/Voter/UserPerspectiveVoter.php +++ b/src/Security/Voter/UserPerspectiveVoter.php @@ -59,10 +59,6 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter $this->userPerspectiveService->validatePerspectiveAccess($user, $this->getPerspectiveFromRequest()); - if ($this->securityService->isSessionWritable()) { - session_write_close(); - } - return true; } From b707619bd9d78c8e4e6ef5a2591401b741f48b8a Mon Sep 17 00:00:00 2001 From: lukmzig Date: Fri, 22 Aug 2025 10:50:25 +0200 Subject: [PATCH 4/5] remove unused class --- src/Security/Voter/AuthorizationVoter.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Security/Voter/AuthorizationVoter.php b/src/Security/Voter/AuthorizationVoter.php index 91d00c3f2..0d4e76114 100644 --- a/src/Security/Voter/AuthorizationVoter.php +++ b/src/Security/Voter/AuthorizationVoter.php @@ -15,7 +15,6 @@ use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NoRequestException; use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotAuthorizedException; -use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; @@ -24,12 +23,6 @@ */ final class AuthorizationVoter extends Voter { - public function __construct( - private readonly SecurityServiceInterface $securityService - - ) { - } - private const string SUPPORTED_ATTRIBUTE = 'STUDIO_API'; /** From 824b802e927d0901467a44dfdca618a4f50aa78b Mon Sep 17 00:00:00 2001 From: lukmzig Date: Fri, 22 Aug 2025 11:09:41 +0200 Subject: [PATCH 5/5] use request from event --- src/EventSubscriber/SessionCloseSubscriber.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/EventSubscriber/SessionCloseSubscriber.php b/src/EventSubscriber/SessionCloseSubscriber.php index aaf6c072b..05a558467 100644 --- a/src/EventSubscriber/SessionCloseSubscriber.php +++ b/src/EventSubscriber/SessionCloseSubscriber.php @@ -16,7 +16,7 @@ use Pimcore\Bundle\StudioBackendBundle\Util\Trait\StudioBackendPathTrait; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; @@ -29,8 +29,7 @@ use StudioBackendPathTrait; public function __construct( - private string $urlPrefix, - private RequestStack $requestStack, + private string $urlPrefix ) { } @@ -49,7 +48,7 @@ public function onLoginSuccess(LoginSuccessEvent $event): void return; } - $this->closeSessionWrite(); + $this->closeSessionWrite($request->getSession()); } public function onKernelRequest(RequestEvent $event): void @@ -59,12 +58,11 @@ public function onKernelRequest(RequestEvent $event): void return; } - $this->closeSessionWrite(); + $this->closeSessionWrite($request->getSession()); } - private function closeSessionWrite(): void + private function closeSessionWrite(SessionInterface $session): void { - $session = $this->requestStack->getSession(); if ($session->isStarted()) { $session->save(); }