parent
e3b509220b
commit
2791b8f00d
@ -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 |
||||
); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue