[SECURITY] Refactor and fix FAL user permission handling 97/23597/2
authorHelmut Hummel <helmut.hummel@typo3.org>
Wed, 4 Sep 2013 11:14:05 +0000 (13:14 +0200)
committerOliver Hader <oliver.hader@typo3.org>
Wed, 4 Sep 2013 11:14:12 +0000 (13:14 +0200)
* User permissions are only applied to storage objects
  that are attached to a member variable of
  BackendUserAuthentication. This is error prone
  and leads to insufficient (no) checks if the code
  fetches a storage directly from the factory
  (like edit document controller does)
  Instead, apply the permissions by using a signal
  in StorageFactory directly after the storage object
  is built.

* Refactor the mount point handling, especially the
  user and group home directories, which was completely
  broken after the introduction of FAL. File mounts
  are now also applied to the storage on creation.

* Make fallback storage 0 read only and not browsable.

Fixes: #51327
Releases: 6.2, 6.1, 6.0
Change-Id: If1fa18486cf051a7f4489e36691d42786386df63
Security-Commit: 936dbaf5d16acd36b668dcf033eb343fc5e2f7bf
Security-Bulletin: TYPO3-CORE-SA-2013-003
Reviewed-on: https://review.typo3.org/23597
Reviewed-by: Oliver Hader
Tested-by: Oliver Hader
typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
typo3/sysext/core/Classes/Resource/ResourceFactory.php
typo3/sysext/core/Classes/Resource/Security/StoragePermissionsAspect.php [new file with mode: 0644]
typo3/sysext/core/ext_localconf.php [new file with mode: 0644]

index 142d268..569266c 100644 (file)
@@ -1455,60 +1455,88 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
                                $this->fileStorages[$storageObject->getUid()] = $storageObject;
                        }
                } else {
-                       // If userHomePath is set, we attempt to mount it
-                       if ($GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath']) {
-                               list($userHomeStorageUid, $userHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'], 2);
-                               $userHomeStorageUid = intval($userHomeStorageUid);
-                               if ($userHomeStorageUid > 0) {
-                                       $storageObject = $storageRepository->findByUid($userHomeStorageUid);
-                                       // First try and mount with [uid]_[username]
-                                       $userHomeFilterIdentifier = $userHomeFilter . $this->user['uid'] . '_' . $this->user['username'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'];
-                                       $didMount = $storageObject->addFileMount($userHomeFilterIdentifier);
-                                       // If that failed, try and mount with only [uid]
-                                       if (!$didMount) {
-                                               $userHomeFilterIdentifier = $userHomeFilter . $this->user['uid'] . '_' . $this->user['username'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'];
-                                               $storageObject->addFileMount($userHomeFilterIdentifier);
-                                       }
-                                       $this->fileStorages[$storageObject->getUid()] = $storageObject;
-                               }
-                       }
-                       // Mount group home-dirs
-                       if (($this->user['options'] & 2) == 2 && $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'] != '') {
-                               // If groupHomePath is set, we attempt to mount it
-                               list($groupHomeStorageUid, $groupHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'], 2);
-                               $groupHomeStorageUid = intval($groupHomeStorageUid);
-                               if ($groupHomeStorageUid > 0) {
-                                       $storageObject = $storageRepository->findByUid($groupHomeStorageUid);
-                                       foreach ($this->userGroups as $groupUid => $groupData) {
-                                               $groupHomeFilterIdentifier = $groupHomeFilter . $groupData['uid'];
-                                               $storageObject->addFileMount($groupHomeFilterIdentifier);
-                                       }
+                       // Regular users only have storages that are defined in their filemounts
+                       // Permissions and file mounts for the storage are added in StoragePermissionAspect
+                       foreach ($this->getFileMountRecords() as $row) {
+                               if (!array_key_exists(intval($row['base']), $this->fileStorages)) {
+                                       $storageObject = $storageRepository->findByUid($row['base']);
                                        $this->fileStorages[$storageObject->getUid()] = $storageObject;
                                }
                        }
+               }
+
+               // This has to be called always in order to set certain filters
+               $this->evaluateUserSpecificFileFilterSettings();
+       }
+
+       /**
+        * Returns an array of file mount records, taking workspaces and user home and group home directories into account
+        * Needs to be called AFTER the groups have been loaded.
+        *
+        * @return array
+        * @internal
+        */
+       public function getFileMountRecords() {
+               static $fileMountRecords = array();
+
+               if (empty($fileMountRecords)) {
                        // Processing filemounts (both from the user and the groups)
-                       $this->dataLists['filemount_list'] = GeneralUtility::uniqueList($this->dataLists['filemount_list']);
-                       if ($this->dataLists['filemount_list']) {
+                       $fileMounts = array_unique(GeneralUtility::intExplode(',', $this->dataLists['filemount_list'], TRUE));
+
+                       // Limit filemounts if set in workspace record
+                       if ($this->workspace > 0 && !empty($this->workspaceRec['file_mountpoints'])) {
+                               $workspaceFileMounts = GeneralUtility::intExplode(',', $this->workspaceRec['file_mountpoints'], TRUE);
+                               $fileMounts = array_intersect($fileMounts, $workspaceFileMounts);
+                       }
+
+                       if (!empty($fileMounts)) {
                                $orderBy = $GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'] ? $GLOBALS['TYPO3_DB']->stripOrderBy($GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby']) : 'sorting';
-                               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_filemounts', 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . $this->dataLists['filemount_list'] . ')', '', $orderBy);
-                               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
-                                       $storageObject = $storageRepository->findByUid($row['base']);
-                                       $storageObject->addFileMount($row['path'], $row);
-                                       $this->fileStorages[$storageObject->getUid()] = $storageObject;
+                               $fileMountRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_filemounts', 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $fileMounts) . ')', '', $orderBy);
+                       }
+
+                       // Personal or Group filemounts are not accessible if file mount list is set in workspace record
+                       if ($this->workspace <= 0 || empty($this->workspaceRec['file_mountpoints'])) {
+                               // If userHomePath is set, we attempt to mount it
+                               if ($GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath']) {
+                                       list($userHomeStorageUid, $userHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'], 2);
+                                       $userHomeStorageUid = intval($userHomeStorageUid);
+                                       $userHomeFilter = '/' . ltrim($userHomeFilter, '/');
+                                       if ($userHomeStorageUid > 0) {
+                                               // Try and mount with [uid]_[username]
+                                               $fileMountRecords[] = array(
+                                                       'base' => $userHomeStorageUid,
+                                                       'title' => $this->user['username'],
+                                                       'path' => $userHomeFilter . $this->user['uid'] . '_' . $this->user['username'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir']
+                                               );
+                                               // Try and mount with only [uid]
+                                               $fileMountRecords[] = array(
+                                                       'base' => $userHomeStorageUid,
+                                                       'title' => $this->user['username'],
+                                                       'path' => $userHomeFilter . $this->user['uid'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir']
+                                               );
+                                       }
+                               }
+
+                               // Mount group home-dirs
+                               if (($this->user['options'] & 2) == 2 && $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'] != '') {
+                                       // If groupHomePath is set, we attempt to mount it
+                                       list($groupHomeStorageUid, $groupHomeFilter) = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'], 2);
+                                       $groupHomeStorageUid = intval($groupHomeStorageUid);
+                                       $groupHomeFilter = '/' . ltrim($groupHomeFilter, '/');
+                                       if ($groupHomeStorageUid > 0) {
+                                               foreach ($this->userGroups as $groupData) {
+                                                       $fileMountRecords[] = array(
+                                                               'base' => $groupHomeStorageUid,
+                                                               'title' => $groupData['title'],
+                                                               'path' => $groupHomeFilter . $groupData['uid']
+                                                       );
+                                               }
+                                       }
                                }
-                               $GLOBALS['TYPO3_DB']->sql_free_result($res);
                        }
                }
-               // Injects the users' permissions to each storage
-               foreach ($this->fileStorages as $storageObject) {
-                       $storagePermissions = $this->getFilePermissionsForStorage($storageObject);
-                       $storageObject->setUserPermissions($storagePermissions);
-               }
-               // more narrowing down through the workspace
-               $this->initializeFileStoragesForWorkspace();
-               // this has to be called always in order to set certain filters
-               // @todo Should be in BE_USER object then
-               $GLOBALS['BE_USER']->evaluateUserSpecificFileFilterSettings();
+
+               return $fileMountRecords;
        }
 
        /**
@@ -1533,7 +1561,6 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         * but only when needed
         *
         * @return void
-        * @todo Should be in BE_USER object then
         */
        public function evaluateUserSpecificFileFilterSettings() {
                // Add the option for also displaying the non-hidden files
@@ -1854,43 +1881,6 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
        }
 
        /**
-        * Adds more limitations for users who are no admins
-        * this was previously in workspaceInit but has now been moved to "
-        *
-        * @return void
-        */
-       protected function initializeFileStoragesForWorkspace() {
-               // Filtering the file mountpoints
-               // if there some selected in the workspace record
-               if ($this->workspace > 0) {
-                       $storageFiltersInWorkspace = trim($this->workspaceRec['file_mountpoints']);
-                       // no custom filemounts that should serve as filter or user is admin
-                       // so all user mountpoints are re-applied
-                       if (!$this->isAdmin() && $storageFiltersInWorkspace !== '') {
-                               // empty the fileStorages (will be re-applied later)
-                               $existingFileStoragesOfUser = $this->fileStorages;
-                               $this->fileStorages = array();
-                               $storageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
-                               // Fetching all filemounts from the workspace
-                               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_filemounts', 'deleted = 0 AND hidden = 0 AND pid = 0 AND uid IN (' . $GLOBALS['TYPO3_DB']->cleanIntList($storageFiltersInWorkspace) . ')');
-                               // add every filemount of this workspace record
-                               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
-                                       // get the added entry, and check if it was in the users' original filemounts
-                                       // if not, remove it from the new filemount list again
-                                       // see self::addFileMount
-                                       // TODO: check if the filter is narrowing down the existing user
-                                       $storageObject = $storageRepository->findByUid($row['base']);
-                                       if (isset($existingFileStoragesOfUser[$storageObject->getUid()])) {
-                                               $storageObject->addFileMount($row['path']);
-                                               $this->fileStorages[$storageObject->getUid()] = $storageObject;
-                                       }
-                               }
-                               $GLOBALS['TYPO3_DB']->sql_free_result($res);
-                       }
-               }
-       }
-
-       /**
         * Checking if a workspace is allowed for backend user
         *
         * @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 and -1 (online and offline)
index 6a5b8a0..c955681 100644 (file)
@@ -139,9 +139,9 @@ class ResourceFactory implements \TYPO3\CMS\Core\SingletonInterface {
                                        // legacy code
                                        'configuration' => '',
                                        'is_online' => TRUE,
-                                       'is_browsable' => TRUE,
+                                       'is_browsable' => FALSE,
                                        'is_public' => TRUE,
-                                       'is_writable' => TRUE
+                                       'is_writable' => FALSE
                                );
                                $storageConfiguration = array(
                                        'basePath' => '/',
diff --git a/typo3/sysext/core/Classes/Resource/Security/StoragePermissionsAspect.php b/typo3/sysext/core/Classes/Resource/Security/StoragePermissionsAspect.php
new file mode 100644 (file)
index 0000000..2ea9e9f
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+namespace TYPO3\CMS\Core\Resource\Security;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2013 Helmut Hummel <helmut.hummel@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\ResourceStorage;
+
+/**
+ * Class StoragePermissionsAspect
+ *
+ * We do not have AOP in TYPO3 for now, thus the acspect which
+ * deals with resource security is a slot which reacts on a signal
+ * on storage object creation.
+ *
+ * The aspect injects user permissions and mount points into the storage
+ * based on user or group configuration.
+ *
+ * @package TYPO3\CMS\Core\Resource\Security
+ */
+class StoragePermissionsAspect {
+
+       /**
+        * @var BackendUserAuthentication
+        */
+       protected $backendUserAuthentication;
+
+       /**
+        * @var array
+        */
+       protected $defaultStorageZeroPermissions = array(
+               'readFile' => TRUE
+       );
+
+
+       /**
+        * @param BackendUserAuthentication|null $backendUserAuthentication
+        */
+       public function __construct($backendUserAuthentication = NULL) {
+               $this->backendUserAuthentication = $backendUserAuthentication ?: $GLOBALS['BE_USER'];
+       }
+
+       /**
+        * The slot for the signal in ResourceFactory where storage objects are created
+        *
+        * @param ResourceFactory $resourceFactory
+        * @param ResourceStorage $storage
+        */
+       public function addUserPermissionsToStorage(ResourceFactory $resourceFactory, ResourceStorage $storage) {
+               if (!$this->backendUserAuthentication->isAdmin()) {
+                       $storage->setEvaluatePermissions(TRUE);
+                       if ($storage->getUid() > 0) {
+                               $storage->setUserPermissions($this->backendUserAuthentication->getFilePermissionsForStorage($storage));
+                       } else {
+                               $storage->setUserPermissions($this->defaultStorageZeroPermissions);
+                       }
+                       $this->addFileMountsToStorage($storage);
+               }
+       }
+
+       /**
+        * Adds file mounts from the user's file mount records
+        *
+        * @param ResourceStorage $storage
+        */
+       protected function addFileMountsToStorage(ResourceStorage $storage) {
+               foreach ($this->backendUserAuthentication->getFileMountRecords() as $fileMountRow) {
+                       if ((int)$fileMountRow['base'] === (int)$storage->getUid()) {
+                               try {
+                                       $storage->addFileMount($fileMountRow['path'], $fileMountRow);
+                               } catch (FolderDoesNotExistException $e) {
+                                       // That file mount does not seem to be valid, fail silently
+                               }
+                       }
+               }
+       }
+}
\ No newline at end of file
diff --git a/typo3/sysext/core/ext_localconf.php b/typo3/sysext/core/ext_localconf.php
new file mode 100644 (file)
index 0000000..a683ba6
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+if (!defined('TYPO3_MODE')) {
+       die('Access denied.');
+}
+
+if (TYPO3_MODE === 'BE') {
+       \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher')->connect(
+               'TYPO3\\CMS\\Core\\Resource\\ResourceFactory',
+               \TYPO3\CMS\Core\Resource\ResourceFactory::SIGNAL_PostProcessStorage,
+               'TYPO3\\CMS\\Core\\Resource\\Security\\StoragePermissionsAspect',
+               'addUserPermissionsToStorage'
+       );
+}
+
+?>
\ No newline at end of file