Merge pull request #46456 from nextcloud/fix/check-datadir

pull/46663/head
Benjamin Gaussorgues 2024-08-08 22:58:03 +07:00 committed by GitHub
commit 1b8dbcbf2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 84 additions and 32 deletions

@ -42,13 +42,21 @@ class DataDirectoryProtected implements ISetupCheck {
public function run(): SetupResult {
$datadir = str_replace(\OC::$SERVERROOT . '/', '', $this->config->getSystemValue('datadirectory', ''));
$dataUrl = $this->urlGenerator->getWebroot() . '/' . $datadir . '/.ocdata';
$dataUrl = $this->urlGenerator->getWebroot() . '/' . $datadir . '/.ncdata';
$noResponse = true;
foreach ($this->runHEAD($dataUrl, httpErrors:false) as $response) {
foreach ($this->runRequest('GET', $dataUrl, [ 'httpErrors' => false ]) as $response) {
$noResponse = false;
if ($response->getStatusCode() === 200) {
return SetupResult::error($this->l10n->t('Your data directory and files are probably accessible from the internet. The .htaccess file is not working. It is strongly recommended that you configure your web server so that the data directory is no longer accessible, or move the data directory outside the web server document root.'));
if ($response->getStatusCode() < 400) {
// Read the response body
$body = $response->getBody();
if (is_resource($body)) {
$body = stream_get_contents($body, 64);
}
if (str_contains($body, '# Nextcloud data directory')) {
return SetupResult::error($this->l10n->t('Your data directory and files are probably accessible from the internet. The .htaccess file is not working. It is strongly recommended that you configure your web server so that the data directory is no longer accessible, or move the data directory outside the web server document root.'));
}
} else {
$this->logger->debug('[expected] Could not access data directory from outside.', ['url' => $dataUrl]);
}

@ -45,7 +45,7 @@ class DataDirectoryProtectedTest extends TestCase {
$this->logger = $this->createMock(LoggerInterface::class);
$this->setupcheck = $this->getMockBuilder(DataDirectoryProtected::class)
->onlyMethods(['runHEAD'])
->onlyMethods(['runRequest'])
->setConstructorArgs([
$this->l10n,
$this->config,
@ -59,16 +59,17 @@ class DataDirectoryProtectedTest extends TestCase {
/**
* @dataProvider dataTestStatusCode
*/
public function testStatusCode(array $status, string $expected): void {
$responses = array_map(function ($state) {
public function testStatusCode(array $status, string $expected, bool $hasBody): void {
$responses = array_map(function ($state) use ($hasBody) {
$response = $this->createMock(IResponse::class);
$response->expects($this->any())->method('getStatusCode')->willReturn($state);
$response->expects(($this->atMost(1)))->method('getBody')->willReturn($hasBody ? '# Nextcloud data directory' : 'something else');
return $response;
}, $status);
$this->setupcheck
->expects($this->once())
->method('runHEAD')
->method('runRequest')
->will($this->generate($responses));
$this->config
@ -82,10 +83,11 @@ class DataDirectoryProtectedTest extends TestCase {
public function dataTestStatusCode(): array {
return [
'success: forbidden access' => [[403], SetupResult::SUCCESS],
'error: can access' => [[200], SetupResult::ERROR],
'error: one forbidden one can access' => [[403, 200], SetupResult::ERROR],
'warning: connection issue' => [[], SetupResult::WARNING],
'success: forbidden access' => [[403], SetupResult::SUCCESS, true],
'success: forbidden access with redirect' => [[200], SetupResult::SUCCESS, false],
'error: can access' => [[200], SetupResult::ERROR, true],
'error: one forbidden one can access' => [[403, 200], SetupResult::ERROR, true],
'warning: connection issue' => [[], SetupResult::WARNING, true],
];
}
@ -95,7 +97,7 @@ class DataDirectoryProtectedTest extends TestCase {
$this->setupcheck
->expects($this->once())
->method('runHEAD')
->method('runRequest')
->will($this->generate([]));
$this->config

@ -1793,6 +1793,7 @@ return array(
'OC\\Repair\\NC22\\LookupServerSendCheck' => $baseDir . '/lib/private/Repair/NC22/LookupServerSendCheck.php',
'OC\\Repair\\NC24\\AddTokenCleanupJob' => $baseDir . '/lib/private/Repair/NC24/AddTokenCleanupJob.php',
'OC\\Repair\\NC25\\AddMissingSecretJob' => $baseDir . '/lib/private/Repair/NC25/AddMissingSecretJob.php',
'OC\\Repair\\NC30\\RemoveLegacyDatadirFile' => $baseDir . '/lib/private/Repair/NC30/RemoveLegacyDatadirFile.php',
'OC\\Repair\\OldGroupMembershipShares' => $baseDir . '/lib/private/Repair/OldGroupMembershipShares.php',
'OC\\Repair\\Owncloud\\CleanPreviews' => $baseDir . '/lib/private/Repair/Owncloud/CleanPreviews.php',
'OC\\Repair\\Owncloud\\CleanPreviewsBackgroundJob' => $baseDir . '/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php',

@ -1826,6 +1826,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Repair\\NC22\\LookupServerSendCheck' => __DIR__ . '/../../..' . '/lib/private/Repair/NC22/LookupServerSendCheck.php',
'OC\\Repair\\NC24\\AddTokenCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC24/AddTokenCleanupJob.php',
'OC\\Repair\\NC25\\AddMissingSecretJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC25/AddMissingSecretJob.php',
'OC\\Repair\\NC30\\RemoveLegacyDatadirFile' => __DIR__ . '/../../..' . '/lib/private/Repair/NC30/RemoveLegacyDatadirFile.php',
'OC\\Repair\\OldGroupMembershipShares' => __DIR__ . '/../../..' . '/lib/private/Repair/OldGroupMembershipShares.php',
'OC\\Repair\\Owncloud\\CleanPreviews' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/CleanPreviews.php',
'OC\\Repair\\Owncloud\\CleanPreviewsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php',

@ -41,6 +41,7 @@ use OC\Repair\NC21\ValidatePhoneNumber;
use OC\Repair\NC22\LookupServerSendCheck;
use OC\Repair\NC24\AddTokenCleanupJob;
use OC\Repair\NC25\AddMissingSecretJob;
use OC\Repair\NC30\RemoveLegacyDatadirFile;
use OC\Repair\OldGroupMembershipShares;
use OC\Repair\Owncloud\CleanPreviews;
use OC\Repair\Owncloud\DropAccountTermsTable;
@ -187,6 +188,7 @@ class Repair implements IOutput {
\OCP\Server::get(AddMetadataGenerationJob::class),
\OCP\Server::get(AddAppConfigLazyMigration::class),
\OCP\Server::get(RepairLogoDimension::class),
\OCP\Server::get(RemoveLegacyDatadirFile::class),
];
}

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Repair\NC30;
use OCP\IConfig;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class RemoveLegacyDatadirFile implements IRepairStep {
public function __construct(
private IConfig $config,
) {
}
public function getName(): string {
return 'Remove legacy ".ocdata" file';
}
public function run(IOutput $output): void {
$ocdata = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata';
if (file_exists($ocdata)) {
unlink($ocdata);
}
}
}

@ -360,9 +360,12 @@ class Setup {
Installer::installShippedApps(false, $output);
// create empty file in data dir, so we can later find
// out that this is indeed an ownCloud data directory
// out that this is indeed a Nextcloud data directory
$this->outputDebug($output, 'Setup data directory');
file_put_contents($config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
file_put_contents(
$config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/.ncdata',
"# Nextcloud data directory\n# Do not change this file",
);
// Update .htaccess files
self::updateHtaccess();

@ -208,9 +208,12 @@ class Updater extends BasicEmitter {
}
// create empty file in data dir, so we can later find
// out that this is indeed an ownCloud data directory
// out that this is indeed a Nextcloud data directory
// (in case it didn't exist before)
file_put_contents($this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
file_put_contents(
$this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/.ncdata',
"# Nextcloud data directory\n# Do not change this file",
);
// pre-upgrade repairs
$repair = \OCP\Server::get(Repair::class);

@ -783,7 +783,7 @@ class Manager extends PublicEmitter implements IUserManager {
'.htaccess',
'files_external',
'__groupfolders',
'.ocdata',
'.ncdata',
'owncloud.log',
'nextcloud.log',
'updater.log',

@ -687,7 +687,7 @@ class OC_Util {
/**
* Check that the data directory exists and is valid by
* checking the existence of the ".ocdata" file.
* checking the existence of the ".ncdata" file.
*
* @param string $dataDirectory data directory path
* @return array errors found
@ -701,11 +701,11 @@ class OC_Util {
'hint' => $l->t('Check the value of "datadirectory" in your configuration.')
];
}
if (!file_exists($dataDirectory . '/.ocdata')) {
if (!file_exists($dataDirectory . '/.ncdata')) {
$errors[] = [
'error' => $l->t('Your data directory is invalid.'),
'hint' => $l->t('Ensure there is a file called ".ocdata"' .
' in the root of the data directory.')
'hint' => $l->t('Ensure there is a file called "%1$s" in the root of the data directory. It should have the content: "%2$s"', ['.ncdata', '# Nextcloud data directory']),
];
}
return $errors;

@ -39,13 +39,13 @@ class UtilCheckServerTest extends \Test\TestCase {
$this->datadir = \OC::$server->getTempManager()->getTemporaryFolder();
file_put_contents($this->datadir . '/.ocdata', '');
file_put_contents($this->datadir . '/.ncdata', '# Nextcloud data directory');
\OC::$server->getSession()->set('checkServer_succeeded', false);
}
protected function tearDown(): void {
// clean up
@unlink($this->datadir . '/.ocdata');
@unlink($this->datadir . '/.ncdata');
parent::tearDown();
}
@ -66,9 +66,9 @@ class UtilCheckServerTest extends \Test\TestCase {
*/
public function testCheckServerSkipDataDirValidityOnSetup() {
// simulate old version that didn't have it
unlink($this->datadir . '/.ocdata');
unlink($this->datadir . '/.ncdata');
// even though ".ocdata" is missing, the error isn't
// even though ".ncdata" is missing, the error isn't
// triggered to allow setup to run
$result = \OC_Util::checkServer($this->getConfig([
'installed' => false
@ -83,7 +83,7 @@ class UtilCheckServerTest extends \Test\TestCase {
*/
public function testCheckServerSkipDataDirValidityOnUpgrade() {
// simulate old version that didn't have it
unlink($this->datadir . '/.ocdata');
unlink($this->datadir . '/.ncdata');
$session = \OC::$server->getSession();
$oldCurrentVersion = $session->get('OC_Version');
@ -91,7 +91,7 @@ class UtilCheckServerTest extends \Test\TestCase {
// upgrade condition to simulate needUpgrade() === true
$session->set('OC_Version', [6, 0, 0, 2]);
// even though ".ocdata" is missing, the error isn't
// even though ".ncdata" is missing, the error isn't
// triggered to allow for upgrade
$result = \OC_Util::checkServer($this->getConfig([
'installed' => true,
@ -105,7 +105,7 @@ class UtilCheckServerTest extends \Test\TestCase {
/**
* Test that checkDataDirectoryValidity returns no error
* when ".ocdata" is present.
* when ".ncdata" is present.
*/
public function testCheckDataDirValidity() {
$result = \OC_Util::checkDataDirectoryValidity($this->datadir);
@ -114,10 +114,10 @@ class UtilCheckServerTest extends \Test\TestCase {
/**
* Test that checkDataDirectoryValidity and checkServer
* both return an error when ".ocdata" is missing.
* both return an error when ".ncdata" is missing.
*/
public function testCheckDataDirValidityWhenFileMissing() {
unlink($this->datadir . '/.ocdata');
unlink($this->datadir . '/.ncdata');
$result = \OC_Util::checkDataDirectoryValidity($this->datadir);
$this->assertEquals(1, count($result));

@ -162,7 +162,7 @@ class UtilTest extends \Test\TestCase {
public function testCheckDataDirectoryValidity() {
$dataDir = \OC::$server->getTempManager()->getTemporaryFolder();
touch($dataDir . '/.ocdata');
touch($dataDir . '/.ncdata');
$errors = \OC_Util::checkDataDirectoryValidity($dataDir);
$this->assertEmpty($errors);
\OCP\Files::rmdirr($dataDir);