Merge pull request #10628 from nextcloud/feature/10154/app-directory-permission-check
Adds a permission check for app directories
This commit is contained in:
commit
37869d9b2f
4 changed files with 217 additions and 12 deletions
|
@ -316,6 +316,23 @@
|
||||||
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
|
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(data.appDirsWithDifferentOwner.length > 0) {
|
||||||
|
var appDirsWithDifferentOwner = data.appDirsWithDifferentOwner.reduce(
|
||||||
|
function(appDirsWithDifferentOwner, directory) {
|
||||||
|
return appDirsWithDifferentOwner + '<li>' + directory + '</li>';
|
||||||
|
},
|
||||||
|
''
|
||||||
|
);
|
||||||
|
messages.push({
|
||||||
|
msg: t('core', 'Some app directories are owned by a different user than the web server one. ' +
|
||||||
|
'This may be the case if apps have been installed manually. ' +
|
||||||
|
'Check the permissions of the following app directories:')
|
||||||
|
+ '<ul>' + appDirsWithDifferentOwner + '</ul>',
|
||||||
|
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
messages.push({
|
messages.push({
|
||||||
msg: t('core', 'Error occurred while checking server setup'),
|
msg: t('core', 'Error occurred while checking server setup'),
|
||||||
|
|
|
@ -170,7 +170,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -217,7 +218,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -265,7 +267,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -311,7 +314,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -355,7 +359,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -368,6 +373,53 @@ describe('OC.SetupChecks tests', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return a warning if there are app directories with wrong permissions', function(done) {
|
||||||
|
var async = OC.SetupChecks.checkSetup();
|
||||||
|
|
||||||
|
suite.server.requests[0].respond(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
JSON.stringify({
|
||||||
|
hasFileinfoInstalled: true,
|
||||||
|
isGetenvServerWorking: true,
|
||||||
|
isReadOnlyConfig: false,
|
||||||
|
hasWorkingFileLocking: true,
|
||||||
|
hasValidTransactionIsolationLevel: true,
|
||||||
|
suggestedOverwriteCliURL: '',
|
||||||
|
isUrandomAvailable: true,
|
||||||
|
securityDocs: 'https://docs.owncloud.org/myDocs.html',
|
||||||
|
serverHasInternetConnection: true,
|
||||||
|
isMemcacheConfigured: true,
|
||||||
|
forwardedForHeadersWorking: true,
|
||||||
|
isCorrectMemcachedPHPModuleInstalled: true,
|
||||||
|
hasPassedCodeIntegrityCheck: true,
|
||||||
|
isOpcacheProperlySetup: true,
|
||||||
|
hasOpcacheLoaded: true,
|
||||||
|
isSettimelimitAvailable: true,
|
||||||
|
hasFreeTypeSupport: true,
|
||||||
|
missingIndexes: [],
|
||||||
|
outdatedCaches: [],
|
||||||
|
cronErrors: [],
|
||||||
|
cronInfo: {
|
||||||
|
diffInSeconds: 0
|
||||||
|
},
|
||||||
|
appDirsWithDifferentOwner: [
|
||||||
|
'/some/path'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
async.done(function( data, s, x ){
|
||||||
|
expect(data).toEqual([{
|
||||||
|
msg: 'Some app directories are owned by a different user than the web server one. This may be the case if apps have been installed manually. Check the permissions of the following app directories:<ul><li>/some/path</li></ul>',
|
||||||
|
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
|
||||||
|
}]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return an error if the forwarded for headers are not working', function(done) {
|
it('should return an error if the forwarded for headers are not working', function(done) {
|
||||||
var async = OC.SetupChecks.checkSetup();
|
var async = OC.SetupChecks.checkSetup();
|
||||||
|
|
||||||
|
@ -399,7 +451,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -443,7 +496,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -508,7 +562,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -553,7 +608,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -598,7 +654,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -643,7 +700,8 @@ describe('OC.SetupChecks tests', function() {
|
||||||
cronErrors: [],
|
cronErrors: [],
|
||||||
cronInfo: {
|
cronInfo: {
|
||||||
diffInSeconds: 0
|
diffInSeconds: 0
|
||||||
}
|
},
|
||||||
|
appDirsWithDifferentOwner: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,11 @@
|
||||||
namespace OC\Settings\Controller;
|
namespace OC\Settings\Controller;
|
||||||
|
|
||||||
use bantu\IniGetWrapper\IniGetWrapper;
|
use bantu\IniGetWrapper\IniGetWrapper;
|
||||||
|
use DirectoryIterator;
|
||||||
use Doctrine\DBAL\DBALException;
|
use Doctrine\DBAL\DBALException;
|
||||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use OC;
|
||||||
use OC\AppFramework\Http;
|
use OC\AppFramework\Http;
|
||||||
use OC\DB\Connection;
|
use OC\DB\Connection;
|
||||||
use OC\DB\MissingIndexInformation;
|
use OC\DB\MissingIndexInformation;
|
||||||
|
@ -529,6 +531,54 @@ Raw output
|
||||||
return function_exists('opcache_get_status');
|
return function_exists('opcache_get_status');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates through the configured app roots and
|
||||||
|
* tests if the subdirectories are owned by the same user than the current user.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getAppDirsWithDifferentOwner(): array {
|
||||||
|
$currentUser = posix_getpwuid(posix_getuid());
|
||||||
|
$appDirsWithDifferentOwner = [];
|
||||||
|
|
||||||
|
foreach (OC::$APPSROOTS as $appRoot) {
|
||||||
|
if ($appRoot['writable'] === true) {
|
||||||
|
$appDirsWithDifferentOwner = array_merge(
|
||||||
|
$appDirsWithDifferentOwner,
|
||||||
|
$this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort($appDirsWithDifferentOwner);
|
||||||
|
return $appDirsWithDifferentOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the directories for one apps directory are writable by the current user.
|
||||||
|
*
|
||||||
|
* @param array $currentUser The current user
|
||||||
|
* @param array $appRoot The app root config
|
||||||
|
* @return string[] The none writable directory paths inside the app root
|
||||||
|
*/
|
||||||
|
private function getAppDirsWithDifferentOwnerForAppRoot(array $currentUser, array $appRoot): array {
|
||||||
|
$appDirsWithDifferentOwner = [];
|
||||||
|
$appsPath = $appRoot['path'];
|
||||||
|
$appsDir = new DirectoryIterator($appRoot['path']);
|
||||||
|
|
||||||
|
foreach ($appsDir as $fileInfo) {
|
||||||
|
if ($fileInfo->isDir() && !$fileInfo->isDot()) {
|
||||||
|
$absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
|
||||||
|
$appDirUser = posix_getpwuid(fileowner($absAppPath));
|
||||||
|
if ($appDirUser !== $currentUser) {
|
||||||
|
$appDirsWithDifferentOwner[] = $absAppPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $appDirsWithDifferentOwner;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return DataResponse
|
* @return DataResponse
|
||||||
*/
|
*/
|
||||||
|
@ -565,7 +615,8 @@ Raw output
|
||||||
'isSqliteUsed' => $this->isSqliteUsed(),
|
'isSqliteUsed' => $this->isSqliteUsed(),
|
||||||
'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
|
'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
|
||||||
'isPhpMailerUsed' => $this->isPhpMailerUsed(),
|
'isPhpMailerUsed' => $this->isPhpMailerUsed(),
|
||||||
'mailSettingsDocumentation' => $this->urlGenerator->getAbsoluteURL('index.php/settings/admin')
|
'mailSettingsDocumentation' => $this->urlGenerator->getAbsoluteURL('index.php/settings/admin'),
|
||||||
|
'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
namespace Tests\Settings\Controller;
|
namespace Tests\Settings\Controller;
|
||||||
|
|
||||||
|
use OC;
|
||||||
use OC\DB\Connection;
|
use OC\DB\Connection;
|
||||||
use OC\Settings\Controller\CheckSetupController;
|
use OC\Settings\Controller\CheckSetupController;
|
||||||
use OCP\AppFramework\Http;
|
use OCP\AppFramework\Http;
|
||||||
|
@ -44,6 +45,7 @@ use OC\IntegrityCheck\Checker;
|
||||||
/**
|
/**
|
||||||
* Class CheckSetupControllerTest
|
* Class CheckSetupControllerTest
|
||||||
*
|
*
|
||||||
|
* @backupStaticAttributes
|
||||||
* @package Tests\Settings\Controller
|
* @package Tests\Settings\Controller
|
||||||
*/
|
*/
|
||||||
class CheckSetupControllerTest extends TestCase {
|
class CheckSetupControllerTest extends TestCase {
|
||||||
|
@ -74,6 +76,13 @@ class CheckSetupControllerTest extends TestCase {
|
||||||
/** @var IDateTimeFormatter|\PHPUnit_Framework_MockObject_MockObject */
|
/** @var IDateTimeFormatter|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
private $dateTimeFormatter;
|
private $dateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds a list of directories created during tests.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $dirsToRemove = [];
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
@ -135,9 +144,23 @@ class CheckSetupControllerTest extends TestCase {
|
||||||
'isSqliteUsed',
|
'isSqliteUsed',
|
||||||
'isPhpMailerUsed',
|
'isPhpMailerUsed',
|
||||||
'hasOpcacheLoaded',
|
'hasOpcacheLoaded',
|
||||||
|
'getAppDirsWithDifferentOwner',
|
||||||
])->getMock();
|
])->getMock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes directories created during tests.
|
||||||
|
*
|
||||||
|
* @after
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeTestDirectories() {
|
||||||
|
foreach ($this->dirsToRemove as $dirToRemove) {
|
||||||
|
rmdir($dirToRemove);
|
||||||
|
}
|
||||||
|
$this->dirsToRemove = [];
|
||||||
|
}
|
||||||
|
|
||||||
public function testIsInternetConnectionWorkingDisabledViaConfig() {
|
public function testIsInternetConnectionWorkingDisabledViaConfig() {
|
||||||
$this->config->expects($this->once())
|
$this->config->expects($this->once())
|
||||||
->method('getSystemValue')
|
->method('getSystemValue')
|
||||||
|
@ -425,6 +448,11 @@ class CheckSetupControllerTest extends TestCase {
|
||||||
->method('hasPassedCheck')
|
->method('hasPassedCheck')
|
||||||
->willReturn(true);
|
->willReturn(true);
|
||||||
|
|
||||||
|
$this->checkSetupController
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getAppDirsWithDifferentOwner')
|
||||||
|
->willReturn([]);
|
||||||
|
|
||||||
$expected = new DataResponse(
|
$expected = new DataResponse(
|
||||||
[
|
[
|
||||||
'isGetenvServerWorking' => true,
|
'isGetenvServerWorking' => true,
|
||||||
|
@ -465,6 +493,7 @@ class CheckSetupControllerTest extends TestCase {
|
||||||
'missingIndexes' => [],
|
'missingIndexes' => [],
|
||||||
'isPhpMailerUsed' => false,
|
'isPhpMailerUsed' => false,
|
||||||
'mailSettingsDocumentation' => 'https://server/index.php/settings/admin',
|
'mailSettingsDocumentation' => 'https://server/index.php/settings/admin',
|
||||||
|
'appDirsWithDifferentOwner' => [],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$this->assertEquals($expected, $this->checkSetupController->check());
|
$this->assertEquals($expected, $this->checkSetupController->check());
|
||||||
|
@ -571,6 +600,56 @@ class CheckSetupControllerTest extends TestCase {
|
||||||
$this->assertSame('', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated'));
|
$this->assertSame('', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setups a temp directory and some subdirectories.
|
||||||
|
* Then calls the 'getAppDirsWithDifferentOwner' method.
|
||||||
|
* The result is expected to be empty since
|
||||||
|
* there are no directories with different owners than the current user.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testAppDirectoryOwnersOk() {
|
||||||
|
$tempDir = tempnam(sys_get_temp_dir(), 'apps') . 'dir';
|
||||||
|
mkdir($tempDir);
|
||||||
|
mkdir($tempDir . DIRECTORY_SEPARATOR . 'app1');
|
||||||
|
mkdir($tempDir . DIRECTORY_SEPARATOR . 'app2');
|
||||||
|
$this->dirsToRemove[] = $tempDir . DIRECTORY_SEPARATOR . 'app1';
|
||||||
|
$this->dirsToRemove[] = $tempDir . DIRECTORY_SEPARATOR . 'app2';
|
||||||
|
$this->dirsToRemove[] = $tempDir;
|
||||||
|
OC::$APPSROOTS = [
|
||||||
|
[
|
||||||
|
'path' => $tempDir,
|
||||||
|
'url' => '/apps',
|
||||||
|
'writable' => true,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$this->assertSame(
|
||||||
|
[],
|
||||||
|
$this->invokePrivate($this->checkSetupController, 'getAppDirsWithDifferentOwner')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the check for a none existing app root that is marked as not writable.
|
||||||
|
* It's expected that no error happens since the check shouldn't apply.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testAppDirectoryOwnersNotWritable() {
|
||||||
|
$tempDir = tempnam(sys_get_temp_dir(), 'apps') . 'dir';
|
||||||
|
OC::$APPSROOTS = [
|
||||||
|
[
|
||||||
|
'path' => $tempDir,
|
||||||
|
'url' => '/apps',
|
||||||
|
'writable' => false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$this->assertSame(
|
||||||
|
[],
|
||||||
|
$this->invokePrivate($this->checkSetupController, 'getAppDirsWithDifferentOwner')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testIsBuggyNss400() {
|
public function testIsBuggyNss400() {
|
||||||
$this->config->expects($this->any())
|
$this->config->expects($this->any())
|
||||||
->method('getSystemValue')
|
->method('getSystemValue')
|
||||||
|
|
Loading…
Reference in a new issue