diff options
Diffstat (limited to 'core/Command')
53 files changed, 5733 insertions, 0 deletions
diff --git a/core/Command/App/CheckCode.php b/core/Command/App/CheckCode.php new file mode 100644 index 00000000000..78f4390e70a --- /dev/null +++ b/core/Command/App/CheckCode.php @@ -0,0 +1,181 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\Command\App; + +use OC\App\CodeChecker\CodeChecker; +use OC\App\CodeChecker\EmptyCheck; +use OC\App\CodeChecker\InfoChecker; +use OC\App\InfoParser; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class CheckCode extends Command { + + /** @var InfoParser */ + private $infoParser; + + protected $checkers = [ + 'private' => '\OC\App\CodeChecker\PrivateCheck', + 'deprecation' => '\OC\App\CodeChecker\DeprecationCheck', + 'strong-comparison' => '\OC\App\CodeChecker\StrongComparisonCheck', + ]; + + public function __construct(InfoParser $infoParser) { + parent::__construct(); + $this->infoParser = $infoParser; + } + + protected function configure() { + $this + ->setName('app:check-code') + ->setDescription('check code to be compliant') + ->addArgument( + 'app-id', + InputArgument::REQUIRED, + 'check the specified app' + ) + ->addOption( + 'checker', + 'c', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'enable the specified checker(s)', + [ 'private', 'deprecation', 'strong-comparison' ] + ) + ->addOption( + '--skip-validate-info', + null, + InputOption::VALUE_NONE, + 'skips the info.xml/version check' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $appId = $input->getArgument('app-id'); + + $checkList = new EmptyCheck(); + foreach ($input->getOption('checker') as $checker) { + if (!isset($this->checkers[$checker])) { + throw new \InvalidArgumentException('Invalid checker: '.$checker); + } + $checkerClass = $this->checkers[$checker]; + $checkList = new $checkerClass($checkList); + } + + $codeChecker = new CodeChecker($checkList); + + $codeChecker->listen('CodeChecker', 'analyseFileBegin', function($params) use ($output) { + if(OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln("<info>Analysing {$params}</info>"); + } + }); + $codeChecker->listen('CodeChecker', 'analyseFileFinished', function($filename, $errors) use ($output) { + $count = count($errors); + + // show filename if the verbosity is low, but there are errors in a file + if($count > 0 && OutputInterface::VERBOSITY_VERBOSE > $output->getVerbosity()) { + $output->writeln("<info>Analysing {$filename}</info>"); + } + + // show error count if there are errors present or the verbosity is high + if($count > 0 || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln(" {$count} errors"); + } + usort($errors, function($a, $b) { + return $a['line'] >$b['line']; + }); + + foreach($errors as $p) { + $line = sprintf("%' 4d", $p['line']); + $output->writeln(" <error>line $line: {$p['disallowedToken']} - {$p['reason']}</error>"); + } + }); + $errors = $codeChecker->analyse($appId); + + if(!$input->getOption('skip-validate-info')) { + $infoChecker = new InfoChecker($this->infoParser); + + $infoChecker->listen('InfoChecker', 'mandatoryFieldMissing', function($key) use ($output) { + $output->writeln("<error>Mandatory field missing: $key</error>"); + }); + + $infoChecker->listen('InfoChecker', 'deprecatedFieldFound', function($key, $value) use ($output) { + if($value === [] || is_null($value) || $value === '') { + $output->writeln("<info>Deprecated field available: $key</info>"); + } else { + $output->writeln("<info>Deprecated field available: $key => $value</info>"); + } + }); + + $infoChecker->listen('InfoChecker', 'missingRequirement', function($minMax) use ($output) { + $output->writeln("<comment>ownCloud $minMax version requirement missing (will be an error in ownCloud 11 and later)</comment>"); + }); + + $infoChecker->listen('InfoChecker', 'duplicateRequirement', function($minMax) use ($output) { + $output->writeln("<error>Duplicate $minMax ownCloud version requirement found</error>"); + }); + + $infoChecker->listen('InfoChecker', 'differentVersions', function($versionFile, $infoXML) use ($output) { + $output->writeln("<error>Different versions provided (appinfo/version: $versionFile - appinfo/info.xml: $infoXML)</error>"); + }); + + $infoChecker->listen('InfoChecker', 'sameVersions', function($path) use ($output) { + $output->writeln("<info>Version file isn't needed anymore and can be safely removed ($path)</info>"); + }); + + $infoChecker->listen('InfoChecker', 'migrateVersion', function($version) use ($output) { + $output->writeln("<info>Migrate the app version to appinfo/info.xml (add <version>$version</version> to appinfo/info.xml and remove appinfo/version)</info>"); + }); + + if(OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $infoChecker->listen('InfoChecker', 'mandatoryFieldFound', function($key, $value) use ($output) { + $output->writeln("<info>Mandatory field available: $key => $value</info>"); + }); + + $infoChecker->listen('InfoChecker', 'optionalFieldFound', function($key, $value) use ($output) { + $output->writeln("<info>Optional field available: $key => $value</info>"); + }); + + $infoChecker->listen('InfoChecker', 'unusedFieldFound', function($key, $value) use ($output) { + $output->writeln("<info>Unused field available: $key => $value</info>"); + }); + } + + $infoErrors = $infoChecker->analyse($appId); + + $errors = array_merge($errors, $infoErrors); + } + + if (empty($errors)) { + $output->writeln('<info>App is compliant - awesome job!</info>'); + return 0; + } else { + $output->writeln('<error>App is not compliant</error>'); + return 101; + } + } +} diff --git a/core/Command/App/Disable.php b/core/Command/App/Disable.php new file mode 100644 index 00000000000..743a78cb88d --- /dev/null +++ b/core/Command/App/Disable.php @@ -0,0 +1,71 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Vincent Petry <pvince81@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\Command\App; + +use OCP\App\IAppManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Disable extends Command { + + /** @var IAppManager */ + protected $manager; + + /** + * @param IAppManager $manager + */ + public function __construct(IAppManager $manager) { + parent::__construct(); + $this->manager = $manager; + } + + protected function configure() { + $this + ->setName('app:disable') + ->setDescription('disable an app') + ->addArgument( + 'app-id', + InputArgument::REQUIRED, + 'disable the specified app' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $appId = $input->getArgument('app-id'); + if ($this->manager->isInstalled($appId)) { + try { + $this->manager->disableApp($appId); + $output->writeln($appId . ' disabled'); + } catch(\Exception $e) { + $output->writeln($e->getMessage()); + return 2; + } + } else { + $output->writeln('No such app enabled: ' . $appId); + } + } +} diff --git a/core/Command/App/Enable.php b/core/Command/App/Enable.php new file mode 100644 index 00000000000..0f6ce51fe8f --- /dev/null +++ b/core/Command/App/Enable.php @@ -0,0 +1,83 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Vincent Petry <pvince81@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\Command\App; + +use OCP\App\IAppManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Enable extends Command { + + /** @var IAppManager */ + protected $manager; + + /** + * @param IAppManager $manager + */ + public function __construct(IAppManager $manager) { + parent::__construct(); + $this->manager = $manager; + } + + protected function configure() { + $this + ->setName('app:enable') + ->setDescription('enable an app') + ->addArgument( + 'app-id', + InputArgument::REQUIRED, + 'enable the specified app' + ) + ->addOption( + 'groups', + 'g', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'enable the app only for a list of groups' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $appId = $input->getArgument('app-id'); + + if (!\OC_App::getAppPath($appId)) { + $output->writeln($appId . ' not found'); + return 1; + } + + $groups = $input->getOption('groups'); + if (empty($groups)) { + \OC_App::enable($appId); + $output->writeln($appId . ' enabled'); + } else { + \OC_App::enable($appId, $groups); + $output->writeln($appId . ' enabled for groups: ' . implode(', ', $groups)); + } + return 0; + } +} diff --git a/core/Command/App/GetPath.php b/core/Command/App/GetPath.php new file mode 100644 index 00000000000..33a3f64c53d --- /dev/null +++ b/core/Command/App/GetPath.php @@ -0,0 +1,62 @@ +<?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\Command\App; + +use OC\Core\Command\Base; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class GetPath extends Base { + protected function configure() { + parent::configure(); + + $this + ->setName('app:getpath') + ->setDescription('Get an absolute path to the app directory') + ->addArgument( + 'app', + InputArgument::REQUIRED, + 'Name of the app' + ) + ; + } + + /** + * Executes the current command. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * @return null|int null or 0 if everything went fine, or an error code + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $appName = $input->getArgument('app'); + $path = \OC_App::getAppPath($appName); + if ($path !== false) { + $output->writeln($path); + return 0; + } + + // App not found, exit with non-zero + return 1; + } +} diff --git a/core/Command/App/ListApps.php b/core/Command/App/ListApps.php new file mode 100644 index 00000000000..d7546b3c0c7 --- /dev/null +++ b/core/Command/App/ListApps.php @@ -0,0 +1,119 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @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\Command\App; + +use OC\Core\Command\Base; +use OCP\App\IAppManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ListApps extends Base { + + /** @var IAppManager */ + protected $manager; + + /** + * @param IAppManager $manager + */ + public function __construct(IAppManager $manager) { + parent::__construct(); + $this->manager = $manager; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('app:list') + ->setDescription('List all available apps') + ->addOption( + 'shipped', + null, + InputOption::VALUE_REQUIRED, + 'true - limit to shipped apps only, false - limit to non-shipped apps only' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if ($input->getOption('shipped') === 'true' || $input->getOption('shipped') === 'false'){ + $shippedFilter = $input->getOption('shipped') === 'true'; + } else { + $shippedFilter = null; + } + + $apps = \OC_App::getAllApps(); + $enabledApps = $disabledApps = []; + $versions = \OC_App::getAppVersions(); + + //sort enabled apps above disabled apps + foreach ($apps as $app) { + if ($shippedFilter !== null && \OC_App::isShipped($app) !== $shippedFilter){ + continue; + } + if ($this->manager->isInstalled($app)) { + $enabledApps[] = $app; + } else { + $disabledApps[] = $app; + } + } + + $apps = ['enabled' => [], 'disabled' => []]; + + sort($enabledApps); + foreach ($enabledApps as $app) { + $apps['enabled'][$app] = (isset($versions[$app])) ? $versions[$app] : true; + } + + sort($disabledApps); + foreach ($disabledApps as $app) { + $apps['disabled'][$app] = null; + } + + $this->writeAppList($input, $output, $apps); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param array $items + */ + protected function writeAppList(InputInterface $input, OutputInterface $output, $items) { + switch ($input->getOption('output')) { + case self::OUTPUT_FORMAT_PLAIN: + $output->writeln('Enabled:'); + parent::writeArrayInOutputFormat($input, $output, $items['enabled']); + + $output->writeln('Disabled:'); + parent::writeArrayInOutputFormat($input, $output, $items['disabled']); + break; + + default: + parent::writeArrayInOutputFormat($input, $output, $items); + break; + } + } +} diff --git a/core/Command/Background/Ajax.php b/core/Command/Background/Ajax.php new file mode 100644 index 00000000000..e9cd1405ebd --- /dev/null +++ b/core/Command/Background/Ajax.php @@ -0,0 +1,33 @@ +<?php +/** +* The MIT License (MIT) +* +* Copyright (c) 2015 Christian Kampka <christian@kampka.net> +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +namespace OC\Core\Command\Background; + +class Ajax extends Base { + + protected function getMode() { + return 'ajax'; + } +} diff --git a/core/Command/Background/Base.php b/core/Command/Background/Base.php new file mode 100644 index 00000000000..48fee818d0a --- /dev/null +++ b/core/Command/Background/Base.php @@ -0,0 +1,77 @@ +<?php +/** +* The MIT License (MIT) +* +* Copyright (c) 2015 Christian Kampka <christian@kampka.net> +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +namespace OC\Core\Command\Background; + +use \OCP\IConfig; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** +* An abstract base class for configuring the background job mode +* from the command line interface. +* Subclasses will override the getMode() function to specify the mode to configure. +*/ +abstract class Base extends Command { + + + abstract protected function getMode(); + + /** + * @var \OCP\IConfig + */ + protected $config; + + /** + * @param \OCP\IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $mode = $this->getMode(); + $this + ->setName("background:$mode") + ->setDescription("Use $mode to run background jobs"); + } + + /** + * Executing this command will set the background job mode for owncloud. + * The mode to set is specified by the concrete sub class by implementing the + * getMode() function. + * + * @param InputInterface $input + * @param OutputInterface $output + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $mode = $this->getMode(); + $this->config->setAppValue( 'core', 'backgroundjobs_mode', $mode ); + $output->writeln("Set mode for background jobs to '$mode'"); + } +} diff --git a/core/Command/Background/Cron.php b/core/Command/Background/Cron.php new file mode 100644 index 00000000000..434e88893b2 --- /dev/null +++ b/core/Command/Background/Cron.php @@ -0,0 +1,33 @@ +<?php +/** +* The MIT License (MIT) +* +* Copyright (c) 2015 Christian Kampka <christian@kampka.net> +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +namespace OC\Core\Command\Background; + +class Cron extends Base { + + protected function getMode() { + return 'cron'; + } +} diff --git a/core/Command/Background/WebCron.php b/core/Command/Background/WebCron.php new file mode 100644 index 00000000000..23dbe98e635 --- /dev/null +++ b/core/Command/Background/WebCron.php @@ -0,0 +1,33 @@ +<?php +/** +* The MIT License (MIT) +* +* Copyright (c) 2015 Christian Kampka <christian@kampka.net> +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +namespace OC\Core\Command\Background; + +class WebCron extends Base { + + protected function getMode() { + return 'webcron'; + } +} diff --git a/core/Command/Base.php b/core/Command/Base.php new file mode 100644 index 00000000000..7538effd74a --- /dev/null +++ b/core/Command/Base.php @@ -0,0 +1,160 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Base extends Command { + const OUTPUT_FORMAT_PLAIN = 'plain'; + const OUTPUT_FORMAT_JSON = 'json'; + const OUTPUT_FORMAT_JSON_PRETTY = 'json_pretty'; + + protected $defaultOutputFormat = self::OUTPUT_FORMAT_PLAIN; + + /** @var boolean */ + private $php_pcntl_signal = false; + + /** @var boolean */ + private $interrupted = false; + + protected function configure() { + $this + ->addOption( + 'output', + null, + InputOption::VALUE_OPTIONAL, + 'Output format (plain, json or json_pretty, default is plain)', + $this->defaultOutputFormat + ) + ; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param array $items + * @param string $prefix + */ + protected function writeArrayInOutputFormat(InputInterface $input, OutputInterface $output, $items, $prefix = ' - ') { + switch ($input->getOption('output')) { + case self::OUTPUT_FORMAT_JSON: + $output->writeln(json_encode($items)); + break; + case self::OUTPUT_FORMAT_JSON_PRETTY: + $output->writeln(json_encode($items, JSON_PRETTY_PRINT)); + break; + default: + foreach ($items as $key => $item) { + if (is_array($item)) { + $output->writeln($prefix . $key . ':'); + $this->writeArrayInOutputFormat($input, $output, $item, ' ' . $prefix); + continue; + } + if (!is_int($key)) { + $value = $this->valueToString($item); + if (!is_null($value)) { + $output->writeln($prefix . $key . ': ' . $value); + } else { + $output->writeln($prefix . $key); + } + } else { + $output->writeln($prefix . $this->valueToString($item)); + } + } + break; + } + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param mixed $item + */ + protected function writeMixedInOutputFormat(InputInterface $input, OutputInterface $output, $item) { + if (is_array($item)) { + $this->writeArrayInOutputFormat($input, $output, $item, ''); + return; + } + + switch ($input->getOption('output')) { + case self::OUTPUT_FORMAT_JSON: + $output->writeln(json_encode($item)); + break; + case self::OUTPUT_FORMAT_JSON_PRETTY: + $output->writeln(json_encode($item, JSON_PRETTY_PRINT)); + break; + default: + $output->writeln($this->valueToString($item, false)); + break; + } + } + + protected function valueToString($value, $returnNull = true) { + if ($value === false) { + return 'false'; + } else if ($value === true) { + return 'true'; + } else if ($value === null) { + return ($returnNull) ? null : 'null'; + } else { + return $value; + } + } + + /** + * @return bool + */ + protected function hasBeenInterrupted() { + // return always false if pcntl_signal functions are not accessible + if ($this->php_pcntl_signal) { + pcntl_signal_dispatch(); + return $this->interrupted; + } else { + return false; + } + } + + /** + * Changes the status of the command to "interrupted" if ctrl-c has been pressed + * + * Gives a chance to the command to properly terminate what it's doing + */ + protected function cancelOperation() { + $this->interrupted = true; + } + + public function run(InputInterface $input, OutputInterface $output) { + // check if the php pcntl_signal functions are accessible + $this->php_pcntl_signal = function_exists('pcntl_signal'); + if ($this->php_pcntl_signal) { + // Collect interrupts and notify the running command + pcntl_signal(SIGTERM, [$this, 'cancelOperation']); + pcntl_signal(SIGINT, [$this, 'cancelOperation']); + } + + return parent::run($input, $output); + } +} diff --git a/core/Command/Check.php b/core/Command/Check.php new file mode 100644 index 00000000000..c2e92f7a8da --- /dev/null +++ b/core/Command/Check.php @@ -0,0 +1,61 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\Command; + +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Check extends Base { + /** + * @var IConfig + */ + private $config; + + public function __construct(IConfig $config) { + parent::__construct(); + $this->config = $config; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('check') + ->setDescription('check dependencies of the server environment') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $errors = \OC_Util::checkServer($this->config); + if (!empty($errors)) { + $errors = array_map(function($item) { + return (string) $item['error']; + }, $errors); + + $this->writeArrayInOutputFormat($input, $output, $errors); + return 1; + } + return 0; + } +} diff --git a/core/Command/Config/App/DeleteConfig.php b/core/Command/Config/App/DeleteConfig.php new file mode 100644 index 00000000000..cccd92ea3d6 --- /dev/null +++ b/core/Command/Config/App/DeleteConfig.php @@ -0,0 +1,81 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Config\App; + +use OC\Core\Command\Base; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class DeleteConfig extends Base { + /** * @var IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + parent::__construct(); + $this->config = $config; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('config:app:delete') + ->setDescription('Delete an app config value') + ->addArgument( + 'app', + InputArgument::REQUIRED, + 'Name of the app' + ) + ->addArgument( + 'name', + InputArgument::REQUIRED, + 'Name of the config to delete' + ) + ->addOption( + 'error-if-not-exists', + null, + InputOption::VALUE_NONE, + 'Checks whether the config exists before deleting it' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $appName = $input->getArgument('app'); + $configName = $input->getArgument('name'); + + if ($input->hasParameterOption('--error-if-not-exists') && !in_array($configName, $this->config->getAppKeys($appName))) { + $output->writeln('<error>Config ' . $configName . ' of app ' . $appName . ' could not be deleted because it did not exist</error>'); + return 1; + } + + $this->config->deleteAppValue($appName, $configName); + $output->writeln('<info>Config value ' . $configName . ' of app ' . $appName . ' deleted</info>'); + return 0; + } +} diff --git a/core/Command/Config/App/GetConfig.php b/core/Command/Config/App/GetConfig.php new file mode 100644 index 00000000000..abe71e57d8c --- /dev/null +++ b/core/Command/Config/App/GetConfig.php @@ -0,0 +1,93 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Config\App; + +use OC\Core\Command\Base; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class GetConfig extends Base { + /** * @var IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + parent::__construct(); + $this->config = $config; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('config:app:get') + ->setDescription('Get an app config value') + ->addArgument( + 'app', + InputArgument::REQUIRED, + 'Name of the app' + ) + ->addArgument( + 'name', + InputArgument::REQUIRED, + 'Name of the config to get' + ) + ->addOption( + 'default-value', + null, + InputOption::VALUE_OPTIONAL, + 'If no default value is set and the config does not exist, the command will exit with 1' + ) + ; + } + + /** + * Executes the current command. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * @return null|int null or 0 if everything went fine, or an error code + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $appName = $input->getArgument('app'); + $configName = $input->getArgument('name'); + $defaultValue = $input->getOption('default-value'); + + if (!in_array($configName, $this->config->getAppKeys($appName)) && !$input->hasParameterOption('--default-value')) { + return 1; + } + + if (!in_array($configName, $this->config->getAppKeys($appName))) { + $configValue = $defaultValue; + } else { + $configValue = $this->config->getAppValue($appName, $configName); + } + + $this->writeMixedInOutputFormat($input, $output, $configValue); + return 0; + } +} diff --git a/core/Command/Config/App/SetConfig.php b/core/Command/Config/App/SetConfig.php new file mode 100644 index 00000000000..097fde6ba95 --- /dev/null +++ b/core/Command/Config/App/SetConfig.php @@ -0,0 +1,89 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Config\App; + +use OC\Core\Command\Base; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class SetConfig extends Base { + /** * @var IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + parent::__construct(); + $this->config = $config; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('config:app:set') + ->setDescription('Set an app config value') + ->addArgument( + 'app', + InputArgument::REQUIRED, + 'Name of the app' + ) + ->addArgument( + 'name', + InputArgument::REQUIRED, + 'Name of the config to set' + ) + ->addOption( + 'value', + null, + InputOption::VALUE_REQUIRED, + 'The new value of the config' + ) + ->addOption( + 'update-only', + null, + InputOption::VALUE_NONE, + 'Only updates the value, if it is not set before, it is not being added' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $appName = $input->getArgument('app'); + $configName = $input->getArgument('name'); + + if (!in_array($configName, $this->config->getAppKeys($appName)) && $input->hasParameterOption('--update-only')) { + $output->writeln('<comment>Config value ' . $configName . ' for app ' . $appName . ' not updated, as it has not been set before.</comment>'); + return 1; + } + + $configValue = $input->getOption('value'); + $this->config->setAppValue($appName, $configName, $configValue); + + $output->writeln('<info>Config value ' . $configName . ' for app ' . $appName . ' set to ' . $configValue . '</info>'); + return 0; + } +} diff --git a/core/Command/Config/Import.php b/core/Command/Config/Import.php new file mode 100644 index 00000000000..7f1e09d2c95 --- /dev/null +++ b/core/Command/Config/Import.php @@ -0,0 +1,195 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Config; + +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Import extends Command { + protected $validRootKeys = ['system', 'apps']; + + /** @var IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + parent::__construct(); + $this->config = $config; + } + + protected function configure() { + $this + ->setName('config:import') + ->setDescription('Import a list of configs') + ->addArgument( + 'file', + InputArgument::OPTIONAL, + 'File with the json array to import' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $importFile = $input->getArgument('file'); + if ($importFile !== null) { + $content = $this->getArrayFromFile($importFile); + } else { + $content = $this->getArrayFromStdin(); + } + + try { + $configs = $this->validateFileContent($content); + } catch (\UnexpectedValueException $e) { + $output->writeln('<error>' . $e->getMessage(). '</error>'); + return; + } + + if (!empty($configs['system'])) { + $this->config->setSystemValues($configs['system']); + } + + if (!empty($configs['apps'])) { + foreach ($configs['apps'] as $app => $appConfigs) { + foreach ($appConfigs as $key => $value) { + if ($value === null) { + $this->config->deleteAppValue($app, $key); + } else { + $this->config->setAppValue($app, $key, $value); + } + } + } + } + + $output->writeln('<info>Config successfully imported from: ' . $importFile . '</info>'); + } + + /** + * Get the content from stdin ("config:import < file.json") + * + * @return string + */ + protected function getArrayFromStdin() { + // Read from stdin. stream_set_blocking is used to prevent blocking + // when nothing is passed via stdin. + stream_set_blocking(STDIN, 0); + $content = file_get_contents('php://stdin'); + stream_set_blocking(STDIN, 1); + return $content; + } + + /** + * Get the content of the specified file ("config:import file.json") + * + * @param string $importFile + * @return string + */ + protected function getArrayFromFile($importFile) { + $content = file_get_contents($importFile); + return $content; + } + + /** + * @param string $content + * @return array + * @throws \UnexpectedValueException when the array is invalid + */ + protected function validateFileContent($content) { + $decodedContent = json_decode($content, true); + if (!is_array($decodedContent) || empty($decodedContent)) { + throw new \UnexpectedValueException('The file must contain a valid json array'); + } + + $this->validateArray($decodedContent); + + return $decodedContent; + } + + /** + * Validates that the array only contains `system` and `apps` + * + * @param array $array + */ + protected function validateArray($array) { + $arrayKeys = array_keys($array); + $additionalKeys = array_diff($arrayKeys, $this->validRootKeys); + $commonKeys = array_intersect($arrayKeys, $this->validRootKeys); + if (!empty($additionalKeys)) { + throw new \UnexpectedValueException('Found invalid entries in root: ' . implode(', ', $additionalKeys)); + } + if (empty($commonKeys)) { + throw new \UnexpectedValueException('At least one key of the following is expected: ' . implode(', ', $this->validRootKeys)); + } + + if (isset($array['system'])) { + if (is_array($array['system'])) { + foreach ($array['system'] as $name => $value) { + $this->checkTypeRecursively($value, $name); + } + } else { + throw new \UnexpectedValueException('The system config array is not an array'); + } + } + + if (isset($array['apps'])) { + if (is_array($array['apps'])) { + $this->validateAppsArray($array['apps']); + } else { + throw new \UnexpectedValueException('The apps config array is not an array'); + } + } + } + + /** + * @param mixed $configValue + * @param string $configName + */ + protected function checkTypeRecursively($configValue, $configName) { + if (!is_array($configValue) && !is_bool($configValue) && !is_int($configValue) && !is_string($configValue) && !is_null($configValue)) { + throw new \UnexpectedValueException('Invalid system config value for "' . $configName . '". Only arrays, bools, integers, strings and null (delete) are allowed.'); + } + if (is_array($configValue)) { + foreach ($configValue as $key => $value) { + $this->checkTypeRecursively($value, $configName); + } + } + } + + /** + * Validates that app configs are only integers and strings + * + * @param array $array + */ + protected function validateAppsArray($array) { + foreach ($array as $app => $configs) { + foreach ($configs as $name => $value) { + if (!is_int($value) && !is_string($value) && !is_null($value)) { + throw new \UnexpectedValueException('Invalid app config value for "' . $app . '":"' . $name . '". Only integers, strings and null (delete) are allowed.'); + } + } + } + } +} diff --git a/core/Command/Config/ListConfigs.php b/core/Command/Config/ListConfigs.php new file mode 100644 index 00000000000..afebe4c4c07 --- /dev/null +++ b/core/Command/Config/ListConfigs.php @@ -0,0 +1,129 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Config; + +use OC\Core\Command\Base; +use OC\SystemConfig; +use OCP\IAppConfig; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ListConfigs extends Base { + protected $defaultOutputFormat = self::OUTPUT_FORMAT_JSON_PRETTY; + + /** * @var SystemConfig */ + protected $systemConfig; + + /** @var IAppConfig */ + protected $appConfig; + + /** + * @param SystemConfig $systemConfig + * @param IAppConfig $appConfig + */ + public function __construct(SystemConfig $systemConfig, IAppConfig $appConfig) { + parent::__construct(); + $this->systemConfig = $systemConfig; + $this->appConfig = $appConfig; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('config:list') + ->setDescription('List all configs') + ->addArgument( + 'app', + InputArgument::OPTIONAL, + 'Name of the app ("system" to get the config.php values, "all" for all apps and system)', + 'all' + ) + ->addOption( + 'private', + null, + InputOption::VALUE_NONE, + 'Use this option when you want to include sensitive configs like passwords, salts, ...' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $app = $input->getArgument('app'); + $noSensitiveValues = !$input->getOption('private'); + + switch ($app) { + case 'system': + $configs = [ + 'system' => $this->getSystemConfigs($noSensitiveValues), + ]; + break; + + case 'all': + $apps = $this->appConfig->getApps(); + $configs = [ + 'system' => $this->getSystemConfigs($noSensitiveValues), + 'apps' => [], + ]; + foreach ($apps as $appName) { + $configs['apps'][$appName] = $this->appConfig->getValues($appName, false); + } + break; + + default: + $configs = [ + 'apps' => [ + $app => $this->appConfig->getValues($app, false), + ], + ]; + } + + $this->writeArrayInOutputFormat($input, $output, $configs); + } + + /** + * Get the system configs + * + * @param bool $noSensitiveValues + * @return array + */ + protected function getSystemConfigs($noSensitiveValues) { + $keys = $this->systemConfig->getKeys(); + + $configs = []; + foreach ($keys as $key) { + if ($noSensitiveValues) { + $value = $this->systemConfig->getFilteredValue($key, serialize(null)); + } else { + $value = $this->systemConfig->getValue($key, serialize(null)); + } + + if ($value !== 'N;') { + $configs[$key] = $value; + } + } + + return $configs; + } +} diff --git a/core/Command/Config/System/DeleteConfig.php b/core/Command/Config/System/DeleteConfig.php new file mode 100644 index 00000000000..374f5ac69b7 --- /dev/null +++ b/core/Command/Config/System/DeleteConfig.php @@ -0,0 +1,117 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Config\System; + +use OC\Core\Command\Base; +use OC\SystemConfig; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class DeleteConfig extends Base { + /** * @var SystemConfig */ + protected $systemConfig; + + /** + * @param SystemConfig $systemConfig + */ + public function __construct(SystemConfig $systemConfig) { + parent::__construct(); + $this->systemConfig = $systemConfig; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('config:system:delete') + ->setDescription('Delete a system config value') + ->addArgument( + 'name', + InputArgument::REQUIRED | InputArgument::IS_ARRAY, + 'Name of the config to delete, specify multiple for array parameter' + ) + ->addOption( + 'error-if-not-exists', + null, + InputOption::VALUE_NONE, + 'Checks whether the config exists before deleting it' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $configNames = $input->getArgument('name'); + $configName = $configNames[0]; + + if (sizeof($configNames) > 1) { + if ($input->hasParameterOption('--error-if-not-exists') && !in_array($configName, $this->systemConfig->getKeys())) { + $output->writeln('<error>System config ' . implode(' => ', $configNames) . ' could not be deleted because it did not exist</error>'); + return 1; + } + + $value = $this->systemConfig->getValue($configName); + + try { + $value = $this->removeSubValue(array_slice($configNames, 1), $value, $input->hasParameterOption('--error-if-not-exists')); + } + catch (\UnexpectedValueException $e) { + $output->writeln('<error>System config ' . implode(' => ', $configNames) . ' could not be deleted because it did not exist</error>'); + return 1; + } + + $this->systemConfig->setValue($configName, $value); + $output->writeln('<info>System config value ' . implode(' => ', $configNames) . ' deleted</info>'); + return 0; + } else { + if ($input->hasParameterOption('--error-if-not-exists') && !in_array($configName, $this->systemConfig->getKeys())) { + $output->writeln('<error>System config ' . $configName . ' could not be deleted because it did not exist</error>'); + return 1; + } + + $this->systemConfig->deleteValue($configName); + $output->writeln('<info>System config value ' . $configName . ' deleted</info>'); + return 0; + } + } + + protected function removeSubValue($keys, $currentValue, $throwError) { + $nextKey = array_shift($keys); + + if (is_array($currentValue)) { + if (isset($currentValue[$nextKey])) { + if (empty($keys)) { + unset($currentValue[$nextKey]); + } else { + $currentValue[$nextKey] = $this->removeSubValue($keys, $currentValue[$nextKey], $throwError); + } + } else if ($throwError) { + throw new \UnexpectedValueException('Config parameter does not exist'); + } + } else if ($throwError) { + throw new \UnexpectedValueException('Config parameter does not exist'); + } + + return $currentValue; + } +} diff --git a/core/Command/Config/System/GetConfig.php b/core/Command/Config/System/GetConfig.php new file mode 100644 index 00000000000..b76474112a0 --- /dev/null +++ b/core/Command/Config/System/GetConfig.php @@ -0,0 +1,100 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Config\System; + +use OC\Core\Command\Base; +use OC\SystemConfig; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class GetConfig extends Base { + /** * @var SystemConfig */ + protected $systemConfig; + + /** + * @param SystemConfig $systemConfig + */ + public function __construct(SystemConfig $systemConfig) { + parent::__construct(); + $this->systemConfig = $systemConfig; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('config:system:get') + ->setDescription('Get a system config value') + ->addArgument( + 'name', + InputArgument::REQUIRED | InputArgument::IS_ARRAY, + 'Name of the config to get, specify multiple for array parameter' + ) + ->addOption( + 'default-value', + null, + InputOption::VALUE_OPTIONAL, + 'If no default value is set and the config does not exist, the command will exit with 1' + ) + ; + } + + /** + * Executes the current command. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * @return null|int null or 0 if everything went fine, or an error code + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $configNames = $input->getArgument('name'); + $configName = array_shift($configNames); + $defaultValue = $input->getOption('default-value'); + + if (!in_array($configName, $this->systemConfig->getKeys()) && !$input->hasParameterOption('--default-value')) { + return 1; + } + + if (!in_array($configName, $this->systemConfig->getKeys())) { + $configValue = $defaultValue; + } else { + $configValue = $this->systemConfig->getValue($configName); + if (!empty($configNames)) { + foreach ($configNames as $configName) { + if (isset($configValue[$configName])) { + $configValue = $configValue[$configName]; + } else if (!$input->hasParameterOption('--default-value')) { + return 1; + } else { + $configValue = $defaultValue; + break; + } + } + } + } + + $this->writeMixedInOutputFormat($input, $output, $configValue); + return 0; + } +} diff --git a/core/Command/Config/System/SetConfig.php b/core/Command/Config/System/SetConfig.php new file mode 100644 index 00000000000..c7f206b05d1 --- /dev/null +++ b/core/Command/Config/System/SetConfig.php @@ -0,0 +1,198 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * + * @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\Command\Config\System; + +use OC\Core\Command\Base; +use OC\SystemConfig; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class SetConfig extends Base { + /** * @var SystemConfig */ + protected $systemConfig; + + /** + * @param SystemConfig $systemConfig + */ + public function __construct(SystemConfig $systemConfig) { + parent::__construct(); + $this->systemConfig = $systemConfig; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('config:system:set') + ->setDescription('Set a system config value') + ->addArgument( + 'name', + InputArgument::REQUIRED | InputArgument::IS_ARRAY, + 'Name of the config parameter, specify multiple for array parameter' + ) + ->addOption( + 'type', + null, + InputOption::VALUE_REQUIRED, + 'Value type [string, integer, double, boolean]', + 'string' + ) + ->addOption( + 'value', + null, + InputOption::VALUE_REQUIRED, + 'The new value of the config' + ) + ->addOption( + 'update-only', + null, + InputOption::VALUE_NONE, + 'Only updates the value, if it is not set before, it is not being added' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $configNames = $input->getArgument('name'); + $configName = $configNames[0]; + $configValue = $this->castValue($input->getOption('value'), $input->getOption('type')); + $updateOnly = $input->getOption('update-only'); + + if (sizeof($configNames) > 1) { + $existingValue = $this->systemConfig->getValue($configName); + + $newValue = $this->mergeArrayValue( + array_slice($configNames, 1), $existingValue, $configValue['value'], $updateOnly + ); + + $this->systemConfig->setValue($configName, $newValue); + } else { + if ($updateOnly && !in_array($configName, $this->systemConfig->getKeys(), true)) { + throw new \UnexpectedValueException('Config parameter does not exist'); + } + + $this->systemConfig->setValue($configName, $configValue['value']); + } + + $output->writeln('<info>System config value ' . implode(' => ', $configNames) . ' set to ' . $configValue['readable-value'] . '</info>'); + return 0; + } + + /** + * @param string $value + * @param string $type + * @return mixed + * @throws \InvalidArgumentException + */ + protected function castValue($value, $type) { + switch ($type) { + case 'integer': + case 'int': + if (!is_numeric($value)) { + throw new \InvalidArgumentException('Non-numeric value specified'); + } + return [ + 'value' => (int) $value, + 'readable-value' => 'integer ' . (int) $value, + ]; + + case 'double': + case 'float': + if (!is_numeric($value)) { + throw new \InvalidArgumentException('Non-numeric value specified'); + } + return [ + 'value' => (double) $value, + 'readable-value' => 'double ' . (double) $value, + ]; + + case 'boolean': + case 'bool': + $value = strtolower($value); + switch ($value) { + case 'true': + return [ + 'value' => true, + 'readable-value' => 'boolean ' . $value, + ]; + + case 'false': + return [ + 'value' => false, + 'readable-value' => 'boolean ' . $value, + ]; + + default: + throw new \InvalidArgumentException('Unable to parse value as boolean'); + } + + case 'null': + return [ + 'value' => null, + 'readable-value' => 'null', + ]; + + case 'string': + $value = (string) $value; + return [ + 'value' => $value, + 'readable-value' => ($value === '') ? 'empty string' : 'string ' . $value, + ]; + + default: + throw new \InvalidArgumentException('Invalid type'); + } + } + + /** + * @param array $configNames + * @param mixed $existingValues + * @param mixed $value + * @param bool $updateOnly + * @return array merged value + * @throws \UnexpectedValueException + */ + protected function mergeArrayValue(array $configNames, $existingValues, $value, $updateOnly) { + $configName = array_shift($configNames); + if (!is_array($existingValues)) { + $existingValues = []; + } + if (!empty($configNames)) { + if (isset($existingValues[$configName])) { + $existingValue = $existingValues[$configName]; + } else { + $existingValue = []; + } + $existingValues[$configName] = $this->mergeArrayValue($configNames, $existingValue, $value, $updateOnly); + } else { + if (!isset($existingValues[$configName]) && $updateOnly) { + throw new \UnexpectedValueException('Config parameter does not exist'); + } + $existingValues[$configName] = $value; + } + return $existingValues; + } + +} diff --git a/core/Command/Db/ConvertType.php b/core/Command/Db/ConvertType.php new file mode 100644 index 00000000000..864499dcce0 --- /dev/null +++ b/core/Command/Db/ConvertType.php @@ -0,0 +1,311 @@ +<?php +/** + * @author Andreas Fischer <bantu@owncloud.com> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author tbelau666 <thomas.belau@gmx.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author unclejamal3000 <andreas.pramhaas@posteo.de> + * + * @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\Command\Db; + +use \OCP\IConfig; +use OC\DB\Connection; +use OC\DB\ConnectionFactory; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ConvertType extends Command { + /** + * @var \OCP\IConfig + */ + protected $config; + + /** + * @var \OC\DB\ConnectionFactory + */ + protected $connectionFactory; + + /** + * @param \OCP\IConfig $config + * @param \OC\DB\ConnectionFactory $connectionFactory + */ + public function __construct(IConfig $config, ConnectionFactory $connectionFactory) { + $this->config = $config; + $this->connectionFactory = $connectionFactory; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('db:convert-type') + ->setDescription('Convert the ownCloud database to the newly configured one') + ->addArgument( + 'type', + InputArgument::REQUIRED, + 'the type of the database to convert to' + ) + ->addArgument( + 'username', + InputArgument::REQUIRED, + 'the username of the database to convert to' + ) + ->addArgument( + 'hostname', + InputArgument::REQUIRED, + 'the hostname of the database to convert to' + ) + ->addArgument( + 'database', + InputArgument::REQUIRED, + 'the name of the database to convert to' + ) + ->addOption( + 'port', + null, + InputOption::VALUE_REQUIRED, + 'the port of the database to convert to' + ) + ->addOption( + 'password', + null, + InputOption::VALUE_REQUIRED, + 'the password of the database to convert to. Will be asked when not specified. Can also be passed via stdin.' + ) + ->addOption( + 'clear-schema', + null, + InputOption::VALUE_NONE, + 'remove all tables from the destination database' + ) + ->addOption( + 'all-apps', + null, + InputOption::VALUE_NONE, + 'whether to create schema for all apps instead of only installed apps' + ) + ; + } + + protected function validateInput(InputInterface $input, OutputInterface $output) { + $type = $this->connectionFactory->normalizeType($input->getArgument('type')); + if ($type === 'sqlite3') { + throw new \InvalidArgumentException( + 'Converting to SQLite (sqlite3) is currently not supported.' + ); + } + if ($type === $this->config->getSystemValue('dbtype', '')) { + throw new \InvalidArgumentException(sprintf( + 'Can not convert from %1$s to %1$s.', + $type + )); + } + if ($type === 'oci' && $input->getOption('clear-schema')) { + // Doctrine unconditionally tries (at least in version 2.3) + // to drop sequence triggers when dropping a table, even though + // such triggers may not exist. This results in errors like + // "ORA-04080: trigger 'OC_STORAGES_AI_PK' does not exist". + throw new \InvalidArgumentException( + 'The --clear-schema option is not supported when converting to Oracle (oci).' + ); + } + } + + protected function readPassword(InputInterface $input, OutputInterface $output) { + // Explicitly specified password + if ($input->getOption('password')) { + return; + } + + // Read from stdin. stream_set_blocking is used to prevent blocking + // when nothing is passed via stdin. + stream_set_blocking(STDIN, 0); + $password = file_get_contents('php://stdin'); + stream_set_blocking(STDIN, 1); + if (trim($password) !== '') { + $input->setOption('password', $password); + return; + } + + // Read password by interacting + if ($input->isInteractive()) { + /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */ + $dialog = $this->getHelperSet()->get('dialog'); + $password = $dialog->askHiddenResponse( + $output, + '<question>What is the database password?</question>', + false + ); + $input->setOption('password', $password); + return; + } + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $this->validateInput($input, $output); + $this->readPassword($input, $output); + + $fromDB = \OC::$server->getDatabaseConnection(); + $toDB = $this->getToDBConnection($input, $output); + + if ($input->getOption('clear-schema')) { + $this->clearSchema($toDB, $input, $output); + } + + $this->createSchema($toDB, $input, $output); + + $toTables = $this->getTables($toDB); + $fromTables = $this->getTables($fromDB); + + // warn/fail if there are more tables in 'from' database + $extraFromTables = array_diff($fromTables, $toTables); + if (!empty($extraFromTables)) { + $output->writeln('<comment>The following tables will not be converted:</comment>'); + $output->writeln($extraFromTables); + if (!$input->getOption('all-apps')) { + $output->writeln('<comment>Please note that tables belonging to available but currently not installed apps</comment>'); + $output->writeln('<comment>can be included by specifying the --all-apps option.</comment>'); + } + /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */ + $dialog = $this->getHelperSet()->get('dialog'); + if (!$dialog->askConfirmation( + $output, + '<question>Continue with the conversion (y/n)? [n] </question>', + false + )) { + return; + } + } + $intersectingTables = array_intersect($toTables, $fromTables); + $this->convertDB($fromDB, $toDB, $intersectingTables, $input, $output); + } + + protected function createSchema(Connection $toDB, InputInterface $input, OutputInterface $output) { + $output->writeln('<info>Creating schema in new database</info>'); + $schemaManager = new \OC\DB\MDB2SchemaManager($toDB); + $schemaManager->createDbFromStructure(\OC::$SERVERROOT.'/db_structure.xml'); + $apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps(); + foreach($apps as $app) { + if (file_exists(\OC_App::getAppPath($app).'/appinfo/database.xml')) { + $schemaManager->createDbFromStructure(\OC_App::getAppPath($app).'/appinfo/database.xml'); + } + } + } + + protected function getToDBConnection(InputInterface $input, OutputInterface $output) { + $type = $input->getArgument('type'); + $connectionParams = array( + 'host' => $input->getArgument('hostname'), + 'user' => $input->getArgument('username'), + 'password' => $input->getOption('password'), + 'dbname' => $input->getArgument('database'), + 'tablePrefix' => $this->config->getSystemValue('dbtableprefix', 'oc_'), + ); + if ($input->getOption('port')) { + $connectionParams['port'] = $input->getOption('port'); + } + return $this->connectionFactory->getConnection($type, $connectionParams); + } + + protected function clearSchema(Connection $db, InputInterface $input, OutputInterface $output) { + $toTables = $this->getTables($db); + if (!empty($toTables)) { + $output->writeln('<info>Clearing schema in new database</info>'); + } + foreach($toTables as $table) { + $db->getSchemaManager()->dropTable($table); + } + } + + protected function getTables(Connection $db) { + $filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/'; + $db->getConfiguration()-> + setFilterSchemaAssetsExpression($filterExpression); + return $db->getSchemaManager()->listTableNames(); + } + + protected function copyTable(Connection $fromDB, Connection $toDB, $table, InputInterface $input, OutputInterface $output) { + /** @var $progress \Symfony\Component\Console\Helper\ProgressHelper */ + $progress = $this->getHelperSet()->get('progress'); + $query = 'SELECT COUNT(*) FROM '.$table; + $count = $fromDB->fetchColumn($query); + $query = 'SELECT * FROM '.$table; + $statement = $fromDB->executeQuery($query); + $progress->start($output, $count); + $progress->setRedrawFrequency($count > 100 ? 5 : 1); + while($row = $statement->fetch()) { + $progress->advance(); + if ($input->getArgument('type') === 'oci') { + $data = $row; + } else { + $data = array(); + foreach ($row as $columnName => $value) { + $data[$toDB->quoteIdentifier($columnName)] = $value; + } + } + $toDB->insert($table, $data); + } + $progress->finish(); + } + + protected function convertDB(Connection $fromDB, Connection $toDB, array $tables, InputInterface $input, OutputInterface $output) { + $this->config->setSystemValue('maintenance', true); + try { + // copy table rows + foreach($tables as $table) { + $output->writeln($table); + $this->copyTable($fromDB, $toDB, $table, $input, $output); + } + if ($input->getArgument('type') === 'pgsql') { + $tools = new \OC\DB\PgSqlTools($this->config); + $tools->resynchronizeDatabaseSequences($toDB); + } + // save new database config + $this->saveDBInfo($input); + } catch(\Exception $e) { + $this->config->setSystemValue('maintenance', false); + throw $e; + } + $this->config->setSystemValue('maintenance', false); + } + + protected function saveDBInfo(InputInterface $input) { + $type = $input->getArgument('type'); + $username = $input->getArgument('username'); + $dbHost = $input->getArgument('hostname'); + $dbName = $input->getArgument('database'); + $password = $input->getOption('password'); + if ($input->getOption('port')) { + $dbHost .= ':'.$input->getOption('port'); + } + + $this->config->setSystemValues([ + 'dbtype' => $type, + 'dbname' => $dbName, + 'dbhost' => $dbHost, + 'dbuser' => $username, + 'dbpassword' => $password, + ]); + } +} diff --git a/core/Command/Db/GenerateChangeScript.php b/core/Command/Db/GenerateChangeScript.php new file mode 100644 index 00000000000..85436b02d65 --- /dev/null +++ b/core/Command/Db/GenerateChangeScript.php @@ -0,0 +1,58 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\Command\Db; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class GenerateChangeScript extends Command { + protected function configure() { + $this + ->setName('db:generate-change-script') + ->setDescription('generates the change script from the current connected db to db_structure.xml') + ->addArgument( + 'schema-xml', + InputArgument::OPTIONAL, + 'the schema xml to be used as target schema', + \OC::$SERVERROOT . '/db_structure.xml' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + + $file = $input->getArgument('schema-xml'); + + $schemaManager = new \OC\DB\MDB2SchemaManager(\OC::$server->getDatabaseConnection()); + + try { + $result = $schemaManager->updateDbFromStructure($file, true); + $output->writeln($result); + } catch (\Exception $e) { + $output->writeln('Failed to update database structure ('.$e.')'); + } + + } +} diff --git a/core/Command/Encryption/ChangeKeyStorageRoot.php b/core/Command/Encryption/ChangeKeyStorageRoot.php new file mode 100644 index 00000000000..801a08b42a8 --- /dev/null +++ b/core/Command/Encryption/ChangeKeyStorageRoot.php @@ -0,0 +1,270 @@ +<?php +/** + * @author Björn Schießle <schiessle@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\Command\Encryption; + +use OC\Encryption\Keys\Storage; +use OC\Encryption\Util; +use OC\Files\Filesystem; +use OC\Files\View; +use OCP\IConfig; +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class ChangeKeyStorageRoot extends Command { + + /** @var View */ + protected $rootView; + + /** @var IUserManager */ + protected $userManager; + + /** @var IConfig */ + protected $config; + + /** @var Util */ + protected $util; + + /** @var QuestionHelper */ + protected $questionHelper; + + /** + * @param View $view + * @param IUserManager $userManager + * @param IConfig $config + * @param Util $util + * @param QuestionHelper $questionHelper + */ + public function __construct(View $view, IUserManager $userManager, IConfig $config, Util $util, QuestionHelper $questionHelper) { + parent::__construct(); + $this->rootView = $view; + $this->userManager = $userManager; + $this->config = $config; + $this->util = $util; + $this->questionHelper = $questionHelper; + } + + protected function configure() { + parent::configure(); + $this + ->setName('encryption:change-key-storage-root') + ->setDescription('Change key storage root') + ->addArgument( + 'newRoot', + InputArgument::OPTIONAL, + 'new root of the key storage relative to the data folder' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $oldRoot = $this->util->getKeyStorageRoot(); + $newRoot = $input->getArgument('newRoot'); + + if ($newRoot === null) { + $question = new ConfirmationQuestion('No storage root given, do you want to reset the key storage root to the default location? (y/n) ', false); + if (!$this->questionHelper->ask($input, $output, $question)) { + return; + } + $newRoot = ''; + } + + $oldRootDescription = $oldRoot !== '' ? $oldRoot : 'default storage location'; + $newRootDescription = $newRoot !== '' ? $newRoot : 'default storage location'; + $output->writeln("Change key storage root from <info>$oldRootDescription</info> to <info>$newRootDescription</info>"); + $success = $this->moveAllKeys($oldRoot, $newRoot, $output); + if ($success) { + $this->util->setKeyStorageRoot($newRoot); + $output->writeln(''); + $output->writeln("Key storage root successfully changed to <info>$newRootDescription</info>"); + } + } + + /** + * move keys to new key storage root + * + * @param string $oldRoot + * @param string $newRoot + * @param OutputInterface $output + * @return bool + * @throws \Exception + */ + protected function moveAllKeys($oldRoot, $newRoot, OutputInterface $output) { + + $output->writeln("Start to move keys:"); + + if ($this->rootView->is_dir(($oldRoot)) === false) { + $output->writeln("No old keys found: Nothing needs to be moved"); + return false; + } + + $this->prepareNewRoot($newRoot); + $this->moveSystemKeys($oldRoot, $newRoot); + $this->moveUserKeys($oldRoot, $newRoot, $output); + + return true; + } + + /** + * prepare new key storage + * + * @param string $newRoot + * @throws \Exception + */ + protected function prepareNewRoot($newRoot) { + if ($this->rootView->is_dir($newRoot) === false) { + throw new \Exception("New root folder doesn't exist. Please create the folder or check the permissions and try again."); + } + + $result = $this->rootView->file_put_contents( + $newRoot . '/' . Storage::KEY_STORAGE_MARKER, + 'ownCloud will detect this folder as key storage root only if this file exists' + ); + + if ($result === false) { + throw new \Exception("Can't write to new root folder. Please check the permissions and try again"); + } + + } + + + /** + * move system key folder + * + * @param string $oldRoot + * @param string $newRoot + */ + protected function moveSystemKeys($oldRoot, $newRoot) { + if ( + $this->rootView->is_dir($oldRoot . '/files_encryption') && + $this->targetExists($newRoot . '/files_encryption') === false + ) { + $this->rootView->rename($oldRoot . '/files_encryption', $newRoot . '/files_encryption'); + } + } + + + /** + * setup file system for the given user + * + * @param string $uid + */ + protected function setupUserFS($uid) { + \OC_Util::tearDownFS(); + \OC_Util::setupFS($uid); + } + + + /** + * iterate over each user and move the keys to the new storage + * + * @param string $oldRoot + * @param string $newRoot + * @param OutputInterface $output + */ + protected function moveUserKeys($oldRoot, $newRoot, OutputInterface $output) { + + $progress = new ProgressBar($output); + $progress->start(); + + + foreach($this->userManager->getBackends() as $backend) { + $limit = 500; + $offset = 0; + do { + $users = $backend->getUsers('', $limit, $offset); + foreach ($users as $user) { + $progress->advance(); + $this->setupUserFS($user); + $this->moveUserEncryptionFolder($user, $oldRoot, $newRoot); + } + $offset += $limit; + } while(count($users) >= $limit); + } + $progress->finish(); + } + + /** + * move user encryption folder to new root folder + * + * @param string $user + * @param string $oldRoot + * @param string $newRoot + * @throws \Exception + */ + protected function moveUserEncryptionFolder($user, $oldRoot, $newRoot) { + + if ($this->userManager->userExists($user)) { + + $source = $oldRoot . '/' . $user . '/files_encryption'; + $target = $newRoot . '/' . $user . '/files_encryption'; + if ( + $this->rootView->is_dir($source) && + $this->targetExists($target) === false + ) { + $this->prepareParentFolder($newRoot . '/' . $user); + $this->rootView->rename($source, $target); + } + } + } + + /** + * Make preparations to filesystem for saving a key file + * + * @param string $path relative to data/ + */ + protected function prepareParentFolder($path) { + $path = Filesystem::normalizePath($path); + // If the file resides within a subdirectory, create it + if ($this->rootView->file_exists($path) === false) { + $sub_dirs = explode('/', ltrim($path, '/')); + $dir = ''; + foreach ($sub_dirs as $sub_dir) { + $dir .= '/' . $sub_dir; + if ($this->rootView->file_exists($dir) === false) { + $this->rootView->mkdir($dir); + } + } + } + } + + /** + * check if target already exists + * + * @param $path + * @return bool + * @throws \Exception + */ + protected function targetExists($path) { + if ($this->rootView->file_exists($path)) { + throw new \Exception("new folder '$path' already exists"); + } + + return false; + } + +} diff --git a/core/Command/Encryption/DecryptAll.php b/core/Command/Encryption/DecryptAll.php new file mode 100644 index 00000000000..0a126db5b17 --- /dev/null +++ b/core/Command/Encryption/DecryptAll.php @@ -0,0 +1,160 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@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\Command\Encryption; + +use OCP\App\IAppManager; +use OCP\Encryption\IManager; +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class DecryptAll extends Command { + + /** @var IManager */ + protected $encryptionManager; + + /** @var IAppManager */ + protected $appManager; + + /** @var IConfig */ + protected $config; + + /** @var QuestionHelper */ + protected $questionHelper; + + /** @var bool */ + protected $wasTrashbinEnabled; + + /** @var bool */ + protected $wasSingleUserModeEnabled; + + /** @var \OC\Encryption\DecryptAll */ + protected $decryptAll; + + /** + * @param IManager $encryptionManager + * @param IAppManager $appManager + * @param IConfig $config + * @param \OC\Encryption\DecryptAll $decryptAll + * @param QuestionHelper $questionHelper + */ + public function __construct( + IManager $encryptionManager, + IAppManager $appManager, + IConfig $config, + \OC\Encryption\DecryptAll $decryptAll, + QuestionHelper $questionHelper + ) { + parent::__construct(); + + $this->appManager = $appManager; + $this->encryptionManager = $encryptionManager; + $this->config = $config; + $this->decryptAll = $decryptAll; + $this->questionHelper = $questionHelper; + } + + /** + * Set single user mode and disable the trashbin app + */ + protected function forceSingleUserAndTrashbin() { + $this->wasTrashbinEnabled = $this->appManager->isEnabledForUser('files_trashbin'); + $this->wasSingleUserModeEnabled = $this->config->getSystemValue('singleuser', false); + $this->config->setSystemValue('singleuser', true); + $this->appManager->disableApp('files_trashbin'); + } + + /** + * Reset the single user mode and re-enable the trashbin app + */ + protected function resetSingleUserAndTrashbin() { + $this->config->setSystemValue('singleuser', $this->wasSingleUserModeEnabled); + if ($this->wasTrashbinEnabled) { + $this->appManager->enableApp('files_trashbin'); + } + } + + protected function configure() { + parent::configure(); + + $this->setName('encryption:decrypt-all'); + $this->setDescription('Disable server-side encryption and decrypt all files'); + $this->setHelp( + 'This will disable server-side encryption and decrypt all files for ' + . 'all users if it is supported by your encryption module. ' + . 'Please make sure that no user access his files during this process!' + ); + $this->addArgument( + 'user', + InputArgument::OPTIONAL, + 'user for which you want to decrypt all files (optional)' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + + try { + if ($this->encryptionManager->isEnabled() === true) { + $output->write('Disable server side encryption... '); + $this->config->setAppValue('core', 'encryption_enabled', 'no'); + $output->writeln('done.'); + } else { + $output->writeln('Server side encryption not enabled. Nothing to do.'); + return; + } + + $output->writeln("\n"); + $output->writeln('You are about to start to decrypt all files stored in your ownCloud.'); + $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!'); + $output->writeln(''); + $question = new ConfirmationQuestion('Do you really want to continue? (y/n) ', false); + if ($this->questionHelper->ask($input, $output, $question)) { + $this->forceSingleUserAndTrashbin(); + $user = $input->getArgument('user'); + $result = $this->decryptAll->decryptAll($input, $output, $user); + if ($result === false) { + $output->writeln(' aborted.'); + $this->config->setAppValue('core', 'encryption_enabled', 'yes'); + } + $this->resetSingleUserAndTrashbin(); + } else { + $output->write('Enable server side encryption... '); + $this->config->setAppValue('core', 'encryption_enabled', 'yes'); + $output->writeln('done.'); + $output->writeln('aborted'); + } + } catch (\Exception $e) { + // enable server side encryption again if something went wrong + $this->config->setAppValue('core', 'encryption_enabled', 'yes'); + $this->resetSingleUserAndTrashbin(); + throw $e; + } + + } +} diff --git a/core/Command/Encryption/Disable.php b/core/Command/Encryption/Disable.php new file mode 100644 index 00000000000..0e08a314473 --- /dev/null +++ b/core/Command/Encryption/Disable.php @@ -0,0 +1,56 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Encryption; + +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Disable extends Command { + /** @var IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + parent::__construct(); + $this->config = $config; + } + + protected function configure() { + $this + ->setName('encryption:disable') + ->setDescription('Disable encryption') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if ($this->config->getAppValue('core', 'encryption_enabled', 'no') !== 'yes') { + $output->writeln('Encryption is already disabled'); + } else { + $this->config->setAppValue('core', 'encryption_enabled', 'no'); + $output->writeln('<info>Encryption disabled</info>'); + } + } +} diff --git a/core/Command/Encryption/Enable.php b/core/Command/Encryption/Enable.php new file mode 100644 index 00000000000..273320e6155 --- /dev/null +++ b/core/Command/Encryption/Enable.php @@ -0,0 +1,78 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Encryption; + +use OCP\Encryption\IManager; +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Enable extends Command { + /** @var IConfig */ + protected $config; + + /** @var IManager */ + protected $encryptionManager; + + /** + * @param IConfig $config + * @param IManager $encryptionManager + */ + public function __construct(IConfig $config, IManager $encryptionManager) { + parent::__construct(); + + $this->encryptionManager = $encryptionManager; + $this->config = $config; + } + + protected function configure() { + $this + ->setName('encryption:enable') + ->setDescription('Enable encryption') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if ($this->config->getAppValue('core', 'encryption_enabled', 'no') === 'yes') { + $output->writeln('Encryption is already enabled'); + } else { + $this->config->setAppValue('core', 'encryption_enabled', 'yes'); + $output->writeln('<info>Encryption enabled</info>'); + } + $output->writeln(''); + + $modules = $this->encryptionManager->getEncryptionModules(); + if (empty($modules)) { + $output->writeln('<error>No encryption module is loaded</error>'); + } else { + $defaultModule = $this->config->getAppValue('core', 'default_encryption_module', null); + if ($defaultModule === null) { + $output->writeln('<error>No default module is set</error>'); + } else if (!isset($modules[$defaultModule])) { + $output->writeln('<error>The current default module does not exist: ' . $defaultModule . '</error>'); + } else { + $output->writeln('Default module: ' . $defaultModule); + } + } + } +} diff --git a/core/Command/Encryption/EncryptAll.php b/core/Command/Encryption/EncryptAll.php new file mode 100644 index 00000000000..02f74a9dea4 --- /dev/null +++ b/core/Command/Encryption/EncryptAll.php @@ -0,0 +1,134 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@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\Command\Encryption; + +use OCP\App\IAppManager; +use OCP\Encryption\IManager; +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class EncryptAll extends Command { + + /** @var IManager */ + protected $encryptionManager; + + /** @var IAppManager */ + protected $appManager; + + /** @var IConfig */ + protected $config; + + /** @var QuestionHelper */ + protected $questionHelper; + + /** @var bool */ + protected $wasTrashbinEnabled; + + /** @var bool */ + protected $wasSingleUserModeEnabled; + + /** + * @param IManager $encryptionManager + * @param IAppManager $appManager + * @param IConfig $config + * @param QuestionHelper $questionHelper + */ + public function __construct( + IManager $encryptionManager, + IAppManager $appManager, + IConfig $config, + QuestionHelper $questionHelper + ) { + parent::__construct(); + $this->appManager = $appManager; + $this->encryptionManager = $encryptionManager; + $this->config = $config; + $this->questionHelper = $questionHelper; + } + + /** + * Set single user mode and disable the trashbin app + */ + protected function forceSingleUserAndTrashbin() { + $this->wasTrashbinEnabled = $this->appManager->isEnabledForUser('files_trashbin'); + $this->wasSingleUserModeEnabled = $this->config->getSystemValue('singleuser', false); + $this->config->setSystemValue('singleuser', true); + $this->appManager->disableApp('files_trashbin'); + } + + /** + * Reset the single user mode and re-enable the trashbin app + */ + protected function resetSingleUserAndTrashbin() { + $this->config->setSystemValue('singleuser', $this->wasSingleUserModeEnabled); + if ($this->wasTrashbinEnabled) { + $this->appManager->enableApp('files_trashbin'); + } + } + + protected function configure() { + parent::configure(); + + $this->setName('encryption:encrypt-all'); + $this->setDescription('Encrypt all files for all users'); + $this->setHelp( + 'This will encrypt all files for all users. ' + . 'Please make sure that no user access his files during this process!' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + + if ($this->encryptionManager->isEnabled() === false) { + throw new \Exception('Server side encryption is not enabled'); + } + + $output->writeln("\n"); + $output->writeln('You are about to start to encrypt all files stored in your ownCloud.'); + $output->writeln('It will depend on the encryption module you use which files get encrypted.'); + $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!'); + $output->writeln(''); + $question = new ConfirmationQuestion('Do you really want to continue? (y/n) ', false); + if ($this->questionHelper->ask($input, $output, $question)) { + $this->forceSingleUserAndTrashbin(); + + try { + $defaultModule = $this->encryptionManager->getEncryptionModule(); + $defaultModule->encryptAll($input, $output); + } catch (\Exception $ex) { + $this->resetSingleUserAndTrashbin(); + throw $ex; + } + + $this->resetSingleUserAndTrashbin(); + } else { + $output->writeln('aborted'); + } + } + +} diff --git a/core/Command/Encryption/ListModules.php b/core/Command/Encryption/ListModules.php new file mode 100644 index 00000000000..9c061b6e764 --- /dev/null +++ b/core/Command/Encryption/ListModules.php @@ -0,0 +1,80 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Encryption; + +use OC\Core\Command\Base; +use OCP\Encryption\IManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ListModules extends Base { + /** @var IManager */ + protected $encryptionManager; + + /** + * @param IManager $encryptionManager + */ + public function __construct(IManager $encryptionManager) { + parent::__construct(); + $this->encryptionManager = $encryptionManager; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('encryption:list-modules') + ->setDescription('List all available encryption modules') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $encryptionModules = $this->encryptionManager->getEncryptionModules(); + $defaultEncryptionModuleId = $this->encryptionManager->getDefaultEncryptionModuleId(); + + $encModules = array(); + foreach ($encryptionModules as $module) { + $encModules[$module['id']]['displayName'] = $module['displayName']; + $encModules[$module['id']]['default'] = $module['id'] === $defaultEncryptionModuleId; + } + $this->writeModuleList($input, $output, $encModules); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param array $items + */ + protected function writeModuleList(InputInterface $input, OutputInterface $output, $items) { + if ($input->getOption('output') === self::OUTPUT_FORMAT_PLAIN) { + array_walk($items, function(&$item) { + if (!$item['default']) { + $item = $item['displayName']; + } else { + $item = $item['displayName'] . ' [default*]'; + } + }); + } + + $this->writeArrayInOutputFormat($input, $output, $items); + } +} diff --git a/core/Command/Encryption/SetDefaultModule.php b/core/Command/Encryption/SetDefaultModule.php new file mode 100644 index 00000000000..e9978536201 --- /dev/null +++ b/core/Command/Encryption/SetDefaultModule.php @@ -0,0 +1,68 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Encryption; + + +use OCP\Encryption\IManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class SetDefaultModule extends Command { + /** @var IManager */ + protected $encryptionManager; + + /** + * @param IManager $encryptionManager + */ + public function __construct(IManager $encryptionManager) { + parent::__construct(); + $this->encryptionManager = $encryptionManager; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('encryption:set-default-module') + ->setDescription('Set the encryption default module') + ->addArgument( + 'module', + InputArgument::REQUIRED, + 'ID of the encryption module that should be used' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $moduleId = $input->getArgument('module'); + + if ($moduleId === $this->encryptionManager->getDefaultEncryptionModuleId()) { + $output->writeln('"' . $moduleId . '"" is already the default module'); + } else if ($this->encryptionManager->setDefaultEncryptionModule($moduleId)) { + $output->writeln('<info>Set default module to "' . $moduleId . '"</info>'); + } else { + $output->writeln('<error>The specified module "' . $moduleId . '" does not exist</error>'); + } + } +} diff --git a/core/Command/Encryption/ShowKeyStorageRoot.php b/core/Command/Encryption/ShowKeyStorageRoot.php new file mode 100644 index 00000000000..402352c4bcf --- /dev/null +++ b/core/Command/Encryption/ShowKeyStorageRoot.php @@ -0,0 +1,58 @@ +<?php +/** + * @author Björn Schießle <schiessle@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\Command\Encryption; + +use OC\Encryption\Util; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ShowKeyStorageRoot extends Command{ + + /** @var Util */ + protected $util; + + /** + * @param Util $util + */ + public function __construct(Util $util) { + parent::__construct(); + $this->util = $util; + } + + protected function configure() { + parent::configure(); + $this + ->setName('encryption:show-key-storage-root') + ->setDescription('Show current key storage root'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $currentRoot = $this->util->getKeyStorageRoot(); + + $rootDescription = $currentRoot !== '' ? $currentRoot : 'default storage location (data/)'; + + $output->writeln("Current key storage root: <info>$rootDescription</info>"); + } + +} diff --git a/core/Command/Encryption/Status.php b/core/Command/Encryption/Status.php new file mode 100644 index 00000000000..b97ea8833fa --- /dev/null +++ b/core/Command/Encryption/Status.php @@ -0,0 +1,56 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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\Command\Encryption; + +use OC\Core\Command\Base; +use OCP\Encryption\IManager; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Status extends Base { + /** @var IManager */ + protected $encryptionManager; + + /** + * @param IManager $encryptionManager + */ + public function __construct(IManager $encryptionManager) { + parent::__construct(); + $this->encryptionManager = $encryptionManager; + } + + protected function configure() { + parent::configure(); + + $this + ->setName('encryption:status') + ->setDescription('Lists the current status of encryption') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $this->writeArrayInOutputFormat($input, $output, [ + 'enabled' => $this->encryptionManager->isEnabled(), + 'defaultModule' => $this->encryptionManager->getDefaultEncryptionModuleId(), + ]); + } +} diff --git a/core/Command/Integrity/CheckApp.php b/core/Command/Integrity/CheckApp.php new file mode 100644 index 00000000000..643af5285b4 --- /dev/null +++ b/core/Command/Integrity/CheckApp.php @@ -0,0 +1,69 @@ +<?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\Command\Integrity; + +use OC\IntegrityCheck\Checker; +use OC\Core\Command\Base; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Class CheckApp + * + * @package OC\Core\Command\Integrity + */ +class CheckApp extends Base { + + /** + * @var Checker + */ + private $checker; + + public function __construct(Checker $checker) { + parent::__construct(); + $this->checker = $checker; + } + + /** + * {@inheritdoc } + */ + protected function configure() { + parent::configure(); + $this + ->setName('integrity:check-app') + ->setDescription('Check integrity of an app using a signature.') + ->addArgument('appid', null, InputArgument::REQUIRED, 'Application to check') + ->addOption('path', null, InputOption::VALUE_OPTIONAL, 'Path to application. If none is given it will be guessed.'); + } + + /** + * {@inheritdoc } + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $appid = $input->getArgument('appid'); + $path = strval($input->getOption('path')); + $result = $this->checker->verifyAppSignature($appid, $path); + $this->writeArrayInOutputFormat($input, $output, $result); + } + +} diff --git a/core/Command/Integrity/CheckCore.php b/core/Command/Integrity/CheckCore.php new file mode 100644 index 00000000000..460a78e4da7 --- /dev/null +++ b/core/Command/Integrity/CheckCore.php @@ -0,0 +1,62 @@ +<?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\Command\Integrity; + +use OC\IntegrityCheck\Checker; +use OC\Core\Command\Base; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Class CheckCore + * + * @package OC\Core\Command\Integrity + */ +class CheckCore extends Base { + /** + * @var Checker + */ + private $checker; + + public function __construct(Checker $checker) { + parent::__construct(); + $this->checker = $checker; + } + + /** + * {@inheritdoc } + */ + protected function configure() { + parent::configure(); + $this + ->setName('integrity:check-core') + ->setDescription('Check integrity of core code using a signature.'); + } + + /** + * {@inheritdoc } + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $result = $this->checker->verifyCoreSignature(); + $this->writeArrayInOutputFormat($input, $output, $result); + } +} diff --git a/core/Command/Integrity/SignApp.php b/core/Command/Integrity/SignApp.php new file mode 100644 index 00000000000..53df9619c6d --- /dev/null +++ b/core/Command/Integrity/SignApp.php @@ -0,0 +1,107 @@ +<?php +/** + * @author Lukas Reschke <lukas@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\Command\Integrity; + +use OC\IntegrityCheck\Checker; +use OC\IntegrityCheck\Helpers\FileAccessHelper; +use OCP\IURLGenerator; +use phpseclib\Crypt\RSA; +use phpseclib\File\X509; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Class SignApp + * + * @package OC\Core\Command\Integrity + */ +class SignApp extends Command { + /** @var Checker */ + private $checker; + /** @var FileAccessHelper */ + private $fileAccessHelper; + /** @var IURLGenerator */ + private $urlGenerator; + + /** + * @param Checker $checker + * @param FileAccessHelper $fileAccessHelper + * @param IURLGenerator $urlGenerator + */ + public function __construct(Checker $checker, + FileAccessHelper $fileAccessHelper, + IURLGenerator $urlGenerator) { + parent::__construct(null); + $this->checker = $checker; + $this->fileAccessHelper = $fileAccessHelper; + $this->urlGenerator = $urlGenerator; + } + + protected function configure() { + $this + ->setName('integrity:sign-app') + ->setDescription('Signs an app using a private key.') + ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Application to sign') + ->addOption('privateKey', null, InputOption::VALUE_REQUIRED, 'Path to private key to use for signing') + ->addOption('certificate', null, InputOption::VALUE_REQUIRED, 'Path to certificate to use for signing'); + } + + /** + * {@inheritdoc } + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $path = $input->getOption('path'); + $privateKeyPath = $input->getOption('privateKey'); + $keyBundlePath = $input->getOption('certificate'); + if(is_null($path) || is_null($privateKeyPath) || is_null($keyBundlePath)) { + $documentationUrl = $this->urlGenerator->linkToDocs('developer-code-integrity'); + $output->writeln('This command requires the --path, --privateKey and --certificate.'); + $output->writeln('Example: ./occ integrity:sign-app --path="/Users/lukasreschke/Programming/myapp/" --privateKey="/Users/lukasreschke/private/myapp.key" --certificate="/Users/lukasreschke/public/mycert.crt"'); + $output->writeln('For more information please consult the documentation: '. $documentationUrl); + return null; + } + + $privateKey = $this->fileAccessHelper->file_get_contents($privateKeyPath); + $keyBundle = $this->fileAccessHelper->file_get_contents($keyBundlePath); + + if($privateKey === false) { + $output->writeln(sprintf('Private key "%s" does not exists.', $privateKeyPath)); + return null; + } + + if($keyBundle === false) { + $output->writeln(sprintf('Certificate "%s" does not exists.', $keyBundlePath)); + return null; + } + + $rsa = new RSA(); + $rsa->loadKey($privateKey); + $x509 = new X509(); + $x509->loadX509($keyBundle); + $x509->setPrivateKey($rsa); + $this->checker->writeAppSignature($path, $x509, $rsa); + + $output->writeln('Successfully signed "'.$path.'"'); + } +} diff --git a/core/Command/Integrity/SignCore.php b/core/Command/Integrity/SignCore.php new file mode 100644 index 00000000000..e5c2de73e00 --- /dev/null +++ b/core/Command/Integrity/SignCore.php @@ -0,0 +1,100 @@ +<?php +/** + * @author Lukas Reschke <lukas@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\Command\Integrity; + +use OC\IntegrityCheck\Checker; +use OC\IntegrityCheck\Helpers\EnvironmentHelper; +use OC\IntegrityCheck\Helpers\FileAccessHelper; +use phpseclib\Crypt\RSA; +use phpseclib\File\X509; +use Symfony\Component\Console\Command\Command; +use OCP\IConfig; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Class SignCore + * + * @package OC\Core\Command\Integrity + */ +class SignCore extends Command { + /** @var Checker */ + private $checker; + /** @var FileAccessHelper */ + private $fileAccessHelper; + + /** + * @param Checker $checker + * @param FileAccessHelper $fileAccessHelper + */ + public function __construct(Checker $checker, + FileAccessHelper $fileAccessHelper) { + parent::__construct(null); + $this->checker = $checker; + $this->fileAccessHelper = $fileAccessHelper; + } + + protected function configure() { + $this + ->setName('integrity:sign-core') + ->setDescription('Sign core using a private key.') + ->addOption('privateKey', null, InputOption::VALUE_REQUIRED, 'Path to private key to use for signing') + ->addOption('certificate', null, InputOption::VALUE_REQUIRED, 'Path to certificate to use for signing') + ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Path of core to sign'); + } + + /** + * {@inheritdoc } + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $privateKeyPath = $input->getOption('privateKey'); + $keyBundlePath = $input->getOption('certificate'); + $path = $input->getOption('path'); + if(is_null($privateKeyPath) || is_null($keyBundlePath) || is_null($path)) { + $output->writeln('--privateKey, --certificate and --path are required.'); + return null; + } + + $privateKey = $this->fileAccessHelper->file_get_contents($privateKeyPath); + $keyBundle = $this->fileAccessHelper->file_get_contents($keyBundlePath); + + if($privateKey === false) { + $output->writeln(sprintf('Private key "%s" does not exists.', $privateKeyPath)); + return null; + } + + if($keyBundle === false) { + $output->writeln(sprintf('Certificate "%s" does not exists.', $keyBundlePath)); + return null; + } + + $rsa = new RSA(); + $rsa->loadKey($privateKey); + $x509 = new X509(); + $x509->loadX509($keyBundle); + $x509->setPrivateKey($rsa); + $this->checker->writeCoreSignature($x509, $rsa, $path); + + $output->writeln('Successfully signed "core"'); + } +} diff --git a/core/Command/L10n/CreateJs.php b/core/Command/L10n/CreateJs.php new file mode 100644 index 00000000000..c2cfc5d0934 --- /dev/null +++ b/core/Command/L10n/CreateJs.php @@ -0,0 +1,137 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\Command\L10n; + +use DirectoryIterator; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use UnexpectedValueException; + +class CreateJs extends Command { + + protected function configure() { + $this + ->setName('l10n:createjs') + ->setDescription('Create javascript translation files for a given app') + ->addArgument( + 'app', + InputOption::VALUE_REQUIRED, + 'name of the app' + ) + ->addArgument( + 'lang', + InputOption::VALUE_OPTIONAL, + 'name of the language' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $app = $input->getArgument('app'); + $lang = $input->getArgument('lang'); + + $path = \OC_App::getAppPath($app); + if ($path === false) { + $output->writeln("The app <$app> is unknown."); + return; + } + $languages = $lang; + if (empty($lang)) { + $languages= $this->getAllLanguages($path); + } + + foreach($languages as $lang) { + $this->writeFiles($app, $path, $lang, $output); + } + } + + private function getAllLanguages($path) { + $result = array(); + foreach (new DirectoryIterator("$path/l10n") as $fileInfo) { + if($fileInfo->isDot()) { + continue; + } + if($fileInfo->isDir()) { + continue; + } + if($fileInfo->getExtension() !== 'php') { + continue; + } + $result[]= substr($fileInfo->getBasename(), 0, -4); + } + + return $result; + } + + private function writeFiles($app, $path, $lang, OutputInterface $output) { + list($translations, $plurals) = $this->loadTranslations($path, $lang); + $this->writeJsFile($app, $path, $lang, $output, $translations, $plurals); + $this->writeJsonFile($path, $lang, $output, $translations, $plurals); + } + + private function writeJsFile($app, $path, $lang, OutputInterface $output, $translations, $plurals) { + $jsFile = "$path/l10n/$lang.js"; + if (file_exists($jsFile)) { + $output->writeln("File already exists: $jsFile"); + return; + } + $content = "OC.L10N.register(\n \"$app\",\n {\n "; + $jsTrans = array(); + foreach ($translations as $id => $val) { + if (is_array($val)) { + $val = '[ ' . join(',', $val) . ']'; + } + $jsTrans[] = "\"$id\" : \"$val\""; + } + $content .= join(",\n ", $jsTrans); + $content .= "\n},\n\"$plurals\");\n"; + + file_put_contents($jsFile, $content); + $output->writeln("Javascript translation file generated: $jsFile"); + } + + private function writeJsonFile($path, $lang, OutputInterface $output, $translations, $plurals) { + $jsFile = "$path/l10n/$lang.json"; + if (file_exists($jsFile)) { + $output->writeln("File already exists: $jsFile"); + return; + } + $content = array('translations' => $translations, 'pluralForm' => $plurals); + file_put_contents($jsFile, json_encode($content)); + $output->writeln("Json translation file generated: $jsFile"); + } + + private function loadTranslations($path, $lang) { + $phpFile = "$path/l10n/$lang.php"; + $TRANSLATIONS = array(); + $PLURAL_FORMS = ''; + if (!file_exists($phpFile)) { + throw new UnexpectedValueException("PHP translation file <$phpFile> does not exist."); + } + require $phpFile; + + return array($TRANSLATIONS, $PLURAL_FORMS); + } +} diff --git a/core/Command/Log/Manage.php b/core/Command/Log/Manage.php new file mode 100644 index 00000000000..1d65d7ed0d8 --- /dev/null +++ b/core/Command/Log/Manage.php @@ -0,0 +1,171 @@ +<?php +/** + * @author Robin McCorkell <robin@mccorkell.me.uk> + * + * @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\Command\Log; + +use \OCP\IConfig; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Manage extends Command { + + const DEFAULT_BACKEND = 'owncloud'; + const DEFAULT_LOG_LEVEL = 2; + const DEFAULT_TIMEZONE = 'UTC'; + + /** @var IConfig */ + protected $config; + + public function __construct(IConfig $config) { + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('log:manage') + ->setDescription('manage logging configuration') + ->addOption( + 'backend', + null, + InputOption::VALUE_REQUIRED, + 'set the logging backend [owncloud, syslog, errorlog]' + ) + ->addOption( + 'level', + null, + InputOption::VALUE_REQUIRED, + 'set the log level [debug, info, warning, error]' + ) + ->addOption( + 'timezone', + null, + InputOption::VALUE_REQUIRED, + 'set the logging timezone' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + // collate config setting to the end, to avoid partial configuration + $toBeSet = []; + + if ($backend = $input->getOption('backend')) { + $this->validateBackend($backend); + $toBeSet['log_type'] = $backend; + } + + if ($level = $input->getOption('level')) { + if (is_numeric($level)) { + $levelNum = $level; + // sanity check + $this->convertLevelNumber($levelNum); + } else { + $levelNum = $this->convertLevelString($level); + } + $toBeSet['loglevel'] = $levelNum; + } + + if ($timezone = $input->getOption('timezone')) { + $this->validateTimezone($timezone); + $toBeSet['logtimezone'] = $timezone; + } + + // set config + foreach ($toBeSet as $option => $value) { + $this->config->setSystemValue($option, $value); + } + + // display configuration + $backend = $this->config->getSystemValue('log_type', self::DEFAULT_BACKEND); + $output->writeln('Enabled logging backend: '.$backend); + + $levelNum = $this->config->getSystemValue('loglevel', self::DEFAULT_LOG_LEVEL); + $level = $this->convertLevelNumber($levelNum); + $output->writeln('Log level: '.$level.' ('.$levelNum.')'); + + $timezone = $this->config->getSystemValue('logtimezone', self::DEFAULT_TIMEZONE); + $output->writeln('Log timezone: '.$timezone); + } + + /** + * @param string $backend + * @throws \InvalidArgumentException + */ + protected function validateBackend($backend) { + if (!class_exists('OC_Log_'.$backend)) { + throw new \InvalidArgumentException('Invalid backend'); + } + } + + /** + * @param string $timezone + * @throws \Exception + */ + protected function validateTimezone($timezone) { + new \DateTimeZone($timezone); + } + + /** + * @param string $level + * @return int + * @throws \InvalidArgumentException + */ + protected function convertLevelString($level) { + $level = strtolower($level); + switch ($level) { + case 'debug': + return 0; + case 'info': + return 1; + case 'warning': + case 'warn': + return 2; + case 'error': + case 'err': + return 3; + } + throw new \InvalidArgumentException('Invalid log level string'); + } + + /** + * @param int $levelNum + * @return string + * @throws \InvalidArgumentException + */ + protected function convertLevelNumber($levelNum) { + switch ($levelNum) { + case 0: + return 'Debug'; + case 1: + return 'Info'; + case 2: + return 'Warning'; + case 3: + return 'Error'; + } + throw new \InvalidArgumentException('Invalid log level number'); + } +} diff --git a/core/Command/Log/OwnCloud.php b/core/Command/Log/OwnCloud.php new file mode 100644 index 00000000000..7213f6726a2 --- /dev/null +++ b/core/Command/Log/OwnCloud.php @@ -0,0 +1,124 @@ +<?php +/** + * @author Robin McCorkell <robin@mccorkell.me.uk> + * + * @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\Command\Log; + +use \OCP\IConfig; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class OwnCloud extends Command { + + /** @var IConfig */ + protected $config; + + public function __construct(IConfig $config) { + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('log:owncloud') + ->setDescription('manipulate ownCloud logging backend') + ->addOption( + 'enable', + null, + InputOption::VALUE_NONE, + 'enable this logging backend' + ) + ->addOption( + 'file', + null, + InputOption::VALUE_REQUIRED, + 'set the log file path' + ) + ->addOption( + 'rotate-size', + null, + InputOption::VALUE_REQUIRED, + 'set the file size for log rotation, 0 = disabled' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $toBeSet = []; + + if ($input->getOption('enable')) { + $toBeSet['log_type'] = 'owncloud'; + } + + if ($file = $input->getOption('file')) { + $toBeSet['logfile'] = $file; + } + + if (($rotateSize = $input->getOption('rotate-size')) !== null) { + $rotateSize = \OCP\Util::computerFileSize($rotateSize); + $this->validateRotateSize($rotateSize); + $toBeSet['log_rotate_size'] = $rotateSize; + } + + // set config + foreach ($toBeSet as $option => $value) { + $this->config->setSystemValue($option, $value); + } + + // display config + if ($this->config->getSystemValue('log_type', 'owncloud') === 'owncloud') { + $enabledText = 'enabled'; + } else { + $enabledText = 'disabled'; + } + $output->writeln('Log backend ownCloud: '.$enabledText); + + $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data'); + $defaultLogFile = rtrim($dataDir, '/').'/owncloud.log'; + $output->writeln('Log file: '.$this->config->getSystemValue('logfile', $defaultLogFile)); + + $rotateSize = $this->config->getSystemValue('log_rotate_size', 0); + if ($rotateSize) { + $rotateString = \OCP\Util::humanFileSize($rotateSize); + } else { + $rotateString = 'disabled'; + } + $output->writeln('Rotate at: '.$rotateString); + } + + /** + * @param mixed $rotateSize + * @throws \InvalidArgumentException + */ + protected function validateRotateSize(&$rotateSize) { + if ($rotateSize === false) { + throw new \InvalidArgumentException('Error parsing log rotation file size'); + } + $rotateSize = (int) $rotateSize; + if ($rotateSize < 0) { + throw new \InvalidArgumentException('Log rotation file size must be non-negative'); + } + } + +} diff --git a/core/Command/Maintenance/Install.php b/core/Command/Maintenance/Install.php new file mode 100644 index 00000000000..b1b63b9b3bd --- /dev/null +++ b/core/Command/Maintenance/Install.php @@ -0,0 +1,178 @@ +<?php +/** + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @author Christian Kampka <christian@kampka.net> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\Command\Maintenance; + +use InvalidArgumentException; +use OC\Setup; +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Install extends Command { + + /** + * @var IConfig + */ + private $config; + + public function __construct(IConfig $config) { + parent::__construct(); + $this->config = $config; + } + + protected function configure() { + $this + ->setName('maintenance:install') + ->setDescription('install ownCloud') + ->addOption('database', null, InputOption::VALUE_REQUIRED, 'Supported database type', 'sqlite') + ->addOption('database-name', null, InputOption::VALUE_REQUIRED, 'Name of the database') + ->addOption('database-host', null, InputOption::VALUE_REQUIRED, 'Hostname of the database', 'localhost') + ->addOption('database-user', null, InputOption::VALUE_REQUIRED, 'User name to connect to the database') + ->addOption('database-pass', null, InputOption::VALUE_OPTIONAL, 'Password of the database user', null) + ->addOption('database-table-prefix', null, InputOption::VALUE_OPTIONAL, 'Prefix for all tables (default: oc_)', null) + ->addOption('admin-user', null, InputOption::VALUE_REQUIRED, 'User name of the admin account', 'admin') + ->addOption('admin-pass', null, InputOption::VALUE_REQUIRED, 'Password of the admin account') + ->addOption('data-dir', null, InputOption::VALUE_REQUIRED, 'Path to data directory', \OC::$SERVERROOT."/data"); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + + // validate the environment + $server = \OC::$server; + $setupHelper = new Setup($this->config, $server->getIniWrapper(), + $server->getL10N('lib'), new \OC_Defaults(), $server->getLogger(), + $server->getSecureRandom()); + $sysInfo = $setupHelper->getSystemInfo(true); + $errors = $sysInfo['errors']; + if (count($errors) > 0) { + $this->printErrors($output, $errors); + + // ignore the OS X setup warning + if(count($errors) !== 1 || + (string)($errors[0]['error']) !== 'Mac OS X is not supported and ownCloud will not work properly on this platform. Use it at your own risk! ') { + return 1; + } + } + + // validate user input + $options = $this->validateInput($input, $output, array_keys($sysInfo['databases'])); + + // perform installation + $errors = $setupHelper->install($options); + if (count($errors) > 0) { + $this->printErrors($output, $errors); + return 1; + } + $output->writeln("ownCloud was successfully installed"); + return 0; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param string[] $supportedDatabases + * @return array + */ + protected function validateInput(InputInterface $input, OutputInterface $output, $supportedDatabases) { + $db = strtolower($input->getOption('database')); + + if (!in_array($db, $supportedDatabases)) { + throw new InvalidArgumentException("Database <$db> is not supported."); + } + + $dbUser = $input->getOption('database-user'); + $dbPass = $input->getOption('database-pass'); + $dbName = $input->getOption('database-name'); + $dbHost = $input->getOption('database-host'); + $dbTablePrefix = 'oc_'; + if ($input->hasParameterOption('--database-table-prefix')) { + $dbTablePrefix = (string) $input->getOption('database-table-prefix'); + $dbTablePrefix = trim($dbTablePrefix); + } + if ($input->hasParameterOption('--database-pass')) { + $dbPass = (string) $input->getOption('database-pass'); + } + $adminLogin = $input->getOption('admin-user'); + $adminPassword = $input->getOption('admin-pass'); + $dataDir = $input->getOption('data-dir'); + + if ($db !== 'sqlite') { + if (is_null($dbUser)) { + throw new InvalidArgumentException("Database user not provided."); + } + if (is_null($dbName)) { + throw new InvalidArgumentException("Database name not provided."); + } + if (is_null($dbPass)) { + /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */ + $dialog = $this->getHelperSet()->get('dialog'); + $dbPass = $dialog->askHiddenResponse( + $output, + "<question>What is the password to access the database with user <$dbUser>?</question>", + false + ); + } + } + + if (is_null($adminPassword)) { + /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */ + $dialog = $this->getHelperSet()->get('dialog'); + $adminPassword = $dialog->askHiddenResponse( + $output, + "<question>What is the password you like to use for the admin account <$adminLogin>?</question>", + false + ); + } + + $options = [ + 'dbtype' => $db, + 'dbuser' => $dbUser, + 'dbpass' => $dbPass, + 'dbname' => $dbName, + 'dbhost' => $dbHost, + 'dbtableprefix' => $dbTablePrefix, + 'adminlogin' => $adminLogin, + 'adminpass' => $adminPassword, + 'directory' => $dataDir + ]; + return $options; + } + + /** + * @param OutputInterface $output + * @param $errors + */ + protected function printErrors(OutputInterface $output, $errors) { + foreach ($errors as $error) { + if (is_array($error)) { + $output->writeln('<error>' . (string)$error['error'] . '</error>'); + $output->writeln('<info> -> ' . (string)$error['hint'] . '</info>'); + } else { + $output->writeln('<error>' . (string)$error . '</error>'); + } + } + } +} diff --git a/core/Command/Maintenance/Mimetype/UpdateDB.php b/core/Command/Maintenance/Mimetype/UpdateDB.php new file mode 100644 index 00000000000..9532f9e1cd9 --- /dev/null +++ b/core/Command/Maintenance/Mimetype/UpdateDB.php @@ -0,0 +1,97 @@ +<?php +/** + * @author Robin McCorkell <robin@mccorkell.me.uk> + * + * @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\Command\Maintenance\Mimetype; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputOption; + +use OCP\Files\IMimeTypeDetector; +use OCP\Files\IMimeTypeLoader; + +class UpdateDB extends Command { + + const DEFAULT_MIMETYPE = 'application/octet-stream'; + + /** @var IMimeTypeDetector */ + protected $mimetypeDetector; + + /** @var IMimeTypeLoader */ + protected $mimetypeLoader; + + public function __construct( + IMimeTypeDetector $mimetypeDetector, + IMimeTypeLoader $mimetypeLoader + ) { + parent::__construct(); + $this->mimetypeDetector = $mimetypeDetector; + $this->mimetypeLoader = $mimetypeLoader; + } + + protected function configure() { + $this + ->setName('maintenance:mimetype:update-db') + ->setDescription('Update database mimetypes and update filecache') + ->addOption( + 'repair-filecache', + null, + InputOption::VALUE_NONE, + 'Repair filecache for all mimetypes, not just new ones' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $mappings = $this->mimetypeDetector->getAllMappings(); + + $totalFilecacheUpdates = 0; + $totalNewMimetypes = 0; + + foreach ($mappings as $ext => $mimetypes) { + if ($ext[0] === '_') { + // comment + continue; + } + $mimetype = $mimetypes[0]; + $existing = $this->mimetypeLoader->exists($mimetype); + // this will add the mimetype if it didn't exist + $mimetypeId = $this->mimetypeLoader->getId($mimetype); + + if (!$existing) { + $output->writeln('Added mimetype "'.$mimetype.'" to database'); + $totalNewMimetypes++; + } + + if (!$existing || $input->getOption('repair-filecache')) { + $touchedFilecacheRows = $this->mimetypeLoader->updateFilecache($ext, $mimetypeId); + if ($touchedFilecacheRows > 0) { + $output->writeln('Updated '.$touchedFilecacheRows.' filecache rows for mimetype "'.$mimetype.'"'); + } + $totalFilecacheUpdates += $touchedFilecacheRows; + } + } + + $output->writeln('Added '.$totalNewMimetypes.' new mimetypes'); + $output->writeln('Updated '.$totalFilecacheUpdates.' filecache rows'); + } +} diff --git a/core/Command/Maintenance/Mimetype/UpdateJS.php b/core/Command/Maintenance/Mimetype/UpdateJS.php new file mode 100644 index 00000000000..a87f50e32de --- /dev/null +++ b/core/Command/Maintenance/Mimetype/UpdateJS.php @@ -0,0 +1,129 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Roeland Jago Douma <rullzer@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\Command\Maintenance\Mimetype; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +use OCP\Files\IMimeTypeDetector; + +class UpdateJS extends Command { + + /** @var IMimeTypeDetector */ + protected $mimetypeDetector; + + public function __construct( + IMimeTypeDetector $mimetypeDetector + ) { + parent::__construct(); + $this->mimetypeDetector = $mimetypeDetector; + } + + protected function configure() { + $this + ->setName('maintenance:mimetype:update-js') + ->setDescription('Update mimetypelist.js'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + // Fetch all the aliases + $aliases = $this->mimetypeDetector->getAllAliases(); + + // Remove comments + $keys = array_filter(array_keys($aliases), function($k) { + return $k[0] === '_'; + }); + foreach($keys as $key) { + unset($aliases[$key]); + } + + // Fetch all files + $dir = new \DirectoryIterator(\OC::$SERVERROOT.'/core/img/filetypes'); + + $files = []; + foreach($dir as $fileInfo) { + if ($fileInfo->isFile()) { + $file = preg_replace('/.[^.]*$/', '', $fileInfo->getFilename()); + $files[] = $file; + } + } + + //Remove duplicates + $files = array_values(array_unique($files)); + sort($files); + + // Fetch all themes! + $themes = []; + $dirs = new \DirectoryIterator(\OC::$SERVERROOT.'/themes/'); + foreach($dirs as $dir) { + //Valid theme dir + if ($dir->isFile() || $dir->isDot()) { + continue; + } + + $theme = $dir->getFilename(); + $themeDir = $dir->getPath() . '/' . $theme . '/core/img/filetypes/'; + // Check if this theme has its own filetype icons + if (!file_exists($themeDir)) { + continue; + } + + $themes[$theme] = []; + // Fetch all the theme icons! + $themeIt = new \DirectoryIterator($themeDir); + foreach ($themeIt as $fileInfo) { + if ($fileInfo->isFile()) { + $file = preg_replace('/.[^.]*$/', '', $fileInfo->getFilename()); + $themes[$theme][] = $file; + } + } + + //Remove Duplicates + $themes[$theme] = array_values(array_unique($themes[$theme])); + sort($themes[$theme]); + } + + //Generate the JS + $js = '/** +* This file is automatically generated +* DO NOT EDIT MANUALLY! +* +* You can update the list of MimeType Aliases in config/mimetypealiases.json +* The list of files is fetched from core/img/filetypes +* To regenerate this file run ./occ maintenance:mimetypesjs +*/ +OC.MimeTypeList={ + aliases: ' . json_encode($aliases, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . ', + files: ' . json_encode($files, JSON_PRETTY_PRINT) . ', + themes: ' . json_encode($themes, JSON_PRETTY_PRINT) . ' +}; +'; + + //Output the JS + file_put_contents(\OC::$SERVERROOT.'/core/js/mimetypelist.js', $js); + + $output->writeln('<info>mimetypelist.js is updated'); + } +} diff --git a/core/Command/Maintenance/Mode.php b/core/Command/Maintenance/Mode.php new file mode 100644 index 00000000000..28f4fb2f7f1 --- /dev/null +++ b/core/Command/Maintenance/Mode.php @@ -0,0 +1,75 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author scolebrook <scolebrook@mac.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\Command\Maintenance; + +use \OCP\IConfig; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Mode extends Command { + + /** @var IConfig */ + protected $config; + + public function __construct(IConfig $config) { + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('maintenance:mode') + ->setDescription('set maintenance mode') + ->addOption( + 'on', + null, + InputOption::VALUE_NONE, + 'enable maintenance mode' + ) + ->addOption( + 'off', + null, + InputOption::VALUE_NONE, + 'disable maintenance mode' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if ($input->getOption('on')) { + $this->config->setSystemValue('maintenance', true); + $output->writeln('Maintenance mode enabled'); + } elseif ($input->getOption('off')) { + $this->config->setSystemValue('maintenance', false); + $output->writeln('Maintenance mode disabled'); + } else { + if ($this->config->getSystemValue('maintenance', false)) { + $output->writeln('Maintenance mode is currently enabled'); + } else { + $output->writeln('Maintenance mode is currently disabled'); + } + } + } +} diff --git a/core/Command/Maintenance/Repair.php b/core/Command/Maintenance/Repair.php new file mode 100644 index 00000000000..95e2b872227 --- /dev/null +++ b/core/Command/Maintenance/Repair.php @@ -0,0 +1,91 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Vincent Petry <pvince81@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\Command\Maintenance; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Repair extends Command { + /** + * @var \OC\Repair $repair + */ + protected $repair; + /** @var \OCP\IConfig */ + protected $config; + + /** + * @param \OC\Repair $repair + * @param \OCP\IConfig $config + */ + public function __construct(\OC\Repair $repair, \OCP\IConfig $config) { + $this->repair = $repair; + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('maintenance:repair') + ->setDescription('repair this installation') + ->addOption( + 'include-expensive', + null, + InputOption::VALUE_NONE, + 'Use this option when you want to include resource and load expensive tasks' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $includeExpensive = $input->getOption('include-expensive'); + if ($includeExpensive) { + foreach ($this->repair->getExpensiveRepairSteps() as $step) { + $this->repair->addStep($step); + } + } + + $maintenanceMode = $this->config->getSystemValue('maintenance', false); + $this->config->setSystemValue('maintenance', true); + + $this->repair->listen('\OC\Repair', 'step', function ($description) use ($output) { + $output->writeln(' - ' . $description); + }); + $this->repair->listen('\OC\Repair', 'info', function ($description) use ($output) { + $output->writeln(' - ' . $description); + }); + $this->repair->listen('\OC\Repair', 'warning', function ($description) use ($output) { + $output->writeln(' - WARNING: ' . $description); + }); + $this->repair->listen('\OC\Repair', 'error', function ($description) use ($output) { + $output->writeln(' - ERROR: ' . $description); + }); + + $this->repair->run(); + + $this->config->setSystemValue('maintenance', $maintenanceMode); + } +} diff --git a/core/Command/Maintenance/SingleUser.php b/core/Command/Maintenance/SingleUser.php new file mode 100644 index 00000000000..2e6f1f136e7 --- /dev/null +++ b/core/Command/Maintenance/SingleUser.php @@ -0,0 +1,78 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@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\Command\Maintenance; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +use OCP\IConfig; + +class SingleUser extends Command { + + /** @var IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('maintenance:singleuser') + ->setDescription('set single user mode') + ->addOption( + 'on', + null, + InputOption::VALUE_NONE, + 'enable single user mode' + ) + ->addOption( + 'off', + null, + InputOption::VALUE_NONE, + 'disable single user mode' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if ($input->getOption('on')) { + $this->config->setSystemValue('singleuser', true); + $output->writeln('Single user mode enabled'); + } elseif ($input->getOption('off')) { + $this->config->setSystemValue('singleuser', false); + $output->writeln('Single user mode disabled'); + } else { + if ($this->config->getSystemValue('singleuser', false)) { + $output->writeln('Single user mode is currently enabled'); + } else { + $output->writeln('Single user mode is currently disabled'); + } + } + } +} diff --git a/core/Command/Security/ImportCertificate.php b/core/Command/Security/ImportCertificate.php new file mode 100644 index 00000000000..6aae7ad1d9f --- /dev/null +++ b/core/Command/Security/ImportCertificate.php @@ -0,0 +1,67 @@ +<?php +/** + * @author Robin Appelman <icewind@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\Command\Security; + +use OC\Core\Command\Base; +use OCP\ICertificateManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ImportCertificate extends Base { + + /** @var ICertificateManager */ + protected $certificateManager; + + public function __construct(ICertificateManager $certificateManager) { + $this->certificateManager = $certificateManager; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('security:certificates:import') + ->setDescription('import trusted certificate') + ->addArgument( + 'path', + InputArgument::REQUIRED, + 'path to the certificate to import' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $path = $input->getArgument('path'); + + if (!file_exists($path)) { + $output->writeln('<error>certificate not found</error>'); + return; + } + + $certData = file_get_contents($path); + $name = basename($path); + + $this->certificateManager->addCertificate($certData, $name); + } +} diff --git a/core/Command/Security/ListCertificates.php b/core/Command/Security/ListCertificates.php new file mode 100644 index 00000000000..91deb2d340a --- /dev/null +++ b/core/Command/Security/ListCertificates.php @@ -0,0 +1,96 @@ +<?php +/** + * @author Robin Appelman <icewind@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\Command\Security; + +use OC\Core\Command\Base; +use OCP\ICertificate; +use OCP\ICertificateManager; +use OCP\IL10N; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ListCertificates extends Base { + + /** @var ICertificateManager */ + protected $certificateManager; + /** @var IL10N */ + protected $l; + + public function __construct(ICertificateManager $certificateManager, IL10N $l) { + $this->certificateManager = $certificateManager; + $this->l = $l; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('security:certificates') + ->setDescription('list trusted certificates'); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $outputType = $input->getOption('output'); + if ($outputType === self::OUTPUT_FORMAT_JSON || $outputType === self::OUTPUT_FORMAT_JSON_PRETTY) { + $certificates = array_map(function (ICertificate $certificate) { + return [ + 'name' => $certificate->getName(), + 'common_name' => $certificate->getCommonName(), + 'organization' => $certificate->getOrganization(), + 'expire' => $certificate->getExpireDate()->format(\DateTime::ATOM), + 'issuer' => $certificate->getIssuerName(), + 'issuer_organization' => $certificate->getIssuerOrganization(), + 'issue_date' => $certificate->getIssueDate()->format(\DateTime::ATOM) + ]; + }, $this->certificateManager->listCertificates()); + if ($outputType === self::OUTPUT_FORMAT_JSON) { + $output->writeln(json_encode(array_values($certificates))); + } else { + $output->writeln(json_encode(array_values($certificates), JSON_PRETTY_PRINT)); + } + } else { + $table = new Table($output); + $table->setHeaders([ + 'File Name', + 'Common Name', + 'Organization', + 'Valid Until', + 'Issued By' + ]); + + $rows = array_map(function (ICertificate $certificate) { + return [ + $certificate->getName(), + $certificate->getCommonName(), + $certificate->getOrganization(), + $this->l->l('date', $certificate->getExpireDate()), + $certificate->getIssuerName() + ]; + }, $this->certificateManager->listCertificates()); + $table->setRows($rows); + $table->render(); + } + } +} diff --git a/core/Command/Security/RemoveCertificate.php b/core/Command/Security/RemoveCertificate.php new file mode 100644 index 00000000000..68e409aee1c --- /dev/null +++ b/core/Command/Security/RemoveCertificate.php @@ -0,0 +1,59 @@ +<?php +/** + * @author Robin Appelman <icewind@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\Command\Security; + +use OC\Core\Command\Base; +use OCP\ICertificateManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class RemoveCertificate extends Base { + + /** @var ICertificateManager */ + protected $certificateManager; + + public function __construct(ICertificateManager $certificateManager) { + $this->certificateManager = $certificateManager; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('security:certificates:remove') + ->setDescription('remove trusted certificate') + ->addArgument( + 'name', + InputArgument::REQUIRED, + 'the file name of the certificate to remove' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $name = $input->getArgument('name'); + + $this->certificateManager->removeCertificate($name); + } +} diff --git a/core/Command/Status.php b/core/Command/Status.php new file mode 100644 index 00000000000..6bc9c28d4d1 --- /dev/null +++ b/core/Command/Status.php @@ -0,0 +1,49 @@ +<?php +/** + * @author Bart Visscher <bartv@thisnet.nl> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @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\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Status extends Base { + protected function configure() { + parent::configure(); + + $this + ->setName('status') + ->setDescription('show some status information') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $values = array( + 'installed' => (bool) \OC::$server->getConfig()->getSystemValue('installed', false), + 'version' => implode('.', \OCP\Util::getVersion()), + 'versionstring' => \OC_Util::getVersionString(), + 'edition' => \OC_Util::getEditionString(), + ); + + $this->writeArrayInOutputFormat($input, $output, $values); + } +} diff --git a/core/Command/Upgrade.php b/core/Command/Upgrade.php new file mode 100644 index 00000000000..cbb1f26f938 --- /dev/null +++ b/core/Command/Upgrade.php @@ -0,0 +1,295 @@ +<?php +/** + * @author Andreas Fischer <bantu@owncloud.com> + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Owen Winkler <a_github@midnightcircus.com> + * @author Steffen Lindner <mail@steffen-lindner.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@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\Command; + +use OC\Console\TimestampFormatter; +use OC\Updater; +use OCP\IConfig; +use OCP\ILogger; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\EventDispatcher\GenericEvent; + +class Upgrade extends Command { + + const ERROR_SUCCESS = 0; + const ERROR_NOT_INSTALLED = 1; + const ERROR_MAINTENANCE_MODE = 2; + const ERROR_UP_TO_DATE = 3; + const ERROR_INVALID_ARGUMENTS = 4; + const ERROR_FAILURE = 5; + + /** @var IConfig */ + private $config; + + /** @var ILogger */ + private $logger; + + /** + * @param IConfig $config + * @param ILogger $logger + */ + public function __construct(IConfig $config, ILogger $logger) { + parent::__construct(); + $this->config = $config; + $this->logger = $logger; + } + + protected function configure() { + $this + ->setName('upgrade') + ->setDescription('run upgrade routines after installation of a new release. The release has to be installed before.') + ->addOption( + '--skip-migration-test', + null, + InputOption::VALUE_NONE, + 'skips the database schema migration simulation and update directly' + ) + ->addOption( + '--dry-run', + null, + InputOption::VALUE_NONE, + 'only runs the database schema migration simulation, do not actually update' + ) + ->addOption( + '--no-app-disable', + null, + InputOption::VALUE_NONE, + 'skips the disable of third party apps' + ); + } + + /** + * Execute the upgrade command + * + * @param InputInterface $input input interface + * @param OutputInterface $output output interface + */ + protected function execute(InputInterface $input, OutputInterface $output) { + + $simulateStepEnabled = true; + $updateStepEnabled = true; + $skip3rdPartyAppsDisable = false; + + if ($input->getOption('skip-migration-test')) { + $simulateStepEnabled = false; + } + if ($input->getOption('dry-run')) { + $updateStepEnabled = false; + } + if ($input->getOption('no-app-disable')) { + $skip3rdPartyAppsDisable = true; + } + + if (!$simulateStepEnabled && !$updateStepEnabled) { + $output->writeln( + '<error>Only one of "--skip-migration-test" or "--dry-run" ' . + 'can be specified at a time.</error>' + ); + return self::ERROR_INVALID_ARGUMENTS; + } + + if(\OC::checkUpgrade(false)) { + if (OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) { + // Prepend each line with a little timestamp + $timestampFormatter = new TimestampFormatter($this->config, $output->getFormatter()); + $output->setFormatter($timestampFormatter); + } + + $self = $this; + $updater = new Updater( + \OC::$server->getHTTPHelper(), + $this->config, + \OC::$server->getIntegrityCodeChecker(), + $this->logger + ); + + $updater->setSimulateStepEnabled($simulateStepEnabled); + $updater->setUpdateStepEnabled($updateStepEnabled); + $updater->setSkip3rdPartyAppsDisable($skip3rdPartyAppsDisable); + $dispatcher = \OC::$server->getEventDispatcher(); + $progress = new ProgressBar($output); + $progress->setFormat(" %message%\n %current%/%max% [%bar%] %percent:3s%%"); + $listener = function($event) use ($progress, $output) { + if ($event instanceof GenericEvent) { + $message = $event->getSubject(); + if (OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) { + $output->writeln(' Checking table ' . $message); + } else { + if (strlen($message) > 60) { + $message = substr($message, 0, 57) . '...'; + } + $progress->setMessage($message); + if ($event[0] === 1) { + $output->writeln(''); + $progress->start($event[1]); + } + $progress->setProgress($event[0]); + if ($event[0] === $event[1]) { + $progress->setMessage('Done'); + $progress->finish(); + $output->writeln(''); + } + } + } + }; + $dispatcher->addListener('\OC\DB\Migrator::executeSql', $listener); + $dispatcher->addListener('\OC\DB\Migrator::checkTable', $listener); + + $updater->listen('\OC\Updater', 'maintenanceEnabled', function () use($output) { + $output->writeln('<info>Turned on maintenance mode</info>'); + }); + $updater->listen('\OC\Updater', 'maintenanceDisabled', function () use($output) { + $output->writeln('<info>Turned off maintenance mode</info>'); + }); + $updater->listen('\OC\Updater', 'maintenanceActive', function () use($output) { + $output->writeln('<info>Maintenance mode is kept active</info>'); + }); + $updater->listen('\OC\Updater', 'updateEnd', + function ($success) use($output, $updateStepEnabled, $self) { + $mode = $updateStepEnabled ? 'Update' : 'Update simulation'; + if ($success) { + $message = "<info>$mode successful</info>"; + } else { + $message = "<error>$mode failed</error>"; + } + $output->writeln($message); + }); + $updater->listen('\OC\Updater', 'dbUpgradeBefore', function () use($output) { + $output->writeln('<info>Updating database schema</info>'); + }); + $updater->listen('\OC\Updater', 'dbUpgrade', function () use($output) { + $output->writeln('<info>Updated database</info>'); + }); + $updater->listen('\OC\Updater', 'dbSimulateUpgradeBefore', function () use($output) { + $output->writeln('<info>Checking whether the database schema can be updated (this can take a long time depending on the database size)</info>'); + }); + $updater->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($output) { + $output->writeln('<info>Checked database schema update</info>'); + }); + $updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use($output) { + $output->writeln('<info>Disabled incompatible app: ' . $app . '</info>'); + }); + $updater->listen('\OC\Updater', 'thirdPartyAppDisabled', function ($app) use ($output) { + $output->writeln('<info>Disabled 3rd-party app: ' . $app . '</info>'); + }); + $updater->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use($output) { + $output->writeln('<info>Update 3rd-party app: ' . $app . '</info>'); + }); + $updater->listen('\OC\Updater', 'repairWarning', function ($app) use($output) { + $output->writeln('<error>Repair warning: ' . $app . '</error>'); + }); + $updater->listen('\OC\Updater', 'repairError', function ($app) use($output) { + $output->writeln('<error>Repair error: ' . $app . '</error>'); + }); + $updater->listen('\OC\Updater', 'appUpgradeCheckBefore', function () use ($output) { + $output->writeln('<info>Checking updates of apps</info>'); + }); + $updater->listen('\OC\Updater', 'appSimulateUpdate', function ($app) use ($output) { + $output->writeln("<info>Checking whether the database schema for <$app> can be updated (this can take a long time depending on the database size)</info>"); + }); + $updater->listen('\OC\Updater', 'appUpgradeCheck', function () use ($output) { + $output->writeln('<info>Checked database schema update for apps</info>'); + }); + $updater->listen('\OC\Updater', 'appUpgradeStarted', function ($app, $version) use ($output) { + $output->writeln("<info>Updating <$app> ...</info>"); + }); + $updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($output) { + $output->writeln("<info>Updated <$app> to $version</info>"); + }); + $updater->listen('\OC\Updater', 'failure', function ($message) use($output, $self) { + $output->writeln("<error>$message</error>"); + }); + $updater->listen('\OC\Updater', 'setDebugLogLevel', function ($logLevel, $logLevelName) use($output) { + $output->writeln("<info>Set log level to debug</info>"); + }); + $updater->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use($output) { + $output->writeln("<info>Reset log level</info>"); + }); + $updater->listen('\OC\Updater', 'startCheckCodeIntegrity', function () use($output) { + $output->writeln("<info>Starting code integrity check...</info>"); + }); + $updater->listen('\OC\Updater', 'finishedCheckCodeIntegrity', function () use($output) { + $output->writeln("<info>Finished code integrity check</info>"); + }); + + if(OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) { + $updater->listen('\OC\Updater', 'repairInfo', function ($message) use($output) { + $output->writeln('<info>Repair info: ' . $message . '</info>'); + }); + $updater->listen('\OC\Updater', 'repairStep', function ($message) use($output) { + $output->writeln('<info>Repair step: ' . $message . '</info>'); + }); + } + + $success = $updater->upgrade(); + + $this->postUpgradeCheck($input, $output); + + if(!$success) { + return self::ERROR_FAILURE; + } + + return self::ERROR_SUCCESS; + } else if($this->config->getSystemValue('maintenance', false)) { + //Possible scenario: ownCloud core is updated but an app failed + $output->writeln('<warning>ownCloud is in maintenance mode</warning>'); + $output->write('<comment>Maybe an upgrade is already in process. Please check the ' + . 'logfile (data/owncloud.log). If you want to re-run the ' + . 'upgrade procedure, remove the "maintenance mode" from ' + . 'config.php and call this script again.</comment>' + , true); + return self::ERROR_MAINTENANCE_MODE; + } else { + $output->writeln('<info>ownCloud is already latest version</info>'); + return self::ERROR_UP_TO_DATE; + } + } + + /** + * Perform a post upgrade check (specific to the command line tool) + * + * @param InputInterface $input input interface + * @param OutputInterface $output output interface + */ + protected function postUpgradeCheck(InputInterface $input, OutputInterface $output) { + $trustedDomains = $this->config->getSystemValue('trusted_domains', array()); + if (empty($trustedDomains)) { + $output->write( + '<warning>The setting "trusted_domains" could not be ' . + 'set automatically by the upgrade script, ' . + 'please set it manually</warning>' + ); + } + } +} diff --git a/core/Command/User/Add.php b/core/Command/User/Add.php new file mode 100644 index 00000000000..6c7e3a47231 --- /dev/null +++ b/core/Command/User/Add.php @@ -0,0 +1,154 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Laurens Post <lkpost@scept.re> + * + * @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\Command\User; + +use OC\Files\Filesystem; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Question\Question; + +class Add extends Command { + /** @var \OCP\IUserManager */ + protected $userManager; + + /** @var \OCP\IGroupManager */ + protected $groupManager; + + /** + * @param IUserManager $userManager + * @param IGroupManager $groupManager + */ + public function __construct(IUserManager $userManager, IGroupManager $groupManager) { + parent::__construct(); + $this->userManager = $userManager; + $this->groupManager = $groupManager; + } + + protected function configure() { + $this + ->setName('user:add') + ->setDescription('adds a user') + ->addArgument( + 'uid', + InputArgument::REQUIRED, + 'User ID used to login (must only contain a-z, A-Z, 0-9, -, _ and @)' + ) + ->addOption( + 'password-from-env', + null, + InputOption::VALUE_NONE, + 'read password from environment variable OC_PASS' + ) + ->addOption( + 'display-name', + null, + InputOption::VALUE_OPTIONAL, + 'User name used in the web UI (can contain any characters)' + ) + ->addOption( + 'group', + 'g', + InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'groups the user should be added to (The group will be created if it does not exist)' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $uid = $input->getArgument('uid'); + if ($this->userManager->userExists($uid)) { + $output->writeln('<error>The user "' . $uid . '" already exists.</error>'); + return 1; + } + + if ($input->getOption('password-from-env')) { + $password = getenv('OC_PASS'); + if (!$password) { + $output->writeln('<error>--password-from-env given, but OC_PASS is empty!</error>'); + return 1; + } + } elseif ($input->isInteractive()) { + /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */ + $dialog = $this->getHelperSet()->get('dialog'); + $password = $dialog->askHiddenResponse( + $output, + '<question>Enter password: </question>', + false + ); + $confirm = $dialog->askHiddenResponse( + $output, + '<question>Confirm password: </question>', + false + ); + + if ($password !== $confirm) { + $output->writeln("<error>Passwords did not match!</error>"); + return 1; + } + } else { + $output->writeln("<error>Interactive input or --password-from-env is needed for entering a password!</error>"); + return 1; + } + + $user = $this->userManager->createUser( + $input->getArgument('uid'), + $password + ); + + if ($user instanceof IUser) { + $output->writeln('<info>The user "' . $user->getUID() . '" was created successfully</info>'); + } else { + $output->writeln('<error>An error occurred while creating the user</error>'); + return 1; + } + + if ($input->getOption('display-name')) { + $user->setDisplayName($input->getOption('display-name')); + $output->writeln('Display name set to "' . $user->getDisplayName() . '"'); + } + + $groups = $input->getOption('group'); + + if (!empty($groups)) { + // Make sure we init the Filesystem for the user, in case we need to + // init some group shares. + Filesystem::init($user->getUID(), ''); + } + + foreach ($groups as $groupName) { + $group = $this->groupManager->get($groupName); + if (!$group) { + $this->groupManager->createGroup($groupName); + $group = $this->groupManager->get($groupName); + $output->writeln('Created group "' . $group->getGID() . '"'); + } + $group->addUser($user); + $output->writeln('User "' . $user->getUID() . '" added to group "' . $group->getGID() . '"'); + } + } +} diff --git a/core/Command/User/Delete.php b/core/Command/User/Delete.php new file mode 100644 index 00000000000..b9a0a0e3950 --- /dev/null +++ b/core/Command/User/Delete.php @@ -0,0 +1,70 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Jens-Christian Fischer <jens-christian.fischer@switch.ch> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @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\Command\User; + +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; + +class Delete extends Command { + /** @var IUserManager */ + protected $userManager; + + /** + * @param IUserManager $userManager + */ + public function __construct(IUserManager $userManager) { + $this->userManager = $userManager; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('user:delete') + ->setDescription('deletes the specified user') + ->addArgument( + 'uid', + InputArgument::REQUIRED, + 'the username' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $user = $this->userManager->get($input->getArgument('uid')); + if (is_null($user)) { + $output->writeln('<error>User does not exist</error>'); + return; + } + + if ($user->delete()) { + $output->writeln('<info>The specified user was deleted</info>'); + return; + } + + $output->writeln('<error>The specified user could not be deleted. Please check the logs.</error>'); + } +} diff --git a/core/Command/User/LastSeen.php b/core/Command/User/LastSeen.php new file mode 100644 index 00000000000..6bb45a87875 --- /dev/null +++ b/core/Command/User/LastSeen.php @@ -0,0 +1,74 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Pierre Ozoux <pierre@ozoux.net> + * + * @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\Command\User; + +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; + +class LastSeen extends Command { + /** @var IUserManager */ + protected $userManager; + + /** + * @param IUserManager $userManager + */ + public function __construct(IUserManager $userManager) { + $this->userManager = $userManager; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('user:lastseen') + ->setDescription('shows when the user was logged in last time') + ->addArgument( + 'uid', + InputArgument::REQUIRED, + 'the username' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $user = $this->userManager->get($input->getArgument('uid')); + if(is_null($user)) { + $output->writeln('<error>User does not exist</error>'); + return; + } + + $lastLogin = $user->getLastLogin(); + if($lastLogin === 0) { + $output->writeln('User ' . $user->getUID() . + ' has never logged in, yet.'); + } else { + $date = new \DateTime(); + $date->setTimestamp($lastLogin); + $output->writeln($user->getUID() . + '`s last login: ' . $date->format('d.m.Y H:i')); + } + } +} diff --git a/core/Command/User/Report.php b/core/Command/User/Report.php new file mode 100644 index 00000000000..df9f7e41620 --- /dev/null +++ b/core/Command/User/Report.php @@ -0,0 +1,86 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\Command\User; + +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Report extends Command { + /** @var IUserManager */ + protected $userManager; + + /** + * @param IUserManager $userManager + */ + public function __construct(IUserManager $userManager) { + $this->userManager = $userManager; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('user:report') + ->setDescription('shows how many users have access'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + /** @var \Symfony\Component\Console\Helper\TableHelper $table */ + $table = $this->getHelperSet()->get('table'); + $table->setHeaders(array('User Report', '')); + $userCountArray = $this->countUsers(); + if(!empty($userCountArray)) { + $total = 0; + $rows = array(); + foreach($userCountArray as $classname => $users) { + $total += $users; + $rows[] = array($classname, $users); + } + + $rows[] = array(' '); + $rows[] = array('total users', $total); + } else { + $rows[] = array('No backend enabled that supports user counting', ''); + } + + $userDirectoryCount = $this->countUserDirectories(); + $rows[] = array(' '); + $rows[] = array('user directories', $userDirectoryCount); + + $table->setRows($rows); + $table->render($output); + } + + private function countUsers() { + return $this->userManager->countUsers(); + } + + private function countUserDirectories() { + $dataview = new \OC\Files\View('/'); + $userDirectories = $dataview->getDirectoryContent('/', 'httpd/unix-directory'); + return count($userDirectories); + } +} diff --git a/core/Command/User/ResetPassword.php b/core/Command/User/ResetPassword.php new file mode 100644 index 00000000000..f3f2d5b0630 --- /dev/null +++ b/core/Command/User/ResetPassword.php @@ -0,0 +1,121 @@ +<?php +/** + * @author Andreas Fischer <bantu@owncloud.com> + * @author Christopher Schäpers <kondou@ts.unde.re> + * @author Clark Tomlinson <fallen013@gmail.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Laurens Post <lkpost@scept.re> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @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\Command\User; + +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ResetPassword extends Command { + + /** @var IUserManager */ + protected $userManager; + + public function __construct(IUserManager $userManager) { + $this->userManager = $userManager; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('user:resetpassword') + ->setDescription('Resets the password of the named user') + ->addArgument( + 'user', + InputArgument::REQUIRED, + 'Username to reset password' + ) + ->addOption( + 'password-from-env', + null, + InputOption::VALUE_NONE, + 'read password from environment variable OC_PASS' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $username = $input->getArgument('user'); + + /** @var $user \OCP\IUser */ + $user = $this->userManager->get($username); + if (is_null($user)) { + $output->writeln('<error>User does not exist</error>'); + return 1; + } + + if ($input->getOption('password-from-env')) { + $password = getenv('OC_PASS'); + if (!$password) { + $output->writeln('<error>--password-from-env given, but OC_PASS is empty!</error>'); + return 1; + } + } elseif ($input->isInteractive()) { + /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */ + $dialog = $this->getHelperSet()->get('dialog'); + + if (\OCP\App::isEnabled('encryption')) { + $output->writeln( + '<error>Warning: Resetting the password when using encryption will result in data loss!</error>' + ); + if (!$dialog->askConfirmation($output, '<question>Do you want to continue?</question>', true)) { + return 1; + } + } + + $password = $dialog->askHiddenResponse( + $output, + '<question>Enter a new password: </question>', + false + ); + $confirm = $dialog->askHiddenResponse( + $output, + '<question>Confirm the new password: </question>', + false + ); + + if ($password !== $confirm) { + $output->writeln("<error>Passwords did not match!</error>"); + return 1; + } + } else { + $output->writeln("<error>Interactive input or --password-from-env is needed for entering a new password!</error>"); + return 1; + } + + $success = $user->setPassword($password); + if ($success) { + $output->writeln("<info>Successfully reset password for " . $username . "</info>"); + } else { + $output->writeln("<error>Error while resetting password!</error>"); + return 1; + } + } +} |