From 1f71285137322200ab93c5b3d2ecfb81e0ed13eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Wed, 29 Jun 2016 12:13:59 +0200 Subject: [PATCH 01/15] get only vcards which match both the address book id and the vcard uri (#25294) --- apps/dav/lib/carddav/carddavbackend.php | 2 +- apps/dav/tests/unit/carddav/carddavbackendtest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/carddav/carddavbackend.php b/apps/dav/lib/carddav/carddavbackend.php index 97f5706049af7..64c251980c67c 100644 --- a/apps/dav/lib/carddav/carddavbackend.php +++ b/apps/dav/lib/carddav/carddavbackend.php @@ -845,7 +845,7 @@ public function getContact($addressBookId, $uri) { $query = $this->db->getQueryBuilder(); $query->select('*')->from($this->dbCardsTable) ->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) - ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))); + ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))); $queryResult = $query->execute(); $contact = $queryResult->fetch(); $queryResult->closeCursor(); diff --git a/apps/dav/tests/unit/carddav/carddavbackendtest.php b/apps/dav/tests/unit/carddav/carddavbackendtest.php index 1ee09260c88ce..0e746e12d7840 100644 --- a/apps/dav/tests/unit/carddav/carddavbackendtest.php +++ b/apps/dav/tests/unit/carddav/carddavbackendtest.php @@ -605,6 +605,10 @@ public function testGetContact() { $this->assertSame(5489543, (int)$result['lastmodified']); $this->assertSame('etag0', $result['etag']); $this->assertSame(120, (int)$result['size']); + + // this shouldn't return any result because 'uri1' is in address book 1 + $result = $this->backend->getContact(0, 'uri1'); + $this->assertEmpty($result); } public function testGetContactFail() { From 4a4103b92375b191700b9e0f52fe363d3fe989ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 1 Jul 2016 11:30:14 +0200 Subject: [PATCH 02/15] Suppress warnings from DAV migration if there's nothing to do (#25279) --- apps/dav/appinfo/application.php | 5 ++++ apps/dav/lib/migration/addressbookadapter.php | 2 +- apps/dav/lib/migration/calendaradapter.php | 2 +- .../lib/migration/nothingtodoexception.php | 27 +++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 apps/dav/lib/migration/nothingtodoexception.php diff --git a/apps/dav/appinfo/application.php b/apps/dav/appinfo/application.php index d06daf97f5452..1f56193e4b60d 100644 --- a/apps/dav/appinfo/application.php +++ b/apps/dav/appinfo/application.php @@ -32,6 +32,7 @@ use OCA\Dav\Migration\CalendarAdapter; use OCA\Dav\Migration\MigrateAddressbooks; use OCA\Dav\Migration\MigrateCalendars; +use OCA\Dav\Migration\NothingToDoException; use \OCP\AppFramework\App; use OCP\AppFramework\IAppContainer; use OCP\Contacts\IManager; @@ -190,6 +191,8 @@ public function migrateAddressbooks() { /** @var IUser $user */ $migration->migrateForUser($user->getUID()); }); + } catch (NothingToDoException $ex) { + // nothing to do, yay! } catch (\Exception $ex) { $this->getContainer()->getServer()->getLogger()->logException($ex); } @@ -206,6 +209,8 @@ public function migrateCalendars() { /** @var IUser $user */ $migration->migrateForUser($user->getUID()); }); + } catch (NothingToDoException $ex) { + // nothing to do, yay! } catch (\Exception $ex) { $this->getContainer()->getServer()->getLogger()->logException($ex); } diff --git a/apps/dav/lib/migration/addressbookadapter.php b/apps/dav/lib/migration/addressbookadapter.php index 5264747a004c0..ea9982f4061ca 100644 --- a/apps/dav/lib/migration/addressbookadapter.php +++ b/apps/dav/lib/migration/addressbookadapter.php @@ -69,7 +69,7 @@ public function foreachBook($user, \Closure $callBack) { public function setup() { if (!$this->dbConnection->tableExists($this->sourceBookTable)) { - throw new \DomainException('Contacts tables are missing. Nothing to do.'); + throw new NothingToDoException('Contacts tables are missing'); } } diff --git a/apps/dav/lib/migration/calendaradapter.php b/apps/dav/lib/migration/calendaradapter.php index d02f2256c342e..952c9a212f679 100644 --- a/apps/dav/lib/migration/calendaradapter.php +++ b/apps/dav/lib/migration/calendaradapter.php @@ -65,7 +65,7 @@ public function foreachCalendar($user, \Closure $callBack) { public function setup() { if (!$this->dbConnection->tableExists($this->sourceCalendarTable)) { - throw new \DomainException('Calendar tables are missing. Nothing to do.'); + throw new NothingToDoException('Calendar tables are missing'); } } diff --git a/apps/dav/lib/migration/nothingtodoexception.php b/apps/dav/lib/migration/nothingtodoexception.php new file mode 100644 index 0000000000000..d85d3c92d795d --- /dev/null +++ b/apps/dav/lib/migration/nothingtodoexception.php @@ -0,0 +1,27 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\Dav\Migration; + +/** + * Exception if no migration needs to be done + */ +class NothingToDoException extends \DomainException {} From 4ac256ea6cd14f531dad0841ce7a9c5f5ffdeb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 1 Jul 2016 11:30:39 +0200 Subject: [PATCH 03/15] [stable9] Fix decrypt message stable9 (#25188) * Fix Decrypt message via occ * Comments fixed * Fixed reviews * Originally: commit 2304e4bda027e61ff1302c55c2f70f8e4c8f47d0 Author: Joas Schilling Date: Tue Jun 7 09:13:11 2016 +0200 Allow to decrypt user '0' files only * Fix uid comparison --- core/command/encryption/decryptall.php | 15 +++++++-- lib/private/encryption/decryptall.php | 18 +++++++---- tests/lib/encryption/decryptalltest.php | 42 ++++++++++++++++--------- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/core/command/encryption/decryptall.php b/core/command/encryption/decryptall.php index 0a126db5b1762..83c6c1dc1689d 100644 --- a/core/command/encryption/decryptall.php +++ b/core/command/encryption/decryptall.php @@ -1,6 +1,6 @@ + * @author Björn Schießle * @author Joas Schilling * * @copyright Copyright (c) 2016, ownCloud, Inc. @@ -111,7 +111,8 @@ protected function configure() { $this->addArgument( 'user', InputArgument::OPTIONAL, - 'user for which you want to decrypt all files (optional)' + 'user for which you want to decrypt all files (optional)', + '' ); } @@ -127,8 +128,15 @@ protected function execute(InputInterface $input, OutputInterface $output) { return; } + $uid = $input->getArgument('user'); + if ($uid === '') { + $message = 'your ownCloud'; + } else { + $message = "$uid's account"; + } + $output->writeln("\n"); - $output->writeln('You are about to start to decrypt all files stored in your ownCloud.'); + $output->writeln("You are about to start to decrypt all files stored in $message."); $output->writeln('It will depend on the encryption module and your setup if this is possible.'); $output->writeln('Depending on the number and size of your files this can take some time'); $output->writeln('Please make sure that no user access his files during this process!'); @@ -140,6 +148,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { $result = $this->decryptAll->decryptAll($input, $output, $user); if ($result === false) { $output->writeln(' aborted.'); + $output->writeln('Server side encryption remains enabled'); $this->config->setAppValue('core', 'encryption_enabled', 'yes'); } $this->resetSingleUserAndTrashbin(); diff --git a/lib/private/encryption/decryptall.php b/lib/private/encryption/decryptall.php index 7a965a5f227d9..34a3e1bff911a 100644 --- a/lib/private/encryption/decryptall.php +++ b/lib/private/encryption/decryptall.php @@ -1,6 +1,6 @@ + * @author Björn Schießle * * @copyright Copyright (c) 2016, ownCloud, Inc. * @license AGPL-3.0 @@ -80,7 +80,7 @@ public function decryptAll(InputInterface $input, OutputInterface $output, $user $this->input = $input; $this->output = $output; - if (!empty($user) && $this->userManager->userExists($user) === false) { + if ($user !== '' && $this->userManager->userExists($user) === false) { $this->output->writeln('User "' . $user . '" does not exist. Please check the username and try again'); return false; } @@ -133,6 +133,7 @@ protected function prepareEncryptionModules($user) { /** * iterate over all user and encrypt their files + * * @param string $user which users files should be decrypted, default = all users */ protected function decryptAllUsersFiles($user = '') { @@ -140,7 +141,7 @@ protected function decryptAllUsersFiles($user = '') { $this->output->writeln("\n"); $userList = []; - if (empty($user)) { + if ($user === '') { $fetchUsersProgress = new ProgressBar($this->output); $fetchUsersProgress->setFormat(" %message% \n [%bar%]"); @@ -200,9 +201,9 @@ protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) { $this->setupUserFS($uid); $directories = array(); - $directories[] = '/' . $uid . '/files'; + $directories[] = '/' . $uid . '/files'; - while($root = array_pop($directories)) { + while ($root = array_pop($directories)) { $content = $this->rootView->getDirectoryContent($root); foreach ($content as $file) { $path = $root . '/' . $file['name']; @@ -213,9 +214,14 @@ protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) { try { $progress->setMessage("decrypt files for user $userCount: $path"); $progress->advance(); - if ($this->decryptFile($path) === false) { + if ($file->isEncrypted() === false) { $progress->setMessage("decrypt files for user $userCount: $path (already decrypted)"); $progress->advance(); + } else { + if ($this->decryptFile($path) === false) { + $progress->setMessage("decrypt files for user $userCount: $path (already decrypted)"); + $progress->advance(); + } } } catch (\Exception $e) { if (isset($this->failed[$uid])) { diff --git a/tests/lib/encryption/decryptalltest.php b/tests/lib/encryption/decryptalltest.php index 85fbe3e0ed954..d7cf2fb7baf73 100644 --- a/tests/lib/encryption/decryptalltest.php +++ b/tests/lib/encryption/decryptalltest.php @@ -26,6 +26,7 @@ use OC\Encryption\DecryptAll; use OC\Encryption\Exceptions\DecryptionFailedException; use OC\Encryption\Manager; +use OC\Files\FileInfo; use OC\Files\View; use OCP\IUserManager; use Test\TestCase; @@ -85,13 +86,25 @@ public function setUp() { $this->invokePrivate($this->instance, 'output', [$this->outputInterface]); } + public function dataDecryptAll() { + return [ + [true, 'user1', true], + [false, 'user1', true], + [true, '0', true], + [false, '0', true], + [true, '', false], + ]; + } + /** - * @dataProvider dataTrueFalse + * @dataProvider dataDecryptAll * @param bool $prepareResult + * @param string $user + * @param bool $userExistsChecked */ - public function testDecryptAll($prepareResult, $user) { + public function testDecryptAll($prepareResult, $user, $userExistsChecked) { - if (!empty($user)) { + if ($userExistsChecked) { $this->userManager->expects($this->once())->method('userExists')->willReturn(true); } else { $this->userManager->expects($this->never())->method('userExists'); @@ -124,15 +137,6 @@ public function testDecryptAll($prepareResult, $user) { $instance->decryptAll($this->inputInterface, $this->outputInterface, $user); } - public function dataTrueFalse() { - return [ - [true, 'user1'], - [false, 'user1'], - [true, ''], - [true, null] - ]; - } - /** * test decrypt all call with a user who doesn't exists */ @@ -146,8 +150,16 @@ public function testDecryptAllWrongUser() { ); } + public function dataTrueFalse() { + return [ + [true], + [false], + ]; + } + /** * @dataProvider dataTrueFalse + * @param bool $success */ public function testPrepareEncryptionModules($success) { @@ -242,15 +254,15 @@ public function testDecryptUsersFiles() { $this->view->expects($this->at(0))->method('getDirectoryContent') ->with('/user1/files')->willReturn( [ - ['name' => 'foo', 'type'=>'dir'], - ['name' => 'bar', 'type'=>'file'], + new FileInfo('path', null, 'intPath', ['name' => 'foo', 'type'=>'dir'], null), + new FileInfo('path', null, 'intPath', ['name' => 'bar', 'type'=>'file', 'encrypted'=>true], null) ] ); $this->view->expects($this->at(3))->method('getDirectoryContent') ->with('/user1/files/foo')->willReturn( [ - ['name' => 'subfile', 'type'=>'file'] + new FileInfo('path', null, 'intPath', ['name' => 'subfile', 'type'=>'file', 'encrypted'=>true], null) ] ); From 21bdd3005ba586e717b1c812ad1755eee06ff871 Mon Sep 17 00:00:00 2001 From: VicDeo Date: Fri, 1 Jul 2016 12:31:02 +0300 Subject: [PATCH 04/15] Fix OC_Helper::rmdirr for nested symlinks (#25255) --- lib/private/helper.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/private/helper.php b/lib/private/helper.php index 7a17352024915..cabd1143f8a35 100644 --- a/lib/private/helper.php +++ b/lib/private/helper.php @@ -221,7 +221,9 @@ static function rmdirr($dir, $deleteSelf = true) { foreach ($files as $fileInfo) { /** @var SplFileInfo $fileInfo */ - if ($fileInfo->isDir()) { + if ($fileInfo->isLink()) { + unlink($fileInfo->getPathname()); + } else if ($fileInfo->isDir()) { rmdir($fileInfo->getRealPath()); } else { unlink($fileInfo->getRealPath()); From 9fcb26910e98282459fc7bf291f0a21275a1c919 Mon Sep 17 00:00:00 2001 From: VicDeo Date: Wed, 22 Jun 2016 14:12:36 +0300 Subject: [PATCH 05/15] occ web executor (#24957) * Initial web executor * Fix PHPDoc Fix broken integration test OccControllerTests do not require database access - moch them all! Kill unused sprintf --- core/application.php | 13 ++ core/controller/occcontroller.php | 147 ++++++++++++++++++++ core/routes.php | 1 + lib/base.php | 19 ++- lib/private/console/application.php | 3 +- public.php | 4 +- tests/Core/Controller/OccControllerTest.php | 143 +++++++++++++++++++ 7 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 core/controller/occcontroller.php create mode 100644 tests/Core/Controller/OccControllerTest.php diff --git a/core/application.php b/core/application.php index 30376ee4f2e0f..ded9a3236fac5 100644 --- a/core/application.php +++ b/core/application.php @@ -28,6 +28,7 @@ use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\TimeFactory; +use OC\Core\Controller\OccController; use \OCP\AppFramework\App; use OC\Core\Controller\LostController; use OC\Core\Controller\UserController; @@ -89,6 +90,18 @@ public function __construct(array $urlParams=array()){ $c->query('Logger') ); }); + $container->registerService('OccController', function(SimpleContainer $c) { + return new OccController( + $c->query('AppName'), + $c->query('Request'), + $c->query('Config'), + new \OC\Console\Application( + $c->query('Config'), + $c->query('ServerContainer')->getEventDispatcher(), + $c->query('Request') + ) + ); + }); /** * Core class wrappers diff --git a/core/controller/occcontroller.php b/core/controller/occcontroller.php new file mode 100644 index 0000000000000..917d02f37f19c --- /dev/null +++ b/core/controller/occcontroller.php @@ -0,0 +1,147 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Core\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OC\Console\Application; +use OCP\IConfig; +use OCP\IRequest; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; + +class OccController extends Controller { + + /** @var array */ + private $allowedCommands = [ + 'app:disable', + 'app:enable', + 'app:getpath', + 'app:list', + 'check', + 'config:list', + 'maintenance:mode', + 'status', + 'upgrade' + ]; + + /** @var IConfig */ + private $config; + /** @var Application */ + private $console; + + /** + * OccController constructor. + * + * @param string $appName + * @param IRequest $request + * @param IConfig $config + * @param Application $console + */ + public function __construct($appName, IRequest $request, + IConfig $config, Application $console) { + parent::__construct($appName, $request); + $this->config = $config; + $this->console = $console; + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * Execute occ command + * Sample request + * POST http://domain.tld/index.php/occ/status', + * { + * 'params': { + * '--no-warnings':'1', + * '--output':'json' + * }, + * 'token': 'someToken' + * } + * + * @param string $command + * @param string $token + * @param array $params + * + * @return JSONResponse + * @throws \Exception + */ + public function execute($command, $token, $params = []) { + try { + $this->validateRequest($command, $token); + + $output = new BufferedOutput(); + $formatter = $output->getFormatter(); + $formatter->setDecorated(false); + $this->console->setAutoExit(false); + $this->console->loadCommands(new ArrayInput([]), $output); + + $inputArray = array_merge(['command' => $command], $params); + $input = new ArrayInput($inputArray); + + $exitCode = $this->console->run($input, $output); + $response = $output->fetch(); + + $json = [ + 'exitCode' => $exitCode, + 'response' => $response + ]; + + } catch (\UnexpectedValueException $e){ + $json = [ + 'exitCode' => 126, + 'response' => 'Not allowed', + 'details' => $e->getMessage() + ]; + } + return new JSONResponse($json); + } + + /** + * Check if command is allowed and has a valid security token + * @param $command + * @param $token + */ + protected function validateRequest($command, $token){ + if (!in_array($this->request->getRemoteAddress(), ['::1', '127.0.0.1', 'localhost'])) { + throw new \UnexpectedValueException('Web executor is not allowed to run from a different host'); + } + + if (!in_array($command, $this->allowedCommands)) { + throw new \UnexpectedValueException(sprintf('Command "%s" is not allowed to run via web request', $command)); + } + + $coreToken = $this->config->getSystemValue('updater.secret', ''); + if ($coreToken === '') { + throw new \UnexpectedValueException( + 'updater.secret is undefined in config/config.php. Either browse the admin settings in your ownCloud and click "Open updater" or define a strong secret using
php -r \'echo password_hash("MyStrongSecretDoUseYourOwn!", PASSWORD_DEFAULT)."\n";\'
and set this in the config.php.' + ); + } + + if (!password_verify($token, $coreToken)) { + throw new \UnexpectedValueException( + 'updater.secret does not match the provided token' + ); + } + } +} diff --git a/core/routes.php b/core/routes.php index 8981eb618f3f4..20fbc1d580f9e 100644 --- a/core/routes.php +++ b/core/routes.php @@ -42,6 +42,7 @@ ['name' => 'avatar#postCroppedAvatar', 'url' => '/avatar/cropped', 'verb' => 'POST'], ['name' => 'avatar#getTmpAvatar', 'url' => '/avatar/tmp', 'verb' => 'GET'], ['name' => 'avatar#postAvatar', 'url' => '/avatar/', 'verb' => 'POST'], + ['name' => 'occ#execute', 'url' => '/occ/{command}', 'verb' => 'POST'], ] ]); diff --git a/lib/base.php b/lib/base.php index e77a07239c432..07d8f3b788bfe 100644 --- a/lib/base.php +++ b/lib/base.php @@ -53,6 +53,8 @@ * */ +use OCP\IRequest; + require_once 'public/constants.php'; /** @@ -296,9 +298,20 @@ public static function checkInstalled() { } } - public static function checkMaintenanceMode() { + /** + * Limit maintenance mode access + * @param IRequest $request + */ + public static function checkMaintenanceMode(IRequest $request) { + // Check if requested URL matches 'index.php/occ' + $isOccControllerRequested = preg_match('|/index\.php$|', $request->getScriptName()) === 1 + && strpos($request->getPathInfo(), '/occ/') === 0; // Allow ajax update script to execute without being stopped - if (\OC::$server->getSystemConfig()->getValue('maintenance', false) && OC::$SUBURI != '/core/ajax/update.php') { + if ( + \OC::$server->getSystemConfig()->getValue('maintenance', false) + && OC::$SUBURI != '/core/ajax/update.php' + && !$isOccControllerRequested + ) { // send http status 503 header('HTTP/1.1 503 Service Temporarily Unavailable'); header('Status: 503 Service Temporarily Unavailable'); @@ -836,7 +849,7 @@ public static function handleRequest() { $request = \OC::$server->getRequest(); $requestPath = $request->getRawPathInfo(); if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade - self::checkMaintenanceMode(); + self::checkMaintenanceMode($request); self::checkUpgrade(); } diff --git a/lib/private/console/application.php b/lib/private/console/application.php index 7f12db4eca66e..7a8ec49c65bbd 100644 --- a/lib/private/console/application.php +++ b/lib/private/console/application.php @@ -138,9 +138,10 @@ public function setAutoExit($boolean) { * @throws \Exception */ public function run(InputInterface $input = null, OutputInterface $output = null) { + $args = isset($this->request->server['argv']) ? $this->request->server['argv'] : []; $this->dispatcher->dispatch(ConsoleEvent::EVENT_RUN, new ConsoleEvent( ConsoleEvent::EVENT_RUN, - $this->request->server['argv'] + $args )); return $this->application->run($input, $output); } diff --git a/public.php b/public.php index 65257f1a46ed0..fb18bba4958cf 100644 --- a/public.php +++ b/public.php @@ -35,9 +35,9 @@ exit; } - OC::checkMaintenanceMode(); - OC::checkSingleUserMode(true); $request = \OC::$server->getRequest(); + OC::checkMaintenanceMode($request); + OC::checkSingleUserMode(true); $pathInfo = $request->getPathInfo(); if (!$pathInfo && $request->getParam('service', '') === '') { diff --git a/tests/Core/Controller/OccControllerTest.php b/tests/Core/Controller/OccControllerTest.php new file mode 100644 index 0000000000000..682d9170096b0 --- /dev/null +++ b/tests/Core/Controller/OccControllerTest.php @@ -0,0 +1,143 @@ + + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace Tests\Core\Controller; + +use OC\Console\Application; +use OC\Core\Controller\OccController; +use OCP\IConfig; +use Symfony\Component\Console\Output\Output; +use Test\TestCase; + +/** + * Class OccControllerTest + * + * @package OC\Core\Controller + */ +class OccControllerTest extends TestCase { + + const TEMP_SECRET = 'test'; + + /** @var \OC\AppFramework\Http\Request | \PHPUnit_Framework_MockObject_MockObject */ + private $request; + /** @var \OC\Core\Controller\OccController | \PHPUnit_Framework_MockObject_MockObject */ + private $controller; + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ + private $config; + /** @var Application | \PHPUnit_Framework_MockObject_MockObject */ + private $console; + + public function testFromInvalidLocation(){ + $this->getControllerMock('example.org'); + + $response = $this->controller->execute('status', ''); + $responseData = $response->getData(); + + $this->assertArrayHasKey('exitCode', $responseData); + $this->assertEquals(126, $responseData['exitCode']); + + $this->assertArrayHasKey('details', $responseData); + $this->assertEquals('Web executor is not allowed to run from a different host', $responseData['details']); + } + + public function testNotWhiteListedCommand(){ + $this->getControllerMock('localhost'); + + $response = $this->controller->execute('missing_command', ''); + $responseData = $response->getData(); + + $this->assertArrayHasKey('exitCode', $responseData); + $this->assertEquals(126, $responseData['exitCode']); + + $this->assertArrayHasKey('details', $responseData); + $this->assertEquals('Command "missing_command" is not allowed to run via web request', $responseData['details']); + } + + public function testWrongToken(){ + $this->getControllerMock('localhost'); + + $response = $this->controller->execute('status', self::TEMP_SECRET . '-'); + $responseData = $response->getData(); + + $this->assertArrayHasKey('exitCode', $responseData); + $this->assertEquals(126, $responseData['exitCode']); + + $this->assertArrayHasKey('details', $responseData); + $this->assertEquals('updater.secret does not match the provided token', $responseData['details']); + } + + public function testSuccess(){ + $this->getControllerMock('localhost'); + $this->console->expects($this->once())->method('run') + ->willReturnCallback( + function ($input, $output) { + /** @var Output $output */ + $output->writeln('{"installed":true,"version":"9.1.0.8","versionstring":"9.1.0 beta 2","edition":""}'); + return 0; + } + ); + + $response = $this->controller->execute('status', self::TEMP_SECRET, ['--output'=>'json']); + $responseData = $response->getData(); + + $this->assertArrayHasKey('exitCode', $responseData); + $this->assertEquals(0, $responseData['exitCode']); + + $this->assertArrayHasKey('response', $responseData); + $decoded = json_decode($responseData['response'], true); + + $this->assertArrayHasKey('installed', $decoded); + $this->assertEquals(true, $decoded['installed']); + } + + private function getControllerMock($host){ + $this->request = $this->getMockBuilder('OC\AppFramework\Http\Request') + ->setConstructorArgs([ + ['server' => []], + \OC::$server->getSecureRandom(), + \OC::$server->getConfig() + ]) + ->setMethods(['getRemoteAddress']) + ->getMock(); + + $this->request->expects($this->any())->method('getRemoteAddress') + ->will($this->returnValue($host)); + + $this->config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor() + ->getMock(); + $this->config->expects($this->any())->method('getSystemValue') + ->with('updater.secret') + ->willReturn(password_hash(self::TEMP_SECRET, PASSWORD_DEFAULT)); + + $this->console = $this->getMockBuilder('\OC\Console\Application') + ->disableOriginalConstructor() + ->getMock(); + + $this->controller = new OccController( + 'core', + $this->request, + $this->config, + $this->console + ); + } + +} From 1a4297910496631542f65843ec30d65ee28d3b6e Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 4 Jul 2016 12:49:46 +0200 Subject: [PATCH 06/15] Revert "Remove repair steps for broken updater repair" This reverts commit 58ed5b9328e9e12dd4319c0730202f385ca0dc5d. --- lib/private/repair.php | 2 + lib/private/repair/brokenupdaterrepair.php | 110 +++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 lib/private/repair/brokenupdaterrepair.php diff --git a/lib/private/repair.php b/lib/private/repair.php index 098d4e7eb9369..0cbb43293e8cc 100644 --- a/lib/private/repair.php +++ b/lib/private/repair.php @@ -32,6 +32,7 @@ use OC\Hooks\Emitter; use OC\Repair\AssetCache; use OC\Repair\AvatarPermissions; +use OC\Repair\BrokenUpdaterRepair; use OC\Repair\CleanTags; use OC\Repair\Collation; use OC\Repair\CopyRewriteBaseToConfig; @@ -118,6 +119,7 @@ public static function getRepairSteps() { new UpdateOutdatedOcsIds(\OC::$server->getConfig()), new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new AvatarPermissions(\OC::$server->getDatabaseConnection()), + new BrokenUpdaterRepair(), ]; } diff --git a/lib/private/repair/brokenupdaterrepair.php b/lib/private/repair/brokenupdaterrepair.php new file mode 100644 index 0000000000000..0e4431f6ba3b9 --- /dev/null +++ b/lib/private/repair/brokenupdaterrepair.php @@ -0,0 +1,110 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Repair; + +use OC\Hooks\BasicEmitter; + +/** + * Class BrokenUpdaterRepair fixes some issues caused by bugs in the ownCloud + * updater below version 9.0.2. + * + * FIXME: This file should be removed after the 9.0.2 release. The update server + * is instructed to deliver 9.0.2 for 9.0.0 and 9.0.1. + * + * @package OC\Repair + */ +class BrokenUpdaterRepair extends BasicEmitter implements \OC\RepairStep { + + public function getName() { + return 'Manually copies the third-party folder changes since 9.0.0 due ' . + 'to a bug in the updater.'; + } + + /** + * Manually copy the third-party files that have changed since 9.0.0 because + * the old updater does not copy over third-party changes. + * + * @return bool True if action performed, false otherwise + */ + private function manuallyCopyThirdPartyFiles() { + $resourceDir = __DIR__ . '/../../../resources/updater-fixes/'; + $thirdPartyDir = __DIR__ . '/../../../3rdparty/'; + + $filesToCopy = [ + // Composer updates + 'composer.json', + 'composer.lock', + 'composer/autoload_classmap.php', + 'composer/installed.json', + 'composer/LICENSE', + // Icewind stream library + 'icewind/streams/src/DirectoryFilter.php', + 'icewind/streams/src/DirectoryWrapper.php', + 'icewind/streams/src/RetryWrapper.php', + 'icewind/streams/src/SeekableWrapper.php', + // Sabre update + 'sabre/dav/CHANGELOG.md', + 'sabre/dav/composer.json', + 'sabre/dav/lib/CalDAV/Plugin.php', + 'sabre/dav/lib/CardDAV/Backend/PDO.php', + 'sabre/dav/lib/DAV/CorePlugin.php', + 'sabre/dav/lib/DAV/Version.php', + ]; + + // Check the hash for the autoload_classmap.php file, if the hash does match + // the expected value then the third-party folder has already been copied + // properly. + if(hash_file('sha512', $thirdPartyDir . '/composer/autoload_classmap.php') === 'abe09be19b6d427283cbfa7c4156d2c342cd9368d7d0564828a00ae02c435b642e7092cef444f94635f370dbe507eb6b2aa05109b32d8fb5d8a65c3a5a1c658f') { + $this->emit('\OC\Repair', 'info', ['Third-party files seem already to have been copied. No repair necessary.']); + return false; + } + + foreach($filesToCopy as $file) { + $state = copy($resourceDir . '/' . $file, $thirdPartyDir . '/' . $file); + if($state === true) { + $this->emit('\OC\Repair', 'info', ['Successfully replaced '.$file.' with new version.']); + } else { + $this->emit('\OC\Repair', 'warning', ['Could not replace '.$file.' with new version.']); + } + } + return true; + } + + /** + * Rerun the integrity check after the update since the repair step has + * repaired some invalid copied files. + */ + private function recheckIntegrity() { + \OC::$server->getIntegrityCodeChecker()->runInstanceVerification(); + } + + public function run() { + if($this->manuallyCopyThirdPartyFiles()) { + $this->emit('\OC\Repair', 'info', ['Start integrity recheck.']); + $this->recheckIntegrity(); + $this->emit('\OC\Repair', 'info', ['Finished integrity recheck.']); + } else { + $this->emit('\OC\Repair', 'info', ['Rechecking code integrity not necessary.']); + } + } +} + From fdb0d4ad528425b934d9b039c9f09c132b86d0f4 Mon Sep 17 00:00:00 2001 From: felixboehm Date: Mon, 4 Jul 2016 14:16:13 +0200 Subject: [PATCH 07/15] check if renamed user is still valid by reapplying the ldap filter (#25338) --- apps/user_ldap/user_ldap.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index a11e5ec592bcc..136bb2490febb 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -228,6 +228,11 @@ public function userExistsOnLDAP($user) { return false; } $newDn = $this->access->getUserDnByUuid($uuid); + //check if renamed user is still valid by reapplying the ldap filter + if(!is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) { + return false; + } + $this->access->getUserMapper()->setDNbyUUID($newDn, $uuid); return true; } catch (\Exception $e) { From a2bbc220e0d920084d79d004d043d20b24c454d2 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 5 Jul 2016 10:58:00 +0200 Subject: [PATCH 08/15] Change order of share creation validation Makes sure that the share owner is set before entering the checks that need it. Partial backport of afa37d3 --- lib/private/share20/manager.php | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php index 7a48de6a11c5e..b00f7ccd5b6e9 100644 --- a/lib/private/share20/manager.php +++ b/lib/private/share20/manager.php @@ -505,6 +505,24 @@ public function createShare(\OCP\Share\IShare $share) { $this->generalCreateChecks($share); + // Verify if there are any issues with the path + $this->pathCreateChecks($share->getNode()); + + /* + * On creation of a share the owner is always the owner of the path + * Except for mounted federated shares. + */ + $storage = $share->getNode()->getStorage(); + if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { + $parent = $share->getNode()->getParent(); + while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { + $parent = $parent->getParent(); + } + $share->setShareOwner($parent->getOwner()->getUID()); + } else { + $share->setShareOwner($share->getNode()->getOwner()->getUID()); + } + //Verify share type if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { $this->userCreateChecks($share); @@ -538,24 +556,6 @@ public function createShare(\OCP\Share\IShare $share) { } } - // Verify if there are any issues with the path - $this->pathCreateChecks($share->getNode()); - - /* - * On creation of a share the owner is always the owner of the path - * Except for mounted federated shares. - */ - $storage = $share->getNode()->getStorage(); - if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { - $parent = $share->getNode()->getParent(); - while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { - $parent = $parent->getParent(); - } - $share->setShareOwner($parent->getOwner()->getUID()); - } else { - $share->setShareOwner($share->getNode()->getOwner()->getUID()); - } - // Cannot share with the owner if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() === $share->getShareOwner()) { From ffe1a8bffc9a459b15d3b07fdc3f89a2857e27ac Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 5 Jul 2016 11:08:19 +0200 Subject: [PATCH 09/15] Add integration test for sharing with group, then user in group Add integration test for the use case where a group share exists and then the same owner creates a direct share to a user in that group. --- build/integration/features/sharing-v1.feature | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build/integration/features/sharing-v1.feature b/build/integration/features/sharing-v1.feature index 56399eefdb060..ed8f3b23f2527 100644 --- a/build/integration/features/sharing-v1.feature +++ b/build/integration/features/sharing-v1.feature @@ -26,6 +26,21 @@ Feature: sharing Then the OCS status code should be "100" And the HTTP status code should be "200" + Scenario: Creating a new share with user who already received a share through their group + Given As an "admin" + And user "user0" exists + And user "user1" exists + And group "sharing-group" exists + And user "user1" belongs to group "sharing-group" + And file "welcome.txt" of user "user0" is shared with group "sharing-group" + And As an "user0" + Then sending "POST" to "/apps/files_sharing/api/v1/shares" with + | path | welcome.txt | + | shareWith | user1 | + | shareType | 0 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + Scenario: Creating a new public share Given user "user0" exists And As an "user0" From c92c234059f8b1dc7d53122985ec0d398895a2cf Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 6 Jul 2016 11:55:02 +0200 Subject: [PATCH 10/15] Ignore invalid paths in the JS file list (#25368) --- apps/files/js/filelist.js | 14 ++++++++++++++ apps/files/tests/js/filelistSpec.js | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index b79dd0f66f2a8..e3f1a1ed02cbf 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -1327,6 +1327,16 @@ return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); }, + _isValidPath: function(path) { + var sections = path.split('/'); + for (var i = 0; i < sections.length; i++) { + if (sections[i] === '..') { + return false; + } + } + return true; + }, + /** * Sets the current directory name and updates the breadcrumb. * @param targetDir directory to display @@ -1334,6 +1344,10 @@ */ _setCurrentDir: function(targetDir, changeUrl) { targetDir = targetDir.replace(/\\/g, '/'); + if (!this._isValidPath(targetDir)) { + targetDir = '/'; + changeUrl = true; + } var previousDir = this.getCurrentDirectory(), baseDir = OC.basename(targetDir); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index a83c8c4c0bc6c..7ca6c4b16f9f1 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -1323,6 +1323,31 @@ describe('OCA.Files.FileList tests', function() { fileList.changeDirectory('/another\\subdir'); expect(fileList.getCurrentDirectory()).toEqual('/another/subdir'); }); + it('switches to root dir when current directory is invalid', function() { + _.each([ + '..', + '/..', + '../', + '/../', + '/../abc', + '/abc/..', + '/abc/../', + '/../abc/' + ], function(path) { + fileList.changeDirectory(path); + expect(fileList.getCurrentDirectory()).toEqual('/'); + }); + }); + it('allows paths with dotdot at the beginning or end', function() { + _.each([ + '..abc', + 'def..', + '...' + ], function(path) { + fileList.changeDirectory(path); + expect(fileList.getCurrentDirectory()).toEqual(path); + }); + }); it('switches to root dir when current directory does not exist', function() { fileList.changeDirectory('/unexist'); deferredList.reject(404); From 445a76bb1b0249a642a9a3f2e400c1221be7993f Mon Sep 17 00:00:00 2001 From: VicDeo Date: Wed, 6 Jul 2016 23:08:58 +0300 Subject: [PATCH 11/15] Bypass upgrade page when occ controller is requested (#25363) --- lib/base.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/base.php b/lib/base.php index 07d8f3b788bfe..1a6d1b8d3f653 100644 --- a/lib/base.php +++ b/lib/base.php @@ -847,10 +847,14 @@ public static function handleRequest() { } $request = \OC::$server->getRequest(); + // Check if requested URL matches 'index.php/occ' + $isOccControllerRequested = preg_match('|/index\.php$|', $request->getScriptName()) === 1 + && strpos($request->getPathInfo(), '/occ/') === 0; + $requestPath = $request->getRawPathInfo(); if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade self::checkMaintenanceMode($request); - self::checkUpgrade(); + $needUpgrade = self::checkUpgrade(!$isOccControllerRequested); } // emergency app disabling @@ -868,8 +872,16 @@ public static function handleRequest() { exit(); } - // Always load authentication apps - OC_App::loadApps(['authentication']); + try { + // Always load authentication apps + OC_App::loadApps(['authentication']); + } catch (\OC\NeedsUpdateException $e) { + if ($isOccControllerRequested && $needUpgrade){ + OC::$server->getRouter()->match(\OC::$server->getRequest()->getRawPathInfo()); + return; + } + throw $e; + } // Load minimum set of apps if (!self::checkUpgrade(false) From 64a15191e4397da9712d4d17675993a9acdab31e Mon Sep 17 00:00:00 2001 From: Carlos Damken Date: Wed, 6 Jul 2016 22:10:52 +0200 Subject: [PATCH 12/15] Backport 25367 to Stable 9.0 (#25384) --- apps/files_versions/lib/storage.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/files_versions/lib/storage.php b/apps/files_versions/lib/storage.php index 1062f49cf6960..36021bf4baacc 100644 --- a/apps/files_versions/lib/storage.php +++ b/apps/files_versions/lib/storage.php @@ -634,7 +634,7 @@ protected static function getAutoExpireList($time, $versions) { //distance between two version too small, mark to delete $toDelete[$key] = $version['path'] . '.v' . $version['version']; $size += $version['size']; - \OCP\Util::writeLog('files_versions', 'Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, \OCP\Util::DEBUG); + \OCP\Util::writeLog('files_versions', 'Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, \OCP\Util::INFO); } else { $nextVersion = $version['version'] - $step; $prevTimestamp = $version['version']; @@ -755,7 +755,7 @@ public static function expire($filename) { self::deleteVersion($versionsFileview, $path); \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED)); unset($allVersions[$key]); // update array with the versions we keep - \OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::DEBUG); + \OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::INFO); } // Check if enough space is available after versions are rearranged. @@ -771,7 +771,7 @@ public static function expire($filename) { \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED)); self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']); \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED)); - \OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::DEBUG); + \OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::INFO); $versionsSize -= $version['size']; $availableSpace += $version['size']; next($allVersions); From 06964b32f3fe05977b44b1e757c6e8c70923f763 Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Thu, 7 Jul 2016 12:04:57 +0200 Subject: [PATCH 13/15] Revert "Revert "Remove repair steps for broken updater repair"" This reverts commit 1a4297910496631542f65843ec30d65ee28d3b6e. --- lib/private/repair.php | 1 - lib/private/repair/brokenupdaterrepair.php | 110 --------------------- 2 files changed, 111 deletions(-) delete mode 100644 lib/private/repair/brokenupdaterrepair.php diff --git a/lib/private/repair.php b/lib/private/repair.php index 152123bb057b4..a7c451c123938 100644 --- a/lib/private/repair.php +++ b/lib/private/repair.php @@ -32,7 +32,6 @@ use OC\Hooks\Emitter; use OC\Repair\AssetCache; use OC\Repair\AvatarPermissions; -use OC\Repair\BrokenUpdaterRepair; use OC\Repair\CleanTags; use OC\Repair\Collation; use OC\Repair\CopyRewriteBaseToConfig; diff --git a/lib/private/repair/brokenupdaterrepair.php b/lib/private/repair/brokenupdaterrepair.php deleted file mode 100644 index 0e4431f6ba3b9..0000000000000 --- a/lib/private/repair/brokenupdaterrepair.php +++ /dev/null @@ -1,110 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Repair; - -use OC\Hooks\BasicEmitter; - -/** - * Class BrokenUpdaterRepair fixes some issues caused by bugs in the ownCloud - * updater below version 9.0.2. - * - * FIXME: This file should be removed after the 9.0.2 release. The update server - * is instructed to deliver 9.0.2 for 9.0.0 and 9.0.1. - * - * @package OC\Repair - */ -class BrokenUpdaterRepair extends BasicEmitter implements \OC\RepairStep { - - public function getName() { - return 'Manually copies the third-party folder changes since 9.0.0 due ' . - 'to a bug in the updater.'; - } - - /** - * Manually copy the third-party files that have changed since 9.0.0 because - * the old updater does not copy over third-party changes. - * - * @return bool True if action performed, false otherwise - */ - private function manuallyCopyThirdPartyFiles() { - $resourceDir = __DIR__ . '/../../../resources/updater-fixes/'; - $thirdPartyDir = __DIR__ . '/../../../3rdparty/'; - - $filesToCopy = [ - // Composer updates - 'composer.json', - 'composer.lock', - 'composer/autoload_classmap.php', - 'composer/installed.json', - 'composer/LICENSE', - // Icewind stream library - 'icewind/streams/src/DirectoryFilter.php', - 'icewind/streams/src/DirectoryWrapper.php', - 'icewind/streams/src/RetryWrapper.php', - 'icewind/streams/src/SeekableWrapper.php', - // Sabre update - 'sabre/dav/CHANGELOG.md', - 'sabre/dav/composer.json', - 'sabre/dav/lib/CalDAV/Plugin.php', - 'sabre/dav/lib/CardDAV/Backend/PDO.php', - 'sabre/dav/lib/DAV/CorePlugin.php', - 'sabre/dav/lib/DAV/Version.php', - ]; - - // Check the hash for the autoload_classmap.php file, if the hash does match - // the expected value then the third-party folder has already been copied - // properly. - if(hash_file('sha512', $thirdPartyDir . '/composer/autoload_classmap.php') === 'abe09be19b6d427283cbfa7c4156d2c342cd9368d7d0564828a00ae02c435b642e7092cef444f94635f370dbe507eb6b2aa05109b32d8fb5d8a65c3a5a1c658f') { - $this->emit('\OC\Repair', 'info', ['Third-party files seem already to have been copied. No repair necessary.']); - return false; - } - - foreach($filesToCopy as $file) { - $state = copy($resourceDir . '/' . $file, $thirdPartyDir . '/' . $file); - if($state === true) { - $this->emit('\OC\Repair', 'info', ['Successfully replaced '.$file.' with new version.']); - } else { - $this->emit('\OC\Repair', 'warning', ['Could not replace '.$file.' with new version.']); - } - } - return true; - } - - /** - * Rerun the integrity check after the update since the repair step has - * repaired some invalid copied files. - */ - private function recheckIntegrity() { - \OC::$server->getIntegrityCodeChecker()->runInstanceVerification(); - } - - public function run() { - if($this->manuallyCopyThirdPartyFiles()) { - $this->emit('\OC\Repair', 'info', ['Start integrity recheck.']); - $this->recheckIntegrity(); - $this->emit('\OC\Repair', 'info', ['Finished integrity recheck.']); - } else { - $this->emit('\OC\Repair', 'info', ['Rechecking code integrity not necessary.']); - } - } -} - From 4782b9234724297cb5c08da2f055060d67c0b51c Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Thu, 7 Jul 2016 12:07:35 +0200 Subject: [PATCH 14/15] Revert "Bypass upgrade page when occ controller is requested (#25363)" This reverts commit 445a76bb1b0249a642a9a3f2e400c1221be7993f. --- lib/base.php | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/base.php b/lib/base.php index df1fa61b74013..def9d64b4aea1 100644 --- a/lib/base.php +++ b/lib/base.php @@ -932,14 +932,10 @@ public static function handleRequest() { } $request = \OC::$server->getRequest(); - // Check if requested URL matches 'index.php/occ' - $isOccControllerRequested = preg_match('|/index\.php$|', $request->getScriptName()) === 1 - && strpos($request->getPathInfo(), '/occ/') === 0; - $requestPath = $request->getRawPathInfo(); if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade self::checkMaintenanceMode($request); - $needUpgrade = self::checkUpgrade(!$isOccControllerRequested); + self::checkUpgrade(); } // emergency app disabling @@ -957,16 +953,8 @@ public static function handleRequest() { exit(); } - try { - // Always load authentication apps - OC_App::loadApps(['authentication']); - } catch (\OC\NeedsUpdateException $e) { - if ($isOccControllerRequested && $needUpgrade){ - OC::$server->getRouter()->match(\OC::$server->getRequest()->getRawPathInfo()); - return; - } - throw $e; - } + // Always load authentication apps + OC_App::loadApps(['authentication']); // Load minimum set of apps if (!self::checkUpgrade(false) From f593a0936089d26beec69f49c20654c7568f56a4 Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Thu, 7 Jul 2016 12:08:10 +0200 Subject: [PATCH 15/15] Revert "occ web executor (#24957)" This reverts commit 9fcb26910e98282459fc7bf291f0a21275a1c919. --- core/application.php | 13 -- core/controller/occcontroller.php | 147 -------------------- core/routes.php | 1 - lib/base.php | 19 +-- lib/private/console/application.php | 3 +- public.php | 4 +- tests/Core/Controller/OccControllerTest.php | 143 ------------------- 7 files changed, 6 insertions(+), 324 deletions(-) delete mode 100644 core/controller/occcontroller.php delete mode 100644 tests/Core/Controller/OccControllerTest.php diff --git a/core/application.php b/core/application.php index ded9a3236fac5..30376ee4f2e0f 100644 --- a/core/application.php +++ b/core/application.php @@ -28,7 +28,6 @@ use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\TimeFactory; -use OC\Core\Controller\OccController; use \OCP\AppFramework\App; use OC\Core\Controller\LostController; use OC\Core\Controller\UserController; @@ -90,18 +89,6 @@ public function __construct(array $urlParams=array()){ $c->query('Logger') ); }); - $container->registerService('OccController', function(SimpleContainer $c) { - return new OccController( - $c->query('AppName'), - $c->query('Request'), - $c->query('Config'), - new \OC\Console\Application( - $c->query('Config'), - $c->query('ServerContainer')->getEventDispatcher(), - $c->query('Request') - ) - ); - }); /** * Core class wrappers diff --git a/core/controller/occcontroller.php b/core/controller/occcontroller.php deleted file mode 100644 index 917d02f37f19c..0000000000000 --- a/core/controller/occcontroller.php +++ /dev/null @@ -1,147 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Core\Controller; - -use OCP\AppFramework\Controller; -use OCP\AppFramework\Http\JSONResponse; -use OC\Console\Application; -use OCP\IConfig; -use OCP\IRequest; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; - -class OccController extends Controller { - - /** @var array */ - private $allowedCommands = [ - 'app:disable', - 'app:enable', - 'app:getpath', - 'app:list', - 'check', - 'config:list', - 'maintenance:mode', - 'status', - 'upgrade' - ]; - - /** @var IConfig */ - private $config; - /** @var Application */ - private $console; - - /** - * OccController constructor. - * - * @param string $appName - * @param IRequest $request - * @param IConfig $config - * @param Application $console - */ - public function __construct($appName, IRequest $request, - IConfig $config, Application $console) { - parent::__construct($appName, $request); - $this->config = $config; - $this->console = $console; - } - - /** - * @PublicPage - * @NoCSRFRequired - * - * Execute occ command - * Sample request - * POST http://domain.tld/index.php/occ/status', - * { - * 'params': { - * '--no-warnings':'1', - * '--output':'json' - * }, - * 'token': 'someToken' - * } - * - * @param string $command - * @param string $token - * @param array $params - * - * @return JSONResponse - * @throws \Exception - */ - public function execute($command, $token, $params = []) { - try { - $this->validateRequest($command, $token); - - $output = new BufferedOutput(); - $formatter = $output->getFormatter(); - $formatter->setDecorated(false); - $this->console->setAutoExit(false); - $this->console->loadCommands(new ArrayInput([]), $output); - - $inputArray = array_merge(['command' => $command], $params); - $input = new ArrayInput($inputArray); - - $exitCode = $this->console->run($input, $output); - $response = $output->fetch(); - - $json = [ - 'exitCode' => $exitCode, - 'response' => $response - ]; - - } catch (\UnexpectedValueException $e){ - $json = [ - 'exitCode' => 126, - 'response' => 'Not allowed', - 'details' => $e->getMessage() - ]; - } - return new JSONResponse($json); - } - - /** - * Check if command is allowed and has a valid security token - * @param $command - * @param $token - */ - protected function validateRequest($command, $token){ - if (!in_array($this->request->getRemoteAddress(), ['::1', '127.0.0.1', 'localhost'])) { - throw new \UnexpectedValueException('Web executor is not allowed to run from a different host'); - } - - if (!in_array($command, $this->allowedCommands)) { - throw new \UnexpectedValueException(sprintf('Command "%s" is not allowed to run via web request', $command)); - } - - $coreToken = $this->config->getSystemValue('updater.secret', ''); - if ($coreToken === '') { - throw new \UnexpectedValueException( - 'updater.secret is undefined in config/config.php. Either browse the admin settings in your ownCloud and click "Open updater" or define a strong secret using
php -r \'echo password_hash("MyStrongSecretDoUseYourOwn!", PASSWORD_DEFAULT)."\n";\'
and set this in the config.php.' - ); - } - - if (!password_verify($token, $coreToken)) { - throw new \UnexpectedValueException( - 'updater.secret does not match the provided token' - ); - } - } -} diff --git a/core/routes.php b/core/routes.php index 20fbc1d580f9e..8981eb618f3f4 100644 --- a/core/routes.php +++ b/core/routes.php @@ -42,7 +42,6 @@ ['name' => 'avatar#postCroppedAvatar', 'url' => '/avatar/cropped', 'verb' => 'POST'], ['name' => 'avatar#getTmpAvatar', 'url' => '/avatar/tmp', 'verb' => 'GET'], ['name' => 'avatar#postAvatar', 'url' => '/avatar/', 'verb' => 'POST'], - ['name' => 'occ#execute', 'url' => '/occ/{command}', 'verb' => 'POST'], ] ]); diff --git a/lib/base.php b/lib/base.php index def9d64b4aea1..2ad453189d0e7 100644 --- a/lib/base.php +++ b/lib/base.php @@ -53,8 +53,6 @@ * */ -use OCP\IRequest; - require_once 'public/constants.php'; /** @@ -298,20 +296,9 @@ public static function checkInstalled() { } } - /** - * Limit maintenance mode access - * @param IRequest $request - */ - public static function checkMaintenanceMode(IRequest $request) { - // Check if requested URL matches 'index.php/occ' - $isOccControllerRequested = preg_match('|/index\.php$|', $request->getScriptName()) === 1 - && strpos($request->getPathInfo(), '/occ/') === 0; + public static function checkMaintenanceMode() { // Allow ajax update script to execute without being stopped - if ( - \OC::$server->getSystemConfig()->getValue('maintenance', false) - && OC::$SUBURI != '/core/ajax/update.php' - && !$isOccControllerRequested - ) { + if (\OC::$server->getSystemConfig()->getValue('maintenance', false) && OC::$SUBURI != '/core/ajax/update.php') { // send http status 503 header('HTTP/1.1 503 Service Temporarily Unavailable'); header('Status: 503 Service Temporarily Unavailable'); @@ -934,7 +921,7 @@ public static function handleRequest() { $request = \OC::$server->getRequest(); $requestPath = $request->getRawPathInfo(); if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade - self::checkMaintenanceMode($request); + self::checkMaintenanceMode(); self::checkUpgrade(); } diff --git a/lib/private/console/application.php b/lib/private/console/application.php index ffb0224bdc741..d23f41f487e49 100644 --- a/lib/private/console/application.php +++ b/lib/private/console/application.php @@ -138,10 +138,9 @@ public function setAutoExit($boolean) { * @throws \Exception */ public function run(InputInterface $input = null, OutputInterface $output = null) { - $args = isset($this->request->server['argv']) ? $this->request->server['argv'] : []; $this->dispatcher->dispatch(ConsoleEvent::EVENT_RUN, new ConsoleEvent( ConsoleEvent::EVENT_RUN, - $args + $this->request->server['argv'] )); return $this->application->run($input, $output); } diff --git a/public.php b/public.php index fb18bba4958cf..65257f1a46ed0 100644 --- a/public.php +++ b/public.php @@ -35,9 +35,9 @@ exit; } - $request = \OC::$server->getRequest(); - OC::checkMaintenanceMode($request); + OC::checkMaintenanceMode(); OC::checkSingleUserMode(true); + $request = \OC::$server->getRequest(); $pathInfo = $request->getPathInfo(); if (!$pathInfo && $request->getParam('service', '') === '') { diff --git a/tests/Core/Controller/OccControllerTest.php b/tests/Core/Controller/OccControllerTest.php deleted file mode 100644 index 682d9170096b0..0000000000000 --- a/tests/Core/Controller/OccControllerTest.php +++ /dev/null @@ -1,143 +0,0 @@ - - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace Tests\Core\Controller; - -use OC\Console\Application; -use OC\Core\Controller\OccController; -use OCP\IConfig; -use Symfony\Component\Console\Output\Output; -use Test\TestCase; - -/** - * Class OccControllerTest - * - * @package OC\Core\Controller - */ -class OccControllerTest extends TestCase { - - const TEMP_SECRET = 'test'; - - /** @var \OC\AppFramework\Http\Request | \PHPUnit_Framework_MockObject_MockObject */ - private $request; - /** @var \OC\Core\Controller\OccController | \PHPUnit_Framework_MockObject_MockObject */ - private $controller; - /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ - private $config; - /** @var Application | \PHPUnit_Framework_MockObject_MockObject */ - private $console; - - public function testFromInvalidLocation(){ - $this->getControllerMock('example.org'); - - $response = $this->controller->execute('status', ''); - $responseData = $response->getData(); - - $this->assertArrayHasKey('exitCode', $responseData); - $this->assertEquals(126, $responseData['exitCode']); - - $this->assertArrayHasKey('details', $responseData); - $this->assertEquals('Web executor is not allowed to run from a different host', $responseData['details']); - } - - public function testNotWhiteListedCommand(){ - $this->getControllerMock('localhost'); - - $response = $this->controller->execute('missing_command', ''); - $responseData = $response->getData(); - - $this->assertArrayHasKey('exitCode', $responseData); - $this->assertEquals(126, $responseData['exitCode']); - - $this->assertArrayHasKey('details', $responseData); - $this->assertEquals('Command "missing_command" is not allowed to run via web request', $responseData['details']); - } - - public function testWrongToken(){ - $this->getControllerMock('localhost'); - - $response = $this->controller->execute('status', self::TEMP_SECRET . '-'); - $responseData = $response->getData(); - - $this->assertArrayHasKey('exitCode', $responseData); - $this->assertEquals(126, $responseData['exitCode']); - - $this->assertArrayHasKey('details', $responseData); - $this->assertEquals('updater.secret does not match the provided token', $responseData['details']); - } - - public function testSuccess(){ - $this->getControllerMock('localhost'); - $this->console->expects($this->once())->method('run') - ->willReturnCallback( - function ($input, $output) { - /** @var Output $output */ - $output->writeln('{"installed":true,"version":"9.1.0.8","versionstring":"9.1.0 beta 2","edition":""}'); - return 0; - } - ); - - $response = $this->controller->execute('status', self::TEMP_SECRET, ['--output'=>'json']); - $responseData = $response->getData(); - - $this->assertArrayHasKey('exitCode', $responseData); - $this->assertEquals(0, $responseData['exitCode']); - - $this->assertArrayHasKey('response', $responseData); - $decoded = json_decode($responseData['response'], true); - - $this->assertArrayHasKey('installed', $decoded); - $this->assertEquals(true, $decoded['installed']); - } - - private function getControllerMock($host){ - $this->request = $this->getMockBuilder('OC\AppFramework\Http\Request') - ->setConstructorArgs([ - ['server' => []], - \OC::$server->getSecureRandom(), - \OC::$server->getConfig() - ]) - ->setMethods(['getRemoteAddress']) - ->getMock(); - - $this->request->expects($this->any())->method('getRemoteAddress') - ->will($this->returnValue($host)); - - $this->config = $this->getMockBuilder('\OCP\IConfig') - ->disableOriginalConstructor() - ->getMock(); - $this->config->expects($this->any())->method('getSystemValue') - ->with('updater.secret') - ->willReturn(password_hash(self::TEMP_SECRET, PASSWORD_DEFAULT)); - - $this->console = $this->getMockBuilder('\OC\Console\Application') - ->disableOriginalConstructor() - ->getMock(); - - $this->controller = new OccController( - 'core', - $this->request, - $this->config, - $this->console - ); - } - -}