diff options
Diffstat (limited to 'apps/settings/lib')
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; + } +} |