migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
*
* @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\DataResponse;
use OCP\IConfig;
use OCP\IRequest;
class AISettingsController extends Controller {
/**
* @param string $appName
* @param IRequest $request
* @param IConfig $config
*/
public function __construct(
$appName,
IRequest $request,
private IConfig $config,
) {
parent::__construct($appName, $request);
}
/**
* Sets the email settings
*
* @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\ArtificialIntelligence)
*
* @param array $settings
* @return DataResponse
*/
public function update($settings) {
$keys = ['ai.stt_provider', 'ai.textprocessing_provider_preferences', 'ai.translation_provider_preferences', 'ai.text2image_provider'];
foreach ($keys as $key) {
if (!isset($settings[$key])) {
continue;
}
$this->config->setAppValue('core', $key, json_encode($settings[$key]));
}
return new DataResponse();
}
}
@@ -0,0 +1,123 @@
<?php
/**
* @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Robin Appelman <robin@icewind.nl>
* @author Kate Döen <kate.doeen@nextcloud.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\Controller;
use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
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;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
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;
}
/**
* @NoCSRFRequired
* @NoAdminRequired
* @NoSubAdminRequired
* We are checking the permissions in the getSettings method. If there is no allowed
* settings for the given section. The user will be gretted by an error message.
*/
public function index(string $section): TemplateResponse {
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->getAllowedAdminSettings($section, $user);
if (empty($settings)) {
throw new NotAdminException("Logged in user doesn't have permission to access these settings.");
}
$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 [
'anchor' => $anchor,
'section-name' => $sectionName,
'form' => $form
];
}
return [
'form' => $form
];
}, $forms);
$out = new Template('settings', 'settings/additional');
$out->assign('forms', $forms);
return $out->fetchPage();
}
}
@@ -0,0 +1,565 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.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>
* @author Kate Döen <kate.doeen@nextcloud.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\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\Attribute\OpenAPI;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\L10N\IFactory;
use Psr\Log\LoggerInterface;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
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 LoggerInterface */
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 LoggerInterface $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,
LoggerInterface $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 {
$params = [];
$params['appstoreEnabled'] = $this->config->getSystemValueBool('appstoreenabled', 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, 'pageTitle' => $this->l10n->t('Apps')]);
$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' => $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('');
$supportedApps = $appClass->getSupportedApps();
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']]);
}
if (in_array($app['id'], $supportedApps)) {
$this->allApps[$app['id']]['level'] = \OC_App::supportedApp;
}
}
// 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['bundleIds'][] = $bundle->getIdentifier();
continue;
}
}
}
}
}
private function getAllApps() {
return $this->allApps;
}
/**
* Get all available apps in a category
*
* @return JSONResponse
* @throws \Exception
*/
public function listApps(): JSONResponse {
$this->fetchApps();
$apps = $this->getAllApps();
$dependencyAnalyzer = new DependencyAnalyzer(new Platform($this->config), $this->l10n);
$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
if (!is_array($ignoreMaxApps)) {
$this->logger->warning('The value given for app_install_overwrite is not an array. Ignoring...');
$ignoreMaxApps = [];
}
// Extend existing app details
$apps = array_map(function (array $appData) use ($dependencyAnalyzer, $ignoreMaxApps) {
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'];
$appData['releases'] = $appstoreData['releases'];
}
$newVersion = $this->installer->isUpdateAvailable($appData['id']);
if ($newVersion) {
$appData['update'] = $newVersion;
}
// fix groups to be an array
$groups = [];
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'];
}
$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 {
$currentVersion = $app['releases'][0]['version'];
}
$formattedApps[] = [
'id' => $app['id'],
'name' => $app['translations'][$currentLanguage]['name'] ?? $app['translations']['en']['name'],
'description' => $app['translations'][$currentLanguage]['description'] ?? $app['translations']['en']['description'],
'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->error('could not enable apps', ['exception' => $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->error('could not disable app', ['exception' => $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('Could not 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('Could not 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);
$this->appManager->ignoreNextcloudRequirementForApp($appId);
return new JSONResponse();
}
}
@@ -0,0 +1,329 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Fabrizio Steiner <fabrizio.steiner@gmail.com>
* @author Greta Doci <gretadoci@gmail.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Marcel Waldvogel <marcel.waldvogel@uni-konstanz.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Sergej Nikolaev <kinolaev@gmail.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 BadMethodCallException;
use OC\Authentication\Exceptions\InvalidTokenException as OcInvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Token\INamedToken;
use OC\Authentication\Token\IProvider;
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\Authentication\Exceptions\ExpiredTokenException;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\Exceptions\WipeTokenException;
use OCP\Authentication\Token\IToken;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUserSession;
use OCP\Security\ISecureRandom;
use OCP\Session\Exceptions\SessionNotAvailableException;
use Psr\Log\LoggerInterface;
class AuthSettingsController extends Controller {
/** @var IProvider */
private $tokenProvider;
/** @var ISession */
private $session;
/** @var IUserSession */
private $userSession;
/** @var string */
private $uid;
/** @var ISecureRandom */
private $random;
/** @var IManager */
private $activityManager;
/** @var RemoteWipe */
private $remoteWipe;
/** @var LoggerInterface */
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 LoggerInterface $logger
*/
public function __construct(string $appName,
IRequest $request,
IProvider $tokenProvider,
ISession $session,
ISecureRandom $random,
?string $userId,
IUserSession $userSession,
IManager $activityManager,
RemoteWipe $remoteWipe,
LoggerInterface $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) {
if ($this->checkAppToken()) {
return $this->getServiceNotAvailableResponse();
}
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();
}
if (mb_strlen($name) > 128) {
$name = mb_substr($name, 0, 120) . '…';
}
$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);
}
private function checkAppToken(): bool {
return $this->session->exists('app_password');
}
/**
* @NoAdminRequired
* @NoSubAdminRequired
*
* @param int $id
* @return array|JSONResponse
*/
public function destroy($id) {
if ($this->checkAppToken()) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
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) {
if ($this->checkAppToken()) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
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 (mb_strlen($name) > 128) {
$name = mb_substr($name, 0, 120) . '…';
}
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', ['exception' => $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
*/
private function findTokenByIdAndUser(int $id): IToken {
try {
$token = $this->tokenProvider->getTokenById($id);
} catch (ExpiredTokenException $e) {
$token = $e->getToken();
}
if ($token->getUID() !== $this->uid) {
/** @psalm-suppress DeprecatedClass We have to throw the OC version so both OC and OCP catches catch it */
throw new OcInvalidTokenException('This token does not belong to you!');
}
return $token;
}
/**
* @NoAdminRequired
* @NoSubAdminRequired
* @PasswordConfirmationRequired
*
* @param int $id
* @return JSONResponse
* @throws InvalidTokenException
* @throws ExpiredTokenException
*/
public function wipe(int $id): JSONResponse {
if ($this->checkAppToken()) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
try {
$token = $this->findTokenByIdAndUser($id);
} catch (InvalidTokenException $e) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
if (!$this->remoteWipe->markTokenForWipe($token)) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
return new JSONResponse([]);
}
}
@@ -0,0 +1,80 @@
<?php
/**
* @copyright Copyright (c) 2021 Carl Schwan <carl@carlschwan.eu>
*
* @author Carl Schwan <carl@carlschwan.eu>
*
* @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 <https://www.gnu.org/licenses/>.
*
*/
namespace OCA\Settings\Controller;
use OC\Settings\AuthorizedGroup;
use OCA\Settings\Service\AuthorizedGroupService;
use OCA\Settings\Service\NotFoundException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\DB\Exception;
use OCP\IRequest;
class AuthorizedGroupController extends Controller {
/** @var AuthorizedGroupService $authorizedGroupService */
private $authorizedGroupService;
public function __construct(string $AppName, IRequest $request, AuthorizedGroupService $authorizedGroupService) {
parent::__construct($AppName, $request);
$this->authorizedGroupService = $authorizedGroupService;
}
/**
* @throws NotFoundException
* @throws Exception
*/
public function saveSettings(array $newGroups, string $class): DataResponse {
$currentGroups = $this->authorizedGroupService->findExistingGroupsForClass($class);
foreach ($currentGroups as $group) {
/** @var AuthorizedGroup $group */
$removed = true;
foreach ($newGroups as $groupData) {
if ($groupData['gid'] === $group->getGroupId()) {
$removed = false;
break;
}
}
if ($removed) {
$this->authorizedGroupService->delete($group->getId());
}
}
foreach ($newGroups as $groupData) {
$added = true;
foreach ($currentGroups as $group) {
/** @var AuthorizedGroup $group */
if ($groupData['gid'] === $group->getGroupId()) {
$added = false;
break;
}
}
if ($added) {
$this->authorizedGroupService->create($groupData['gid'], $class);
}
}
return new DataResponse(['valid' => true]);
}
}
@@ -0,0 +1,255 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @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/>.
*
*/
// FIXME: disabled for now to be able to inject IGroupManager and also use
// getSubAdmin()
//declare(strict_types=1);
namespace OCA\Settings\Controller;
use OC\Group\Manager as GroupManager;
use OC\User\Session;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\HintException;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
class ChangePasswordController extends Controller {
private ?string $userId;
private IUserManager $userManager;
private IL10N $l;
private GroupManager $groupManager;
private Session $userSession;
private IAppManager $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 {
$loginName = $this->userSession->getLoginName();
/** @var IUser $user */
$user = $this->userManager->checkPassword($loginName, $oldpassword);
if ($user === false) {
$response = new JSONResponse([
'status' => 'error',
'data' => [
'message' => $this->l->t('Wrong password'),
],
]);
$response->throttle();
return $response;
}
try {
if ($newpassword === null || strlen($newpassword) > IUserManager::MAX_PASSWORD_LENGTH || $user->setPassword($newpassword) === false) {
return new JSONResponse([
'status' => 'error',
'data' => [
'message' => $this->l->t('Unable to change personal password'),
],
]);
}
// 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'),
],
]);
}
if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
return new JSONResponse([
'status' => 'error',
'data' => [
'message' => $this->l->t('Unable to change password. Password too long.'),
],
]);
}
$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
$keyManager = \OCP\Server::get(\OCA\Encryption\KeyManager::class);
$recovery = \OCP\Server::get(\OCA\Encryption\Recovery::class);
$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 does not 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,
],
]);
}
}
@@ -0,0 +1,307 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Cthulhux <git@tuxproject.de>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Derek <derek.kelly27@gmail.com>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author J0WI <J0WI@users.noreply.github.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Ko- <k.stoffelen@cs.ru.nl>
* @author Lauris Binde <laurisb@users.noreply.github.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Michael Weimann <mail@michael-weimann.eu>
* @author Morris Jobke <hey@morrisjobke.de>
* @author nhirokinet <nhirokinet@nhiroki.net>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Sven Strickroth <email@cs-ware.de>
* @author Sylvia van Os <sylvia@hackerchick.me>
* @author timm2k <timm2k@gmx.de>
* @author Timo Förster <tfoerster@webfoersterei.de>
* @author Valdnet <47037905+Valdnet@users.noreply.github.com>
* @author MichaIng <micha@dietpi.com>
* @author Kate Döen <kate.doeen@nextcloud.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\AppFramework\Http;
use OC\IntegrityCheck\Checker;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ITempManager;
use OCP\IURLGenerator;
use OCP\Notification\IManager;
use OCP\SetupCheck\ISetupCheckManager;
use Psr\Log\LoggerInterface;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class CheckSetupController extends Controller {
/** @var IConfig */
private $config;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IL10N */
private $l10n;
/** @var Checker */
private $checker;
/** @var LoggerInterface */
private $logger;
/** @var ITempManager */
private $tempManager;
/** @var IManager */
private $manager;
private ISetupCheckManager $setupCheckManager;
public function __construct($AppName,
IRequest $request,
IConfig $config,
IURLGenerator $urlGenerator,
IL10N $l10n,
Checker $checker,
LoggerInterface $logger,
ITempManager $tempManager,
IManager $manager,
ISetupCheckManager $setupCheckManager,
) {
parent::__construct($AppName, $request);
$this->config = $config;
$this->urlGenerator = $urlGenerator;
$this->l10n = $l10n;
$this->checker = $checker;
$this->logger = $logger;
$this->tempManager = $tempManager;
$this->manager = $manager;
$this->setupCheckManager = $setupCheckManager;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @return DataResponse
*/
public function setupCheckManager(): DataResponse {
return new DataResponse($this->setupCheckManager->runAll());
}
/**
* Check if is fair use of free push service
* @return bool
*/
private function isFairUseOfFreePushService(): bool {
$rateLimitReached = (int) $this->config->getAppValue('notifications', 'rate_limit_reached', '0');
if ($rateLimitReached >= (time() - 7 * 24 * 3600)) {
// Notifications app is showing a message already
return true;
}
return $this->manager->isFairUseOfFreePushService();
}
/**
* 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() {
$memcacheDistributedClass = $this->config->getSystemValue('memcache.distributed', null);
if ($memcacheDistributedClass === null || ltrim($memcacheDistributedClass, '\\') !== \OC\Memcache\Memcached::class) {
return true;
}
// there are two different memcache 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')
&& !str_contains(ini_get('disable_functions'), 'set_time_limit')) {
return true;
}
return false;
}
/**
* @NoCSRFRequired
* @return RedirectResponse
* @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
*/
public function rescanFailedIntegrityCheck(): RedirectResponse {
$this->checker->runInstanceVerification();
return new RedirectResponse(
$this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
);
}
/**
* @NoCSRFRequired
* @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
*/
public function getFailedIntegrityCheckFiles(): DataDisplayResponse {
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.';
}
return new DataDisplayResponse(
$formattedTextResponse,
Http::STATUS_OK,
[
'Content-Type' => 'text/plain',
]
);
}
private function isTemporaryDirectoryWritable(): bool {
try {
if (!empty($this->tempManager->getTempBaseDir())) {
return true;
}
} catch (\Exception $e) {
}
return false;
}
protected function areWebauthnExtensionsEnabled(): bool {
if (!extension_loaded('bcmath')) {
return false;
}
if (!extension_loaded('gmp')) {
return false;
}
return true;
}
protected function isMysqlUsedWithoutUTF8MB4(): bool {
return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
}
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. Returned value: ' . $tempPath);
return false;
}
$freeSpaceInTemp = function_exists('disk_free_space') ? disk_free_space($tempPath) : false;
if ($freeSpaceInTemp === false) {
$this->logger->error('Error while checking the available disk space of temporary PHP path or 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
* @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
*/
public function check() {
return new DataResponse(
[
'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(),
'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
'areWebauthnExtensionsEnabled' => $this->areWebauthnExtensionsEnabled(),
'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
'temporaryDirectoryWritable' => $this->isTemporaryDirectoryWritable(),
'generic' => $this->setupCheckManager->runAll(),
]
);
}
}
@@ -0,0 +1,161 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.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\Controller;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Group\ISubAdmin;
use OCP\IGroupManager;
use OCP\INavigationManager;
use OCP\IUserSession;
use OCP\Settings\IIconSection;
use OCP\Settings\IManager as ISettingsManager;
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;
/**
* @return array{forms: array{personal: array, admin: array}}
*/
private function getNavigationParameters(string $currentType, string $currentSection): array {
$templateParameters = [
'personal' => $this->formatPersonalSections($currentType, $currentSection),
'admin' => []
];
$templateParameters['admin'] = $this->formatAdminSections(
$currentType,
$currentSection
);
return [
'forms' => $templateParameters
];
}
/**
* @param IIconSection[][] $sections
* @psam-param 'admin'|'personal' $type
* @return list<array{anchor: string, section-name: string, active: bool, icon: string}>
*/
protected function formatSections(array $sections, string $currentSection, string $type, string $currentType): array {
$templateParameters = [];
foreach ($sections as $prioritizedSections) {
foreach ($prioritizedSections as $section) {
if ($type === 'admin') {
$settings = $this->settingsManager->getAllowedAdminSettings($section->getID(), $this->userSession->getUser());
} elseif ($type === 'personal') {
$settings = $this->settingsManager->getPersonalSettings($section->getID());
}
if (empty($settings) && !($section->getID() === 'additional' && count(\OC_App::getForms('admin')) > 0)) {
continue;
}
$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(string $currentType, string $currentSections): array {
$sections = $this->settingsManager->getPersonalSections();
return $this->formatSections($sections, $currentSections, 'personal', $currentType);
}
protected function formatAdminSections(string $currentType, string $currentSections): array {
$sections = $this->settingsManager->getAdminSections();
return $this->formatSections($sections, $currentSections, 'admin', $currentType);
}
/**
* @param array<int, list<\OCP\Settings\ISettings>> $settings
* @return array{content: string}
*/
private function formatSettings(array $settings): array {
$html = '';
foreach ($settings as $prioritizedSettings) {
foreach ($prioritizedSettings as $setting) {
/** @var ISettings $setting */
$form = $setting->getForm();
$html .= $form->renderAs('')->render();
}
}
return ['content' => $html];
}
private function getIndexResponse(string $type, string $section): TemplateResponse {
if ($type === 'personal') {
if ($section === 'theming') {
$this->navigationManager->setActiveEntry('accessibility_settings');
} else {
$this->navigationManager->setActiveEntry('settings');
}
} elseif ($type === 'admin') {
$this->navigationManager->setActiveEntry('admin_settings');
}
$templateParams = [];
$templateParams = array_merge($templateParams, $this->getNavigationParameters($type, $section));
$templateParams = array_merge($templateParams, $this->getSettings($section));
$activeSection = $this->settingsManager->getSection($type, $section);
if ($activeSection) {
$templateParams['pageTitle'] = $activeSection->getName();
$templateParams['activeSectionId'] = $activeSection->getID();
$templateParams['activeSectionType'] = $type;
}
return new TemplateResponse('settings', 'settings/frame', $templateParams);
}
abstract protected function getSettings($section);
}
@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.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\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IURLGenerator;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class HelpController extends Controller {
/** @var INavigationManager */
private $navigationManager;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IGroupManager */
private $groupManager;
/** @var IL10N */
private $l10n;
/** @var string */
private $userId;
/** @var IConfig */
private $config;
public function __construct(
string $appName,
IRequest $request,
INavigationManager $navigationManager,
IURLGenerator $urlGenerator,
?string $userId,
IGroupManager $groupManager,
IL10N $l10n,
IConfig $config,
) {
parent::__construct($appName, $request);
$this->navigationManager = $navigationManager;
$this->urlGenerator = $urlGenerator;
$this->userId = $userId;
$this->groupManager = $groupManager;
$this->l10n = $l10n;
$this->config = $config;
}
/**
* @return TemplateResponse
*
* @NoCSRFRequired
* @NoAdminRequired
* @NoSubAdminRequired
*/
public function help(string $mode = 'user'): TemplateResponse {
$this->navigationManager->setActiveEntry('help');
$pageTitle = $this->l10n->t('Administrator documentation');
if ($mode !== 'admin') {
$pageTitle = $this->l10n->t('User documentation');
$mode = 'user';
}
$documentationUrl = $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkTo('', 'core/doc/' . $mode . '/index.html')
);
$urlUserDocs = $this->urlGenerator->linkToRoute('settings.Help.help', ['mode' => 'user']);
$urlAdminDocs = $this->urlGenerator->linkToRoute('settings.Help.help', ['mode' => 'admin']);
$knowledgebaseEmbedded = $this->config->getSystemValueBool('knowledgebase.embedded', false);
if (!$knowledgebaseEmbedded) {
$pageTitle = $this->l10n->t('Nextcloud help overview');
$urlUserDocs = $this->urlGenerator->linkToDocs('user');
$urlAdminDocs = $this->urlGenerator->linkToDocs('admin');
}
$response = new TemplateResponse('settings', 'help', [
'admin' => $this->groupManager->isAdmin($this->userId),
'url' => $documentationUrl,
'urlUserDocs' => $urlUserDocs,
'urlAdminDocs' => $urlAdminDocs,
'mode' => $mode,
'pageTitle' => $pageTitle,
'knowledgebaseEmbedded' => $knowledgebaseEmbedded,
]);
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain('\'self\'');
$response->setContentSecurityPolicy($policy);
return $response;
}
}
@@ -0,0 +1,68 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Kate Döen <kate.doeen@nextcloud.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\Log;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
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
*
* @psalm-suppress MoreSpecificReturnType The value of Content-Disposition is not relevant
* @psalm-suppress LessSpecificReturnStatement The value of Content-Disposition is not relevant
* @return StreamResponse<Http::STATUS_OK, array{Content-Type: 'application/octet-stream', 'Content-Disposition': string}>
*
* 200: Logfile returned
*/
public function download() {
if (!$this->log instanceof Log) {
throw new \UnexpectedValueException('Log file not available');
}
$resp = new StreamResponse($this->log->getLogPath());
$resp->setHeaders([
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="nextcloud.log"',
]);
return $resp;
}
}
@@ -0,0 +1,185 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @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\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
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;
/** @var IURLGenerator */
private $urlGenerator;
/**
* @param string $appName
* @param IRequest $request
* @param IL10N $l10n
* @param IConfig $config
* @param IUserSession $userSession
* @param IURLGenerator $urlGenerator,
* @param IMailer $mailer
*/
public function __construct($appName,
IRequest $request,
IL10N $l10n,
IConfig $config,
IUserSession $userSession,
IURLGenerator $urlGenerator,
IMailer $mailer) {
parent::__construct($appName, $request);
$this->l10n = $l10n;
$this->config = $config;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
$this->mailer = $mailer;
}
/**
* Sets the email settings
*
* @PasswordConfirmationRequired
* @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
*
* @param string $mail_domain
* @param string $mail_from_address
* @param string $mail_smtpmode
* @param string $mail_smtpsecure
* @param string $mail_smtphost
* @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_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);
$this->config->setAppValue('core', 'emailTestSuccessful', '0');
return new DataResponse();
}
/**
* Store the credentials used for SMTP in the config
*
* @PasswordConfirmationRequired
* @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
*
* @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,
]);
$this->config->setAppValue('core', 'emailTestSuccessful', '0');
return new DataResponse();
}
/**
* Send a mail to test the settings
* @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
* @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)) {
$this->config->setAppValue('core', 'emailTestSuccessful', '0');
throw new \RuntimeException($this->l10n->t('Email could not be sent. Check your mail server log'));
}
// Store the successful config in the app config
$this->config->setAppValue('core', 'emailTestSuccessful', '1');
return new DataResponse();
} catch (\Exception $e) {
$this->config->setAppValue('core', 'emailTestSuccessful', '0');
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);
}
}
$this->config->setAppValue('core', 'emailTestSuccessful', '0');
return new DataResponse($this->l10n->t('You need to set your user email before being able to send test emails. Go to %s for that.', [$this->urlGenerator->linkToRouteAbsolute('settings.PersonalSettings.index')]), Http::STATUS_BAD_REQUEST);
}
}
@@ -0,0 +1,112 @@
<?php
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Kate Döen <kate.doeen@nextcloud.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\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
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;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
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;
}
/**
* @NoCSRFRequired
* @NoAdminRequired
* @NoSubAdminRequired
*/
public function index(string $section): TemplateResponse {
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 [
'anchor' => $anchor,
'section-name' => $sectionName,
'form' => $form
];
}
return [
'form' => $form
];
}, $forms);
$out = new Template('settings', 'settings/additional');
$out->assign('forms', $forms);
return $out->fetchPage();
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Jan C. Borchardt <hey@jancborchardt.net>
* @author Kate Döen <kate.doeen@nextcloud.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\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataDisplayResponse;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class ReasonsController extends Controller {
/**
* @NoCSRFRequired
* @NoAdminRequired
* @NoSubAdminRequired
*/
public function getPdf() {
$data = file_get_contents(__DIR__ . '/../../data/Reasons to use Nextcloud.pdf');
$resp = new DataDisplayResponse($data);
$resp->addHeader('Content-Type', 'application/pdf');
return $resp;
}
}
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 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());
}
}
@@ -0,0 +1,604 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author GretaD <gretadoci@gmail.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Vincent Petry <vincent@nextcloud.com>
* @author Kate Döen <kate.doeen@nextcloud.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/>
*
*/
// FIXME: disabled for now to be able to inject IGroupManager and also use
// getSubAdmin()
namespace OCA\Settings\Controller;
use InvalidArgumentException;
use OC\AppFramework\Http;
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
use OC\ForbiddenException;
use OC\Group\Manager as GroupManager;
use OC\KnownUser\KnownUserService;
use OC\L10N\Factory;
use OC\Security\IdentityProof\Manager;
use OC\User\Manager as UserManager;
use OCA\Settings\BackgroundJobs\VerifyUserData;
use OCA\Settings\Events\BeforeTemplateRenderedEvent;
use OCA\User_LDAP\User_Proxy;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\PropertyDoesNotExistException;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\BackgroundJob\IJobList;
use OCP\Encryption\IManager;
use OCP\EventDispatcher\IEventDispatcher;
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 function in_array;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class UsersController extends Controller {
/** @var UserManager */
private $userManager;
/** @var GroupManager */
private $groupManager;
/** @var IUserSession */
private $userSession;
/** @var IConfig */
private $config;
/** @var bool */
private $isAdmin;
/** @var IL10N */
private $l10n;
/** @var IMailer */
private $mailer;
/** @var Factory */
private $l10nFactory;
/** @var IAppManager */
private $appManager;
/** @var IAccountManager */
private $accountManager;
/** @var Manager */
private $keyManager;
/** @var IJobList */
private $jobList;
/** @var IManager */
private $encryptionManager;
/** @var KnownUserService */
private $knownUserService;
/** @var IEventDispatcher */
private $dispatcher;
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,
IAccountManager $accountManager,
Manager $keyManager,
IJobList $jobList,
IManager $encryptionManager,
KnownUserService $knownUserService,
IEventDispatcher $dispatcher
) {
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;
$this->knownUserService = $knownUserService;
$this->dispatcher = $dispatcher;
}
/**
* @NoCSRFRequired
* @NoAdminRequired
*
* Display users list template
*
* @return TemplateResponse
*/
public function usersListByGroup(): TemplateResponse {
return $this->usersList();
}
/**
* @NoCSRFRequired
* @NoAdminRequired
*
* Display users list template
*
* @return TemplateResponse
*/
public function usersList(): TemplateResponse {
$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);
[$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;
});
}
$disabledUsers = -1;
$userCount = 0;
if (!$isLDAPUsed) {
if ($this->isAdmin) {
$disabledUsers = $this->userManager->countDisabledUsers();
$userCount = 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 = [];
foreach ($groups as $key => $group) {
// $userCount += (int)$group['usercount'];
$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 += $this->userManager->countUsersOfGroups($groupsInfo->getGroups());
$disabledUsers = $this->userManager->countDisabledUsersOfGroups($groupsNames);
}
$userCount -= $disabledUsers;
}
$disabledUsersGroup = [
'id' => 'disabled',
'name' => 'Disabled users',
'usercount' => $disabledUsers
];
/* QUOTAS PRESETS */
$quotaPreset = $this->parseQuotaPreset($this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
$allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
if (!$allowUnlimitedQuota && count($quotaPreset) > 0) {
$defaultQuota = $this->config->getAppValue('files', 'default_quota', $quotaPreset[0]);
} else {
$defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
}
$event = new BeforeTemplateRenderedEvent();
$this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
$this->dispatcher->dispatchTyped($event);
/* LANGUAGES */
$languages = $this->l10nFactory->getLanguages();
/* FINAL DATA */
$serverData = [];
// groups
$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
// Various data
$serverData['isAdmin'] = $this->isAdmin;
$serverData['sortGroups'] = $sortGroupsBy;
$serverData['quotaPreset'] = $quotaPreset;
$serverData['allowUnlimitedQuota'] = $allowUnlimitedQuota;
$serverData['userCount'] = $userCount;
$serverData['languages'] = $languages;
$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
$serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
// 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';
$serverData['newUserSendEmail'] = $this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes';
return new TemplateResponse('settings', 'settings-vue', ['serverData' => $serverData, 'pageTitle' => $this->l10n->t('Users')]);
}
/**
* @param string $key
* @param string $value
*
* @return JSONResponse
*/
public function setPreference(string $key, string $value): JSONResponse {
$allowed = ['newUser.sendEmail'];
if (!in_array($key, $allowed, true)) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->config->setAppValue('core', $key, $value);
return new JSONResponse([]);
}
/**
* Parse the app value for quota_present
*
* @param string $quotaPreset
* @return array
*/
protected function parseQuotaPreset(string $quotaPreset): array {
// 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
$presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
// Drop default and none, Make array indexes numerically
return array_values(array_diff($presets, ['default', 'none']));
}
/**
* 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(): bool {
$isEncryptionEnabled = $this->encryptionManager->isEnabled();
try {
$noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
$isEncryptionModuleLoaded = true;
} catch (ModuleDoesNotExistsException $e) {
$noUserSpecificEncryptionKeys = true;
$isEncryptionModuleLoaded = false;
}
$canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
|| (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
return $canChangePassword;
}
/**
* @NoAdminRequired
* @NoSubAdminRequired
* @PasswordConfirmationRequired
*
* @param string|null $avatarScope
* @param string|null $displayname
* @param string|null $displaynameScope
* @param string|null $phone
* @param string|null $phoneScope
* @param string|null $email
* @param string|null $emailScope
* @param string|null $website
* @param string|null $websiteScope
* @param string|null $address
* @param string|null $addressScope
* @param string|null $twitter
* @param string|null $twitterScope
* @param string|null $fediverse
* @param string|null $fediverseScope
*
* @return DataResponse
*/
public function setUserSettings(?string $avatarScope = null,
?string $displayname = null,
?string $displaynameScope = null,
?string $phone = null,
?string $phoneScope = null,
?string $email = null,
?string $emailScope = null,
?string $website = null,
?string $websiteScope = null,
?string $address = null,
?string $addressScope = null,
?string $twitter = null,
?string $twitterScope = null,
?string $fediverse = null,
?string $fediverseScope = null
) {
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
return new DataResponse(
[
'status' => 'error',
'data' => [
'message' => $this->l10n->t('Invalid user')
]
],
Http::STATUS_UNAUTHORIZED
);
}
$email = !is_null($email) ? strtolower($email) : $email;
if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
return new DataResponse(
[
'status' => 'error',
'data' => [
'message' => $this->l10n->t('Invalid mail address')
]
],
Http::STATUS_UNPROCESSABLE_ENTITY
);
}
$userAccount = $this->accountManager->getAccount($user);
$oldPhoneValue = $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
$updatable = [
IAccountManager::PROPERTY_AVATAR => ['value' => null, 'scope' => $avatarScope],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => $displayname, 'scope' => $displaynameScope],
IAccountManager::PROPERTY_EMAIL => ['value' => $email, 'scope' => $emailScope],
IAccountManager::PROPERTY_WEBSITE => ['value' => $website, 'scope' => $websiteScope],
IAccountManager::PROPERTY_ADDRESS => ['value' => $address, 'scope' => $addressScope],
IAccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
];
$allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
foreach ($updatable as $property => $data) {
if ($allowUserToChangeDisplayName === false
&& in_array($property, [IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::PROPERTY_EMAIL], true)) {
continue;
}
$property = $userAccount->getProperty($property);
if (null !== $data['value']) {
$property->setValue($data['value']);
}
if (null !== $data['scope']) {
$property->setScope($data['scope']);
}
}
try {
$this->saveUserSettings($userAccount);
if ($oldPhoneValue !== $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue()) {
$this->knownUserService->deleteByContactUserId($user->getUID());
}
return new DataResponse(
[
'status' => 'success',
'data' => [
'userId' => $user->getUID(),
'avatarScope' => $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(),
'displayname' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(),
'displaynameScope' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(),
'phone' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(),
'phoneScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getScope(),
'email' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
'emailScope' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
'website' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(),
'websiteScope' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getScope(),
'address' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getValue(),
'addressScope' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getScope(),
'twitter' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(),
'twitterScope' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
'fediverse' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue(),
'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
'message' => $this->l10n->t('Settings saved'),
],
],
Http::STATUS_OK
);
} catch (ForbiddenException | InvalidArgumentException | PropertyDoesNotExistException $e) {
return new DataResponse([
'status' => 'error',
'data' => [
'message' => $e->getMessage()
],
]);
}
}
/**
* update account manager with new user data
*
* @throws ForbiddenException
* @throws InvalidArgumentException
*/
protected function saveUserSettings(IAccount $userAccount): void {
// keep the user back-end up-to-date with the latest display name and email
// address
$oldDisplayName = $userAccount->getUser()->getDisplayName();
if ($oldDisplayName !== $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue()) {
$result = $userAccount->getUser()->setDisplayName($userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue());
if ($result === false) {
throw new ForbiddenException($this->l10n->t('Unable to change full name'));
}
}
$oldEmailAddress = $userAccount->getUser()->getSystemEMailAddress();
$oldEmailAddress = strtolower((string)$oldEmailAddress);
if ($oldEmailAddress !== strtolower($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue())) {
// this is the only permission a backend provides and is also used
// for the permission of setting a email address
if (!$userAccount->getUser()->canChangeDisplayName()) {
throw new ForbiddenException($this->l10n->t('Unable to change email address'));
}
$userAccount->getUser()->setSystemEMailAddress($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue());
}
try {
$this->accountManager->updateAccount($userAccount);
} catch (InvalidArgumentException $e) {
if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
throw new InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
}
if ($e->getMessage() === IAccountManager::PROPERTY_WEBSITE) {
throw new InvalidArgumentException($this->l10n->t('Unable to set invalid website'));
}
throw new InvalidArgumentException($this->l10n->t('Some account data was invalid'));
}
}
/**
* 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);
}
$userAccount = $this->accountManager->getAccount($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':
$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 = IAccountManager::PROPERTY_TWITTER;
break;
case 'verify-website':
$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 = IAccountManager::PROPERTY_WEBSITE;
break;
default:
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$userProperty = $userAccount->getProperty($type);
$userProperty
->setVerified(IAccountManager::VERIFICATION_IN_PROGRESS)
->setVerificationData($signature);
if ($onlyVerificationCode === false) {
$this->accountManager->updateAccount($userAccount);
$this->jobList->add(VerifyUserData::class,
[
'verificationCode' => $code,
'data' => $userProperty->getValue(),
'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);
}
}
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.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\Controller;
use OC\Authentication\WebAuthn\Manager;
use OCA\Settings\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
use Webauthn\PublicKeyCredentialCreationOptions;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class WebAuthnController extends Controller {
private const WEBAUTHN_REGISTRATION = 'webauthn_registration';
public function __construct(
IRequest $request,
private LoggerInterface $logger,
private Manager $manager,
private IUserSession $userSession,
private ISession $session,
) {
parent::__construct(Application::APP_ID, $request);
}
/**
* @NoAdminRequired
* @NoSubAdminRequired
* @PasswordConfirmationRequired
* @UseSession
* @NoCSRFRequired
*/
public function startRegistration(): JSONResponse {
$this->logger->debug('Starting WebAuthn registration');
$credentialOptions = $this->manager->startRegistration($this->userSession->getUser(), $this->request->getServerHost());
// Set this in the session since we need it on finish
$this->session->set(self::WEBAUTHN_REGISTRATION, $credentialOptions);
return new JSONResponse($credentialOptions);
}
/**
* @NoAdminRequired
* @NoSubAdminRequired
* @PasswordConfirmationRequired
* @UseSession
*/
public function finishRegistration(string $name, string $data): JSONResponse {
$this->logger->debug('Finishing WebAuthn registration');
if (!$this->session->exists(self::WEBAUTHN_REGISTRATION)) {
$this->logger->debug('Trying to finish WebAuthn registration without session data');
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
// Obtain the publicKeyCredentialOptions from when we started the registration
$publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::createFromArray($this->session->get(self::WEBAUTHN_REGISTRATION));
$this->session->remove(self::WEBAUTHN_REGISTRATION);
return new JSONResponse($this->manager->finishRegister($publicKeyCredentialCreationOptions, $name, $data));
}
/**
* @NoAdminRequired
* @NoSubAdminRequired
* @PasswordConfirmationRequired
*/
public function deleteRegistration(int $id): JSONResponse {
$this->logger->debug('Finishing WebAuthn registration');
$this->manager->deleteRegistration($this->userSession->getUser(), $id);
return new JSONResponse([]);
}
}