From da59154044d940ad9f97794f201776ff7bc31c55 Mon Sep 17 00:00:00 2001 From: Benni Mack Date: Sat, 24 Feb 2018 21:03:21 +0100 Subject: [PATCH] [TASK] Move workspace preview user to custom class Instead of faking an existing backend user, the workspace preview functionality (= from a link) should use an anonymous read-only use which has only access to the workspace and the page, in order to remove the hacks to allow read-access for existing backend users. This way, the hook code is getting cleaner and easier to read, and also easier to debug. Resolves: #84039 Releases: master Change-Id: Ia69d66ce25af48b86104ff724f2a3e877aa3a813 Reviewed-on: https://review.typo3.org/55888 Tested-by: TYPO3com Reviewed-by: Benni Mack Tested-by: Benni Mack --- .../PreviewUserAuthentication.php | 110 +++++++++++++++ .../workspaces/Classes/Hook/PreviewHook.php | 125 ++---------------- typo3/sysext/workspaces/ext_localconf.php | 2 - 3 files changed, 119 insertions(+), 118 deletions(-) create mode 100644 typo3/sysext/workspaces/Classes/Authentication/PreviewUserAuthentication.php diff --git a/typo3/sysext/workspaces/Classes/Authentication/PreviewUserAuthentication.php b/typo3/sysext/workspaces/Classes/Authentication/PreviewUserAuthentication.php new file mode 100644 index 000000000000..a29c946e1482 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Authentication/PreviewUserAuthentication.php @@ -0,0 +1,110 @@ + 0 + * - locked to the current page ID as webmount + * + * This class explicitly does not derive from FrontendBackendUserAuthentication. + * As this user is only meant for using against GET/cookie of "ADMCMD_prev" = clicked on a preview link + * This user cannot use any admin panel / frontend editing capabilities. + */ +class PreviewUserAuthentication extends BackendUserAuthentication +{ + public function __construct() + { + parent::__construct(); + $this->name = 'ADMCMD_prev'; + } + + /** + * Checking if a workspace is allowed for backend user + * This method is intentionally called with setTemporaryWorkspace() to check if the workspace exists. + * + * @param mixed $wsRec If integer, workspace record is looked up, if array it is seen as a Workspace record with at least uid, title, members and adminusers columns. Can be faked for workspaces uid 0 + * @param string $fields List of fields to select. Default fields are: uid,title,adminusers,members,reviewers,publish_access,stagechg_notification + * @return array|bool Output will also show how access was granted. For preview users, if the record exists, it's a go. + */ + public function checkWorkspace($wsRec, $fields = 'uid,title,adminusers,members,reviewers,publish_access,stagechg_notification') + { + // If not array, look up workspace record: + if (!is_array($wsRec)) { + switch ((int)$wsRec) { + case '0': + $wsRec = ['uid' => (int)$wsRec]; + break; + default: + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace'); + $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class)); + $wsRec = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields)) + ->from('sys_workspace') + ->where($queryBuilder->expr()->eq( + 'uid', + $queryBuilder->createNamedParameter($wsRec, \PDO::PARAM_INT) + )) + ->orderBy('title') + ->setMaxResults(1) + ->execute() + ->fetch(\PDO::FETCH_ASSOC); + } + } + // If the workspace exists in the database, the preview user is automatically a member to that workspace + if (is_array($wsRec)) { + return array_merge($wsRec, ['_ACCESS' => 'member']); + } + return false; + } + + /** + * A preview user has read-only permissions, always. + * + * @param int $perms + * @return string + */ + public function getPagePermsClause($perms) + { + if ($perms === Permission::PAGE_SHOW) { + return '1=1'; + } + return '0=1'; + } + + /** + * Has read permissions on the whole workspace, but nothing else + * + * @param array $row + * @return int + */ + public function calcPerms($row) + { + return Permission::PAGE_SHOW; + } +} diff --git a/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php b/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php index 54641e862fb5..3086344ae3ed 100644 --- a/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php +++ b/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php @@ -14,18 +14,15 @@ * The TYPO3 project - inspiring people to share! */ -use TYPO3\CMS\Backend\FrontendBackendUserAuthentication; use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; -use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction; -use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Workspaces\Authentication\PreviewUserAuthentication; /** * Hook for checking if the preview mode is activated * preview mode = show a page of a workspace without having to log in */ -class PreviewHook implements \TYPO3\CMS\Core\SingletonInterface +class PreviewHook { /** * the GET parameter to be used @@ -41,14 +38,6 @@ class PreviewHook implements \TYPO3\CMS\Core\SingletonInterface */ protected $previewConfiguration = false; - /** - * Defines whether to force read permissions on pages. - * - * @var bool - * @see \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::getPagePermsClause - */ - protected $forceReadPermissions = false; - /** * Hook after the regular BE user has been initialized * if there is a preview configuration @@ -64,69 +53,19 @@ public function initializePreviewUser(&$params, &$pObj) $this->previewConfiguration = $this->getPreviewConfiguration(); // if there is a valid BE user, and the full workspace should be previewed, the workspacePreview option should be set $workspaceUid = (int)$this->previewConfiguration['fullWorkspace']; - $workspaceRecord = null; - if ($this->previewConfiguration['BEUSER_uid'] > 0) { - // First initialize a temp user object and resolve usergroup information - /** @var FrontendBackendUserAuthentication $tempBackendUser */ - $tempBackendUser = $this->createFrontendBackendUser(); - $tempBackendUser->userTS_dontGetCached = 1; - $tempBackendUser->setBeUserByUid($this->previewConfiguration['BEUSER_uid']); - if ($tempBackendUser->user['uid']) { - $tempBackendUser->unpack_uc(); - $tempBackendUser->setTemporaryWorkspace($workspaceUid); - $tempBackendUser->user['workspace_id'] = $workspaceUid; - $tempBackendUser->fetchGroupData(); - // Handle degradation of admin users - if ($tempBackendUser->isAdmin()) { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('sys_workspace'); - - $queryBuilder->getRestrictions() - ->removeAll() - ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) - ->add(GeneralUtility::makeInstance(RootLevelRestriction::class)); - - $workspaceRecord = $queryBuilder - ->select('uid', 'adminusers', 'reviewers', 'members', 'db_mountpoints') - ->from('sys_workspace') - ->where( - $queryBuilder->expr()->eq( - 'uid', - $queryBuilder->createNamedParameter($workspaceUid, \PDO::PARAM_INT) - ) - ) - ->execute() - ->fetch(); - - // Either use configured workspace mount (of the workspace) or current page id - if (empty($tempBackendUser->groupData['webmounts'])) { - $tempBackendUser->groupData['webmounts'] = !empty($workspaceRecord['db_mountpoints']) ? $workspaceRecord['db_mountpoints'] : $pObj->id; - } - // Force add degraded admin user as member of this workspace - $workspaceRecord['members'] = 'be_users_' . $this->previewConfiguration['BEUSER_uid']; - // Force read permission for degraded admin user - $this->forceReadPermissions = true; - } - // Store only needed information in the real simulate backend - $BE_USER = $this->createFrontendBackendUser(); - $BE_USER->userTS_dontGetCached = 1; - $BE_USER->user = $tempBackendUser->user; - $BE_USER->user['admin'] = 0; - $BE_USER->groupData['webmounts'] = $tempBackendUser->groupData['webmounts']; - $BE_USER->groupList = $tempBackendUser->groupList; - $BE_USER->userGroups = $tempBackendUser->userGroups; - $BE_USER->userGroupsUID = $tempBackendUser->userGroupsUID; - $BE_USER->workspace = (int)$workspaceUid; + if ($workspaceUid > 0) { + $previewUser = GeneralUtility::makeInstance(PreviewUserAuthentication::class); + $previewUser->setWebmounts([$pObj->id]); + if ($previewUser->setTemporaryWorkspace($workspaceUid)) { + $params['BE_USER'] = $previewUser; $pObj->beUserLogin = true; } else { - $BE_USER = null; + $params['BE_USER'] = null; $pObj->beUserLogin = false; } - unset($tempBackendUser); - $params['BE_USER'] = $BE_USER; } - // Now, if "ADMCMD_noBeUser" is set, then ensure that there is no workspace preview and no BE User logged in. + // If "ADMCMD_noBeUser" is set, then ensure that there is no workspace preview and no BE User logged in. // This option is solely used to ensure that a be user can preview the live version of a page in the // workspace preview module. if (GeneralUtility::_GET('ADMCMD_noBeUser')) { @@ -137,42 +76,6 @@ public function initializePreviewUser(&$params, &$pObj) } } - /** - * Overrides the page permission clause in case an admin - * user has been degraded to a regular user without any user - * group assignments. This method is used as hook callback. - * - * @param array $parameters - * @return string - * @see \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::getPagePermsClause - */ - public function overridePagePermissionClause(array $parameters) - { - $clause = $parameters['currentClause']; - if ($parameters['perms'] & 1 && $this->forceReadPermissions) { - $clause = ' 1=1'; - } - return $clause; - } - - /** - * Overrides the row permission value in case an admin - * user has been degraded to a regular user without any user - * group assignments. This method is used as hook callback. - * - * @param array $parameters - * @return int - * @see \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::calcPerms - */ - public function overridePermissionCalculation(array $parameters) - { - $permissions = $parameters['outputPermissions']; - if (!($permissions & Permission::PAGE_SHOW) && $this->forceReadPermissions) { - $permissions |= Permission::PAGE_SHOW; - } - return $permissions; - } - /** * Looking for an ADMCMD_prev code, looks it up if found and returns configuration data. * Background: From the backend a request to the frontend to show a page, possibly with @@ -335,14 +238,4 @@ public function getPreviewLinkLifetime() $ttlHours = (int)$GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours'); return $ttlHours ? $ttlHours : 24 * 2; } - - /** - * @return FrontendBackendUserAuthentication - */ - protected function createFrontendBackendUser() - { - return GeneralUtility::makeInstance( - FrontendBackendUserAuthentication::class - ); - } } diff --git a/typo3/sysext/workspaces/ext_localconf.php b/typo3/sysext/workspaces/ext_localconf.php index 30d668faa76a..677cff6f2dcc 100644 --- a/typo3/sysext/workspaces/ext_localconf.php +++ b/typo3/sysext/workspaces/ext_localconf.php @@ -31,8 +31,6 @@ // Register hook to check for the preview mode in the FE $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['postBeUser']['version_preview'] = \TYPO3\CMS\Workspaces\Hook\PreviewHook::class . '->initializePreviewUser'; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getPagePermsClause']['version_preview'] = \TYPO3\CMS\Workspaces\Hook\PreviewHook::class . '->overridePagePermissionClause'; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['calcPerms']['version_preview'] = \TYPO3\CMS\Workspaces\Hook\PreviewHook::class . '->overridePermissionCalculation'; // Register workspaces cache if not already done in localconf.php or a previously loaded extension. if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['workspaces_cache'] ?? false)) {