Skip to content

Commit

Permalink
[TASK] Move workspace preview user to custom class
Browse files Browse the repository at this point in the history
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 <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
  • Loading branch information
bmack committed Feb 26, 2018
1 parent cb0837e commit da59154
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 118 deletions.
@@ -0,0 +1,110 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Workspaces\Authentication;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
* A backend-user like preview user with read-only permissions for a certain workspace
* is used for previewing a workspace in the frontend without having a full backend user
* available.
*
* Has
* - no user[uid]
* - cookie fetched from ADMCMD_prev cookie name
* - read-only everywhere
* - locked to a certain workspace > 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;
}
}
125 changes: 9 additions & 116 deletions typo3/sysext/workspaces/Classes/Hook/PreviewHook.php
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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')) {
Expand All @@ -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
Expand Down Expand Up @@ -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
);
}
}
2 changes: 0 additions & 2 deletions typo3/sysext/workspaces/ext_localconf.php
Expand Up @@ -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)) {
Expand Down

0 comments on commit da59154

Please sign in to comment.