Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Wurst <christoph@winzerhof-wurst.at>2019-09-17 17:33:27 +0300
committernpmbuildbot[bot] <npmbuildbot[bot]@users.noreply.github.com>2019-09-28 12:39:28 +0300
commitde6940352a2f708376219a89ec84a8e6d25ca59e (patch)
tree459bacfc183b24d611be1877fbe22bbcd4efb1d6 /apps/settings/lib/Controller
parentc8cd607681ac128228f57114ce14dd67ab05de04 (diff)
Move settings to an app
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at> Signed-off-by: npmbuildbot[bot] <npmbuildbot[bot]@users.noreply.github.com>
Diffstat (limited to 'apps/settings/lib/Controller')
-rw-r--r--apps/settings/lib/Controller/AdminSettingsController.php121
-rw-r--r--apps/settings/lib/Controller/AppSettingsController.php563
-rw-r--r--apps/settings/lib/Controller/AuthSettingsController.php289
-rw-r--r--apps/settings/lib/Controller/CertificateController.php178
-rw-r--r--apps/settings/lib/Controller/ChangePasswordController.php275
-rw-r--r--apps/settings/lib/Controller/CheckSetupController.php701
-rw-r--r--apps/settings/lib/Controller/CommonSettingsTrait.php154
-rw-r--r--apps/settings/lib/Controller/LogSettingsController.php60
-rw-r--r--apps/settings/lib/Controller/MailSettingsController.php169
-rw-r--r--apps/settings/lib/Controller/PersonalSettingsController.php112
-rw-r--r--apps/settings/lib/Controller/TwoFactorSettingsController.php60
-rw-r--r--apps/settings/lib/Controller/UsersController.php498
12 files changed, 3180 insertions, 0 deletions
diff --git a/apps/settings/lib/Controller/AdminSettingsController.php b/apps/settings/lib/Controller/AdminSettingsController.php
new file mode 100644
index 00000000000..0217abf3858
--- /dev/null
+++ b/apps/settings/lib/Controller/AdminSettingsController.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Settings\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Group\ISubAdmin;
+use OCP\IGroupManager;
+use OCP\INavigationManager;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserSession;
+use OCP\Settings\IManager as ISettingsManager;
+use OCP\Template;
+
+class AdminSettingsController extends Controller {
+ use CommonSettingsTrait;
+
+ public function __construct(
+ $appName,
+ IRequest $request,
+ INavigationManager $navigationManager,
+ ISettingsManager $settingsManager,
+ IUserSession $userSession,
+ IGroupManager $groupManager,
+ ISubAdmin $subAdmin
+ ) {
+ parent::__construct($appName, $request);
+ $this->navigationManager = $navigationManager;
+ $this->settingsManager = $settingsManager;
+ $this->userSession = $userSession;
+ $this->groupManager = $groupManager;
+ $this->subAdmin = $subAdmin;
+ }
+
+ /**
+ * @param string $section
+ * @return TemplateResponse
+ *
+ * @NoCSRFRequired
+ * @SubAdminRequired
+ */
+ public function index($section) {
+ return $this->getIndexResponse('admin', $section);
+ }
+
+ /**
+ * @param string $section
+ * @return array
+ */
+ protected function getSettings($section) {
+ /** @var IUser $user */
+ $user = $this->userSession->getUser();
+ $isSubAdmin = !$this->groupManager->isAdmin($user->getUID()) && $this->subAdmin->isSubAdmin($user);
+ $settings = $this->settingsManager->getAdminSettings(
+ $section,
+ $isSubAdmin
+ );
+ $formatted = $this->formatSettings($settings);
+ // Do not show legacy forms for sub admins
+ if($section === 'additional' && !$isSubAdmin) {
+ $formatted['content'] .= $this->getLegacyForms();
+ }
+ return $formatted;
+ }
+
+ /**
+ * @return bool|string
+ */
+ private function getLegacyForms() {
+ $forms = \OC_App::getForms('admin');
+
+ $forms = array_map(function ($form) {
+ if (preg_match('%(<h2(?P<class>[^>]*)>.*?</h2>)%i', $form, $regs)) {
+ $sectionName = str_replace('<h2' . $regs['class'] . '>', '', $regs[0]);
+ $sectionName = str_replace('</h2>', '', $sectionName);
+ $anchor = strtolower($sectionName);
+ $anchor = str_replace(' ', '-', $anchor);
+
+ return array(
+ 'anchor' => $anchor,
+ 'section-name' => $sectionName,
+ 'form' => $form
+ );
+ }
+ return array(
+ 'form' => $form
+ );
+ }, $forms);
+
+ $out = new Template('settings', 'settings/additional');
+ $out->assign('forms', $forms);
+
+ return $out->fetchPage();
+ }
+
+
+}
diff --git a/apps/settings/lib/Controller/AppSettingsController.php b/apps/settings/lib/Controller/AppSettingsController.php
new file mode 100644
index 00000000000..93bb2cbb423
--- /dev/null
+++ b/apps/settings/lib/Controller/AppSettingsController.php
@@ -0,0 +1,563 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @author Christoph Wurst <christoph@owncloud.com>
+ * @author Felix A. Epp <work@felixepp.de>
+ * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Julius Härtl <jus@bitgrid.net>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @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 OCA\Settings\Controller;
+
+use OC\App\AppStore\Bundles\BundleFetcher;
+use OC\App\AppStore\Fetcher\AppFetcher;
+use OC\App\AppStore\Fetcher\CategoryFetcher;
+use OC\App\AppStore\Version\VersionParser;
+use OC\App\DependencyAnalyzer;
+use OC\App\Platform;
+use OC\Installer;
+use OC_App;
+use OCP\App\IAppManager;
+use \OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\ILogger;
+use OCP\INavigationManager;
+use OCP\IRequest;
+use OCP\IL10N;
+use OCP\IConfig;
+use OCP\IURLGenerator;
+use OCP\L10N\IFactory;
+
+class AppSettingsController extends Controller {
+
+ /** @var \OCP\IL10N */
+ private $l10n;
+ /** @var IConfig */
+ private $config;
+ /** @var INavigationManager */
+ private $navigationManager;
+ /** @var IAppManager */
+ private $appManager;
+ /** @var CategoryFetcher */
+ private $categoryFetcher;
+ /** @var AppFetcher */
+ private $appFetcher;
+ /** @var IFactory */
+ private $l10nFactory;
+ /** @var BundleFetcher */
+ private $bundleFetcher;
+ /** @var Installer */
+ private $installer;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var ILogger */
+ private $logger;
+
+ /** @var array */
+ private $allApps = [];
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IL10N $l10n
+ * @param IConfig $config
+ * @param INavigationManager $navigationManager
+ * @param IAppManager $appManager
+ * @param CategoryFetcher $categoryFetcher
+ * @param AppFetcher $appFetcher
+ * @param IFactory $l10nFactory
+ * @param BundleFetcher $bundleFetcher
+ * @param Installer $installer
+ * @param IURLGenerator $urlGenerator
+ * @param ILogger $logger
+ */
+ public function __construct(string $appName,
+ IRequest $request,
+ IL10N $l10n,
+ IConfig $config,
+ INavigationManager $navigationManager,
+ IAppManager $appManager,
+ CategoryFetcher $categoryFetcher,
+ AppFetcher $appFetcher,
+ IFactory $l10nFactory,
+ BundleFetcher $bundleFetcher,
+ Installer $installer,
+ IURLGenerator $urlGenerator,
+ ILogger $logger) {
+ parent::__construct($appName, $request);
+ $this->l10n = $l10n;
+ $this->config = $config;
+ $this->navigationManager = $navigationManager;
+ $this->appManager = $appManager;
+ $this->categoryFetcher = $categoryFetcher;
+ $this->appFetcher = $appFetcher;
+ $this->l10nFactory = $l10nFactory;
+ $this->bundleFetcher = $bundleFetcher;
+ $this->installer = $installer;
+ $this->urlGenerator = $urlGenerator;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @NoCSRFRequired
+ *
+ * @return TemplateResponse
+ */
+ public function viewApps(): TemplateResponse {
+ \OC_Util::addScript('settings', 'apps');
+ $params = [];
+ $params['appstoreEnabled'] = $this->config->getSystemValue('appstoreenabled', true) === true;
+ $params['updateCount'] = count($this->getAppsWithUpdates());
+ $params['developerDocumentation'] = $this->urlGenerator->linkToDocs('developer-manual');
+ $params['bundles'] = $this->getBundles();
+ $this->navigationManager->setActiveEntry('core_apps');
+
+ $templateResponse = new TemplateResponse('settings', 'settings-vue', ['serverData' => $params]);
+ $policy = new ContentSecurityPolicy();
+ $policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
+ $templateResponse->setContentSecurityPolicy($policy);
+
+ return $templateResponse;
+ }
+
+ private function getAppsWithUpdates() {
+ $appClass = new \OC_App();
+ $apps = $appClass->listAllApps();
+ foreach($apps as $key => $app) {
+ $newVersion = $this->installer->isUpdateAvailable($app['id']);
+ if($newVersion === false) {
+ unset($apps[$key]);
+ }
+ }
+ return $apps;
+ }
+
+ private function getBundles() {
+ $result = [];
+ $bundles = $this->bundleFetcher->getBundles();
+ foreach ($bundles as $bundle) {
+ $result[] = [
+ 'name' => $bundle->getName(),
+ 'id' => $bundle->getIdentifier(),
+ 'appIdentifiers' => $bundle->getAppIdentifiers()
+ ];
+ }
+ return $result;
+
+ }
+
+ /**
+ * Get all available categories
+ *
+ * @return JSONResponse
+ */
+ public function listCategories(): JSONResponse {
+ return new JSONResponse($this->getAllCategories());
+ }
+
+ private function getAllCategories() {
+ $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
+
+ $formattedCategories = [];
+ $categories = $this->categoryFetcher->get();
+ foreach($categories as $category) {
+ $formattedCategories[] = [
+ 'id' => $category['id'],
+ 'ident' => $category['id'],
+ 'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
+ ];
+ }
+
+ return $formattedCategories;
+ }
+
+ private function fetchApps() {
+ $appClass = new \OC_App();
+ $apps = $appClass->listAllApps();
+ foreach ($apps as $app) {
+ $app['installed'] = true;
+ $this->allApps[$app['id']] = $app;
+ }
+
+ $apps = $this->getAppsForCategory('');
+ foreach ($apps as $app) {
+ $app['appstore'] = true;
+ if (!array_key_exists($app['id'], $this->allApps)) {
+ $this->allApps[$app['id']] = $app;
+ } else {
+ $this->allApps[$app['id']] = array_merge($app, $this->allApps[$app['id']]);
+ }
+ }
+
+ // add bundle information
+ $bundles = $this->bundleFetcher->getBundles();
+ foreach($bundles as $bundle) {
+ foreach($bundle->getAppIdentifiers() as $identifier) {
+ foreach($this->allApps as &$app) {
+ if($app['id'] === $identifier) {
+ $app['bundleId'] = $bundle->getIdentifier();
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ private function getAllApps() {
+ return $this->allApps;
+ }
+ /**
+ * Get all available apps in a category
+ *
+ * @param string $category
+ * @return JSONResponse
+ * @throws \Exception
+ */
+ public function listApps(): JSONResponse {
+
+ $this->fetchApps();
+ $apps = $this->getAllApps();
+
+ $dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
+
+ // Extend existing app details
+ $apps = array_map(function($appData) use ($dependencyAnalyzer) {
+ if (isset($appData['appstoreData'])) {
+ $appstoreData = $appData['appstoreData'];
+ $appData['screenshot'] = isset($appstoreData['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/' . base64_encode($appstoreData['screenshots'][0]['url']) : '';
+ $appData['category'] = $appstoreData['categories'];
+ }
+
+ $newVersion = $this->installer->isUpdateAvailable($appData['id']);
+ if($newVersion) {
+ $appData['update'] = $newVersion;
+ }
+
+ // fix groups to be an array
+ $groups = array();
+ if (is_string($appData['groups'])) {
+ $groups = json_decode($appData['groups']);
+ }
+ $appData['groups'] = $groups;
+ $appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
+
+ // fix licence vs license
+ if (isset($appData['license']) && !isset($appData['licence'])) {
+ $appData['licence'] = $appData['license'];
+ }
+
+ $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
+ $ignoreMax = in_array($appData['id'], $ignoreMaxApps);
+
+ // analyse dependencies
+ $missing = $dependencyAnalyzer->analyze($appData, $ignoreMax);
+ $appData['canInstall'] = empty($missing);
+ $appData['missingDependencies'] = $missing;
+
+ $appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
+ $appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
+ $appData['isCompatible'] = $dependencyAnalyzer->isMarkedCompatible($appData);
+
+ return $appData;
+ }, $apps);
+
+ usort($apps, [$this, 'sortApps']);
+
+ return new JSONResponse(['apps' => $apps, 'status' => 'success']);
+ }
+
+ /**
+ * Get all apps for a category from the app store
+ *
+ * @param string $requestedCategory
+ * @return array
+ * @throws \Exception
+ */
+ private function getAppsForCategory($requestedCategory = ''): array {
+ $versionParser = new VersionParser();
+ $formattedApps = [];
+ $apps = $this->appFetcher->get();
+ foreach($apps as $app) {
+ // Skip all apps not in the requested category
+ if ($requestedCategory !== '') {
+ $isInCategory = false;
+ foreach($app['categories'] as $category) {
+ if($category === $requestedCategory) {
+ $isInCategory = true;
+ }
+ }
+ if(!$isInCategory) {
+ continue;
+ }
+ }
+
+ if (!isset($app['releases'][0]['rawPlatformVersionSpec'])) {
+ continue;
+ }
+ $nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
+ $nextCloudVersionDependencies = [];
+ if($nextCloudVersion->getMinimumVersion() !== '') {
+ $nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
+ }
+ if($nextCloudVersion->getMaximumVersion() !== '') {
+ $nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
+ }
+ $phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
+ $existsLocally = \OC_App::getAppPath($app['id']) !== false;
+ $phpDependencies = [];
+ if($phpVersion->getMinimumVersion() !== '') {
+ $phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
+ }
+ if($phpVersion->getMaximumVersion() !== '') {
+ $phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
+ }
+ if(isset($app['releases'][0]['minIntSize'])) {
+ $phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
+ }
+ $authors = '';
+ foreach($app['authors'] as $key => $author) {
+ $authors .= $author['name'];
+ if($key !== count($app['authors']) - 1) {
+ $authors .= ', ';
+ }
+ }
+
+ $currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
+ $enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
+ $groups = null;
+ if($enabledValue !== 'no' && $enabledValue !== 'yes') {
+ $groups = $enabledValue;
+ }
+
+ $currentVersion = '';
+ if($this->appManager->isInstalled($app['id'])) {
+ $currentVersion = $this->appManager->getAppVersion($app['id']);
+ } else {
+ $currentLanguage = $app['releases'][0]['version'];
+ }
+
+ $formattedApps[] = [
+ 'id' => $app['id'],
+ 'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
+ 'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
+ 'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
+ 'license' => $app['releases'][0]['licenses'],
+ 'author' => $authors,
+ 'shipped' => false,
+ 'version' => $currentVersion,
+ 'default_enable' => '',
+ 'types' => [],
+ 'documentation' => [
+ 'admin' => $app['adminDocs'],
+ 'user' => $app['userDocs'],
+ 'developer' => $app['developerDocs']
+ ],
+ 'website' => $app['website'],
+ 'bugs' => $app['issueTracker'],
+ 'detailpage' => $app['website'],
+ 'dependencies' => array_merge(
+ $nextCloudVersionDependencies,
+ $phpDependencies
+ ),
+ 'level' => ($app['isFeatured'] === true) ? 200 : 100,
+ 'missingMaxOwnCloudVersion' => false,
+ 'missingMinOwnCloudVersion' => false,
+ 'canInstall' => true,
+ 'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
+ 'score' => $app['ratingOverall'],
+ 'ratingNumOverall' => $app['ratingNumOverall'],
+ 'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
+ 'removable' => $existsLocally,
+ 'active' => $this->appManager->isEnabledForUser($app['id']),
+ 'needsDownload' => !$existsLocally,
+ 'groups' => $groups,
+ 'fromAppStore' => true,
+ 'appstoreData' => $app,
+ ];
+ }
+
+ return $formattedApps;
+ }
+
+ /**
+ * @PasswordConfirmationRequired
+ *
+ * @param string $appId
+ * @param array $groups
+ * @return JSONResponse
+ */
+ public function enableApp(string $appId, array $groups = []): JSONResponse {
+ return $this->enableApps([$appId], $groups);
+ }
+
+ /**
+ * Enable one or more apps
+ *
+ * apps will be enabled for specific groups only if $groups is defined
+ *
+ * @PasswordConfirmationRequired
+ * @param array $appIds
+ * @param array $groups
+ * @return JSONResponse
+ */
+ public function enableApps(array $appIds, array $groups = []): JSONResponse {
+ try {
+ $updateRequired = false;
+
+ foreach ($appIds as $appId) {
+ $appId = OC_App::cleanAppId($appId);
+
+ // Check if app is already downloaded
+ /** @var Installer $installer */
+ $installer = \OC::$server->query(Installer::class);
+ $isDownloaded = $installer->isDownloaded($appId);
+
+ if(!$isDownloaded) {
+ $installer->downloadApp($appId);
+ }
+
+ $installer->installApp($appId);
+
+ if (count($groups) > 0) {
+ $this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
+ } else {
+ $this->appManager->enableApp($appId);
+ }
+ if (\OC_App::shouldUpgrade($appId)) {
+ $updateRequired = true;
+ }
+ }
+ return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
+
+ } catch (\Exception $e) {
+ $this->logger->logException($e);
+ return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ private function getGroupList(array $groups) {
+ $groupManager = \OC::$server->getGroupManager();
+ $groupsList = [];
+ foreach ($groups as $group) {
+ $groupItem = $groupManager->get($group);
+ if ($groupItem instanceof \OCP\IGroup) {
+ $groupsList[] = $groupManager->get($group);
+ }
+ }
+ return $groupsList;
+ }
+
+ /**
+ * @PasswordConfirmationRequired
+ *
+ * @param string $appId
+ * @return JSONResponse
+ */
+ public function disableApp(string $appId): JSONResponse {
+ return $this->disableApps([$appId]);
+ }
+
+ /**
+ * @PasswordConfirmationRequired
+ *
+ * @param array $appIds
+ * @return JSONResponse
+ */
+ public function disableApps(array $appIds): JSONResponse {
+ try {
+ foreach ($appIds as $appId) {
+ $appId = OC_App::cleanAppId($appId);
+ $this->appManager->disableApp($appId);
+ }
+ return new JSONResponse([]);
+ } catch (\Exception $e) {
+ $this->logger->logException($e);
+ return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * @PasswordConfirmationRequired
+ *
+ * @param string $appId
+ * @return JSONResponse
+ */
+ public function uninstallApp(string $appId): JSONResponse {
+ $appId = OC_App::cleanAppId($appId);
+ $result = $this->installer->removeApp($appId);
+ if($result !== false) {
+ $this->appManager->clearAppsCache();
+ return new JSONResponse(['data' => ['appid' => $appId]]);
+ }
+ return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+
+ /**
+ * @param string $appId
+ * @return JSONResponse
+ */
+ public function updateApp(string $appId): JSONResponse {
+ $appId = OC_App::cleanAppId($appId);
+
+ $this->config->setSystemValue('maintenance', true);
+ try {
+ $result = $this->installer->updateAppstoreApp($appId);
+ $this->config->setSystemValue('maintenance', false);
+ } catch (\Exception $ex) {
+ $this->config->setSystemValue('maintenance', false);
+ return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+
+ if ($result !== false) {
+ return new JSONResponse(['data' => ['appid' => $appId]]);
+ }
+ return new JSONResponse(['data' => ['message' => $this->l10n->t('Couldn\'t update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+
+ private function sortApps($a, $b) {
+ $a = (string)$a['name'];
+ $b = (string)$b['name'];
+ if ($a === $b) {
+ return 0;
+ }
+ return ($a < $b) ? -1 : 1;
+ }
+
+ public function force(string $appId): JSONResponse {
+ $appId = OC_App::cleanAppId($appId);
+
+ $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
+ if (!in_array($appId, $ignoreMaxApps, true)) {
+ $ignoreMaxApps[] = $appId;
+ $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
+ }
+
+ return new JSONResponse();
+ }
+
+}
diff --git a/apps/settings/lib/Controller/AuthSettingsController.php b/apps/settings/lib/Controller/AuthSettingsController.php
new file mode 100644
index 00000000000..b948cd5065d
--- /dev/null
+++ b/apps/settings/lib/Controller/AuthSettingsController.php
@@ -0,0 +1,289 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Christoph Wurst <christoph@owncloud.com>
+ * @author Fabrizio Steiner <fabrizio.steiner@gmail.com>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Marcel Waldvogel <marcel.waldvogel@uni-konstanz.de>
+ * @author Robin Appelman <robin@icewind.nl>
+ *
+ * @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 OCA\Settings\Controller;
+
+use BadMethodCallException;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Exceptions\PasswordlessTokenException;
+use OC\Authentication\Exceptions\WipeTokenException;
+use OC\Authentication\Token\INamedToken;
+use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IToken;
+use OC\Authentication\Token\IWipeableToken;
+use OC\Authentication\Token\RemoteWipe;
+use OCA\Settings\Activity\Provider;
+use OCP\Activity\IManager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\ILogger;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IUserSession;
+use OCP\Security\ISecureRandom;
+use OCP\Session\Exceptions\SessionNotAvailableException;
+
+class AuthSettingsController extends Controller {
+
+ /** @var IProvider */
+ private $tokenProvider;
+
+ /** @var ISession */
+ private $session;
+
+ /** IUserSession */
+ private $userSession;
+
+ /** @var string */
+ private $uid;
+
+ /** @var ISecureRandom */
+ private $random;
+
+ /** @var IManager */
+ private $activityManager;
+
+ /** @var RemoteWipe */
+ private $remoteWipe;
+
+ /** @var ILogger */
+ private $logger;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IProvider $tokenProvider
+ * @param ISession $session
+ * @param ISecureRandom $random
+ * @param string|null $userId
+ * @param IUserSession $userSession
+ * @param IManager $activityManager
+ * @param RemoteWipe $remoteWipe
+ * @param ILogger $logger
+ */
+ public function __construct(string $appName,
+ IRequest $request,
+ IProvider $tokenProvider,
+ ISession $session,
+ ISecureRandom $random,
+ ?string $userId,
+ IUserSession $userSession,
+ IManager $activityManager,
+ RemoteWipe $remoteWipe,
+ ILogger $logger) {
+ parent::__construct($appName, $request);
+ $this->tokenProvider = $tokenProvider;
+ $this->uid = $userId;
+ $this->userSession = $userSession;
+ $this->session = $session;
+ $this->random = $random;
+ $this->activityManager = $activityManager;
+ $this->remoteWipe = $remoteWipe;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ * @PasswordConfirmationRequired
+ *
+ * @param string $name
+ * @return JSONResponse
+ */
+ public function create($name) {
+ try {
+ $sessionId = $this->session->getId();
+ } catch (SessionNotAvailableException $ex) {
+ return $this->getServiceNotAvailableResponse();
+ }
+ if ($this->userSession->getImpersonatingUserID() !== null)
+ {
+ return $this->getServiceNotAvailableResponse();
+ }
+
+ try {
+ $sessionToken = $this->tokenProvider->getToken($sessionId);
+ $loginName = $sessionToken->getLoginName();
+ try {
+ $password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
+ } catch (PasswordlessTokenException $ex) {
+ $password = null;
+ }
+ } catch (InvalidTokenException $ex) {
+ return $this->getServiceNotAvailableResponse();
+ }
+
+ $token = $this->generateRandomDeviceToken();
+ $deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN);
+ $tokenData = $deviceToken->jsonSerialize();
+ $tokenData['canDelete'] = true;
+ $tokenData['canRename'] = true;
+
+ $this->publishActivity(Provider::APP_TOKEN_CREATED, $deviceToken->getId(), ['name' => $deviceToken->getName()]);
+
+ return new JSONResponse([
+ 'token' => $token,
+ 'loginName' => $loginName,
+ 'deviceToken' => $tokenData,
+ ]);
+ }
+
+ /**
+ * @return JSONResponse
+ */
+ private function getServiceNotAvailableResponse() {
+ $resp = new JSONResponse();
+ $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
+ return $resp;
+ }
+
+ /**
+ * Return a 25 digit device password
+ *
+ * Example: AbCdE-fGhJk-MnPqR-sTwXy-23456
+ *
+ * @return string
+ */
+ private function generateRandomDeviceToken() {
+ $groups = [];
+ for ($i = 0; $i < 5; $i++) {
+ $groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE);
+ }
+ return implode('-', $groups);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ *
+ * @param int $id
+ * @return array|JSONResponse
+ */
+ public function destroy($id) {
+ try {
+ $token = $this->findTokenByIdAndUser($id);
+ } catch (WipeTokenException $e) {
+ //continue as we can destroy tokens in wipe
+ $token = $e->getToken();
+ } catch (InvalidTokenException $e) {
+ return new JSONResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ $this->tokenProvider->invalidateTokenById($this->uid, $token->getId());
+ $this->publishActivity(Provider::APP_TOKEN_DELETED, $token->getId(), ['name' => $token->getName()]);
+ return [];
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ *
+ * @param int $id
+ * @param array $scope
+ * @param string $name
+ * @return array|JSONResponse
+ */
+ public function update($id, array $scope, string $name) {
+ try {
+ $token = $this->findTokenByIdAndUser($id);
+ } catch (InvalidTokenException $e) {
+ return new JSONResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ $currentName = $token->getName();
+
+ if ($scope !== $token->getScopeAsArray()) {
+ $token->setScope(['filesystem' => $scope['filesystem']]);
+ $this->publishActivity($scope['filesystem'] ? Provider::APP_TOKEN_FILESYSTEM_GRANTED : Provider::APP_TOKEN_FILESYSTEM_REVOKED, $token->getId(), ['name' => $currentName]);
+ }
+
+ if ($token instanceof INamedToken && $name !== $currentName) {
+ $token->setName($name);
+ $this->publishActivity(Provider::APP_TOKEN_RENAMED, $token->getId(), ['name' => $currentName, 'newName' => $name]);
+ }
+
+ $this->tokenProvider->updateToken($token);
+ return [];
+ }
+
+ /**
+ * @param string $subject
+ * @param int $id
+ * @param array $parameters
+ */
+ private function publishActivity(string $subject, int $id, array $parameters = []): void {
+ $event = $this->activityManager->generateEvent();
+ $event->setApp('settings')
+ ->setType('security')
+ ->setAffectedUser($this->uid)
+ ->setAuthor($this->uid)
+ ->setSubject($subject, $parameters)
+ ->setObject('app_token', $id, 'App Password');
+
+ try {
+ $this->activityManager->publish($event);
+ } catch (BadMethodCallException $e) {
+ $this->logger->warning('could not publish activity');
+ $this->logger->logException($e);
+ }
+ }
+
+ /**
+ * Find a token by given id and check if uid for current session belongs to this token
+ *
+ * @param int $id
+ * @return IToken
+ * @throws InvalidTokenException
+ * @throws \OC\Authentication\Exceptions\ExpiredTokenException
+ */
+ private function findTokenByIdAndUser(int $id): IToken {
+ $token = $this->tokenProvider->getTokenById($id);
+ if ($token->getUID() !== $this->uid) {
+ throw new InvalidTokenException('This token does not belong to you!');
+ }
+ return $token;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ * @PasswordConfirmationRequired
+ *
+ * @param int $id
+ * @return JSONResponse
+ * @throws InvalidTokenException
+ * @throws \OC\Authentication\Exceptions\ExpiredTokenException
+ */
+ public function wipe(int $id): JSONResponse {
+ if (!$this->remoteWipe->markTokenForWipe($id)) {
+ return new JSONResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new JSONResponse([]);
+ }
+}
diff --git a/apps/settings/lib/Controller/CertificateController.php b/apps/settings/lib/Controller/CertificateController.php
new file mode 100644
index 00000000000..c3f291c0982
--- /dev/null
+++ b/apps/settings/lib/Controller/CertificateController.php
@@ -0,0 +1,178 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Björn Schießle <bjoern@schiessle.org>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Robin Appelman <robin@icewind.nl>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @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 OCA\Settings\Controller;
+
+use OCP\App\IAppManager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\ICertificateManager;
+use OCP\IL10N;
+use OCP\IRequest;
+
+class CertificateController extends Controller {
+ /** @var ICertificateManager */
+ private $userCertificateManager;
+ /** @var ICertificateManager */
+ private $systemCertificateManager;
+ /** @var IL10N */
+ private $l10n;
+ /** @var IAppManager */
+ private $appManager;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param ICertificateManager $userCertificateManager
+ * @param ICertificateManager $systemCertificateManager
+ * @param IL10N $l10n
+ * @param IAppManager $appManager
+ */
+ public function __construct($appName,
+ IRequest $request,
+ ICertificateManager $userCertificateManager,
+ ICertificateManager $systemCertificateManager,
+ IL10N $l10n,
+ IAppManager $appManager) {
+ parent::__construct($appName, $request);
+ $this->userCertificateManager = $userCertificateManager;
+ $this->systemCertificateManager = $systemCertificateManager;
+ $this->l10n = $l10n;
+ $this->appManager = $appManager;
+ }
+
+ /**
+ * Add a new personal root certificate to the users' trust store
+ *
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ * @return DataResponse
+ */
+ public function addPersonalRootCertificate() {
+ return $this->addCertificate($this->userCertificateManager);
+ }
+
+ /**
+ * Add a new root certificate to a trust store
+ *
+ * @param ICertificateManager $certificateManager
+ * @return DataResponse
+ */
+ private function addCertificate(ICertificateManager $certificateManager) {
+ $headers = [];
+
+ if ($this->isCertificateImportAllowed() === false) {
+ return new DataResponse(['message' => 'Individual certificate management disabled'], Http::STATUS_FORBIDDEN, $headers);
+ }
+
+ $file = $this->request->getUploadedFile('rootcert_import');
+ if (empty($file)) {
+ return new DataResponse(['message' => 'No file uploaded'], Http::STATUS_UNPROCESSABLE_ENTITY, $headers);
+ }
+
+ try {
+ $certificate = $certificateManager->addCertificate(file_get_contents($file['tmp_name']), $file['name']);
+ return new DataResponse(
+ [
+ 'name' => $certificate->getName(),
+ 'commonName' => $certificate->getCommonName(),
+ 'organization' => $certificate->getOrganization(),
+ 'validFrom' => $certificate->getIssueDate()->getTimestamp(),
+ 'validTill' => $certificate->getExpireDate()->getTimestamp(),
+ 'validFromString' => $this->l10n->l('date', $certificate->getIssueDate()),
+ 'validTillString' => $this->l10n->l('date', $certificate->getExpireDate()),
+ 'issuer' => $certificate->getIssuerName(),
+ 'issuerOrganization' => $certificate->getIssuerOrganization(),
+ ],
+ Http::STATUS_OK,
+ $headers
+ );
+ } catch (\Exception $e) {
+ return new DataResponse(['An error occurred.'], Http::STATUS_UNPROCESSABLE_ENTITY, $headers);
+ }
+ }
+
+ /**
+ * Removes a personal root certificate from the users' trust store
+ *
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ * @param string $certificateIdentifier
+ * @return DataResponse
+ */
+ public function removePersonalRootCertificate($certificateIdentifier) {
+
+ if ($this->isCertificateImportAllowed() === false) {
+ return new DataResponse(['Individual certificate management disabled'], Http::STATUS_FORBIDDEN);
+ }
+
+ $this->userCertificateManager->removeCertificate($certificateIdentifier);
+ return new DataResponse();
+ }
+
+ /**
+ * check if certificate import is allowed
+ *
+ * @return bool
+ */
+ protected function isCertificateImportAllowed() {
+ $externalStorageEnabled = $this->appManager->isEnabledForUser('files_external');
+ if ($externalStorageEnabled) {
+ /** @var \OCA\Files_External\Service\BackendService $backendService */
+ $backendService = \OC_Mount_Config::$app->getContainer()->query('\OCA\Files_External\Service\BackendService');
+ if ($backendService->isUserMountingAllowed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Add a new personal root certificate to the system's trust store
+ *
+ * @return DataResponse
+ */
+ public function addSystemRootCertificate() {
+ return $this->addCertificate($this->systemCertificateManager);
+ }
+
+ /**
+ * Removes a personal root certificate from the users' trust store
+ *
+ * @param string $certificateIdentifier
+ * @return DataResponse
+ */
+ public function removeSystemRootCertificate($certificateIdentifier) {
+
+ if ($this->isCertificateImportAllowed() === false) {
+ return new DataResponse(['Individual certificate management disabled'], Http::STATUS_FORBIDDEN);
+ }
+
+ $this->systemCertificateManager->removeCertificate($certificateIdentifier);
+ return new DataResponse();
+ }
+}
diff --git a/apps/settings/lib/Controller/ChangePasswordController.php b/apps/settings/lib/Controller/ChangePasswordController.php
new file mode 100644
index 00000000000..96b8867fff8
--- /dev/null
+++ b/apps/settings/lib/Controller/ChangePasswordController.php
@@ -0,0 +1,275 @@
+<?php
+// FIXME: disabled for now to be able to inject IGroupManager and also use
+// getSubAdmin()
+//declare(strict_types=1);
+/**
+ *
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Matthew Setter <matthew@matthewsetter.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Settings\Controller;
+
+use OC\HintException;
+use OC\User\Session;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\IGroupManager;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\IUserSession;
+
+class ChangePasswordController extends Controller {
+
+ /** @var string */
+ private $userId;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var IL10N */
+ private $l;
+
+ /** @var IGroupManager */
+ private $groupManager;
+
+ /** @var Session */
+ private $userSession;
+
+ /** @var IAppManager */
+ private $appManager;
+
+ public function __construct(string $appName,
+ IRequest $request,
+ string $userId,
+ IUserManager $userManager,
+ IUserSession $userSession,
+ IGroupManager $groupManager,
+ IAppManager $appManager,
+ IL10N $l) {
+ parent::__construct($appName, $request);
+
+ $this->userId = $userId;
+ $this->userManager = $userManager;
+ $this->userSession = $userSession;
+ $this->groupManager = $groupManager;
+ $this->appManager = $appManager;
+ $this->l = $l;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ * @BruteForceProtection(action=changePersonalPassword)
+ */
+ public function changePersonalPassword(string $oldpassword = '', string $newpassword = null): JSONResponse {
+ /** @var IUser $user */
+ $user = $this->userManager->checkPassword($this->userId, $oldpassword);
+ if ($user === false) {
+ $response = new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('Wrong password'),
+ ],
+ ]);
+ $response->throttle();
+ return $response;
+ }
+
+ try {
+ if ($newpassword === null || $user->setPassword($newpassword) === false) {
+ return new JSONResponse([
+ 'status' => 'error'
+ ]);
+ }
+ // password policy app throws exception
+ } catch(HintException $e) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $e->getHint(),
+ ],
+ ]);
+ }
+
+ $this->userSession->updateSessionTokenPassword($newpassword);
+
+ return new JSONResponse([
+ 'status' => 'success',
+ 'data' => [
+ 'message' => $this->l->t('Saved'),
+ ],
+ ]);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @PasswordConfirmationRequired
+ */
+ public function changeUserPassword(string $username = null, string $password = null, string $recoveryPassword = null): JSONResponse {
+ if ($username === null) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('No user supplied'),
+ ],
+ ]);
+ }
+
+ if ($password === null) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('Unable to change password'),
+ ],
+ ]);
+ }
+
+ $currentUser = $this->userSession->getUser();
+ $targetUser = $this->userManager->get($username);
+ if ($currentUser === null || $targetUser === null ||
+ !($this->groupManager->isAdmin($this->userId) ||
+ $this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $targetUser))
+ ) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('Authentication error'),
+ ],
+ ]);
+ }
+
+ if ($this->appManager->isEnabledForUser('encryption')) {
+ //handle the recovery case
+ $crypt = new \OCA\Encryption\Crypto\Crypt(
+ \OC::$server->getLogger(),
+ \OC::$server->getUserSession(),
+ \OC::$server->getConfig(),
+ \OC::$server->getL10N('encryption'));
+ $keyStorage = \OC::$server->getEncryptionKeyStorage();
+ $util = new \OCA\Encryption\Util(
+ new \OC\Files\View(),
+ $crypt,
+ \OC::$server->getLogger(),
+ \OC::$server->getUserSession(),
+ \OC::$server->getConfig(),
+ \OC::$server->getUserManager());
+ $keyManager = new \OCA\Encryption\KeyManager(
+ $keyStorage,
+ $crypt,
+ \OC::$server->getConfig(),
+ \OC::$server->getUserSession(),
+ new \OCA\Encryption\Session(\OC::$server->getSession()),
+ \OC::$server->getLogger(),
+ $util);
+ $recovery = new \OCA\Encryption\Recovery(
+ \OC::$server->getUserSession(),
+ $crypt,
+ \OC::$server->getSecureRandom(),
+ $keyManager,
+ \OC::$server->getConfig(),
+ $keyStorage,
+ \OC::$server->getEncryptionFilesHelper(),
+ new \OC\Files\View());
+ $recoveryAdminEnabled = $recovery->isRecoveryKeyEnabled();
+
+ $validRecoveryPassword = false;
+ $recoveryEnabledForUser = false;
+ if ($recoveryAdminEnabled) {
+ $validRecoveryPassword = $keyManager->checkRecoveryPassword($recoveryPassword);
+ $recoveryEnabledForUser = $recovery->isRecoveryEnabledForUser($username);
+ }
+
+ if ($recoveryEnabledForUser && $recoveryPassword === '') {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('Please provide an admin recovery password; otherwise, all user data will be lost.'),
+ ]
+ ]);
+ } elseif ($recoveryEnabledForUser && ! $validRecoveryPassword) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('Wrong admin recovery password. Please check the password and try again.'),
+ ]
+ ]);
+ } else { // now we know that everything is fine regarding the recovery password, let's try to change the password
+ try {
+ $result = $targetUser->setPassword($password, $recoveryPassword);
+ // password policy app throws exception
+ } catch(HintException $e) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $e->getHint(),
+ ],
+ ]);
+ }
+ if (!$result && $recoveryEnabledForUser) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('Backend doesn\'t support password change, but the user\'s encryption key was updated.'),
+ ]
+ ]);
+ } elseif (!$result && !$recoveryEnabledForUser) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('Unable to change password'),
+ ]
+ ]);
+ }
+ }
+ } else {
+ try {
+ if ($targetUser->setPassword($password) === false) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l->t('Unable to change password'),
+ ],
+ ]);
+ }
+ // password policy app throws exception
+ } catch(HintException $e) {
+ return new JSONResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $e->getHint(),
+ ],
+ ]);
+ }
+ }
+
+ return new JSONResponse([
+ 'status' => 'success',
+ 'data' => [
+ 'username' => $username,
+ ],
+ ]);
+ }
+}
diff --git a/apps/settings/lib/Controller/CheckSetupController.php b/apps/settings/lib/Controller/CheckSetupController.php
new file mode 100644
index 00000000000..620920f777b
--- /dev/null
+++ b/apps/settings/lib/Controller/CheckSetupController.php
@@ -0,0 +1,701 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Bjoern Schiessle <bjoern@schiessle.org>
+ * @author Derek <derek.kelly27@gmail.com>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Ko- <k.stoffelen@cs.ru.nl>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 OCA\Settings\Controller;
+
+use bantu\IniGetWrapper\IniGetWrapper;
+use DirectoryIterator;
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Platforms\SqlitePlatform;
+use Doctrine\DBAL\Types\Type;
+use GuzzleHttp\Exception\ClientException;
+use OC;
+use OC\AppFramework\Http;
+use OC\DB\Connection;
+use OC\DB\MissingIndexInformation;
+use OC\DB\SchemaWrapper;
+use OC\IntegrityCheck\Checker;
+use OC\Lock\NoopLockingProvider;
+use OC\MemoryInfo;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\DataDisplayResponse;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\Http\Client\IClientService;
+use OCP\IConfig;
+use OCP\IDateTimeFormatter;
+use OCP\IDBConnection;
+use OCP\IL10N;
+use OCP\ILogger;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\Lock\ILockingProvider;
+use OCP\Security\ISecureRandom;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+class CheckSetupController extends Controller {
+ /** @var IConfig */
+ private $config;
+ /** @var IClientService */
+ private $clientService;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var IL10N */
+ private $l10n;
+ /** @var Checker */
+ private $checker;
+ /** @var ILogger */
+ private $logger;
+ /** @var EventDispatcherInterface */
+ private $dispatcher;
+ /** @var IDBConnection|Connection */
+ private $db;
+ /** @var ILockingProvider */
+ private $lockingProvider;
+ /** @var IDateTimeFormatter */
+ private $dateTimeFormatter;
+ /** @var MemoryInfo */
+ private $memoryInfo;
+ /** @var ISecureRandom */
+ private $secureRandom;
+
+ public function __construct($AppName,
+ IRequest $request,
+ IConfig $config,
+ IClientService $clientService,
+ IURLGenerator $urlGenerator,
+ IL10N $l10n,
+ Checker $checker,
+ ILogger $logger,
+ EventDispatcherInterface $dispatcher,
+ IDBConnection $db,
+ ILockingProvider $lockingProvider,
+ IDateTimeFormatter $dateTimeFormatter,
+ MemoryInfo $memoryInfo,
+ ISecureRandom $secureRandom) {
+ parent::__construct($AppName, $request);
+ $this->config = $config;
+ $this->clientService = $clientService;
+ $this->urlGenerator = $urlGenerator;
+ $this->l10n = $l10n;
+ $this->checker = $checker;
+ $this->logger = $logger;
+ $this->dispatcher = $dispatcher;
+ $this->db = $db;
+ $this->lockingProvider = $lockingProvider;
+ $this->dateTimeFormatter = $dateTimeFormatter;
+ $this->memoryInfo = $memoryInfo;
+ $this->secureRandom = $secureRandom;
+ }
+
+ /**
+ * Checks if the server can connect to the internet using HTTPS and HTTP
+ * @return bool
+ */
+ private function hasInternetConnectivityProblems(): bool {
+ if ($this->config->getSystemValue('has_internet_connection', true) === false) {
+ return false;
+ }
+
+ $siteArray = $this->config->getSystemValue('connectivity_check_domains', [
+ 'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
+ ]);
+
+ foreach($siteArray as $site) {
+ if ($this->isSiteReachable($site)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
+ * @return bool
+ */
+ private function isSiteReachable($sitename) {
+ $httpSiteName = 'http://' . $sitename . '/';
+ $httpsSiteName = 'https://' . $sitename . '/';
+
+ try {
+ $client = $this->clientService->newClient();
+ $client->get($httpSiteName);
+ $client->get($httpsSiteName);
+ } catch (\Exception $e) {
+ $this->logger->logException($e, ['app' => 'internet_connection_check']);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether a local memcache is installed or not
+ * @return bool
+ */
+ private function isMemcacheConfigured() {
+ return $this->config->getSystemValue('memcache.local', null) !== null;
+ }
+
+ /**
+ * Whether PHP can generate "secure" pseudorandom integers
+ *
+ * @return bool
+ */
+ private function isRandomnessSecure() {
+ try {
+ $this->secureRandom->generate(1);
+ } catch (\Exception $ex) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Public for the sake of unit-testing
+ *
+ * @return array
+ */
+ protected function getCurlVersion() {
+ return curl_version();
+ }
+
+ /**
+ * Check if the used SSL lib is outdated. Older OpenSSL and NSS versions do
+ * have multiple bugs which likely lead to problems in combination with
+ * functionality required by ownCloud such as SNI.
+ *
+ * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
+ * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
+ * @return string
+ */
+ private function isUsedTlsLibOutdated() {
+ // Don't run check when:
+ // 1. Server has `has_internet_connection` set to false
+ // 2. AppStore AND S2S is disabled
+ if(!$this->config->getSystemValue('has_internet_connection', true)) {
+ return '';
+ }
+ if(!$this->config->getSystemValue('appstoreenabled', true)
+ && $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
+ && $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
+ return '';
+ }
+
+ $versionString = $this->getCurlVersion();
+ if(isset($versionString['ssl_version'])) {
+ $versionString = $versionString['ssl_version'];
+ } else {
+ return '';
+ }
+
+ $features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
+ if(!$this->config->getSystemValue('appstoreenabled', true)) {
+ $features = (string)$this->l10n->t('Federated Cloud Sharing');
+ }
+
+ // Check if at least OpenSSL after 1.01d or 1.0.2b
+ if(strpos($versionString, 'OpenSSL/') === 0) {
+ $majorVersion = substr($versionString, 8, 5);
+ $patchRelease = substr($versionString, 13, 6);
+
+ if(($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
+ ($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
+ return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
+ }
+ }
+
+ // Check if NSS and perform heuristic check
+ if(strpos($versionString, 'NSS/') === 0) {
+ try {
+ $firstClient = $this->clientService->newClient();
+ $firstClient->get('https://nextcloud.com/');
+
+ $secondClient = $this->clientService->newClient();
+ $secondClient->get('https://nextcloud.com/');
+ } catch (ClientException $e) {
+ if($e->getResponse()->getStatusCode() === 400) {
+ return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
+ }
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Whether the version is outdated
+ *
+ * @return bool
+ */
+ protected function isPhpOutdated() {
+ if (version_compare(PHP_VERSION, '7.1.0', '<')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Whether the php version is still supported (at time of release)
+ * according to: https://secure.php.net/supported-versions.php
+ *
+ * @return array
+ */
+ private function isPhpSupported() {
+ return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
+ }
+
+ /**
+ * Check if the reverse proxy configuration is working as expected
+ *
+ * @return bool
+ */
+ private function forwardedForHeadersWorking() {
+ $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
+ $remoteAddress = $this->request->getHeader('REMOTE_ADDR');
+
+ if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
+ return false;
+ }
+
+ if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies, true)) {
+ return $remoteAddress !== $this->request->getRemoteAddress();
+ }
+
+ // either not enabled or working correctly
+ return true;
+ }
+
+ /**
+ * Checks if the correct memcache module for PHP is installed. Only
+ * fails if memcached is configured and the working module is not installed.
+ *
+ * @return bool
+ */
+ private function isCorrectMemcachedPHPModuleInstalled() {
+ if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
+ return true;
+ }
+
+ // there are two different memcached modules for PHP
+ // we only support memcached and not memcache
+ // https://code.google.com/p/memcached/wiki/PHPClientComparison
+ return !(!extension_loaded('memcached') && extension_loaded('memcache'));
+ }
+
+ /**
+ * Checks if set_time_limit is not disabled.
+ *
+ * @return bool
+ */
+ private function isSettimelimitAvailable() {
+ if (function_exists('set_time_limit')
+ && strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return RedirectResponse
+ */
+ public function rescanFailedIntegrityCheck() {
+ $this->checker->runInstanceVerification();
+ return new RedirectResponse(
+ $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
+ );
+ }
+
+ /**
+ * @NoCSRFRequired
+ * @return DataResponse
+ */
+ public function getFailedIntegrityCheckFiles() {
+ if(!$this->checker->isCodeCheckEnforced()) {
+ return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
+ }
+
+ $completeResults = $this->checker->getResults();
+
+ if(!empty($completeResults)) {
+ $formattedTextResponse = 'Technical information
+=====================
+The following list covers which files have failed the integrity check. Please read
+the previous linked documentation to learn more about the errors and how to fix
+them.
+
+Results
+=======
+';
+ foreach($completeResults as $context => $contextResult) {
+ $formattedTextResponse .= "- $context\n";
+
+ foreach($contextResult as $category => $result) {
+ $formattedTextResponse .= "\t- $category\n";
+ if($category !== 'EXCEPTION') {
+ foreach ($result as $key => $results) {
+ $formattedTextResponse .= "\t\t- $key\n";
+ }
+ } else {
+ foreach ($result as $key => $results) {
+ $formattedTextResponse .= "\t\t- $results\n";
+ }
+ }
+
+ }
+ }
+
+ $formattedTextResponse .= '
+Raw output
+==========
+';
+ $formattedTextResponse .= print_r($completeResults, true);
+ } else {
+ $formattedTextResponse = 'No errors have been found.';
+ }
+
+
+ $response = new DataDisplayResponse(
+ $formattedTextResponse,
+ Http::STATUS_OK,
+ [
+ 'Content-Type' => 'text/plain',
+ ]
+ );
+
+ return $response;
+ }
+
+ /**
+ * Checks whether a PHP opcache is properly set up
+ * @return bool
+ */
+ protected function isOpcacheProperlySetup() {
+ $iniWrapper = new IniGetWrapper();
+
+ if(!$iniWrapper->getBool('opcache.enable')) {
+ return false;
+ }
+
+ if(!$iniWrapper->getBool('opcache.save_comments')) {
+ return false;
+ }
+
+ if($iniWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
+ return false;
+ }
+
+ if($iniWrapper->getNumeric('opcache.memory_consumption') < 128) {
+ return false;
+ }
+
+ if($iniWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the required FreeType functions are present
+ * @return bool
+ */
+ protected function hasFreeTypeSupport() {
+ return function_exists('imagettfbbox') && function_exists('imagettftext');
+ }
+
+ protected function hasMissingIndexes(): array {
+ $indexInfo = new MissingIndexInformation();
+ // Dispatch event so apps can also hint for pending index updates if needed
+ $event = new GenericEvent($indexInfo);
+ $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
+
+ return $indexInfo->getListOfMissingIndexes();
+ }
+
+ protected function isSqliteUsed() {
+ return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
+ }
+
+ protected function isReadOnlyConfig(): bool {
+ return \OC_Helper::isReadOnlyConfigEnabled();
+ }
+
+ protected function hasValidTransactionIsolationLevel(): bool {
+ try {
+ if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
+ return true;
+ }
+
+ return $this->db->getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
+ } catch (DBALException $e) {
+ // ignore
+ }
+
+ return true;
+ }
+
+ protected function hasFileinfoInstalled(): bool {
+ return \OC_Util::fileInfoLoaded();
+ }
+
+ protected function hasWorkingFileLocking(): bool {
+ return !($this->lockingProvider instanceof NoopLockingProvider);
+ }
+
+ protected function getSuggestedOverwriteCliURL(): string {
+ $suggestedOverwriteCliUrl = '';
+ if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
+ $suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
+ if (!$this->config->getSystemValue('config_is_read_only', false)) {
+ // Set the overwrite URL when it was not set yet.
+ $this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
+ $suggestedOverwriteCliUrl = '';
+ }
+ }
+ return $suggestedOverwriteCliUrl;
+ }
+
+ protected function getLastCronInfo(): array {
+ $lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
+ return [
+ 'diffInSeconds' => time() - $lastCronRun,
+ 'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
+ 'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
+ ];
+ }
+
+ protected function getCronErrors() {
+ $errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
+
+ if (is_array($errors)) {
+ return $errors;
+ }
+
+ return [];
+ }
+
+ protected function isPHPMailerUsed(): bool {
+ return $this->config->getSystemValue('mail_smtpmode', 'smtp') === 'php';
+ }
+
+ protected function hasOpcacheLoaded(): bool {
+ return function_exists('opcache_get_status');
+ }
+
+ /**
+ * Iterates through the configured app roots and
+ * tests if the subdirectories are owned by the same user than the current user.
+ *
+ * @return array
+ */
+ protected function getAppDirsWithDifferentOwner(): array {
+ $currentUser = posix_getuid();
+ $appDirsWithDifferentOwner = [[]];
+
+ foreach (OC::$APPSROOTS as $appRoot) {
+ if ($appRoot['writable'] === true) {
+ $appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
+ }
+ }
+
+ $appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
+ sort($appDirsWithDifferentOwner);
+
+ return $appDirsWithDifferentOwner;
+ }
+
+ /**
+ * Tests if the directories for one apps directory are writable by the current user.
+ *
+ * @param int $currentUser The current user
+ * @param array $appRoot The app root config
+ * @return string[] The none writable directory paths inside the app root
+ */
+ private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
+ $appDirsWithDifferentOwner = [];
+ $appsPath = $appRoot['path'];
+ $appsDir = new DirectoryIterator($appRoot['path']);
+
+ foreach ($appsDir as $fileInfo) {
+ if ($fileInfo->isDir() && !$fileInfo->isDot()) {
+ $absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
+ $appDirUser = fileowner($absAppPath);
+ if ($appDirUser !== $currentUser) {
+ $appDirsWithDifferentOwner[] = $absAppPath;
+ }
+ }
+ }
+
+ return $appDirsWithDifferentOwner;
+ }
+
+ /**
+ * Checks for potential PHP modules that would improve the instance
+ *
+ * @return string[] A list of PHP modules that is recommended
+ */
+ protected function hasRecommendedPHPModules(): array {
+ $recommendedPHPModules = [];
+
+ if (!extension_loaded('intl')) {
+ $recommendedPHPModules[] = 'intl';
+ }
+
+ if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
+ if (!extension_loaded('imagick')) {
+ $recommendedPHPModules[] = 'imagick';
+ }
+ }
+
+ return $recommendedPHPModules;
+ }
+
+ protected function isMysqlUsedWithoutUTF8MB4(): bool {
+ return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
+ }
+
+ protected function hasBigIntConversionPendingColumns(): array {
+ // copy of ConvertFilecacheBigInt::getColumnsByTable()
+ $tables = [
+ 'activity' => ['activity_id', 'object_id'],
+ 'activity_mq' => ['mail_id'],
+ 'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
+ 'mimetypes' => ['id'],
+ 'storages' => ['numeric_id'],
+ ];
+
+ $schema = new SchemaWrapper($this->db);
+ $isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
+ $pendingColumns = [];
+
+ foreach ($tables as $tableName => $columns) {
+ if (!$schema->hasTable($tableName)) {
+ continue;
+ }
+
+ $table = $schema->getTable($tableName);
+ foreach ($columns as $columnName) {
+ $column = $table->getColumn($columnName);
+ $isAutoIncrement = $column->getAutoincrement();
+ $isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
+ if ($column->getType()->getName() !== Type::BIGINT && !$isAutoIncrementOnSqlite) {
+ $pendingColumns[] = $tableName . '.' . $columnName;
+ }
+ }
+ }
+
+ return $pendingColumns;
+ }
+
+ protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
+ $objectStore = $this->config->getSystemValue('objectstore', null);
+ $objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
+
+ if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
+ return true;
+ }
+
+ if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
+ return true;
+ }
+
+ if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
+ return true;
+ }
+
+ $tempPath = sys_get_temp_dir();
+ if (!is_dir($tempPath)) {
+ $this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
+ return false;
+ }
+ $freeSpaceInTemp = disk_free_space($tempPath);
+ if ($freeSpaceInTemp === false) {
+ $this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
+ return false;
+ }
+
+ $freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
+ if ($freeSpaceInTempInGB > 50) {
+ return true;
+ }
+
+ $this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
+ return false;
+ }
+
+ /**
+ * @return DataResponse
+ */
+ public function check() {
+ return new DataResponse(
+ [
+ 'isGetenvServerWorking' => !empty(getenv('PATH')),
+ 'isReadOnlyConfig' => $this->isReadOnlyConfig(),
+ 'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
+ 'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
+ 'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
+ 'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
+ 'cronInfo' => $this->getLastCronInfo(),
+ 'cronErrors' => $this->getCronErrors(),
+ 'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
+ 'isMemcacheConfigured' => $this->isMemcacheConfigured(),
+ 'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
+ 'isRandomnessSecure' => $this->isRandomnessSecure(),
+ 'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
+ 'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
+ 'phpSupported' => $this->isPhpSupported(),
+ 'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
+ 'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
+ 'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
+ 'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
+ 'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
+ 'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
+ 'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
+ 'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
+ 'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
+ 'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
+ 'missingIndexes' => $this->hasMissingIndexes(),
+ 'isSqliteUsed' => $this->isSqliteUsed(),
+ 'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
+ 'isPHPMailerUsed' => $this->isPHPMailerUsed(),
+ 'mailSettingsDocumentation' => $this->urlGenerator->getAbsoluteURL('index.php/settings/admin'),
+ 'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
+ 'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
+ 'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
+ 'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
+ 'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
+ 'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
+ ]
+ );
+ }
+}
diff --git a/apps/settings/lib/Controller/CommonSettingsTrait.php b/apps/settings/lib/Controller/CommonSettingsTrait.php
new file mode 100644
index 00000000000..69fe28d6fe5
--- /dev/null
+++ b/apps/settings/lib/Controller/CommonSettingsTrait.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Settings\Controller;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Group\ISubAdmin;
+use OCP\IGroupManager;
+use OCP\INavigationManager;
+use OCP\IUser;
+use OCP\IUserSession;
+use OCP\Settings\IManager as ISettingsManager;
+use OCP\Settings\IIconSection;
+use OCP\Settings\ISettings;
+
+trait CommonSettingsTrait {
+
+ /** @var ISettingsManager */
+ private $settingsManager;
+
+ /** @var INavigationManager */
+ private $navigationManager;
+
+ /** @var IUserSession */
+ private $userSession;
+
+ /** @var IGroupManager */
+ private $groupManager;
+
+ /** @var ISubAdmin */
+ private $subAdmin;
+
+ /**
+ * @param string $currentSection
+ * @return array
+ */
+ private function getNavigationParameters($currentType, $currentSection) {
+ $templateParameters = [
+ 'personal' => $this->formatPersonalSections($currentType, $currentSection),
+ 'admin' => []
+ ];
+
+ /** @var IUser $user */
+ $user = $this->userSession->getUser();
+ $isAdmin = $this->groupManager->isAdmin($user->getUID());
+ $isSubAdmin = $this->subAdmin->isSubAdmin($user);
+ if ($isAdmin || $isSubAdmin) {
+ $templateParameters['admin'] = $this->formatAdminSections(
+ $currentType,
+ $currentSection,
+ !$isAdmin && $isSubAdmin
+ );
+ }
+
+ return [
+ 'forms' => $templateParameters
+ ];
+ }
+
+ protected function formatSections($sections, $currentSection, $type, $currentType, bool $subAdminOnly = false) {
+ $templateParameters = [];
+ /** @var \OCP\Settings\ISection[] $prioritizedSections */
+ foreach($sections as $prioritizedSections) {
+ foreach ($prioritizedSections as $section) {
+ if($type === 'admin') {
+ $settings = $this->settingsManager->getAdminSettings($section->getID(), $subAdminOnly);
+ } else if($type === 'personal') {
+ $settings = $this->settingsManager->getPersonalSettings($section->getID());
+ }
+ if (empty($settings) && !($section->getID() === 'additional' && count(\OC_App::getForms('admin')) > 0)) {
+ continue;
+ }
+
+ $icon = '';
+ if ($section instanceof IIconSection) {
+ $icon = $section->getIcon();
+ }
+
+ $active = $section->getID() === $currentSection
+ && $type === $currentType;
+
+ $templateParameters[] = [
+ 'anchor' => $section->getID(),
+ 'section-name' => $section->getName(),
+ 'active' => $active,
+ 'icon' => $icon,
+ ];
+ }
+ }
+ return $templateParameters;
+ }
+
+ protected function formatPersonalSections($currentType, $currentSections) {
+ $sections = $this->settingsManager->getPersonalSections();
+ $templateParameters = $this->formatSections($sections, $currentSections, 'personal', $currentType);
+
+ return $templateParameters;
+ }
+
+ protected function formatAdminSections($currentType, $currentSections, bool $subAdminOnly) {
+ $sections = $this->settingsManager->getAdminSections();
+ $templateParameters = $this->formatSections($sections, $currentSections, 'admin', $currentType, $subAdminOnly);
+
+ return $templateParameters;
+ }
+
+ /**
+ * @param ISettings[] $settings
+ * @return array
+ */
+ private function formatSettings($settings) {
+ $html = '';
+ foreach ($settings as $prioritizedSettings) {
+ foreach ($prioritizedSettings as $setting) {
+ /** @var \OCP\Settings\ISettings $setting */
+ $form = $setting->getForm();
+ $html .= $form->renderAs('')->render();
+ }
+ }
+ return ['content' => $html];
+ }
+
+ private function getIndexResponse($type, $section) {
+ $this->navigationManager->setActiveEntry('settings');
+ $templateParams = [];
+ $templateParams = array_merge($templateParams, $this->getNavigationParameters($type, $section));
+ $templateParams = array_merge($templateParams, $this->getSettings($section));
+
+ return new TemplateResponse('settings', 'settings/frame', $templateParams);
+ }
+
+ abstract protected function getSettings($section);
+}
diff --git a/apps/settings/lib/Controller/LogSettingsController.php b/apps/settings/lib/Controller/LogSettingsController.php
new file mode 100644
index 00000000000..67f2953bf29
--- /dev/null
+++ b/apps/settings/lib/Controller/LogSettingsController.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Thomas Pulzer <t.pulzer@kniel.de>
+ *
+ * @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 OCA\Settings\Controller;
+
+use OC\Log;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\StreamResponse;
+use OCP\IRequest;
+
+class LogSettingsController extends Controller {
+
+ /** @var Log */
+ private $log;
+
+ public function __construct(string $appName, IRequest $request, Log $logger) {
+ parent::__construct($appName, $request);
+ $this->log = $logger;
+ }
+
+ /**
+ * download logfile
+ *
+ * @NoCSRFRequired
+ *
+ * @return StreamResponse
+ */
+ public function download() {
+ if(!$this->log instanceof Log) {
+ throw new \UnexpectedValueException('Log file not available');
+ }
+ $resp = new StreamResponse($this->log->getLogPath());
+ $resp->addHeader('Content-Type', 'application/octet-stream');
+ $resp->addHeader('Content-Disposition', 'attachment; filename="nextcloud.log"');
+ return $resp;
+ }
+}
diff --git a/apps/settings/lib/Controller/MailSettingsController.php b/apps/settings/lib/Controller/MailSettingsController.php
new file mode 100644
index 00000000000..3f91586b98e
--- /dev/null
+++ b/apps/settings/lib/Controller/MailSettingsController.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @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 OCA\Settings\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\IRequest;
+use OCP\IL10N;
+use OCP\IConfig;
+use OCP\IUserSession;
+use OCP\Mail\IMailer;
+
+class MailSettingsController extends Controller {
+
+ /** @var IL10N */
+ private $l10n;
+ /** @var IConfig */
+ private $config;
+ /** @var IUserSession */
+ private $userSession;
+ /** @var IMailer */
+ private $mailer;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IL10N $l10n
+ * @param IConfig $config
+ * @param IUserSession $userSession
+ * @param IMailer $mailer
+ */
+ public function __construct($appName,
+ IRequest $request,
+ IL10N $l10n,
+ IConfig $config,
+ IUserSession $userSession,
+ IMailer $mailer) {
+ parent::__construct($appName, $request);
+ $this->l10n = $l10n;
+ $this->config = $config;
+ $this->userSession = $userSession;
+ $this->mailer = $mailer;
+ }
+
+ /**
+ * Sets the email settings
+ *
+ * @PasswordConfirmationRequired
+ *
+ * @param string $mail_domain
+ * @param string $mail_from_address
+ * @param string $mail_smtpmode
+ * @param string $mail_smtpsecure
+ * @param string $mail_smtphost
+ * @param string $mail_smtpauthtype
+ * @param int $mail_smtpauth
+ * @param string $mail_smtpport
+ * @return DataResponse
+ */
+ public function setMailSettings($mail_domain,
+ $mail_from_address,
+ $mail_smtpmode,
+ $mail_smtpsecure,
+ $mail_smtphost,
+ $mail_smtpauthtype,
+ $mail_smtpauth,
+ $mail_smtpport,
+ $mail_sendmailmode) {
+
+ $params = get_defined_vars();
+ $configs = [];
+ foreach($params as $key => $value) {
+ $configs[$key] = empty($value) ? null : $value;
+ }
+
+ // Delete passwords from config in case no auth is specified
+ if ($params['mail_smtpauth'] !== 1) {
+ $configs['mail_smtpname'] = null;
+ $configs['mail_smtppassword'] = null;
+ }
+
+ $this->config->setSystemValues($configs);
+
+ return new DataResponse();
+ }
+
+ /**
+ * Store the credentials used for SMTP in the config
+ *
+ * @PasswordConfirmationRequired
+ *
+ * @param string $mail_smtpname
+ * @param string $mail_smtppassword
+ * @return DataResponse
+ */
+ public function storeCredentials($mail_smtpname, $mail_smtppassword) {
+ if ($mail_smtppassword === '********') {
+ return new DataResponse($this->l10n->t('Invalid SMTP password.'), Http::STATUS_BAD_REQUEST);
+ }
+
+ $this->config->setSystemValues([
+ 'mail_smtpname' => $mail_smtpname,
+ 'mail_smtppassword' => $mail_smtppassword,
+ ]);
+
+ return new DataResponse();
+ }
+
+ /**
+ * Send a mail to test the settings
+ * @return DataResponse
+ */
+ public function sendTestMail() {
+ $email = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'email', '');
+ if (!empty($email)) {
+ try {
+ $displayName = $this->userSession->getUser()->getDisplayName();
+
+ $template = $this->mailer->createEMailTemplate('settings.TestEmail', [
+ 'displayname' => $displayName,
+ ]);
+
+ $template->setSubject($this->l10n->t('Email setting test'));
+ $template->addHeader();
+ $template->addHeading($this->l10n->t('Well done, %s!', [$displayName]));
+ $template->addBodyText($this->l10n->t('If you received this email, the email configuration seems to be correct.'));
+ $template->addFooter();
+
+ $message = $this->mailer->createMessage();
+ $message->setTo([$email => $displayName]);
+ $message->useTemplate($template);
+ $errors = $this->mailer->send($message);
+ if (!empty($errors)) {
+ throw new \RuntimeException($this->l10n->t('Email could not be sent. Check your mail server log'));
+ }
+ return new DataResponse();
+ } catch (\Exception $e) {
+ return new DataResponse($this->l10n->t('A problem occurred while sending the email. Please revise your settings. (Error: %s)', [$e->getMessage()]), Http::STATUS_BAD_REQUEST);
+ }
+ }
+
+ return new DataResponse($this->l10n->t('You need to set your user email before being able to send test emails.'), Http::STATUS_BAD_REQUEST);
+ }
+
+}
diff --git a/apps/settings/lib/Controller/PersonalSettingsController.php b/apps/settings/lib/Controller/PersonalSettingsController.php
new file mode 100644
index 00000000000..74dbfd05ffe
--- /dev/null
+++ b/apps/settings/lib/Controller/PersonalSettingsController.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Settings\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Group\ISubAdmin;
+use OCP\IGroupManager;
+use OCP\INavigationManager;
+use OCP\IRequest;
+use OCP\IUserSession;
+use OCP\Settings\IManager as ISettingsManager;
+use OCP\Template;
+
+class PersonalSettingsController extends Controller {
+ use CommonSettingsTrait;
+
+ public function __construct(
+ $appName,
+ IRequest $request,
+ INavigationManager $navigationManager,
+ ISettingsManager $settingsManager,
+ IUserSession $userSession,
+ IGroupManager $groupManager,
+ ISubAdmin $subAdmin
+ ) {
+ parent::__construct($appName, $request);
+ $this->navigationManager = $navigationManager;
+ $this->settingsManager = $settingsManager;
+ $this->userSession = $userSession;
+ $this->subAdmin = $subAdmin;
+ $this->groupManager = $groupManager;
+ }
+
+ /**
+ * @param string $section
+ * @return TemplateResponse
+ *
+ * @NoCSRFRequired
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ */
+ public function index($section) {
+ return $this->getIndexResponse('personal', $section);
+
+ }
+
+ /**
+ * @param string $section
+ * @return array
+ */
+ protected function getSettings($section) {
+ $settings = $this->settingsManager->getPersonalSettings($section);
+ $formatted = $this->formatSettings($settings);
+ if($section === 'additional') {
+ $formatted['content'] .= $this->getLegacyForms();
+ }
+ return $formatted;
+ }
+
+ /**
+ * @return bool|string
+ */
+ private function getLegacyForms() {
+ $forms = \OC_App::getForms('personal');
+
+ $forms = array_map(function ($form) {
+ if (preg_match('%(<h2(?P<class>[^>]*)>.*?</h2>)%i', $form, $regs)) {
+ $sectionName = str_replace('<h2' . $regs['class'] . '>', '', $regs[0]);
+ $sectionName = str_replace('</h2>', '', $sectionName);
+ $anchor = strtolower($sectionName);
+ $anchor = str_replace(' ', '-', $anchor);
+
+ return array(
+ 'anchor' => $anchor,
+ 'section-name' => $sectionName,
+ 'form' => $form
+ );
+ }
+ return array(
+ 'form' => $form
+ );
+ }, $forms);
+
+ $out = new Template('settings', 'settings/additional');
+ $out->assign('forms', $forms);
+
+ return $out->fetchPage();
+ }
+}
diff --git a/apps/settings/lib/Controller/TwoFactorSettingsController.php b/apps/settings/lib/Controller/TwoFactorSettingsController.php
new file mode 100644
index 00000000000..08f8b4264d6
--- /dev/null
+++ b/apps/settings/lib/Controller/TwoFactorSettingsController.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Settings\Controller;
+
+use OC\Authentication\TwoFactorAuth\EnforcementState;
+use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\IRequest;
+
+class TwoFactorSettingsController extends Controller {
+
+ /** @var MandatoryTwoFactor */
+ private $mandatoryTwoFactor;
+
+ public function __construct(string $appName,
+ IRequest $request,
+ MandatoryTwoFactor $mandatoryTwoFactor) {
+ parent::__construct($appName, $request);
+
+ $this->mandatoryTwoFactor = $mandatoryTwoFactor;
+ }
+
+ public function index(): JSONResponse {
+ return new JSONResponse($this->mandatoryTwoFactor->getState());
+ }
+
+ public function update(bool $enforced, array $enforcedGroups = [], array $excludedGroups = []): JSONResponse {
+ $this->mandatoryTwoFactor->setState(
+ new EnforcementState($enforced, $enforcedGroups, $excludedGroups)
+ );
+
+ return new JSONResponse($this->mandatoryTwoFactor->getState());
+ }
+
+}
diff --git a/apps/settings/lib/Controller/UsersController.php b/apps/settings/lib/Controller/UsersController.php
new file mode 100644
index 00000000000..aaa4736087e
--- /dev/null
+++ b/apps/settings/lib/Controller/UsersController.php
@@ -0,0 +1,498 @@
+<?php
+// FIXME: disabled for now to be able to inject IGroupManager and also use
+// getSubAdmin()
+//declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Bjoern Schiessle <bjoern@schiessle.org>
+ * @author Björn Schießle <bjoern@schiessle.org>
+ * @author Christoph Wurst <christoph@owncloud.com>
+ * @author Clark Tomlinson <fallen013@gmail.com>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <robin@icewind.nl>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Thomas Pulzer <t.pulzer@kniel.de>
+ * @author Tobia De Koninck <tobia@ledfan.be>
+ * @author Tobias Kaminsky <tobias@kaminsky.me>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @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 OCA\Settings\Controller;
+
+use OC\Accounts\AccountManager;
+use OC\AppFramework\Http;
+use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
+use OC\ForbiddenException;
+use OC\Security\IdentityProof\Manager;
+use OCA\User_LDAP\User_Proxy;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\BackgroundJob\IJobList;
+use OCP\Encryption\IManager;
+use OCP\IConfig;
+use OCP\IGroupManager;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\IUserSession;
+use OCP\L10N\IFactory;
+use OCP\Mail\IMailer;
+use OCA\Settings\BackgroundJobs\VerifyUserData;
+
+class UsersController extends Controller {
+ /** @var IUserManager */
+ private $userManager;
+ /** @var IGroupManager */
+ private $groupManager;
+ /** @var IUserSession */
+ private $userSession;
+ /** @var IConfig */
+ private $config;
+ /** @var bool */
+ private $isAdmin;
+ /** @var IL10N */
+ private $l10n;
+ /** @var IMailer */
+ private $mailer;
+ /** @var IFactory */
+ private $l10nFactory;
+ /** @var IAppManager */
+ private $appManager;
+ /** @var AccountManager */
+ private $accountManager;
+ /** @var Manager */
+ private $keyManager;
+ /** @var IJobList */
+ private $jobList;
+ /** @var IManager */
+ private $encryptionManager;
+
+
+ public function __construct(string $appName,
+ IRequest $request,
+ IUserManager $userManager,
+ IGroupManager $groupManager,
+ IUserSession $userSession,
+ IConfig $config,
+ bool $isAdmin,
+ IL10N $l10n,
+ IMailer $mailer,
+ IFactory $l10nFactory,
+ IAppManager $appManager,
+ AccountManager $accountManager,
+ Manager $keyManager,
+ IJobList $jobList,
+ IManager $encryptionManager) {
+ parent::__construct($appName, $request);
+ $this->userManager = $userManager;
+ $this->groupManager = $groupManager;
+ $this->userSession = $userSession;
+ $this->config = $config;
+ $this->isAdmin = $isAdmin;
+ $this->l10n = $l10n;
+ $this->mailer = $mailer;
+ $this->l10nFactory = $l10nFactory;
+ $this->appManager = $appManager;
+ $this->accountManager = $accountManager;
+ $this->keyManager = $keyManager;
+ $this->jobList = $jobList;
+ $this->encryptionManager = $encryptionManager;
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @NoAdminRequired
+ *
+ * Display users list template
+ *
+ * @return TemplateResponse
+ */
+ public function usersListByGroup() {
+ return $this->usersList();
+ }
+
+ /**
+ * @NoCSRFRequired
+ * @NoAdminRequired
+ *
+ * Display users list template
+ *
+ * @return TemplateResponse
+ */
+ public function usersList() {
+ $user = $this->userSession->getUser();
+ $uid = $user->getUID();
+
+ \OC::$server->getNavigationManager()->setActiveEntry('core_users');
+
+ /* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
+ $sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
+ $isLDAPUsed = false;
+ if ($this->config->getSystemValue('sort_groups_by_name', false)) {
+ $sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
+ } else {
+ if ($this->appManager->isEnabledForUser('user_ldap')) {
+ $isLDAPUsed =
+ $this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
+ if ($isLDAPUsed) {
+ // LDAP user count can be slow, so we sort by group name here
+ $sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
+ }
+ }
+ }
+
+ $canChangePassword = $this->canAdminChangeUserPasswords();
+
+ /* GROUPS */
+ $groupsInfo = new \OC\Group\MetaData(
+ $uid,
+ $this->isAdmin,
+ $this->groupManager,
+ $this->userSession
+ );
+
+ $groupsInfo->setSorting($sortGroupsBy);
+ list($adminGroup, $groups) = $groupsInfo->get();
+
+ if(!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
+ $isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
+ return $ldapFound || $backend instanceof User_Proxy;
+ });
+ }
+
+ if ($this->isAdmin) {
+ $disabledUsers = $isLDAPUsed ? -1 : $this->userManager->countDisabledUsers();
+ $userCount = $isLDAPUsed ? 0 : array_reduce($this->userManager->countUsers(), function($v, $w) {
+ return $v + (int)$w;
+ }, 0);
+ } else {
+ // User is subadmin !
+ // Map group list to names to retrieve the countDisabledUsersOfGroups
+ $userGroups = $this->groupManager->getUserGroups($user);
+ $groupsNames = [];
+ $userCount = 0;
+
+ foreach($groups as $key => $group) {
+ // $userCount += (int)$group['usercount'];
+ array_push($groupsNames, $group['name']);
+ // we prevent subadmins from looking up themselves
+ // so we lower the count of the groups he belongs to
+ if (array_key_exists($group['id'], $userGroups)) {
+ $groups[$key]['usercount']--;
+ $userCount = -1; // we also lower from one the total count
+ }
+ };
+ $userCount += $isLDAPUsed ? 0 : $this->userManager->countUsersOfGroups($groupsInfo->getGroups());
+ $disabledUsers = $isLDAPUsed ? -1 : $this->userManager->countDisabledUsersOfGroups($groupsNames);
+ }
+ $disabledUsersGroup = [
+ 'id' => 'disabled',
+ 'name' => 'Disabled users',
+ 'usercount' => $disabledUsers
+ ];
+
+ /* QUOTAS PRESETS */
+ $quotaPreset = $this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
+ $quotaPreset = explode(',', $quotaPreset);
+ foreach ($quotaPreset as &$preset) {
+ $preset = trim($preset);
+ }
+ $quotaPreset = array_diff($quotaPreset, array('default', 'none'));
+ $defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
+
+ \OC::$server->getEventDispatcher()->dispatch('OC\Settings\Users::loadAdditionalScripts');
+
+ /* LANGUAGES */
+ $languages = $this->l10nFactory->getLanguages();
+
+ /* FINAL DATA */
+ $serverData = array();
+ // groups
+ $serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
+ // Various data
+ $serverData['isAdmin'] = $this->isAdmin;
+ $serverData['sortGroups'] = $sortGroupsBy;
+ $serverData['quotaPreset'] = $quotaPreset;
+ $serverData['userCount'] = $userCount - $disabledUsers;
+ $serverData['languages'] = $languages;
+ $serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
+ // Settings
+ $serverData['defaultQuota'] = $defaultQuota;
+ $serverData['canChangePassword'] = $canChangePassword;
+ $serverData['newUserGenerateUserID'] = $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes';
+ $serverData['newUserRequireEmail'] = $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes';
+
+ return new TemplateResponse('settings', 'settings-vue', ['serverData' => $serverData]);
+ }
+
+ /**
+ * check if the admin can change the users password
+ *
+ * The admin can change the passwords if:
+ *
+ * - no encryption module is loaded and encryption is disabled
+ * - encryption module is loaded but it doesn't require per user keys
+ *
+ * The admin can not change the passwords if:
+ *
+ * - an encryption module is loaded and it uses per-user keys
+ * - encryption is enabled but no encryption modules are loaded
+ *
+ * @return bool
+ */
+ protected function canAdminChangeUserPasswords() {
+ $isEncryptionEnabled = $this->encryptionManager->isEnabled();
+ try {
+ $noUserSpecificEncryptionKeys =!$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
+ $isEncryptionModuleLoaded = true;
+ } catch (ModuleDoesNotExistsException $e) {
+ $noUserSpecificEncryptionKeys = true;
+ $isEncryptionModuleLoaded = false;
+ }
+
+ $canChangePassword = ($isEncryptionEnabled && $isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
+ || (!$isEncryptionEnabled && !$isEncryptionModuleLoaded)
+ || (!$isEncryptionEnabled && $isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys);
+
+ return $canChangePassword;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ * @PasswordConfirmationRequired
+ *
+ * @param string $avatarScope
+ * @param string $displayname
+ * @param string $displaynameScope
+ * @param string $phone
+ * @param string $phoneScope
+ * @param string $email
+ * @param string $emailScope
+ * @param string $website
+ * @param string $websiteScope
+ * @param string $address
+ * @param string $addressScope
+ * @param string $twitter
+ * @param string $twitterScope
+ * @return DataResponse
+ */
+ public function setUserSettings($avatarScope,
+ $displayname,
+ $displaynameScope,
+ $phone,
+ $phoneScope,
+ $email,
+ $emailScope,
+ $website,
+ $websiteScope,
+ $address,
+ $addressScope,
+ $twitter,
+ $twitterScope
+ ) {
+ if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
+ return new DataResponse(
+ [
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $this->l10n->t('Invalid mail address')
+ ]
+ ],
+ Http::STATUS_UNPROCESSABLE_ENTITY
+ );
+ }
+ $user = $this->userSession->getUser();
+ $data = $this->accountManager->getUser($user);
+ $data[AccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
+ if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
+ $data[AccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
+ $data[AccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
+ }
+ if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
+ $federatedFileSharing = new \OCA\FederatedFileSharing\AppInfo\Application();
+ $shareProvider = $federatedFileSharing->getFederatedShareProvider();
+ if ($shareProvider->isLookupServerUploadEnabled()) {
+ $data[AccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
+ $data[AccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
+ $data[AccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
+ $data[AccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
+ }
+ }
+ try {
+ $this->saveUserSettings($user, $data);
+ return new DataResponse(
+ [
+ 'status' => 'success',
+ 'data' => [
+ 'userId' => $user->getUID(),
+ 'avatarScope' => $data[AccountManager::PROPERTY_AVATAR]['scope'],
+ 'displayname' => $data[AccountManager::PROPERTY_DISPLAYNAME]['value'],
+ 'displaynameScope' => $data[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
+ 'email' => $data[AccountManager::PROPERTY_EMAIL]['value'],
+ 'emailScope' => $data[AccountManager::PROPERTY_EMAIL]['scope'],
+ 'website' => $data[AccountManager::PROPERTY_WEBSITE]['value'],
+ 'websiteScope' => $data[AccountManager::PROPERTY_WEBSITE]['scope'],
+ 'address' => $data[AccountManager::PROPERTY_ADDRESS]['value'],
+ 'addressScope' => $data[AccountManager::PROPERTY_ADDRESS]['scope'],
+ 'message' => $this->l10n->t('Settings saved')
+ ]
+ ],
+ Http::STATUS_OK
+ );
+ } catch (ForbiddenException $e) {
+ return new DataResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $e->getMessage()
+ ],
+ ]);
+ }
+ }
+ /**
+ * update account manager with new user data
+ *
+ * @param IUser $user
+ * @param array $data
+ * @throws ForbiddenException
+ */
+ protected function saveUserSettings(IUser $user, array $data) {
+ // keep the user back-end up-to-date with the latest display name and email
+ // address
+ $oldDisplayName = $user->getDisplayName();
+ $oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
+ if (isset($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
+ && $oldDisplayName !== $data[AccountManager::PROPERTY_DISPLAYNAME]['value']
+ ) {
+ $result = $user->setDisplayName($data[AccountManager::PROPERTY_DISPLAYNAME]['value']);
+ if ($result === false) {
+ throw new ForbiddenException($this->l10n->t('Unable to change full name'));
+ }
+ }
+ $oldEmailAddress = $user->getEMailAddress();
+ $oldEmailAddress = is_null($oldEmailAddress) ? '' : $oldEmailAddress;
+ if (isset($data[AccountManager::PROPERTY_EMAIL]['value'])
+ && $oldEmailAddress !== $data[AccountManager::PROPERTY_EMAIL]['value']
+ ) {
+ // this is the only permission a backend provides and is also used
+ // for the permission of setting a email address
+ if (!$user->canChangeDisplayName()) {
+ throw new ForbiddenException($this->l10n->t('Unable to change email address'));
+ }
+ $user->setEMailAddress($data[AccountManager::PROPERTY_EMAIL]['value']);
+ }
+ $this->accountManager->updateUser($user, $data);
+ }
+
+ /**
+ * Set the mail address of a user
+ *
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ * @PasswordConfirmationRequired
+ *
+ * @param string $account
+ * @param bool $onlyVerificationCode only return verification code without updating the data
+ * @return DataResponse
+ */
+ public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
+
+ $user = $this->userSession->getUser();
+
+ if ($user === null) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ $accountData = $this->accountManager->getUser($user);
+ $cloudId = $user->getCloudId();
+ $message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
+ $signature = $this->signMessage($user, $message);
+
+ $code = $message . ' ' . $signature;
+ $codeMd5 = $message . ' ' . md5($signature);
+
+ switch ($account) {
+ case 'verify-twitter':
+ $accountData[AccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
+ $msg = $this->l10n->t('In order to verify your Twitter account, post the following tweet on Twitter (please make sure to post it without any line breaks):');
+ $code = $codeMd5;
+ $type = AccountManager::PROPERTY_TWITTER;
+ $data = $accountData[AccountManager::PROPERTY_TWITTER]['value'];
+ $accountData[AccountManager::PROPERTY_TWITTER]['signature'] = $signature;
+ break;
+ case 'verify-website':
+ $accountData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
+ $msg = $this->l10n->t('In order to verify your Website, store the following content in your web-root at \'.well-known/CloudIdVerificationCode.txt\' (please make sure that the complete text is in one line):');
+ $type = AccountManager::PROPERTY_WEBSITE;
+ $data = $accountData[AccountManager::PROPERTY_WEBSITE]['value'];
+ $accountData[AccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
+ break;
+ default:
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ if ($onlyVerificationCode === false) {
+ $this->accountManager->updateUser($user, $accountData);
+
+ $this->jobList->add(VerifyUserData::class,
+ [
+ 'verificationCode' => $code,
+ 'data' => $data,
+ 'type' => $type,
+ 'uid' => $user->getUID(),
+ 'try' => 0,
+ 'lastRun' => $this->getCurrentTime()
+ ]
+ );
+ }
+
+ return new DataResponse(['msg' => $msg, 'code' => $code]);
+ }
+
+ /**
+ * get current timestamp
+ *
+ * @return int
+ */
+ protected function getCurrentTime(): int {
+ return time();
+ }
+
+ /**
+ * sign message with users private key
+ *
+ * @param IUser $user
+ * @param string $message
+ *
+ * @return string base64 encoded signature
+ */
+ protected function signMessage(IUser $user, string $message): string {
+ $privateKey = $this->keyManager->getKey($user)->getPrivate();
+ openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
+ return base64_encode($signature);
+ }
+}