From aaa78f93c6770424ec541afe5236644c2258d4d0 Mon Sep 17 00:00:00 2001 From: Torben Dannhauer Date: Fri, 19 Jun 2026 11:57:35 +0200 Subject: [PATCH 1/2] fix(core): validate portal layout CSRF with session checkToken Use Horde_Session::checkToken() for portal block layout saves so the token matches the value generated by getToken() in the edit form. Drop the Token injector path that rejected valid submissions. --- lib/Horde/Core/Block/Layout/Manager.php | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/lib/Horde/Core/Block/Layout/Manager.php b/lib/Horde/Core/Block/Layout/Manager.php index 626cc792..543564a3 100644 --- a/lib/Horde/Core/Block/Layout/Manager.php +++ b/lib/Horde/Core/Block/Layout/Manager.php @@ -1,8 +1,5 @@ getInstance(Token::class); - try { - $valid = $tokenService->isValid( - (string) Util::getFormData('token'), - HordeSession::CSRF_SEED - ); - } catch (TokenException $e) { - throw new Horde_Exception('Invalid token!'); - } - if (!$valid) { - throw new Horde_Exception('Invalid token!'); - } + // Check form token (same path as Horde_Session::getToken()). + $GLOBALS['session']->checkToken((string) Util::getFormData('token')); // Get requested block type. [$newapp, $newtype] = explode(':', Util::getFormData('app')); From 42b54a01bd2ed3fbc0c2bed38ed8eb292a7546d4 Mon Sep 17 00:00:00 2001 From: Torben Dannhauer Date: Fri, 19 Jun 2026 13:43:15 +0200 Subject: [PATCH 2/2] refactor(core): inject session for portal layout CSRF check Pass Horde_Session into the block layout manager instead of reading $GLOBALS['session']. Collection resolves the session from the injector when callers do not supply one. Keeps checkToken() on the same code path as getToken() in the portal edit form while avoiding new global access in Manager. --- lib/Horde/Core/Block/Collection.php | 10 ++++++++-- lib/Horde/Core/Block/Layout/Manager.php | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/Horde/Core/Block/Collection.php b/lib/Horde/Core/Block/Collection.php index 92a8188d..f6a9736f 100644 --- a/lib/Horde/Core/Block/Collection.php +++ b/lib/Horde/Core/Block/Collection.php @@ -87,11 +87,17 @@ public function getLayout() /** * Return the layout manager for this collection. * + * @param Horde_Session|null $session Session for CSRF token checks. + * * @return Horde_Core_Block_Layout_Manager Layout manager object. */ - public function getLayoutManager() + public function getLayoutManager(?Horde_Session $session = null) { - return new Horde_Core_Block_Layout_Manager($this); + if ($session === null && isset($GLOBALS['injector'])) { + $session = $GLOBALS['injector']->getInstance('Horde_Session'); + } + + return new Horde_Core_Block_Layout_Manager($this, $session); } /** diff --git a/lib/Horde/Core/Block/Layout/Manager.php b/lib/Horde/Core/Block/Layout/Manager.php index 543564a3..bb286387 100644 --- a/lib/Horde/Core/Block/Layout/Manager.php +++ b/lib/Horde/Core/Block/Layout/Manager.php @@ -79,14 +79,23 @@ class Horde_Core_Block_Layout_Manager extends Horde_Core_Block_Layout implements */ protected $_changedCol = null; + /** + * Session instance for CSRF validation. + * + * @var Horde_Session|null + */ + protected $_session; + /** * Constructor. * * @param Horde_Core_Block_Collection $collection TODO + * @param Horde_Session|null $session Session for CSRF token checks. */ - public function __construct(Horde_Core_Block_Collection $collection) + public function __construct(Horde_Core_Block_Collection $collection, ?Horde_Session $session = null) { $this->_collection = $collection; + $this->_session = $session; $this->_editUrl = Horde::selfUrl(); $this->_layout = $collection->getLayout(); @@ -216,7 +225,10 @@ public function handle($action, $row, $col, $url = null) // Save the changes made to a block and continue editing. case 'save-resume': // Check form token (same path as Horde_Session::getToken()). - $GLOBALS['session']->checkToken((string) Util::getFormData('token')); + if ($this->_session === null) { + throw new Horde_Exception('Invalid token!'); + } + $this->_session->checkToken((string) Util::getFormData('token')); // Get requested block type. [$newapp, $newtype] = explode(':', Util::getFormData('app'));