summaryrefslogtreecommitdiffstats
path: root/apps/settings/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/lib')
-rw-r--r--apps/settings/lib/Activity/GroupProvider.php202
-rw-r--r--apps/settings/lib/Activity/GroupSetting.php98
-rw-r--r--apps/settings/lib/Activity/Provider.php220
-rw-r--r--apps/settings/lib/Activity/SecurityFilter.php67
-rw-r--r--apps/settings/lib/Activity/SecurityProvider.php112
-rw-r--r--apps/settings/lib/Activity/SecuritySetting.php66
-rw-r--r--apps/settings/lib/Activity/Setting.php98
-rw-r--r--apps/settings/lib/AppInfo/Application.php229
-rw-r--r--apps/settings/lib/BackgroundJobs/VerifyUserData.php301
-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
-rw-r--r--apps/settings/lib/Hooks.php294
-rw-r--r--apps/settings/lib/Mailer/NewUserMailHelper.php173
-rw-r--r--apps/settings/lib/Middleware/SubadminMiddleware.php92
-rw-r--r--apps/settings/lib/Settings/Admin/Mail.php91
-rw-r--r--apps/settings/lib/Settings/Admin/Overview.php66
-rw-r--r--apps/settings/lib/Settings/Admin/Security.php109
-rw-r--r--apps/settings/lib/Settings/Admin/Server.php77
-rw-r--r--apps/settings/lib/Settings/Admin/Sharing.php137
-rw-r--r--apps/settings/lib/Settings/Personal/Additional.php59
-rw-r--r--apps/settings/lib/Settings/Personal/PersonalInfo.php280
-rw-r--r--apps/settings/lib/Settings/Personal/Security.php128
-rw-r--r--apps/settings/lib/Settings/Personal/Security/Authtokens.php107
-rw-r--r--apps/settings/lib/Settings/Personal/ServerDevNotice.php54
34 files changed, 6240 insertions, 0 deletions
diff --git a/apps/settings/lib/Activity/GroupProvider.php b/apps/settings/lib/Activity/GroupProvider.php
new file mode 100644
index 00000000000..eaf125fcdeb
--- /dev/null
+++ b/apps/settings/lib/Activity/GroupProvider.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author Christoph Wurst <christoph@owncloud.com>
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Activity;
+
+use InvalidArgumentException;
+use OCP\Activity\IEvent;
+use OCP\Activity\IManager;
+use OCP\Activity\IProvider;
+use OCP\IGroup;
+use OCP\IGroupManager;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\L10N\IFactory as L10nFactory;
+
+class GroupProvider implements IProvider {
+
+ public const ADDED_TO_GROUP = 'group_added';
+ public const REMOVED_FROM_GROUP = 'group_removed';
+
+ /** @var L10nFactory */
+ private $l10n;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var IManager */
+ private $activityManager;
+ /** @var IUserManager */
+ protected $userManager;
+ /** @var IGroupManager */
+ protected $groupManager;
+
+ /** @var string[] */
+ protected $groupDisplayNames = [];
+ /** @var string[] */
+ protected $userDisplayNames = [];
+
+
+ public function __construct(L10nFactory $l10n,
+ IURLGenerator $urlGenerator,
+ IManager $activityManager,
+ IUserManager $userManager,
+ IGroupManager $groupManager) {
+ $this->urlGenerator = $urlGenerator;
+ $this->l10n = $l10n;
+ $this->activityManager = $activityManager;
+ $this->userManager = $userManager;
+ $this->groupManager = $groupManager;
+ }
+
+ public function parse($language, IEvent $event, IEvent $previousEvent = null) {
+ if ($event->getType() !== 'group_settings') {
+ throw new InvalidArgumentException();
+ }
+
+ $l = $this->l10n->get('settings', $language);
+
+ $params = $event->getSubjectParameters();
+ $parsedParameters = [
+ 'user' => $this->generateUserParameter($params['user']),
+ 'group' => $this->generateGroupParameter($params['group']),
+ ];
+
+ if (isset($params['actor'])) {
+ $parsedParameters['actor'] = $this->generateUserParameter($params['actor']);
+ }
+
+ switch ($event->getSubject()) {
+ case self::ADDED_TO_GROUP:
+ if (isset($parsedParameters['actor'])) {
+ if ($this->activityManager->getCurrentUserId() === $params['user']) {
+ $subject = $l->t('{actor} added you to group {group}');
+ } elseif (isset($params['actor']) && $this->activityManager->getCurrentUserId() === $params['actor']) {
+ $subject = $l->t('You added {user} to group {group}');
+ } else {
+ $subject = $l->t('{actor} added {user} to group {group}');
+ }
+ } else if ($this->activityManager->getCurrentUserId() === $params['user']) {
+ $subject = $l->t('An administrator added you to group {group}');
+ } else {
+ $subject = $l->t('An administrator added {user} to group {group}');
+ }
+ break;
+ case self::REMOVED_FROM_GROUP:
+ if (isset($parsedParameters['actor'])) {
+ if ($this->activityManager->getCurrentUserId() === $params['user']) {
+ $subject = $l->t('{actor} removed you from group {group}');
+ } elseif (isset($params['actor']) && $this->activityManager->getCurrentUserId() === $params['actor']) {
+ $subject = $l->t('You removed {user} from group {group}');
+ } else {
+ $subject = $l->t('{actor} removed {user} from group {group}');
+ }
+ } else if ($this->activityManager->getCurrentUserId() === $params['user']) {
+ $subject = $l->t('An administrator removed you from group {group}');
+ } else {
+ $subject = $l->t('An administrator removed {user} from group {group}');
+ }
+ break;
+ default:
+ throw new InvalidArgumentException();
+ }
+
+ $this->setSubjects($event, $subject, $parsedParameters);
+
+ return $event;
+ }
+
+ /**
+ * @param IEvent $event
+ * @param string $subject
+ * @param array $parameters
+ * @throws \InvalidArgumentException
+ */
+ protected function setSubjects(IEvent $event, string $subject, array $parameters): void {
+ $placeholders = $replacements = [];
+ foreach ($parameters as $placeholder => $parameter) {
+ $placeholders[] = '{' . $placeholder . '}';
+ $replacements[] = $parameter['name'];
+ }
+
+ $event->setParsedSubject(str_replace($placeholders, $replacements, $subject))
+ ->setRichSubject($subject, $parameters);
+ }
+
+ /**
+ * @param string $gid
+ * @return array
+ */
+ protected function generateGroupParameter(string $gid): array {
+ if (!isset($this->groupDisplayNames[$gid])) {
+ $this->groupDisplayNames[$gid] = $this->getGroupDisplayName($gid);
+ }
+
+ return [
+ 'type' => 'user-group',
+ 'id' => $gid,
+ 'name' => $this->groupDisplayNames[$gid],
+ ];
+ }
+
+ /**
+ * @param string $gid
+ * @return string
+ */
+ protected function getGroupDisplayName(string $gid): string {
+ $group = $this->groupManager->get($gid);
+ if ($group instanceof IGroup) {
+ return $group->getDisplayName();
+ }
+ return $gid;
+ }
+
+ /**
+ * @param string $uid
+ * @return array
+ */
+ protected function generateUserParameter(string $uid): array {
+ if (!isset($this->displayNames[$uid])) {
+ $this->userDisplayNames[$uid] = $this->getDisplayName($uid);
+ }
+
+ return [
+ 'type' => 'user',
+ 'id' => $uid,
+ 'name' => $this->userDisplayNames[$uid],
+ ];
+ }
+
+ /**
+ * @param string $uid
+ * @return string
+ */
+ protected function getDisplayName(string $uid): string {
+ $user = $this->userManager->get($uid);
+ if ($user instanceof IUser) {
+ return $user->getDisplayName();
+ } else {
+ return $uid;
+ }
+ }
+}
diff --git a/apps/settings/lib/Activity/GroupSetting.php b/apps/settings/lib/Activity/GroupSetting.php
new file mode 100644
index 00000000000..c22ea60cf78
--- /dev/null
+++ b/apps/settings/lib/Activity/GroupSetting.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Activity;
+
+use OCP\Activity\ISetting;
+use OCP\IL10N;
+
+class GroupSetting implements ISetting {
+
+ /** @var IL10N */
+ protected $l;
+
+ /**
+ * @param IL10N $l10n
+ */
+ public function __construct(IL10N $l10n) {
+ $this->l = $l10n;
+ }
+
+ /**
+ * @return string Lowercase a-z and underscore only identifier
+ * @since 11.0.0
+ */
+ public function getIdentifier(): string {
+ return 'group_settings';
+ }
+
+ /**
+ * @return string A translated string
+ * @since 11.0.0
+ */
+ public function getName(): string {
+ return $this->l->t('Your <strong>group memberships</strong> were modified');
+ }
+
+ /**
+ * @return int whether the filter should be rather on the top or bottom of
+ * the admin section. The filters are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ * @since 11.0.0
+ */
+ public function getPriority(): int {
+ return 0;
+ }
+
+ /**
+ * @return bool True when the option can be changed for the stream
+ * @since 11.0.0
+ */
+ public function canChangeStream(): bool {
+ return false;
+ }
+
+ /**
+ * @return bool True when the option can be changed for the stream
+ * @since 11.0.0
+ */
+ public function isDefaultEnabledStream(): bool {
+ return true;
+ }
+
+ /**
+ * @return bool True when the option can be changed for the mail
+ * @since 11.0.0
+ */
+ public function canChangeMail(): bool {
+ return false;
+ }
+
+ /**
+ * @return bool True when the option can be changed for the stream
+ * @since 11.0.0
+ */
+ public function isDefaultEnabledMail(): bool {
+ return true;
+ }
+}
diff --git a/apps/settings/lib/Activity/Provider.php b/apps/settings/lib/Activity/Provider.php
new file mode 100644
index 00000000000..73cf81d3506
--- /dev/null
+++ b/apps/settings/lib/Activity/Provider.php
@@ -0,0 +1,220 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Activity;
+
+use OCP\Activity\IEvent;
+use OCP\Activity\IManager;
+use OCP\Activity\IProvider;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
+
+class Provider implements IProvider {
+
+ public const PASSWORD_CHANGED_BY = 'password_changed_by';
+ public const PASSWORD_CHANGED_SELF = 'password_changed_self';
+ public const PASSWORD_RESET = 'password_changed';
+ public const EMAIL_CHANGED_BY = 'email_changed_by';
+ public const EMAIL_CHANGED_SELF = 'email_changed_self';
+ public const EMAIL_CHANGED = 'email_changed';
+ public const APP_TOKEN_CREATED = 'app_token_created';
+ public const APP_TOKEN_DELETED = 'app_token_deleted';
+ public const APP_TOKEN_RENAMED = 'app_token_renamed';
+ public const APP_TOKEN_FILESYSTEM_GRANTED = 'app_token_filesystem_granted';
+ public const APP_TOKEN_FILESYSTEM_REVOKED = 'app_token_filesystem_revoked';
+
+ /** @var IFactory */
+ protected $languageFactory;
+
+ /** @var IL10N */
+ protected $l;
+
+ /** @var IURLGenerator */
+ protected $url;
+
+ /** @var IUserManager */
+ protected $userManager;
+
+ /** @var IManager */
+ private $activityManager;
+
+ /** @var string[] cached displayNames - key is the UID and value the displayname */
+ protected $displayNames = [];
+
+ public function __construct(IFactory $languageFactory,
+ IURLGenerator $url,
+ IUserManager $userManager,
+ IManager $activityManager) {
+ $this->languageFactory = $languageFactory;
+ $this->url = $url;
+ $this->userManager = $userManager;
+ $this->activityManager = $activityManager;
+ }
+
+ /**
+ * @param string $language
+ * @param IEvent $event
+ * @param IEvent|null $previousEvent
+ * @return IEvent
+ * @throws \InvalidArgumentException
+ * @since 11.0.0
+ */
+ public function parse($language, IEvent $event, IEvent $previousEvent = null): IEvent {
+ if ($event->getApp() !== 'settings') {
+ throw new \InvalidArgumentException('Unknown app');
+ }
+
+ $this->l = $this->languageFactory->get('settings', $language);
+
+ if ($this->activityManager->getRequirePNG()) {
+ $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('settings', 'personal.png')));
+ } else {
+ $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('settings', 'personal.svg')));
+ }
+
+ if ($event->getSubject() === self::PASSWORD_CHANGED_BY) {
+ $subject = $this->l->t('{actor} changed your password');
+ } else if ($event->getSubject() === self::PASSWORD_CHANGED_SELF) {
+ $subject = $this->l->t('You changed your password');
+ } else if ($event->getSubject() === self::PASSWORD_RESET) {
+ $subject = $this->l->t('Your password was reset by an administrator');
+
+ } else if ($event->getSubject() === self::EMAIL_CHANGED_BY) {
+ $subject = $this->l->t('{actor} changed your email address');
+ } else if ($event->getSubject() === self::EMAIL_CHANGED_SELF) {
+ $subject = $this->l->t('You changed your email address');
+ } else if ($event->getSubject() === self::EMAIL_CHANGED) {
+ $subject = $this->l->t('Your email address was changed by an administrator');
+
+ } else if ($event->getSubject() === self::APP_TOKEN_CREATED) {
+ $subject = $this->l->t('You created app password "{token}"');
+ } else if ($event->getSubject() === self::APP_TOKEN_DELETED) {
+ $subject = $this->l->t('You deleted app password "{token}"');
+ } else if ($event->getSubject() === self::APP_TOKEN_RENAMED) {
+ $subject = $this->l->t('You renamed app password "{token}" to "{newToken}"');
+ } else if ($event->getSubject() === self::APP_TOKEN_FILESYSTEM_GRANTED) {
+ $subject = $this->l->t('You granted filesystem access to app password "{token}"');
+ } else if ($event->getSubject() === self::APP_TOKEN_FILESYSTEM_REVOKED) {
+ $subject = $this->l->t('You revoked filesystem access from app password "{token}"');
+
+ } else {
+ throw new \InvalidArgumentException('Unknown subject');
+ }
+
+ $parsedParameters = $this->getParameters($event);
+ $this->setSubjects($event, $subject, $parsedParameters);
+
+ return $event;
+ }
+
+ /**
+ * @param IEvent $event
+ * @return array
+ * @throws \InvalidArgumentException
+ */
+ protected function getParameters(IEvent $event): array {
+ $subject = $event->getSubject();
+ $parameters = $event->getSubjectParameters();
+
+ switch ($subject) {
+ case self::PASSWORD_CHANGED_SELF:
+ case self::PASSWORD_RESET:
+ case self::EMAIL_CHANGED_SELF:
+ case self::EMAIL_CHANGED:
+ return [];
+ case self::PASSWORD_CHANGED_BY:
+ case self::EMAIL_CHANGED_BY:
+ return [
+ 'actor' => $this->generateUserParameter($parameters[0]),
+ ];
+ case self::APP_TOKEN_CREATED:
+ case self::APP_TOKEN_DELETED:
+ case self::APP_TOKEN_FILESYSTEM_GRANTED:
+ case self::APP_TOKEN_FILESYSTEM_REVOKED:
+ return [
+ 'token' => [
+ 'type' => 'highlight',
+ 'id' => $event->getObjectId(),
+ 'name' => $parameters['name'],
+ ]
+ ];
+ case self::APP_TOKEN_RENAMED:
+ return [
+ 'token' => [
+ 'type' => 'highlight',
+ 'id' => $event->getObjectId(),
+ 'name' => $parameters['name'],
+ ],
+ 'newToken' => [
+ 'type' => 'highlight',
+ 'id' => $event->getObjectId(),
+ 'name' => $parameters['newName'],
+ ]
+ ];
+ }
+
+ throw new \InvalidArgumentException('Unknown subject');
+ }
+
+ /**
+ * @param IEvent $event
+ * @param string $subject
+ * @param array $parameters
+ * @throws \InvalidArgumentException
+ */
+ protected function setSubjects(IEvent $event, string $subject, array $parameters): void {
+ $placeholders = $replacements = [];
+ foreach ($parameters as $placeholder => $parameter) {
+ $placeholders[] = '{' . $placeholder . '}';
+ $replacements[] = $parameter['name'];
+ }
+
+ $event->setParsedSubject(str_replace($placeholders, $replacements, $subject))
+ ->setRichSubject($subject, $parameters);
+ }
+
+ protected function generateUserParameter(string $uid): array {
+ if (!isset($this->displayNames[$uid])) {
+ $this->displayNames[$uid] = $this->getDisplayName($uid);
+ }
+
+ return [
+ 'type' => 'user',
+ 'id' => $uid,
+ 'name' => $this->displayNames[$uid],
+ ];
+ }
+
+ protected function getDisplayName(string $uid): string {
+ $user = $this->userManager->get($uid);
+ if ($user instanceof IUser) {
+ return $user->getDisplayName();
+ }
+
+ return $uid;
+ }
+}
diff --git a/apps/settings/lib/Activity/SecurityFilter.php b/apps/settings/lib/Activity/SecurityFilter.php
new file mode 100644
index 00000000000..1352925b687
--- /dev/null
+++ b/apps/settings/lib/Activity/SecurityFilter.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ *
+ *
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @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\Activity;
+
+use OCP\Activity\IFilter;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+
+class SecurityFilter implements IFilter {
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var IL10N */
+ private $l10n;
+
+ public function __construct(IURLGenerator $urlGenerator, IL10N $l10n) {
+ $this->urlGenerator = $urlGenerator;
+ $this->l10n = $l10n;
+ }
+
+ public function allowedApps() {
+ return [];
+ }
+
+ public function filterTypes(array $types) {
+ return array_intersect(['security'], $types);
+ }
+
+ public function getIcon() {
+ return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg'));
+ }
+
+ public function getIdentifier() {
+ return 'security';
+ }
+
+ public function getName() {
+ return $this->l10n->t('Security');
+ }
+
+ public function getPriority() {
+ return 30;
+ }
+
+}
diff --git a/apps/settings/lib/Activity/SecurityProvider.php b/apps/settings/lib/Activity/SecurityProvider.php
new file mode 100644
index 00000000000..b7cce5d7364
--- /dev/null
+++ b/apps/settings/lib/Activity/SecurityProvider.php
@@ -0,0 +1,112 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author Christoph Wurst <christoph@owncloud.com>
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Activity;
+
+use InvalidArgumentException;
+use OCP\Activity\IEvent;
+use OCP\Activity\IManager;
+use OCP\Activity\IProvider;
+use OCP\IURLGenerator;
+use OCP\L10N\IFactory as L10nFactory;
+
+class SecurityProvider implements IProvider {
+
+ /** @var L10nFactory */
+ private $l10n;
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var IManager */
+ private $activityManager;
+
+ public function __construct(L10nFactory $l10n, IURLGenerator $urlGenerator, IManager $activityManager) {
+ $this->urlGenerator = $urlGenerator;
+ $this->l10n = $l10n;
+ $this->activityManager = $activityManager;
+ }
+
+ public function parse($language, IEvent $event, IEvent $previousEvent = null) {
+ if ($event->getType() !== 'security') {
+ throw new InvalidArgumentException();
+ }
+
+ $l = $this->l10n->get('settings', $language);
+
+ switch ($event->getSubject()) {
+ case 'twofactor_success':
+ $params = $event->getSubjectParameters();
+ $event->setParsedSubject($l->t('You successfully logged in using two-factor authentication (%1$s)', [
+ $params['provider'],
+ ]));
+ if ($this->activityManager->getRequirePNG()) {
+ $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.png')));
+ } else {
+ $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg')));
+ }
+ break;
+ case 'twofactor_failed':
+ $params = $event->getSubjectParameters();
+ $event->setParsedSubject($l->t('A login attempt using two-factor authentication failed (%1$s)', [
+ $params['provider'],
+ ]));
+ if ($this->activityManager->getRequirePNG()) {
+ $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.png')));
+ } else {
+ $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg')));
+ }
+ break;
+ case 'remote_wipe_start':
+ $params = $event->getSubjectParameters();
+ $event->setParsedSubject($l->t('Remote wipe was started on %1$s', [
+ $params['name'],
+ ]));
+ if ($this->activityManager->getRequirePNG()) {
+ $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.png')));
+ } else {
+ $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.svg')));
+ }
+ break;
+ case 'remote_wipe_finish':
+ $params = $event->getSubjectParameters();
+ $event->setParsedSubject($l->t('Remote wipe has finished on %1$s', [
+ $params['name'],
+ ]));
+ if ($this->activityManager->getRequirePNG()) {
+ $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.png')));
+ } else {
+ $event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.svg')));
+ }
+ break;
+ default:
+ throw new InvalidArgumentException();
+ }
+ return $event;
+ }
+
+}
diff --git a/apps/settings/lib/Activity/SecuritySetting.php b/apps/settings/lib/Activity/SecuritySetting.php
new file mode 100644
index 00000000000..92c288c0f88
--- /dev/null
+++ b/apps/settings/lib/Activity/SecuritySetting.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @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\Activity;
+
+use OCP\Activity\ISetting;
+use OCP\IL10N;
+
+class SecuritySetting implements ISetting {
+
+ /** @var IL10N */
+ private $l10n;
+
+ public function __construct(IL10N $l10n) {
+ $this->l10n = $l10n;
+ }
+
+ public function canChangeMail() {
+ return false;
+ }
+
+ public function canChangeStream() {
+ return false;
+ }
+
+ public function getIdentifier() {
+ return 'security';
+ }
+
+ public function getName() {
+ return $this->l10n->t('Security');
+ }
+
+ public function getPriority() {
+ return 30;
+ }
+
+ public function isDefaultEnabledMail() {
+ return true;
+ }
+
+ public function isDefaultEnabledStream() {
+ return true;
+ }
+
+}
diff --git a/apps/settings/lib/Activity/Setting.php b/apps/settings/lib/Activity/Setting.php
new file mode 100644
index 00000000000..5220697b614
--- /dev/null
+++ b/apps/settings/lib/Activity/Setting.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Activity;
+
+use OCP\Activity\ISetting;
+use OCP\IL10N;
+
+class Setting implements ISetting {
+
+ /** @var IL10N */
+ protected $l;
+
+ /**
+ * @param IL10N $l10n
+ */
+ public function __construct(IL10N $l10n) {
+ $this->l = $l10n;
+ }
+
+ /**
+ * @return string Lowercase a-z and underscore only identifier
+ * @since 11.0.0
+ */
+ public function getIdentifier() {
+ return 'personal_settings';
+ }
+
+ /**
+ * @return string A translated string
+ * @since 11.0.0
+ */
+ public function getName() {
+ return $this->l->t('Your <strong>password</strong> or <strong>email</strong> was modified');
+ }
+
+ /**
+ * @return int whether the filter should be rather on the top or bottom of
+ * the admin section. The filters are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ * @since 11.0.0
+ */
+ public function getPriority() {
+ return 0;
+ }
+
+ /**
+ * @return bool True when the option can be changed for the stream
+ * @since 11.0.0
+ */
+ public function canChangeStream() {
+ return false;
+ }
+
+ /**
+ * @return bool True when the option can be changed for the stream
+ * @since 11.0.0
+ */
+ public function isDefaultEnabledStream() {
+ return true;
+ }
+
+ /**
+ * @return bool True when the option can be changed for the mail
+ * @since 11.0.0
+ */
+ public function canChangeMail() {
+ return false;
+ }
+
+ /**
+ * @return bool True when the option can be changed for the stream
+ * @since 11.0.0
+ */
+ public function isDefaultEnabledMail() {
+ return false;
+ }
+}
diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php
new file mode 100644
index 00000000000..0a564cacc8e
--- /dev/null
+++ b/apps/settings/lib/AppInfo/Application.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Björn Schießle <bjoern@schiessle.org>
+ * @author Christoph Wurst <christoph@owncloud.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>
+ *
+ * @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\AppInfo;
+
+use BadMethodCallException;
+use OC\AppFramework\Utility\TimeFactory;
+use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IToken;
+use OC\Server;
+use OCA\Settings\Activity\GroupProvider;
+use OCA\Settings\Activity\GroupSetting;
+use OCA\Settings\Activity\Provider;
+use OCA\Settings\Activity\SecurityFilter;
+use OCA\Settings\Activity\SecurityProvider;
+use OCA\Settings\Activity\SecuritySetting;
+use OCA\Settings\Activity\Setting;
+use OCA\Settings\Hooks;
+use OCA\Settings\Mailer\NewUserMailHelper;
+use OCA\Settings\Middleware\SubadminMiddleware;
+use OCP\Activity\IManager as IActivityManager;
+use OCP\AppFramework\App;
+use OCP\Defaults;
+use OCP\IContainer;
+use OCP\IGroup;
+use OCP\ILogger;
+use OCP\IUser;
+use OCP\Settings\IManager;
+use OCP\Util;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+class Application extends App {
+
+
+ /**
+ * @param array $urlParams
+ */
+ public function __construct(array $urlParams=[]){
+ parent::__construct('settings', $urlParams);
+
+ $container = $this->getContainer();
+
+ // Register Middleware
+ $container->registerAlias('SubadminMiddleware', SubadminMiddleware::class);
+ $container->registerMiddleWare('SubadminMiddleware');
+
+ /**
+ * Core class wrappers
+ */
+ /** FIXME: Remove once OC_User is non-static and mockable */
+ $container->registerService('isAdmin', function() {
+ return \OC_User::isAdminUser(\OC_User::getUser());
+ });
+ /** FIXME: Remove once OC_SubAdmin is non-static and mockable */
+ $container->registerService('isSubAdmin', function(IContainer $c) {
+ $userObject = \OC::$server->getUserSession()->getUser();
+ $isSubAdmin = false;
+ if($userObject !== null) {
+ $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
+ }
+ return $isSubAdmin;
+ });
+ $container->registerService('userCertificateManager', function(IContainer $c) {
+ return $c->query('ServerContainer')->getCertificateManager();
+ }, false);
+ $container->registerService('systemCertificateManager', function (IContainer $c) {
+ return $c->query('ServerContainer')->getCertificateManager(null);
+ }, false);
+ $container->registerService(IProvider::class, function (IContainer $c) {
+ return $c->query('ServerContainer')->query(IProvider::class);
+ });
+ $container->registerService(IManager::class, function (IContainer $c) {
+ return $c->query('ServerContainer')->getSettingsManager();
+ });
+
+ $container->registerService(NewUserMailHelper::class, function (IContainer $c) {
+ /** @var Server $server */
+ $server = $c->query('ServerContainer');
+ /** @var Defaults $defaults */
+ $defaults = $server->query(Defaults::class);
+
+ return new NewUserMailHelper(
+ $defaults,
+ $server->getURLGenerator(),
+ $server->getL10NFactory(),
+ $server->getMailer(),
+ $server->getSecureRandom(),
+ new TimeFactory(),
+ $server->getConfig(),
+ $server->getCrypto(),
+ Util::getDefaultEmailAddress('no-reply')
+ );
+ });
+
+ /** @var EventDispatcherInterface $eventDispatcher */
+ $eventDispatcher = $container->getServer()->getEventDispatcher();
+ $eventDispatcher->addListener('app_password_created', function (GenericEvent $event) use ($container) {
+ if (($token = $event->getSubject()) instanceof IToken) {
+ /** @var IActivityManager $activityManager */
+ $activityManager = $container->query(IActivityManager::class);
+ /** @var ILogger $logger */
+ $logger = $container->query(ILogger::class);
+
+ $activity = $activityManager->generateEvent();
+ $activity->setApp('settings')
+ ->setType('security')
+ ->setAffectedUser($token->getUID())
+ ->setAuthor($token->getUID())
+ ->setSubject(Provider::APP_TOKEN_CREATED, ['name' => $token->getName()])
+ ->setObject('app_token', $token->getId());
+
+ try {
+ $activityManager->publish($activity);
+ } catch (BadMethodCallException $e) {
+ $logger->logException($e, ['message' => 'could not publish activity', 'level' => ILogger::WARN]);
+ }
+ }
+ });
+ }
+
+ public function register() {
+ $activityManager = $this->getContainer()->getServer()->getActivityManager();
+ $activityManager->registerSetting(Setting::class); // FIXME move to info.xml
+ $activityManager->registerProvider(Provider::class); // FIXME move to info.xml
+ $activityManager->registerFilter(SecurityFilter::class); // FIXME move to info.xml
+ $activityManager->registerSetting(SecuritySetting::class); // FIXME move to info.xml
+ $activityManager->registerProvider(SecurityProvider::class); // FIXME move to info.xml
+ $activityManager->registerSetting(GroupSetting::class); // FIXME move to info.xml
+ $activityManager->registerProvider(GroupProvider::class); // FIXME move to info.xml
+
+ Util::connectHook('OC_User', 'post_setPassword', $this, 'onChangePassword');
+ Util::connectHook('OC_User', 'changeUser', $this, 'onChangeInfo');
+
+ $groupManager = $this->getContainer()->getServer()->getGroupManager();
+ $groupManager->listen('\OC\Group', 'postRemoveUser', [$this, 'removeUserFromGroup']);
+ $groupManager->listen('\OC\Group', 'postAddUser', [$this, 'addUserToGroup']);
+
+ Util::connectHook('\OCP\Config', 'js', $this, 'extendJsConfig');
+ }
+
+ public function addUserToGroup(IGroup $group, IUser $user): void {
+ /** @var Hooks $hooks */
+ $hooks = $this->getContainer()->query(Hooks::class);
+ $hooks->addUserToGroup($group, $user);
+
+ }
+
+ public function removeUserFromGroup(IGroup $group, IUser $user): void {
+ /** @var Hooks $hooks */
+ $hooks = $this->getContainer()->query(Hooks::class);
+ $hooks->removeUserFromGroup($group, $user);
+ }
+
+
+ /**
+ * @param array $parameters
+ * @throws \InvalidArgumentException
+ * @throws \BadMethodCallException
+ * @throws \Exception
+ * @throws \OCP\AppFramework\QueryException
+ */
+ public function onChangePassword(array $parameters) {
+ /** @var Hooks $hooks */
+ $hooks = $this->getContainer()->query(Hooks::class);
+ $hooks->onChangePassword($parameters['uid']);
+ }
+
+ /**
+ * @param array $parameters
+ * @throws \InvalidArgumentException
+ * @throws \BadMethodCallException
+ * @throws \Exception
+ * @throws \OCP\AppFramework\QueryException
+ */
+ public function onChangeInfo(array $parameters) {
+ if ($parameters['feature'] !== 'eMailAddress') {
+ return;
+ }
+
+ /** @var Hooks $hooks */
+ $hooks = $this->getContainer()->query(Hooks::class);
+ $hooks->onChangeEmail($parameters['user'], $parameters['old_value']);
+ }
+
+ /**
+ * @param array $settings
+ */
+ public function extendJsConfig(array $settings) {
+ $appConfig = json_decode($settings['array']['oc_appconfig'], true);
+
+ $publicWebFinger = \OC::$server->getConfig()->getAppValue('core', 'public_webfinger', '');
+ if (!empty($publicWebFinger)) {
+ $appConfig['core']['public_webfinger'] = $publicWebFinger;
+ }
+
+ $publicNodeInfo = \OC::$server->getConfig()->getAppValue('core', 'public_nodeinfo', '');
+ if (!empty($publicNodeInfo)) {
+ $appConfig['core']['public_nodeinfo'] = $publicNodeInfo;
+ }
+
+ $settings['array']['oc_appconfig'] = json_encode($appConfig);
+ }
+}
diff --git a/apps/settings/lib/BackgroundJobs/VerifyUserData.php b/apps/settings/lib/BackgroundJobs/VerifyUserData.php
new file mode 100644
index 00000000000..caac35f2de4
--- /dev/null
+++ b/apps/settings/lib/BackgroundJobs/VerifyUserData.php
@@ -0,0 +1,301 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
+ *
+ * @author Bjoern Schiessle <bjoern@schiessle.org>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Patrik Kernstock <info@pkern.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\BackgroundJobs;
+
+
+use OC\Accounts\AccountManager;
+use OC\BackgroundJob\Job;
+use OC\BackgroundJob\JobList;
+use OCP\AppFramework\Http;
+use OCP\BackgroundJob\IJobList;
+use OCP\Http\Client\IClientService;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\IUserManager;
+
+class VerifyUserData extends Job {
+
+ /** @var bool */
+ private $retainJob = true;
+
+ /** @var int max number of attempts to send the request */
+ private $maxTry = 24;
+
+ /** @var int how much time should be between two tries (1 hour) */
+ private $interval = 3600;
+
+ /** @var AccountManager */
+ private $accountManager;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var IClientService */
+ private $httpClientService;
+
+ /** @var ILogger */
+ private $logger;
+
+ /** @var string */
+ private $lookupServerUrl;
+
+ /** @var IConfig */
+ private $config;
+
+ /**
+ * VerifyUserData constructor.
+ *
+ * @param AccountManager $accountManager
+ * @param IUserManager $userManager
+ * @param IClientService $clientService
+ * @param ILogger $logger
+ * @param IConfig $config
+ */
+ public function __construct(AccountManager $accountManager,
+ IUserManager $userManager,
+ IClientService $clientService,
+ ILogger $logger,
+ IConfig $config
+ ) {
+ $this->accountManager = $accountManager;
+ $this->userManager = $userManager;
+ $this->httpClientService = $clientService;
+ $this->logger = $logger;
+
+ $lookupServerUrl = $config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com');
+ $this->lookupServerUrl = rtrim($lookupServerUrl, '/');
+ $this->config = $config;
+ }
+
+ /**
+ * run the job, then remove it from the jobList
+ *
+ * @param JobList $jobList
+ * @param ILogger|null $logger
+ */
+ public function execute($jobList, ILogger $logger = null) {
+
+ if ($this->shouldRun($this->argument)) {
+ parent::execute($jobList, $logger);
+ $jobList->remove($this, $this->argument);
+ if ($this->retainJob) {
+ $this->reAddJob($jobList, $this->argument);
+ } else {
+ $this->resetVerificationState();
+ }
+ }
+
+ }
+
+ protected function run($argument) {
+
+ $try = (int)$argument['try'] + 1;
+
+ switch($argument['type']) {
+ case AccountManager::PROPERTY_WEBSITE:
+ $result = $this->verifyWebsite($argument);
+ break;
+ case AccountManager::PROPERTY_TWITTER:
+ case AccountManager::PROPERTY_EMAIL:
+ $result = $this->verifyViaLookupServer($argument, $argument['type']);
+ break;
+ default:
+ // no valid type given, no need to retry
+ $this->logger->error($argument['type'] . ' is no valid type for user account data.');
+ $result = true;
+ }
+
+ if ($result === true || $try > $this->maxTry) {
+ $this->retainJob = false;
+ }
+ }
+
+ /**
+ * verify web page
+ *
+ * @param array $argument
+ * @return bool true if we could check the verification code, otherwise false
+ */
+ protected function verifyWebsite(array $argument) {
+
+ $result = false;
+
+ $url = rtrim($argument['data'], '/') . '/.well-known/' . 'CloudIdVerificationCode.txt';
+
+ $client = $this->httpClientService->newClient();
+ try {
+ $response = $client->get($url);
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ if ($response->getStatusCode() === Http::STATUS_OK) {
+ $result = true;
+ $publishedCode = $response->getBody();
+ // remove new lines and spaces
+ $publishedCodeSanitized = trim(preg_replace('/\s\s+/', ' ', $publishedCode));
+ $user = $this->userManager->get($argument['uid']);
+ // we don't check a valid user -> give up
+ if ($user === null) {
+ $this->logger->error($argument['uid'] . ' doesn\'t exist, can\'t verify user data.');
+ return $result;
+ }
+ $userData = $this->accountManager->getUser($user);
+
+ if ($publishedCodeSanitized === $argument['verificationCode']) {
+ $userData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFIED;
+ } else {
+ $userData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::NOT_VERIFIED;
+ }
+
+ $this->accountManager->updateUser($user, $userData);
+ }
+
+ return $result;
+ }
+
+ /**
+ * verify email address
+ *
+ * @param array $argument
+ * @param string $dataType
+ * @return bool true if we could check the verification code, otherwise false
+ */
+ protected function verifyViaLookupServer(array $argument, $dataType) {
+ if(empty($this->lookupServerUrl) ||
+ $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes') !== 'yes' ||
+ $this->config->getSystemValue('has_internet_connection', true) === false) {
+ return false;
+ }
+
+ $user = $this->userManager->get($argument['uid']);
+
+ // we don't check a valid user -> give up
+ if ($user === null) {
+ $this->logger->info($argument['uid'] . ' doesn\'t exist, can\'t verify user data.');
+ return true;
+ }
+
+ $localUserData = $this->accountManager->getUser($user);
+ $cloudId = $user->getCloudId();
+
+ // ask lookup-server for user data
+ $lookupServerData = $this->queryLookupServer($cloudId);
+
+ // for some reasons we couldn't read any data from the lookup server, try again later
+ if (empty($lookupServerData) || empty($lookupServerData[$dataType])) {
+ return false;
+ }
+
+ // lookup server has verification data for wrong user data (e.g. email address), try again later
+ if ($lookupServerData[$dataType]['value'] !== $argument['data']) {
+ return false;
+ }
+
+ // lookup server hasn't verified the email address so far, try again later
+ if ($lookupServerData[$dataType]['verified'] === AccountManager::NOT_VERIFIED) {
+ return false;
+ }
+
+ $localUserData[$dataType]['verified'] = AccountManager::VERIFIED;
+ $this->accountManager->updateUser($user, $localUserData);
+
+ return true;
+ }
+
+ /**
+ * @param string $cloudId
+ * @return array
+ */
+ protected function queryLookupServer($cloudId) {
+ try {
+ $client = $this->httpClientService->newClient();
+ $response = $client->get(
+ $this->lookupServerUrl . '/users?search=' . urlencode($cloudId) . '&exactCloudId=1',
+ [
+ 'timeout' => 10,
+ 'connect_timeout' => 3,
+ ]
+ );
+
+ $body = json_decode($response->getBody(), true);
+
+ if (is_array($body) && isset($body['federationId']) && $body['federationId'] === $cloudId) {
+ return $body;
+ }
+
+ } catch (\Exception $e) {
+ // do nothing, we will just re-try later
+ }
+
+ return [];
+ }
+
+ /**
+ * re-add background job with new arguments
+ *
+ * @param IJobList $jobList
+ * @param array $argument
+ */
+ protected function reAddJob(IJobList $jobList, array $argument) {
+ $jobList->add(VerifyUserData::class,
+ [
+ 'verificationCode' => $argument['verificationCode'],
+ 'data' => $argument['data'],
+ 'type' => $argument['type'],
+ 'uid' => $argument['uid'],
+ 'try' => (int)$argument['try'] + 1,
+ 'lastRun' => time()
+ ]
+ );
+ }
+
+ /**
+ * test if it is time for the next run
+ *
+ * @param array $argument
+ * @return bool
+ */
+ protected function shouldRun(array $argument) {
+ $lastRun = (int)$argument['lastRun'];
+ return ((time() - $lastRun) > $this->interval);
+ }
+
+
+ /**
+ * reset verification state after max tries are reached
+ */
+ protected function resetVerificationState() {
+ $user = $this->userManager->get($this->argument['uid']);
+ if ($user !== null) {
+ $accountData = $this->accountManager->getUser($user);
+ $accountData[$this->argument['type']]['verified'] = AccountManager::NOT_VERIFIED;
+ $this->accountManager->updateUser($user, $accountData);
+ }
+ }
+
+}
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);
+ }
+}
diff --git a/apps/settings/lib/Hooks.php b/apps/settings/lib/Hooks.php
new file mode 100644
index 00000000000..8dae82ef877
--- /dev/null
+++ b/apps/settings/lib/Hooks.php
@@ -0,0 +1,294 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @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;
+
+use OCA\Settings\Activity\GroupProvider;
+use OCA\Settings\Activity\Provider;
+use OCP\Activity\IManager as IActivityManager;
+use OCP\IConfig;
+use OCP\IGroup;
+use OCP\IGroupManager;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\IUserSession;
+use OCP\L10N\IFactory;
+use OCP\Mail\IMailer;
+
+class Hooks {
+
+ /** @var IActivityManager */
+ protected $activityManager;
+ /** @var IGroupManager|\OC\Group\Manager */
+ protected $groupManager;
+ /** @var IUserManager */
+ protected $userManager;
+ /** @var IUserSession */
+ protected $userSession;
+ /** @var IURLGenerator */
+ protected $urlGenerator;
+ /** @var IMailer */
+ protected $mailer;
+ /** @var IConfig */
+ protected $config;
+ /** @var IFactory */
+ protected $languageFactory;
+ /** @var IL10N */
+ protected $l;
+
+ public function __construct(IActivityManager $activityManager,
+ IGroupManager $groupManager,
+ IUserManager $userManager,
+ IUserSession $userSession,
+ IURLGenerator $urlGenerator,
+ IMailer $mailer,
+ IConfig $config,
+ IFactory $languageFactory,
+ IL10N $l) {
+ $this->activityManager = $activityManager;
+ $this->groupManager = $groupManager;
+ $this->userManager = $userManager;
+ $this->userSession = $userSession;
+ $this->urlGenerator = $urlGenerator;
+ $this->mailer = $mailer;
+ $this->config = $config;
+ $this->languageFactory = $languageFactory;
+ $this->l = $l;
+ }
+
+ /**
+ * @param string $uid
+ * @throws \InvalidArgumentException
+ * @throws \BadMethodCallException
+ * @throws \Exception
+ */
+ public function onChangePassword($uid) {
+ $user = $this->userManager->get($uid);
+
+ if (!$user instanceof IUser || $user->getLastLogin() === 0) {
+ // User didn't login, so don't create activities and emails.
+ return;
+ }
+
+ $event = $this->activityManager->generateEvent();
+ $event->setApp('settings')
+ ->setType('personal_settings')
+ ->setAffectedUser($user->getUID());
+
+ $instanceUrl = $this->urlGenerator->getAbsoluteURL('/');
+
+ $actor = $this->userSession->getUser();
+ if ($actor instanceof IUser) {
+ if ($actor->getUID() !== $user->getUID()) {
+ $this->l = $this->languageFactory->get(
+ 'settings',
+ $this->config->getUserValue(
+ $user->getUID(), 'core', 'lang',
+ $this->config->getSystemValue('default_language', 'en')
+ )
+ );
+
+ $text = $this->l->t('%1$s changed your password on %2$s.', [$actor->getDisplayName(), $instanceUrl]);
+ $event->setAuthor($actor->getUID())
+ ->setSubject(Provider::PASSWORD_CHANGED_BY, [$actor->getUID()]);
+ } else {
+ $text = $this->l->t('Your password on %s was changed.', [$instanceUrl]);
+ $event->setAuthor($actor->getUID())
+ ->setSubject(Provider::PASSWORD_CHANGED_SELF);
+ }
+ } else {
+ $text = $this->l->t('Your password on %s was reset by an administrator.', [$instanceUrl]);
+ $event->setSubject(Provider::PASSWORD_RESET);
+ }
+
+ $this->activityManager->publish($event);
+
+ if ($user->getEMailAddress() !== null) {
+ $template = $this->mailer->createEMailTemplate('settings.PasswordChanged', [
+ 'displayname' => $user->getDisplayName(),
+ 'emailAddress' => $user->getEMailAddress(),
+ 'instanceUrl' => $instanceUrl,
+ ]);
+
+ $template->setSubject($this->l->t('Password for %1$s changed on %2$s', [$user->getDisplayName(), $instanceUrl]));
+ $template->addHeader();
+ $template->addHeading($this->l->t('Password changed for %s', [$user->getDisplayName()]), false);
+ $template->addBodyText($text . ' ' . $this->l->t('If you did not request this, please contact an administrator.'));
+ $template->addFooter();
+
+
+ $message = $this->mailer->createMessage();
+ $message->setTo([$user->getEMailAddress() => $user->getDisplayName()]);
+ $message->useTemplate($template);
+ $this->mailer->send($message);
+ }
+ }
+
+ /**
+ * @param IUser $user
+ * @param string|null $oldMailAddress
+ * @throws \InvalidArgumentException
+ * @throws \BadMethodCallException
+ */
+ public function onChangeEmail(IUser $user, $oldMailAddress) {
+
+ if ($oldMailAddress === $user->getEMailAddress() ||
+ $user->getLastLogin() === 0) {
+ // Email didn't really change or user didn't login,
+ // so don't create activities and emails.
+ return;
+ }
+
+ $event = $this->activityManager->generateEvent();
+ $event->setApp('settings')
+ ->setType('personal_settings')
+ ->setAffectedUser($user->getUID());
+
+ $instanceUrl = $this->urlGenerator->getAbsoluteURL('/');
+
+ $actor = $this->userSession->getUser();
+ if ($actor instanceof IUser) {
+ $subject = Provider::EMAIL_CHANGED_SELF;
+ if ($actor->getUID() !== $user->getUID()) {
+ $this->l = $this->languageFactory->get(
+ 'settings',
+ $this->config->getUserValue(
+ $user->getUID(), 'core', 'lang',
+ $this->config->getSystemValue('default_language', 'en')
+ )
+ );
+ $subject = Provider::EMAIL_CHANGED;
+ }
+ $text = $this->l->t('Your email address on %s was changed.', [$instanceUrl]);
+ $event->setAuthor($actor->getUID())
+ ->setSubject($subject);
+ } else {
+ $text = $this->l->t('Your email address on %s was changed by an administrator.', [$instanceUrl]);
+ $event->setSubject(Provider::EMAIL_CHANGED);
+ }
+ $this->activityManager->publish($event);
+
+
+ if ($oldMailAddress !== null) {
+ $template = $this->mailer->createEMailTemplate('settings.EmailChanged', [
+ 'displayname' => $user->getDisplayName(),
+ 'newEMailAddress' => $user->getEMailAddress(),
+ 'oldEMailAddress' => $oldMailAddress,
+ 'instanceUrl' => $instanceUrl,
+ ]);
+
+ $template->setSubject($this->l->t('Email address for %1$s changed on %2$s', [$user->getDisplayName(), $instanceUrl]));
+ $template->addHeader();
+ $template->addHeading($this->l->t('Email address changed for %s', [$user->getDisplayName()]), false);
+ $template->addBodyText($text . ' ' . $this->l->t('If you did not request this, please contact an administrator.'));
+ if ($user->getEMailAddress()) {
+ $template->addBodyText($this->l->t('The new email address is %s', [$user->getEMailAddress()]));
+ }
+ $template->addFooter();
+
+
+ $message = $this->mailer->createMessage();
+ $message->setTo([$oldMailAddress => $user->getDisplayName()]);
+ $message->useTemplate($template);
+ $this->mailer->send($message);
+ }
+ }
+
+ /**
+ * @param IGroup $group
+ * @param IUser $user
+ * @throws \InvalidArgumentException
+ * @throws \BadMethodCallException
+ */
+ public function addUserToGroup(IGroup $group, IUser $user): void {
+ $subAdminManager = $this->groupManager->getSubAdmin();
+ $usersToNotify = $subAdminManager->getGroupsSubAdmins($group);
+ $usersToNotify[] = $user;
+
+
+ $event = $this->activityManager->generateEvent();
+ $event->setApp('settings')
+ ->setType('group_settings');
+
+ $actor = $this->userSession->getUser();
+ if ($actor instanceof IUser) {
+ $event->setAuthor($actor->getUID())
+ ->setSubject(GroupProvider::ADDED_TO_GROUP, [
+ 'user' => $user->getUID(),
+ 'group' => $group->getGID(),
+ 'actor' => $actor->getUID(),
+ ]);
+ } else {
+ $event->setSubject(GroupProvider::ADDED_TO_GROUP, [
+ 'user' => $user->getUID(),
+ 'group' => $group->getGID(),
+ ]);
+ }
+
+ foreach ($usersToNotify as $userToNotify) {
+ $event->setAffectedUser($userToNotify->getUID());
+ $this->activityManager->publish($event);
+ }
+ }
+
+ /**
+ * @param IGroup $group
+ * @param IUser $user
+ * @throws \InvalidArgumentException
+ * @throws \BadMethodCallException
+ */
+ public function removeUserFromGroup(IGroup $group, IUser $user): void {
+ $subAdminManager = $this->groupManager->getSubAdmin();
+ $usersToNotify = $subAdminManager->getGroupsSubAdmins($group);
+ $usersToNotify[] = $user;
+
+
+ $event = $this->activityManager->generateEvent();
+ $event->setApp('settings')
+ ->setType('group_settings');
+
+ $actor = $this->userSession->getUser();
+ if ($actor instanceof IUser) {
+ $event->setAuthor($actor->getUID())
+ ->setSubject(GroupProvider::REMOVED_FROM_GROUP, [
+ 'user' => $user->getUID(),
+ 'group' => $group->getGID(),
+ 'actor' => $actor->getUID(),
+ ]);
+ } else {
+ $event->setSubject(GroupProvider::REMOVED_FROM_GROUP, [
+ 'user' => $user->getUID(),
+ 'group' => $group->getGID(),
+ ]);
+ }
+
+ foreach ($usersToNotify as $userToNotify) {
+ $event->setAffectedUser($userToNotify->getUID());
+ $this->activityManager->publish($event);
+ }
+ }
+}
diff --git a/apps/settings/lib/Mailer/NewUserMailHelper.php b/apps/settings/lib/Mailer/NewUserMailHelper.php
new file mode 100644
index 00000000000..a927080df92
--- /dev/null
+++ b/apps/settings/lib/Mailer/NewUserMailHelper.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Leon Klingele <leon@struktur.de>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @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\Mailer;
+
+use OCP\L10N\IFactory;
+use OCP\Mail\IEMailTemplate;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Defaults;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\Mail\IMailer;
+use OCP\Security\ICrypto;
+use OCP\Security\ISecureRandom;
+
+class NewUserMailHelper {
+ /** @var Defaults */
+ private $themingDefaults;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var IFactory */
+ private $l10nFactory;
+ /** @var IMailer */
+ private $mailer;
+ /** @var ISecureRandom */
+ private $secureRandom;
+ /** @var ITimeFactory */
+ private $timeFactory;
+ /** @var IConfig */
+ private $config;
+ /** @var ICrypto */
+ private $crypto;
+ /** @var string */
+ private $fromAddress;
+
+ /**
+ * @param Defaults $themingDefaults
+ * @param IURLGenerator $urlGenerator
+ * @param IFactory $l10nFactory
+ * @param IMailer $mailer
+ * @param ISecureRandom $secureRandom
+ * @param ITimeFactory $timeFactory
+ * @param IConfig $config
+ * @param ICrypto $crypto
+ * @param string $fromAddress
+ */
+ public function __construct(Defaults $themingDefaults,
+ IURLGenerator $urlGenerator,
+ IFactory $l10nFactory,
+ IMailer $mailer,
+ ISecureRandom $secureRandom,
+ ITimeFactory $timeFactory,
+ IConfig $config,
+ ICrypto $crypto,
+ $fromAddress) {
+ $this->themingDefaults = $themingDefaults;
+ $this->urlGenerator = $urlGenerator;
+ $this->l10nFactory = $l10nFactory;
+ $this->mailer = $mailer;
+ $this->secureRandom = $secureRandom;
+ $this->timeFactory = $timeFactory;
+ $this->config = $config;
+ $this->crypto = $crypto;
+ $this->fromAddress = $fromAddress;
+ }
+
+ /**
+ * @param IUser $user
+ * @param bool $generatePasswordResetToken
+ * @return IEMailTemplate
+ */
+ public function generateTemplate(IUser $user, $generatePasswordResetToken = false) {
+ $userId = $user->getUID();
+ $lang = $this->config->getUserValue($userId, 'core', 'lang', 'en');
+ if (!$this->l10nFactory->languageExists('settings', $lang)) {
+ $lang = 'en';
+ }
+
+ $l10n = $this->l10nFactory->get('settings', $lang);
+
+ if ($generatePasswordResetToken) {
+ $token = $this->secureRandom->generate(
+ 21,
+ ISecureRandom::CHAR_DIGITS .
+ ISecureRandom::CHAR_LOWER .
+ ISecureRandom::CHAR_UPPER
+ );
+ $tokenValue = $this->timeFactory->getTime() . ':' . $token;
+ $mailAddress = (null !== $user->getEMailAddress()) ? $user->getEMailAddress() : '';
+ $encryptedValue = $this->crypto->encrypt($tokenValue, $mailAddress . $this->config->getSystemValue('secret'));
+ $this->config->setUserValue($user->getUID(), 'core', 'lostpassword', $encryptedValue);
+ $link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
+ } else {
+ $link = $this->urlGenerator->getAbsoluteURL('/');
+ }
+ $displayName = $user->getDisplayName();
+
+ $emailTemplate = $this->mailer->createEMailTemplate('settings.Welcome', [
+ 'link' => $link,
+ 'displayname' => $displayName,
+ 'userid' => $userId,
+ 'instancename' => $this->themingDefaults->getName(),
+ 'resetTokenGenerated' => $generatePasswordResetToken,
+ ]);
+
+ $emailTemplate->setSubject($l10n->t('Your %s account was created', [$this->themingDefaults->getName()]));
+ $emailTemplate->addHeader();
+ if ($displayName === $userId) {
+ $emailTemplate->addHeading($l10n->t('Welcome aboard'));
+ } else {
+ $emailTemplate->addHeading($l10n->t('Welcome aboard %s', [$displayName]));
+ }
+ $emailTemplate->addBodyText($l10n->t('Welcome to your %s account, you can add, protect, and share your data.', [$this->themingDefaults->getName()]));
+ if($user->getBackendClassName() !== 'LDAP') {
+ $emailTemplate->addBodyText($l10n->t('Your username is: %s', [$userId]));
+ }
+ if ($generatePasswordResetToken) {
+ $leftButtonText = $l10n->t('Set your password');
+ } else {
+ $leftButtonText = $l10n->t('Go to %s', [$this->themingDefaults->getName()]);
+ }
+ $emailTemplate->addBodyButtonGroup(
+ $leftButtonText,
+ $link,
+ $l10n->t('Install Client'),
+ $this->config->getSystemValue('customclient_desktop', 'https://nextcloud.com/install/#install-clients')
+ );
+ $emailTemplate->addFooter();
+
+ return $emailTemplate;
+ }
+
+ /**
+ * Sends a welcome mail to $user
+ *
+ * @param IUser $user
+ * @param IEmailTemplate $emailTemplate
+ * @throws \Exception If mail could not be sent
+ */
+ public function sendMail(IUser $user,
+ IEMailTemplate $emailTemplate) {
+ $message = $this->mailer->createMessage();
+ $message->setTo([$user->getEMailAddress() => $user->getDisplayName()]);
+ $message->setFrom([$this->fromAddress => $this->themingDefaults->getName()]);
+ $message->useTemplate($emailTemplate);
+ $this->mailer->send($message);
+ }
+}
diff --git a/apps/settings/lib/Middleware/SubadminMiddleware.php b/apps/settings/lib/Middleware/SubadminMiddleware.php
new file mode 100644
index 00000000000..c6f77ac04fe
--- /dev/null
+++ b/apps/settings/lib/Middleware/SubadminMiddleware.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @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\Middleware;
+
+use OC\AppFramework\Http;
+use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
+use OC\AppFramework\Utility\ControllerMethodReflector;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Middleware;
+use OCP\IL10N;
+
+/**
+ * Verifies whether an user has at least subadmin rights.
+ * To bypass use the `@NoSubadminRequired` annotation
+ */
+class SubadminMiddleware extends Middleware {
+ /** @var bool */
+ protected $isSubAdmin;
+ /** @var ControllerMethodReflector */
+ protected $reflector;
+ /** @var IL10N */
+ private $l10n;
+
+ /**
+ * @param ControllerMethodReflector $reflector
+ * @param bool $isSubAdmin
+ * @param IL10N $l10n
+ */
+ public function __construct(ControllerMethodReflector $reflector,
+ $isSubAdmin,
+ IL10N $l10n) {
+ $this->reflector = $reflector;
+ $this->isSubAdmin = $isSubAdmin;
+ $this->l10n = $l10n;
+ }
+
+ /**
+ * Check if sharing is enabled before the controllers is executed
+ * @param Controller $controller
+ * @param string $methodName
+ * @throws \Exception
+ */
+ public function beforeController($controller, $methodName) {
+ if(!$this->reflector->hasAnnotation('NoSubadminRequired')) {
+ if(!$this->isSubAdmin) {
+ throw new NotAdminException($this->l10n->t('Logged in user must be a subadmin'));
+ }
+ }
+ }
+
+ /**
+ * Return 403 page in case of an exception
+ * @param Controller $controller
+ * @param string $methodName
+ * @param \Exception $exception
+ * @return TemplateResponse
+ * @throws \Exception
+ */
+ public function afterException($controller, $methodName, \Exception $exception) {
+ if($exception instanceof NotAdminException) {
+ $response = new TemplateResponse('core', '403', array(), 'guest');
+ $response->setStatus(Http::STATUS_FORBIDDEN);
+ return $response;
+ }
+
+ throw $exception;
+ }
+
+}
diff --git a/apps/settings/lib/Settings/Admin/Mail.php b/apps/settings/lib/Settings/Admin/Mail.php
new file mode 100644
index 00000000000..a630bd07d27
--- /dev/null
+++ b/apps/settings/lib/Settings/Admin/Mail.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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\Admin;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IConfig;
+use OCP\Settings\ISettings;
+
+class Mail implements ISettings {
+ /** @var IConfig */
+ private $config;
+
+ /**
+ * @param IConfig $config
+ */
+ public function __construct(IConfig $config) {
+ $this->config = $config;
+ }
+
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ $parameters = [
+ // Mail
+ 'sendmail_is_available' => (bool) \OC_Helper::findBinaryPath('sendmail'),
+ 'mail_domain' => $this->config->getSystemValue('mail_domain', ''),
+ 'mail_from_address' => $this->config->getSystemValue('mail_from_address', ''),
+ 'mail_smtpmode' => $this->config->getSystemValue('mail_smtpmode', ''),
+ 'mail_smtpsecure' => $this->config->getSystemValue('mail_smtpsecure', ''),
+ 'mail_smtphost' => $this->config->getSystemValue('mail_smtphost', ''),
+ 'mail_smtpport' => $this->config->getSystemValue('mail_smtpport', ''),
+ 'mail_smtpauthtype' => $this->config->getSystemValue('mail_smtpauthtype', ''),
+ 'mail_smtpauth' => $this->config->getSystemValue('mail_smtpauth', false),
+ 'mail_smtpname' => $this->config->getSystemValue('mail_smtpname', ''),
+ 'mail_smtppassword' => $this->config->getSystemValue('mail_smtppassword', ''),
+ 'mail_sendmailmode' => $this->config->getSystemValue('mail_sendmailmode', 'smtp'),
+ ];
+
+ if ($parameters['mail_smtppassword'] !== '') {
+ $parameters['mail_smtppassword'] = '********';
+ }
+
+ if ($parameters['mail_smtpmode'] === '' || $parameters['mail_smtpmode'] === 'php') {
+ $parameters['mail_smtpmode'] = 'smtp';
+ }
+
+ return new TemplateResponse('settings', 'settings/admin/additional-mail', $parameters, '');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ */
+ public function getSection() {
+ return 'server';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ */
+ public function getPriority() {
+ return 10;
+ }
+}
diff --git a/apps/settings/lib/Settings/Admin/Overview.php b/apps/settings/lib/Settings/Admin/Overview.php
new file mode 100644
index 00000000000..52847d8037c
--- /dev/null
+++ b/apps/settings/lib/Settings/Admin/Overview.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @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\Admin;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IConfig;
+use OCP\Settings\ISettings;
+
+class Overview implements ISettings {
+ /** @var IConfig */
+ private $config;
+
+ public function __construct(IConfig $config) {
+ $this->config = $config;
+ }
+
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ $parameters = [
+ 'checkForWorkingWellKnownSetup' => $this->config->getSystemValue('check_for_working_wellknown_setup', true),
+ ];
+
+ return new TemplateResponse('settings', 'settings/admin/overview', $parameters, '');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ */
+ public function getSection() {
+ return 'overview';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ */
+ public function getPriority() {
+ return 10;
+ }
+}
diff --git a/apps/settings/lib/Settings/Admin/Security.php b/apps/settings/lib/Settings/Admin/Security.php
new file mode 100644
index 00000000000..b78f6a88dff
--- /dev/null
+++ b/apps/settings/lib/Settings/Admin/Security.php
@@ -0,0 +1,109 @@
+<?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\Admin;
+
+use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Encryption\IManager;
+use OCP\IInitialStateService;
+use OCP\IUserManager;
+use OCP\Settings\ISettings;
+
+class Security implements ISettings {
+
+ /** @var IManager */
+ private $manager;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var MandatoryTwoFactor */
+ private $mandatoryTwoFactor;
+
+ /** @var IInitialStateService */
+ private $initialState;
+
+ public function __construct(IManager $manager,
+ IUserManager $userManager,
+ MandatoryTwoFactor $mandatoryTwoFactor,
+ IInitialStateService $initialState) {
+ $this->manager = $manager;
+ $this->userManager = $userManager;
+ $this->mandatoryTwoFactor = $mandatoryTwoFactor;
+ $this->initialState = $initialState;
+ }
+
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ $encryptionModules = $this->manager->getEncryptionModules();
+ $defaultEncryptionModuleId = $this->manager->getDefaultEncryptionModuleId();
+ $encryptionModuleList = [];
+ foreach ($encryptionModules as $module) {
+ $encryptionModuleList[$module['id']]['displayName'] = $module['displayName'];
+ $encryptionModuleList[$module['id']]['default'] = false;
+ if ($module['id'] === $defaultEncryptionModuleId) {
+ $encryptionModuleList[$module['id']]['default'] = true;
+ }
+ }
+
+ $this->initialState->provideInitialState(
+ 'settings',
+ 'mandatory2FAState',
+ $this->mandatoryTwoFactor->getState()
+ );
+
+ $parameters = [
+ // Encryption API
+ 'encryptionEnabled' => $this->manager->isEnabled(),
+ 'encryptionReady' => $this->manager->isReady(),
+ 'externalBackendsEnabled' => count($this->userManager->getBackends()) > 1,
+ // Modules
+ 'encryptionModules' => $encryptionModuleList,
+ ];
+
+ return new TemplateResponse('settings', 'settings/admin/security', $parameters, '');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ */
+ public function getSection() {
+ return 'security';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ */
+ public function getPriority() {
+ return 10;
+ }
+}
diff --git a/apps/settings/lib/Settings/Admin/Server.php b/apps/settings/lib/Settings/Admin/Server.php
new file mode 100644
index 00000000000..956d246479f
--- /dev/null
+++ b/apps/settings/lib/Settings/Admin/Server.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @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\Admin;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IConfig;
+use OCP\Settings\ISettings;
+
+class Server implements ISettings {
+ /** @var IConfig */
+ private $config;
+
+ /**
+ * @param IConfig $config
+ */
+ public function __construct(IConfig $config) {
+ $this->config = $config;
+ }
+
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ $parameters = [
+ // Background jobs
+ 'backgroundjobs_mode' => $this->config->getAppValue('core', 'backgroundjobs_mode', 'ajax'),
+ 'lastcron' => $this->config->getAppValue('core', 'lastcron', false),
+ 'cronErrors' => $this->config->getAppValue('core', 'cronErrors'),
+ 'cli_based_cron_possible' => function_exists('posix_getpwuid'),
+ 'cli_based_cron_user' => function_exists('posix_getpwuid') ? posix_getpwuid(fileowner(\OC::$configDir . 'config.php'))['name'] : '',
+ ];
+
+ return new TemplateResponse('settings', 'settings/admin/server', $parameters, '');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ */
+ public function getSection(): string {
+ return 'server';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ */
+ public function getPriority(): int {
+ return 0;
+ }
+}
diff --git a/apps/settings/lib/Settings/Admin/Sharing.php b/apps/settings/lib/Settings/Admin/Sharing.php
new file mode 100644
index 00000000000..18b1f26d9a2
--- /dev/null
+++ b/apps/settings/lib/Settings/Admin/Sharing.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Bjoern Schiessle <bjoern@schiessle.org>
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @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\Admin;
+
+use OC\Share\Share;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Constants;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\L10N\IFactory;
+use OCP\Settings\ISettings;
+use OCP\Share\IManager;
+use OCP\Util;
+
+class Sharing implements ISettings {
+ /** @var IConfig */
+ private $config;
+
+ /** @var IL10N */
+ private $l;
+
+ /** @var IManager */
+ private $shareManager;
+
+ /**
+ * @param IConfig $config
+ */
+ public function __construct(IConfig $config, IFactory $l, IManager $shareManager) {
+ $this->config = $config;
+ $this->l = $l->get('lib');
+ $this->shareManager = $shareManager;
+ }
+
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
+ $excludeGroupsList = !is_null(json_decode($excludedGroups))
+ ? implode('|', json_decode($excludedGroups, true)) : '';
+
+ $parameters = [
+ // Built-In Sharing
+ 'allowGroupSharing' => $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes'),
+ 'allowLinks' => $this->config->getAppValue('core', 'shareapi_allow_links', 'yes'),
+ 'allowPublicUpload' => $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes'),
+ 'allowResharing' => $this->config->getAppValue('core', 'shareapi_allow_resharing', 'yes'),
+ 'allowShareDialogUserEnumeration' => $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes'),
+ 'enforceLinkPassword' => Util::isPublicLinkPasswordRequired(),
+ 'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(),
+ 'shareAPIEnabled' => $this->config->getAppValue('core', 'shareapi_enabled', 'yes'),
+ 'shareDefaultExpireDateSet' => $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no'),
+ 'shareExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'),
+ 'shareEnforceExpireDate' => $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no'),
+ 'shareExcludeGroups' => $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes',
+ 'shareExcludedGroupsList' => $excludeGroupsList,
+ 'publicShareDisclaimerText' => $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null),
+ 'enableLinkPasswordByDefault' => $this->config->getAppValue('core', 'shareapi_enable_link_password_by_default', 'no'),
+ 'shareApiDefaultPermissions' => $this->config->getAppValue('core', 'shareapi_default_permissions', Constants::PERMISSION_ALL),
+ 'shareApiDefaultPermissionsCheckboxes' => $this->getSharePermissionList(),
+ ];
+
+ return new TemplateResponse('settings', 'settings/admin/sharing', $parameters, '');
+ }
+
+ /**
+ * get share permission list for template
+ *
+ * @return array
+ */
+ private function getSharePermissionList() {
+ return [
+ [
+ 'id' => 'cancreate',
+ 'label' => $this->l->t('Create'),
+ 'value' => Constants::PERMISSION_CREATE
+ ],
+ [
+ 'id' => 'canupdate',
+ 'label' => $this->l->t('Change'),
+ 'value' => Constants::PERMISSION_UPDATE
+ ],
+ [
+ 'id' => 'candelete',
+ 'label' => $this->l->t('Delete'),
+ 'value' => Constants::PERMISSION_DELETE
+ ],
+ [
+ 'id' => 'canshare',
+ 'label' => $this->l->t('Share'),
+ 'value' => Constants::PERMISSION_SHARE
+ ],
+ ];
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ */
+ public function getSection() {
+ return 'sharing';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ */
+ public function getPriority() {
+ return 0;
+ }
+}
diff --git a/apps/settings/lib/Settings/Personal/Additional.php b/apps/settings/lib/Settings/Personal/Additional.php
new file mode 100644
index 00000000000..9fea58301cf
--- /dev/null
+++ b/apps/settings/lib/Settings/Personal/Additional.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @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\Personal;
+
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Settings\ISettings;
+
+class Additional implements ISettings {
+
+ /**
+ * @return TemplateResponse returns the instance with all parameters set, ready to be rendered
+ * @since 9.1
+ */
+ public function getForm() {
+ return new TemplateResponse('settings', 'settings/empty');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ * @since 9.1
+ */
+ public function getSection() {
+ return 'additional';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ * @since 9.1
+ */
+ public function getPriority() {
+ return '5';
+ }
+}
diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php
new file mode 100644
index 00000000000..b0c4d6faf48
--- /dev/null
+++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php
@@ -0,0 +1,280 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Citharel <tcit@tcit.fr>
+ *
+ * @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\Personal;
+
+use OC\Accounts\AccountManager;
+use OCA\FederatedFileSharing\AppInfo\Application;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Files\FileInfo;
+use OCP\IConfig;
+use OCP\IGroup;
+use OCP\IGroupManager;
+use OCP\IL10N;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
+use OCP\Settings\ISettings;
+
+class PersonalInfo implements ISettings {
+
+ /** @var IConfig */
+ private $config;
+ /** @var IUserManager */
+ private $userManager;
+ /** @var AccountManager */
+ private $accountManager;
+ /** @var IGroupManager */
+ private $groupManager;
+ /** @var IAppManager */
+ private $appManager;
+ /** @var IFactory */
+ private $l10nFactory;
+ /** @var IL10N */
+ private $l;
+
+ /**
+ * @param IConfig $config
+ * @param IUserManager $userManager
+ * @param IGroupManager $groupManager
+ * @param AccountManager $accountManager
+ * @param IFactory $l10nFactory
+ * @param IL10N $l
+ */
+ public function __construct(
+ IConfig $config,
+ IUserManager $userManager,
+ IGroupManager $groupManager,
+ AccountManager $accountManager,
+ IAppManager $appManager,
+ IFactory $l10nFactory,
+ IL10N $l
+ ) {
+ $this->config = $config;
+ $this->userManager = $userManager;
+ $this->accountManager = $accountManager;
+ $this->groupManager = $groupManager;
+ $this->appManager = $appManager;
+ $this->l10nFactory = $l10nFactory;
+ $this->l = $l;
+ }
+
+ /**
+ * @return TemplateResponse returns the instance with all parameters set, ready to be rendered
+ * @since 9.1
+ */
+ public function getForm() {
+ $federatedFileSharingEnabled = $this->appManager->isEnabledForUser('federatedfilesharing');
+ $lookupServerUploadEnabled = false;
+ if($federatedFileSharingEnabled) {
+ $federatedFileSharing = new Application();
+ $shareProvider = $federatedFileSharing->getFederatedShareProvider();
+ $lookupServerUploadEnabled = $shareProvider->isLookupServerUploadEnabled();
+ }
+
+ $uid = \OC_User::getUser();
+ $user = $this->userManager->get($uid);
+ $userData = $this->accountManager->getUser($user);
+
+ $storageInfo = \OC_Helper::getStorageInfo('/');
+ if ($storageInfo['quota'] === FileInfo::SPACE_UNLIMITED) {
+ $totalSpace = $this->l->t('Unlimited');
+ } else {
+ $totalSpace = \OC_Helper::humanFileSize($storageInfo['total']);
+ }
+
+ $languageParameters = $this->getLanguages($user);
+ $localeParameters = $this->getLocales($user);
+ $messageParameters = $this->getMessageParameters($userData);
+
+ $parameters = [
+ 'total_space' => $totalSpace,
+ 'usage' => \OC_Helper::humanFileSize($storageInfo['used']),
+ 'usage_relative' => round($storageInfo['relative']),
+ 'quota' => $storageInfo['quota'],
+ 'avatarChangeSupported' => $user->canChangeAvatar(),
+ 'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
+ 'avatarScope' => $userData[AccountManager::PROPERTY_AVATAR]['scope'],
+ 'displayNameChangeSupported' => $user->canChangeDisplayName(),
+ 'displayName' => $userData[AccountManager::PROPERTY_DISPLAYNAME]['value'],
+ 'displayNameScope' => $userData[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
+ 'email' => $userData[AccountManager::PROPERTY_EMAIL]['value'],
+ 'emailScope' => $userData[AccountManager::PROPERTY_EMAIL]['scope'],
+ 'emailVerification' => $userData[AccountManager::PROPERTY_EMAIL]['verified'],
+ 'phone' => $userData[AccountManager::PROPERTY_PHONE]['value'],
+ 'phoneScope' => $userData[AccountManager::PROPERTY_PHONE]['scope'],
+ 'address' => $userData[AccountManager::PROPERTY_ADDRESS]['value'],
+ 'addressScope' => $userData[AccountManager::PROPERTY_ADDRESS]['scope'],
+ 'website' => $userData[AccountManager::PROPERTY_WEBSITE]['value'],
+ 'websiteScope' => $userData[AccountManager::PROPERTY_WEBSITE]['scope'],
+ 'websiteVerification' => $userData[AccountManager::PROPERTY_WEBSITE]['verified'],
+ 'twitter' => $userData[AccountManager::PROPERTY_TWITTER]['value'],
+ 'twitterScope' => $userData[AccountManager::PROPERTY_TWITTER]['scope'],
+ 'twitterVerification' => $userData[AccountManager::PROPERTY_TWITTER]['verified'],
+ 'groups' => $this->getGroups($user),
+ ] + $messageParameters + $languageParameters + $localeParameters;
+
+
+ return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, '');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ * @since 9.1
+ */
+ public function getSection() {
+ return 'personal-info';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ * @since 9.1
+ */
+ public function getPriority() {
+ return 10;
+ }
+
+ /**
+ * returns a sorted list of the user's group GIDs
+ *
+ * @param IUser $user
+ * @return array
+ */
+ private function getGroups(IUser $user) {
+ $groups = array_map(
+ function(IGroup $group) {
+ return $group->getDisplayName();
+ },
+ $this->groupManager->getUserGroups($user)
+ );
+ sort($groups);
+
+ return $groups;
+ }
+
+ /**
+ * returns the user language, common language and other languages in an
+ * associative array
+ *
+ * @param IUser $user
+ * @return array
+ */
+ private function getLanguages(IUser $user) {
+ $forceLanguage = $this->config->getSystemValue('force_language', false);
+ if($forceLanguage !== false) {
+ return [];
+ }
+
+ $uid = $user->getUID();
+
+ $userConfLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage());
+ $languages = $this->l10nFactory->getLanguages();
+
+ // associate the user language with the proper array
+ $userLangIndex = array_search($userConfLang, array_column($languages['commonlanguages'], 'code'));
+ $userLang = $languages['commonlanguages'][$userLangIndex];
+ // search in the other languages
+ if ($userLangIndex === false) {
+ $userLangIndex = array_search($userConfLang, array_column($languages['languages'], 'code'));
+ $userLang = $languages['languages'][$userLangIndex];
+ }
+ // if user language is not available but set somehow: show the actual code as name
+ if (!is_array($userLang)) {
+ $userLang = [
+ 'code' => $userConfLang,
+ 'name' => $userConfLang,
+ ];
+ }
+
+ return array_merge(
+ array('activelanguage' => $userLang),
+ $languages
+ );
+ }
+
+ private function getLocales(IUser $user) {
+ $forceLanguage = $this->config->getSystemValue('force_locale', false);
+ if($forceLanguage !== false) {
+ return [];
+ }
+
+ $uid = $user->getUID();
+
+ $userLocaleString = $this->config->getUserValue($uid, 'core', 'locale', $this->l10nFactory->findLocale());
+
+ $userLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage());
+
+ $localeCodes = $this->l10nFactory->findAvailableLocales();
+
+ $userLocale = array_filter($localeCodes, function($value) use ($userLocaleString) {
+ return $userLocaleString === $value['code'];
+ });
+
+ if (!empty($userLocale))
+ {
+ $userLocale = reset($userLocale);
+ }
+
+ $localesForLanguage = array_filter($localeCodes, function($localeCode) use ($userLang) {
+ return 0 === strpos($localeCode['code'], $userLang);
+ });
+
+ return [
+ 'activelocaleLang' => $userLocaleString,
+ 'activelocale' => $userLocale,
+ 'locales' => $localeCodes,
+ 'localesForLanguage' => $localesForLanguage,
+ ];
+ }
+
+ /**
+ * @param array $userData
+ * @return array
+ */
+ private function getMessageParameters(array $userData) {
+ $needVerifyMessage = [AccountManager::PROPERTY_EMAIL, AccountManager::PROPERTY_WEBSITE, AccountManager::PROPERTY_TWITTER];
+ $messageParameters = [];
+ foreach ($needVerifyMessage as $property) {
+ switch ($userData[$property]['verified']) {
+ case AccountManager::VERIFIED:
+ $message = $this->l->t('Verifying');
+ break;
+ case AccountManager::VERIFICATION_IN_PROGRESS:
+ $message = $this->l->t('Verifying …');
+ break;
+ default:
+ $message = $this->l->t('Verify');
+ }
+ $messageParameters[$property . 'Message'] = $message;
+ }
+ return $messageParameters;
+ }
+
+}
diff --git a/apps/settings/lib/Settings/Personal/Security.php b/apps/settings/lib/Settings/Personal/Security.php
new file mode 100644
index 00000000000..c4c4cd0375c
--- /dev/null
+++ b/apps/settings/lib/Settings/Personal/Security.php
@@ -0,0 +1,128 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @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\Personal;
+
+
+use function array_filter;
+use function array_map;
+use function is_null;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Token\INamedToken;
+use OC\Authentication\Token\IProvider as IAuthTokenProvider;
+use OC\Authentication\Token\IToken;
+use OC\Authentication\TwoFactorAuth\Manager as TwoFactorManager;
+use OC\Authentication\TwoFactorAuth\ProviderLoader;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Authentication\TwoFactorAuth\IProvider;
+use OCP\Authentication\TwoFactorAuth\IProvidesPersonalSettings;
+use OCP\IInitialStateService;
+use OCP\ISession;
+use OCP\IUserManager;
+use OCP\IUserSession;
+use OCP\Session\Exceptions\SessionNotAvailableException;
+use OCP\Settings\ISettings;
+use OCP\IConfig;
+
+class Security implements ISettings {
+
+ /** @var IInitialStateService */
+ private $initialStateService;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var ProviderLoader */
+ private $providerLoader;
+
+ /** @var IUserSession */
+ private $userSession;
+
+ /** @var string|null */
+ private $uid;
+
+ /** @var IConfig */
+ private $config;
+
+ public function __construct(IInitialStateService $initialStateService,
+ IUserManager $userManager,
+ ProviderLoader $providerLoader,
+ IUserSession $userSession,
+ IConfig $config,
+ ?string $UserId) {
+ $this->initialStateService = $initialStateService;
+ $this->userManager = $userManager;
+ $this->providerLoader = $providerLoader;
+ $this->userSession = $userSession;
+ $this->uid = $UserId;
+ $this->config = $config;
+ }
+
+ public function getForm(): TemplateResponse {
+ $user = $this->userManager->get($this->uid);
+ $passwordChangeSupported = false;
+ if ($user !== null) {
+ $passwordChangeSupported = $user->canChangePassword();
+ }
+
+ $this->initialStateService->provideInitialState(
+ 'settings',
+ 'can_create_app_token',
+ $this->userSession->getImpersonatingUserID() === null
+ );
+
+ return new TemplateResponse('settings', 'settings/personal/security', [
+ 'passwordChangeSupported' => $passwordChangeSupported,
+ 'twoFactorProviderData' => $this->getTwoFactorProviderData(),
+ 'themedark' => $this->config->getUserValue($this->uid, 'accessibility', 'theme', false)
+ ]);
+
+ }
+
+ public function getSection(): string {
+ return 'security';
+ }
+
+ public function getPriority(): int {
+ return 10;
+ }
+
+ private function getTwoFactorProviderData(): array {
+ $user = $this->userSession->getUser();
+ if (is_null($user)) {
+ // Actually impossible, but still …
+ return [];
+ }
+
+ return [
+ 'providers' => array_map(function (IProvidesPersonalSettings $provider) use ($user) {
+ return [
+ 'provider' => $provider,
+ 'settings' => $provider->getPersonalSettings($user)
+ ];
+ }, array_filter($this->providerLoader->getProviders($user), function (IProvider $provider) {
+ return $provider instanceof IProvidesPersonalSettings;
+ }))
+ ];
+ }
+}
diff --git a/apps/settings/lib/Settings/Personal/Security/Authtokens.php b/apps/settings/lib/Settings/Personal/Security/Authtokens.php
new file mode 100644
index 00000000000..a0c76e88de0
--- /dev/null
+++ b/apps/settings/lib/Settings/Personal/Security/Authtokens.php
@@ -0,0 +1,107 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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\Personal\Security;
+
+use function array_map;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Token\INamedToken;
+use OC\Authentication\Token\IProvider as IAuthTokenProvider;
+use OC\Authentication\Token\IToken;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IInitialStateService;
+use OCP\ISession;
+use OCP\Session\Exceptions\SessionNotAvailableException;
+use OCP\Settings\ISettings;
+
+class Authtokens implements ISettings {
+
+ /** @var IAuthTokenProvider */
+ private $tokenProvider;
+
+ /** @var ISession */
+ private $session;
+
+ /** @var IInitialStateService */
+ private $initialStateService;
+
+ /** @var string|null */
+ private $uid;
+
+ public function __construct(IAuthTokenProvider $tokenProvider,
+ ISession $session,
+ IInitialStateService $initialStateService,
+ ?string $UserId) {
+ $this->tokenProvider = $tokenProvider;
+ $this->session = $session;
+ $this->initialStateService = $initialStateService;
+ $this->uid = $UserId;
+ }
+
+ public function getForm(): TemplateResponse {
+ $this->initialStateService->provideInitialState(
+ 'settings',
+ 'app_tokens',
+ $this->getAppTokens()
+ );
+
+ return new TemplateResponse('settings', 'settings/personal/security/authtokens');
+ }
+
+ public function getSection(): string {
+ return 'security';
+ }
+
+ public function getPriority(): int {
+ return 100;
+ }
+
+ private function getAppTokens(): array {
+ $tokens = $this->tokenProvider->getTokenByUser($this->uid);
+
+ try {
+ $sessionId = $this->session->getId();
+ } catch (SessionNotAvailableException $ex) {
+ return [];
+ }
+ try {
+ $sessionToken = $this->tokenProvider->getToken($sessionId);
+ } catch (InvalidTokenException $ex) {
+ return [];
+ }
+
+ return array_map(function (IToken $token) use ($sessionToken) {
+ $data = $token->jsonSerialize();
+ $data['canDelete'] = true;
+ $data['canRename'] = $token instanceof INamedToken;
+ if ($sessionToken->getId() === $token->getId()) {
+ $data['canDelete'] = false;
+ $data['canRename'] = false;
+ $data['current'] = true;
+ }
+ return $data;
+ }, $tokens);
+ }
+
+}
diff --git a/apps/settings/lib/Settings/Personal/ServerDevNotice.php b/apps/settings/lib/Settings/Personal/ServerDevNotice.php
new file mode 100644
index 00000000000..800bec992a2
--- /dev/null
+++ b/apps/settings/lib/Settings/Personal/ServerDevNotice.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.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\Personal;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Settings\ISettings;
+
+class ServerDevNotice implements ISettings {
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ return new TemplateResponse('settings', 'settings/personal/development.notice');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ */
+ public function getSection() {
+ return 'personal-info';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ */
+ public function getPriority() {
+ return 1000;
+ }
+}