[FEATURE] ext:install Relaxed permission check 30/24130/5
authorChristian Kuhn <lolli@schwarzbu.ch>
Sat, 28 Sep 2013 19:12:51 +0000 (21:12 +0200)
committerSusanne Moog <typo3@susannemoog.de>
Sat, 28 Sep 2013 20:46:56 +0000 (22:46 +0200)
The document root folder may not have correct permissions and
they can not be fixed if the owner is not the executing webserver
user.

This is a common issue during installation, currently the first
step breaks if permission on document root are not 2770 and the
folder is not owned by the executing webserver user.

The patch adds a new option "targetPermissionRelaxed" to
DirectoryNodes and uses it on the RootNode to lower the severity
if permissions are not correct. The installation will proceed
then as long as sub folders can be created.

Change-Id: Ibde9f7ff0dde14502ac24fdb73c0a2ed384119a6
Resolves: #52356
Releases: 6.2
Reviewed-on: https://review.typo3.org/24130
Reviewed-by: Anja Leichsenring
Tested-by: Anja Leichsenring
Reviewed-by: Susanne Moog
Tested-by: Susanne Moog
typo3/sysext/install/Classes/Controller/Action/Tool/FolderStructure.php
typo3/sysext/install/Classes/FolderStructure/AbstractNode.php
typo3/sysext/install/Classes/FolderStructure/DefaultFactory.php
typo3/sysext/install/Classes/FolderStructure/DirectoryNode.php
typo3/sysext/install/Classes/FolderStructure/RootNode.php
typo3/sysext/install/Tests/Unit/FolderStructure/AbstractNodeTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/DirectoryNodeTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/RootNodeTest.php

index c23dd1e..f6eb163 100644 (file)
@@ -53,10 +53,15 @@ class FolderStructure extends Action\AbstractAction implements Action\ActionInte
                /** @var $statusUtility \TYPO3\CMS\Install\Status\StatusUtility */
                $statusUtility = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\StatusUtility');
 
+               $fixableStatus = array_merge(
+                       $statusUtility->filterBySeverity($statusObjects, 'notice'),
+                       $statusUtility->filterBySeverity($statusObjects, 'warning')
+               );
+
                $this->view
                        ->assign('fixedStatus', $fixedStatusObjects)
                        ->assign('notFixableStatus', $statusUtility->filterBySeverity($statusObjects, 'error'))
-                       ->assign('fixableStatus', $statusUtility->filterBySeverity($statusObjects, 'warning'))
+                       ->assign('fixableStatus', $fixableStatus)
                        ->assign('okStatus', $statusUtility->filterBySeverity($statusObjects, 'ok'));
 
                return $this->view->render();
index d50920e..ea6741f 100644 (file)
@@ -42,6 +42,11 @@ abstract class AbstractNode {
        protected $targetPermission = NULL;
 
        /**
+        * @var boolean If TRUE, permission check and fix do not throw error level status if wrong
+        */
+       protected $targetPermissionRelaxed = FALSE;
+
+       /**
         * @var NULL|NodeInterface Parent object of this structure node
         */
        protected $parent = NULL;
@@ -70,6 +75,15 @@ abstract class AbstractNode {
        }
 
        /**
+        * Get target permission relaxed flag
+        *
+        * @return boolean TRUE if relaxed permission check should be done
+        */
+       protected function getTargetPermissionRelaxed() {
+               return $this->targetPermissionRelaxed;
+       }
+
+       /**
         * Get children
         *
         * @return array
@@ -138,12 +152,21 @@ abstract class AbstractNode {
                        $status = new Status\OkStatus();
                        $status->setTitle('Fixed permission on ' . $this->getRelativePathBelowSiteRoot() . '.');
                } else {
-                       $status = new Status\ErrorStatus();
-                       $status->setTitle('Permission change on ' . $this->getRelativePathBelowSiteRoot() . ' not successful!');
-                       $status->setMessage(
-                               'Permissions could not be changed to ' . $this->targetPermission . '. There is probably some' .
-                                       ' group or owner permission problem on the parent directory.'
-                       );
+                       if ($this->getTargetPermissionRelaxed() === TRUE) {
+                               $status = new Status\NoticeStatus();
+                               $status->setTitle('Permission change on ' . $this->getRelativePathBelowSiteRoot() . ' not successful');
+                               $status->setMessage(
+                                       'Permissions could not be changed to ' . $this->targetPermission . '. This is not a problem as' .
+                                               ' long as files and folders within this node can be written.'
+                               );
+                       } else {
+                               $status = new Status\ErrorStatus();
+                               $status->setTitle('Permission change on ' . $this->getRelativePathBelowSiteRoot() . ' not successful!');
+                               $status->setMessage(
+                                       'Permissions could not be changed to ' . $this->targetPermission . '. There is probably some' .
+                                               ' group or owner permission problem on the parent directory.'
+                               );
+                       }
                }
                return $status;
        }
index 1cbb437..a7429fe 100644 (file)
@@ -68,6 +68,7 @@ class DefaultFactory {
                        // Cut off trailing forward / from PATH_site, so root node has no trailing slash like all others
                        'name' => substr(PATH_site, 0, -1),
                        'targetPermission' => $directoryPermission,
+                       'targetPermissionRelaxed' => TRUE,
                        'children' => array(
                                array(
                                        'name' => 'typo3temp',
index 12109e4..fd1dbc0 100644 (file)
@@ -193,13 +193,23 @@ class DirectoryNode extends AbstractNode implements NodeInterface {
                        );
                        $result = $status;
                } elseif (!$this->isPermissionCorrect()) {
-                       $status = new Status\WarningStatus();
-                       $status->setTitle($this->getRelativePathBelowSiteRoot() . ' has wrong permission');
-                       $status->setMessage(
-                               'Target permission are ' . $this->targetPermission .
-                               ' but current permission are ' . $this->getCurrentPermission()
-                       );
-                       $result = $status;
+                       if ($this->getTargetPermissionRelaxed() === TRUE) {
+                               $status = new Status\NoticeStatus();
+                               $status->setTitle($this->getRelativePathBelowSiteRoot() . ' has wrong permission');
+                               $status->setMessage(
+                                       'Target permission are ' . $this->targetPermission .
+                                       ' but current permission are ' . $this->getCurrentPermission()
+                               );
+                               $result = $status;
+                       } else {
+                               $status = new Status\WarningStatus();
+                               $status->setTitle($this->getRelativePathBelowSiteRoot() . ' has wrong permission');
+                               $status->setMessage(
+                                       'Target permission are ' . $this->targetPermission .
+                                       ' but current permission are ' . $this->getCurrentPermission()
+                               );
+                               $result = $status;
+                       }
                } else {
                        $status = new Status\OkStatus();
                        $status->setTitle($this->getRelativePathBelowSiteRoot());
index 4536e3d..6661a90 100644 (file)
@@ -62,6 +62,10 @@ class RootNode extends DirectoryNode implements RootNodeInterface {
                        $this->targetPermission = $structure['targetPermission'];
                }
 
+               if (isset($structure['targetPermissionRelaxed']) && $structure['targetPermissionRelaxed'] === TRUE) {
+                       $this->targetPermissionRelaxed = TRUE;
+               }
+
                if (array_key_exists('children', $structure)) {
                        $this->createChildren($structure['children']);
                }
index b2e7c74..7a52b76 100644 (file)
@@ -73,6 +73,25 @@ class AbstractNodeTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        /**
         * @test
         */
+       public function getTargetPermissionRelaxedReturnsFalseByDefault() {
+               /** @var $node \TYPO3\CMS\Install\FolderStructure\AbstractNode|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+               $node = $this->getAccessibleMock('TYPO3\\CMS\\Install\\FolderStructure\\AbstractNode', array('dummy'), array(), '', FALSE);
+               $this->assertFalse($node->_call('getTargetPermissionRelaxed'));
+       }
+
+       /**
+        * @test
+        */
+       public function getTargetPermissionRelaxedReturnsTrueIfPermissionCheckIsRelaxed() {
+               /** @var $node \TYPO3\CMS\Install\FolderStructure\AbstractNode|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+               $node = $this->getAccessibleMock('TYPO3\\CMS\\Install\\FolderStructure\\AbstractNode', array('dummy'), array(), '', FALSE);
+               $node->_set('targetPermissionRelaxed', TRUE);
+               $this->assertTrue($node->_call('getTargetPermissionRelaxed'));
+       }
+
+       /**
+        * @test
+        */
        public function getChildrenReturnsSetChildren() {
                /** @var $node \TYPO3\CMS\Install\FolderStructure\AbstractNode|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
                $node = $this->getAccessibleMock('TYPO3\\CMS\\Install\\FolderStructure\\AbstractNode', array('dummy'), array(), '', FALSE);
@@ -225,6 +244,39 @@ class AbstractNodeTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        /**
         * @test
         */
+       public function fixPermissionReturnsNoticeStatusIfPermissionsCanNotBeChangedAndRelaxedPermissionCheckIsEnabled() {
+               if (TYPO3_OS === 'WIN') {
+                       $this->markTestSkipped('Test not available on Windows OS.');
+               }
+               if (function_exists('posix_getegid') && posix_getegid() === 0) {
+                       $this->markTestSkipped('Test skipped if run on linux as root');
+               }
+               /** @var $node \TYPO3\CMS\Install\FolderStructure\AbstractNode|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+               $node = $this->getAccessibleMock(
+                       'TYPO3\\CMS\\Install\\FolderStructure\\AbstractNode',
+                       array('isPermissionCorrect', 'getRelativePathBelowSiteRoot', 'getAbsolutePath'),
+                       array(),
+                       '',
+                       FALSE
+               );
+               $node->expects($this->any())->method('getRelativePathBelowSiteRoot')->will($this->returnValue(''));
+               $node->expects($this->once())->method('isPermissionCorrect')->will($this->returnValue(FALSE));
+               $path = PATH_site . 'typo3temp/' . uniqid('root_');
+               mkdir($path);
+               $subPath = $path . '/' . uniqid('dir_');
+               mkdir($subPath);
+               chmod($path, octdec(2000));
+               $this->testNodesToDelete[] = $path;
+               $node->expects($this->any())->method('getAbsolutePath')->will($this->returnValue($subPath));
+               $node->_set('targetPermission', '2770');
+               $node->_set('targetPermissionRelaxed', TRUE);
+               $this->assertInstanceOf('TYPO3\\CMS\\Install\\Status\\NoticeStatus', $node->_call('fixPermission'));
+               chmod($path, octdec(2770));
+       }
+
+       /**
+        * @test
+        */
        public function fixPermissionReturnsOkStatusIfPermissionCanBeFixedAndSetsPermissionToCorrectValue() {
                if (TYPO3_OS === 'WIN') {
                        $this->markTestSkipped('Test not available on Windows OS.');
index bc5ac0d..c8311f2 100644 (file)
@@ -261,6 +261,33 @@ class DirectoryNodeTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        /**
         * @test
         */
+       public function getStatusReturnsArrayWithNoticeStatusIfDirectoryExistsButPermissionAreNotCorrectAndPermissionCheckIsRelaxed() {
+               /** @var $node \TYPO3\CMS\Install\FolderStructure\DirectoryNode|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+               $node = $this->getAccessibleMock(
+                       'TYPO3\\CMS\\Install\\FolderStructure\\DirectoryNode',
+                       array('getAbsolutePath', 'exists', 'isDirectory', 'isWritable', 'isPermissionCorrect', 'getTargetPermissionRelaxed'),
+                       array(),
+                       '',
+                       FALSE
+               );
+               $path = PATH_site . 'typo3temp/' . uniqid('dir_');
+               touch ($path);
+               $this->testNodesToDelete[] = $path;
+               $node->expects($this->any())->method('getAbsolutePath')->will($this->returnValue($path));
+               $node->expects($this->any())->method('exists')->will($this->returnValue(TRUE));
+               $node->expects($this->any())->method('isDirectory')->will($this->returnValue(TRUE));
+               $node->expects($this->any())->method('isPermissionCorrect')->will($this->returnValue(FALSE));
+               $node->expects($this->any())->method('isWritable')->will($this->returnValue(TRUE));
+               $node->expects($this->once())->method('getTargetPermissionRelaxed')->will($this->returnValue(TRUE));
+               $statusArray = $node->getStatus();
+               /** @var $status \TYPO3\CMS\Install\Status\StatusInterface */
+               $status = $statusArray[0];
+               $this->assertInstanceOf('\TYPO3\CMS\Install\Status\NoticeStatus', $status);
+       }
+
+       /**
+        * @test
+        */
        public function getStatusReturnsArrayWithOkStatusIfDirectoryExistsAndPermissionAreCorrect() {
                /** @var $node \TYPO3\CMS\Install\FolderStructure\DirectoryNode|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
                $node = $this->getAccessibleMock(
index 1a866d2..aa08ebb 100644 (file)
@@ -176,6 +176,21 @@ class RootNodeTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        /**
         * @test
         */
+       public function constructorSetsRelaxedPermissionCheckIfGiven() {
+               /** @var $node \TYPO3\CMS\Install\FolderStructure\RootNode|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+               $node = $this->getAccessibleMock('TYPO3\\CMS\\Install\\FolderStructure\\RootNode', array('isWindowsOs'), array(), '', FALSE);
+               $node->expects($this->any())->method('isWindowsOs')->will($this->returnValue(FALSE));
+               $structure = array(
+                       'name' => '/foo',
+                       'targetPermissionRelaxed' => TRUE,
+               );
+               $node->__construct($structure, NULL);
+               $this->assertTrue($node->_call('getTargetPermissionRelaxed'));
+       }
+
+       /**
+        * @test
+        */
        public function constructorSetsName() {
                /** @var $node \TYPO3\CMS\Install\FolderStructure\RootNode|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
                $node = $this->getAccessibleMock('TYPO3\\CMS\\Install\\FolderStructure\\RootNode', array('isWindowsOs'), array(), '', FALSE);