From ee7667f5018457154d89900eb477d83b3691dbfa Mon Sep 17 00:00:00 2001 From: Benni Mack <benni@typo3.org> Date: Wed, 7 Oct 2020 12:22:21 +0200 Subject: [PATCH] [!!!][FEATURE] Always initialize frontend groups after FE user In previous TYPO3 versions (due to historic reasons) the frontend groups were always resolved within TSFE when a page and the rootline was resolved. However, this left the actual Frontend User, which is initialized at the very beginning of a frontend request, in an incomplete state: A user was (correctly) found and "logged in", but the groups were resolved at a later point. This was due to the fact that the Admin Panel allowed to "include hidden records" which also considered fe_groups, and thus be set later-on. This change now moved the resolving of the groups (and setting the right frontend.user aspect) right after the user resolving. This means that the groups are now available much earlier, and not bound to the TSFE instance anymore, allowing to use Middlewares much more professionally without depending on TSFE for custom Routing / APIs. Future options: It would even be possible to filter out PageRouter pages that are not available, which would make the Router itself faster. Resolves: #92562 Releases: master Change-Id: Ia522697433049b0e549f3c65caf6757053ff37e1 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/66066 Tested-by: TYPO3com <noreply@typo3.com> Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> Reviewed-by: Markus Klein <markus.klein@typo3.org> Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> --- ...lvedDirectlyAfterTheFrontendUserItself.rst | 44 +++++++++++++++++ .../FrontendUserAuthentication.php | 44 +++++++++++++++++ .../TypoScriptFrontendController.php | 48 ++----------------- .../Middleware/FrontendUserAuthenticator.php | 20 +++----- 4 files changed, 99 insertions(+), 57 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-92562-FrontendGroupsResolvedDirectlyAfterTheFrontendUserItself.rst diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-92562-FrontendGroupsResolvedDirectlyAfterTheFrontendUserItself.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-92562-FrontendGroupsResolvedDirectlyAfterTheFrontendUserItself.rst new file mode 100644 index 000000000000..ab1c8d957629 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-92562-FrontendGroupsResolvedDirectlyAfterTheFrontendUserItself.rst @@ -0,0 +1,44 @@ +.. include:: ../../Includes.txt + +================================================================================== +Feature: #92562 - Frontend groups resolved directly after the Frontend User itself +================================================================================== + +See :issue:`92562` + +Description +=========== + +For legacy purposes, the valid frontend user groups were added while resolving +the root line in TypoScriptFrontendController. This is much later in the frontend +request process than the preparation of the Frontend User, which is resolved by +the session or form credentials. + +There are several reasons for the historic behavior: +* Special functionality like "pages.fe_login_mode" which can override groups + based on the current root line +* Previewing frontend user groups via the Admin Panel for backend users + +However, this historic behavior led to inconsistencies, especially with the +Context API to retrieve the correct usergroups in PSR-15 middlewares to build custom APIs. + +In addition, this functionality is now extracted from TSFE and into the middleware, +further decoupling the User authentication from the TSFE object. + + +Impact +====== + +When the PSR-15 middleware is setting up the FrontendUserAuthentication object +at a very early stage of the frontend request, the groups are resolved directly +afterwards, leaving the FrontendUserAuthentication object in a consistent state +for further middlewares to work with the appropriate groups. + +Please note that any existing custom AuthenticationService for resolving frontend +user groups, which might rely on a valid TSFE object will have to be evaluated +if it still works in TYPO3 v11. + +Also: Further middlewares and TSFE itself can still override the assigned groups +due to previewing behavior. + +.. index:: ext:frontend diff --git a/typo3/sysext/frontend/Classes/Authentication/FrontendUserAuthentication.php b/typo3/sysext/frontend/Classes/Authentication/FrontendUserAuthentication.php index b17fb2b7672a..eddd0c339cf2 100644 --- a/typo3/sysext/frontend/Classes/Authentication/FrontendUserAuthentication.php +++ b/typo3/sysext/frontend/Classes/Authentication/FrontendUserAuthentication.php @@ -18,6 +18,7 @@ namespace TYPO3\CMS\Frontend\Authentication; use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication; use TYPO3\CMS\Core\Authentication\AuthenticationService; use TYPO3\CMS\Core\Configuration\Features; +use TYPO3\CMS\Core\Context\UserAspect; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException; use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; @@ -382,6 +383,49 @@ class FrontendUserAuthentication extends AbstractUserAuthentication return !empty($this->groupData['uid']) ? count($this->groupData['uid']) : 0; } + /** + * Initializes the front-end user groups for the context API, + * based on the user groups and the logged-in state. + * + * @param bool $respectUserGroups used with the $TSFE->loginAllowedInBranch flag to disable the inclusion of the users' groups + * @return UserAspect + */ + public function createUserAspect(bool $respectUserGroups = true): UserAspect + { + $userGroups = [0]; + $isUserAndGroupSet = is_array($this->user) && !empty($this->groupData['uid']); + if ($isUserAndGroupSet) { + // group -2 is not an existing group, but denotes a 'default' group when a user IS logged in. + // This is used to let elements be shown for all logged in users! + $userGroups[] = -2; + $groupsFromUserRecord = $this->groupData['uid']; + } else { + // group -1 is not an existing group, but denotes a 'default' group when not logged in. + // This is used to let elements be hidden, when a user is logged in! + $userGroups[] = -1; + if ($respectUserGroups) { + // For cases where logins are not banned from a branch usergroups can be set based on IP masks so we should add the usergroups uids. + $groupsFromUserRecord = $this->groupData['uid']; + } else { + // Set to blank since we will NOT risk any groups being set when no logins are allowed! + $groupsFromUserRecord = []; + } + } + // Make unique and sort the groups + $groupsFromUserRecord = array_unique($groupsFromUserRecord); + if ($respectUserGroups && !empty($groupsFromUserRecord)) { + sort($groupsFromUserRecord); + $userGroups = array_merge($userGroups, array_map('intval', $groupsFromUserRecord)); + } + + // For every 60 seconds the is_online timestamp for a logged-in user is updated + if ($isUserAndGroupSet) { + $this->updateOnlineTimestamp(); + } + + $this->logger->debug('Valid frontend usergroups: ' . implode(',', $userGroups)); + return GeneralUtility::makeInstance(UserAspect::class, $this, $userGroups); + } /** * Returns the parsed TSconfig for the fe_user * The TSconfig will be cached in $this->userTS. diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php index 4cf890f55633..1cb3770e1eb4 100644 --- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php +++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php @@ -658,43 +658,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface */ public function initUserGroups() { - $userGroups = [0]; - // no matter if we have an active user we try to fetch matching groups which can be set without an user (simulation for instance!) - $this->fe_user->fetchGroupData(); - $isUserAndGroupSet = is_array($this->fe_user->user) && !empty($this->fe_user->groupData['uid']); - if ($isUserAndGroupSet) { - // group -2 is not an existing group, but denotes a 'default' group when a user IS logged in. - // This is used to let elements be shown for all logged in users! - $userGroups[] = -2; - $groupsFromUserRecord = $this->fe_user->groupData['uid']; - } else { - // group -1 is not an existing group, but denotes a 'default' group when not logged in. - // This is used to let elements be hidden, when a user is logged in! - $userGroups[] = -1; - if ($this->loginAllowedInBranch) { - // For cases where logins are not banned from a branch usergroups can be set based on IP masks so we should add the usergroups uids. - $groupsFromUserRecord = $this->fe_user->groupData['uid']; - } else { - // Set to blank since we will NOT risk any groups being set when no logins are allowed! - $groupsFromUserRecord = []; - } - } - // Clean up. - // Make unique and sort the groups - $groupsFromUserRecord = array_unique($groupsFromUserRecord); - if (!empty($groupsFromUserRecord) && !$this->loginAllowedInBranch_mode) { - sort($groupsFromUserRecord); - $userGroups = array_merge($userGroups, array_map('intval', $groupsFromUserRecord)); - } - - $this->context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $this->fe_user, $userGroups)); - - // For every 60 seconds the is_online timestamp for a logged-in user is updated - if ($isUserAndGroupSet) { - $this->fe_user->updateOnlineTimestamp(); - } - - $this->logger->debug('Valid usergroups for TSFE: ' . implode(',', $userGroups)); + $userAspect = $this->fe_user->createUserAspect((bool)$this->loginAllowedInBranch); + $this->context->setAspect('frontend.user', $userAspect); } /** @@ -777,15 +742,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface $this->loginAllowedInBranch = $this->checkIfLoginAllowedInBranch(); // Logins are not allowed, but there is a login, so will we run this. if (!$this->loginAllowedInBranch && $this->isUserOrGroupSet()) { + // Clear out user, and the group will be re-set in >initUserGroups() due to + // $this->loginAllowedInBranch = false if ($this->loginAllowedInBranch_mode === 'all') { - // Clear out user and group: $this->fe_user->hideActiveLogin(); - $userGroups = [0, -1]; - } else { - $userGroups = [0, -2]; } - $this->context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $this->fe_user, $userGroups)); - // Fetching the id again, now with the preview settings reset. + // Fetching the id again, now with the preview settings reset and respecting $this->loginAllowedInBranch = false $this->fetch_the_id($request); } // Final cleaning. diff --git a/typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php b/typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php index 99e92e9efdfa..63025b4f9312 100644 --- a/typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php +++ b/typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php @@ -21,9 +21,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication; use TYPO3\CMS\Core\Context\Context; -use TYPO3\CMS\Core\Context\UserAspect; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication; @@ -69,9 +67,13 @@ class FrontendUserAuthenticator implements MiddlewareInterface // Authenticate now $frontendUser->start(); $frontendUser->unpack_uc(); + // no matter if we have an active user we try to fetch matching groups which can + // be set without an user (simulation for instance!) + $frontendUser->fetchGroupData(); - // Register the frontend user as aspect and within the session - $this->setFrontendUserAspect($frontendUser); + // Register the frontend user as aspect and within the request + $userAspect = $frontendUser->createUserAspect(); + $this->context->setAspect('frontend.user', $userAspect); $request = $request->withAttribute('frontend.user', $frontendUser); $response = $handler->handle($request); @@ -120,14 +122,4 @@ class FrontendUserAuthenticator implements MiddlewareInterface } return $request; } - - /** - * Register the frontend user as aspect - * - * @param AbstractUserAuthentication $user - */ - protected function setFrontendUserAspect(AbstractUserAuthentication $user) - { - $this->context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $user)); - } } -- GitLab