7 changed files with 6 additions and 324 deletions
-
13core/Application.php
-
147core/Controller/OccController.php
-
1core/routes.php
-
19lib/base.php
-
3lib/private/Console/Application.php
-
4public.php
-
143tests/Core/Controller/OccControllerTest.php
@ -1,147 +0,0 @@ |
|||
<?php |
|||
/** |
|||
* @author Victor Dubiniuk <dubiniuk@owncloud.com> |
|||
* |
|||
* @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 <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
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 <pre>php -r \'echo password_hash("MyStrongSecretDoUseYourOwn!", PASSWORD_DEFAULT)."\n";\'</pre> and set this in the config.php.' |
|||
); |
|||
} |
|||
|
|||
if (!password_verify($token, $coreToken)) { |
|||
throw new \UnexpectedValueException( |
|||
'updater.secret does not match the provided token' |
|||
); |
|||
} |
|||
} |
|||
} |
@ -1,143 +0,0 @@ |
|||
<?php |
|||
/** |
|||
* @author Victor Dubiniuk <dubiniuk@owncloud.com> |
|||
* |
|||
* @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 <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
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 |
|||
); |
|||
} |
|||
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue