[SECURITY] Refactor and fix FAL user permission handling 03/23603/2
authorHelmut Hummel <helmut.hummel@typo3.org>
Wed, 4 Sep 2013 11:23:22 +0000 (13:23 +0200)
committerOliver Hader <oliver.hader@typo3.org>
Wed, 4 Sep 2013 11:23:26 +0000 (13:23 +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.

Change-Id: I5987cc760581f8dabd12b6f0162645eaa687edea
Fixes: #51327
Releases: 6.2, 6.1, 6.0
Security-Commit: 5460c76e1373698bde82883ab4087607fee5e6f5
Security-Bulletin: TYPO3-CORE-SA-2013-003
Reviewed-on: https://review.typo3.org/23603
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 e9f1cae..18634ea 100644 (file)
@@ -26,6 +26,9 @@ namespace TYPO3\CMS\Core\Authentication;
  *
  *  This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
 /**
  * TYPO3 backend user authentication
  * Contains most of the functions used for checking permissions, authenticating users,
@@ -1452,60 +1455,90 @@ 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'] = \TYPO3\CMS\Core\Utility\GeneralUtility::uniqueList($this->dataLists['filemount_list']);
-                       if ($this->dataLists['filemount_list']) {
+                       $fileMountList = GeneralUtility::uniqueList($this->dataLists['filemount_list']);
+                       $fileMounts = GeneralUtility::trimExplode(',', $fileMountList, TRUE);
+
+                       // Limit filemounts if set in workspace record
+                       if ($this->workspace > 0 && !empty($this->workspaceRec['file_mountpoints'])) {
+                               $workspaceFileMounts = GeneralUtility::trimExplode(',', $this->workspaceRec['file_mountpoints'], TRUE);
+                               $fileMounts = array_intersect($fileMounts, $workspaceFileMounts);
+                               $fileMountList = implode(',', $fileMounts);
+                       }
+
+                       if (!empty($fileMountList)) {
                                $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 (' . $GLOBALS['TYPO3_DB']->cleanIntList($fileMountList) . ')', '', $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;
        }
 
        /**
@@ -1530,7 +1563,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
@@ -1851,6 +1883,7 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
        }
 
        /**
+<<<<<<< HEAD
         * Adds more limitations for users who are no admins
         * this was previously in workspaceInit but has now been moved to "
         *
@@ -1888,6 +1921,8 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
        }
 
        /**
+=======
+>>>>>>> 7d7d385... [SECURITY] Refactor and fix FAL user permission handling
         * 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 f0bc2d4..1a7384d 100644 (file)
@@ -127,9 +127,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