aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/lib/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/lib/Controller')
-rw-r--r--apps/settings/lib/Controller/AISettingsController.php46
-rw-r--r--apps/settings/lib/Controller/AdminSettingsController.php101
-rw-r--r--apps/settings/lib/Controller/AppSettingsController.php401
-rw-r--r--apps/settings/lib/Controller/AuthSettingsController.php121
-rw-r--r--apps/settings/lib/Controller/AuthorizedGroupController.php37
-rw-r--r--apps/settings/lib/Controller/ChangePasswordController.php125
-rw-r--r--apps/settings/lib/Controller/CheckSetupController.php848
-rw-r--r--apps/settings/lib/Controller/CommonSettingsTrait.php133
-rw-r--r--apps/settings/lib/Controller/DeclarativeSettingsController.php131
-rw-r--r--apps/settings/lib/Controller/HelpController.php78
-rw-r--r--apps/settings/lib/Controller/LogSettingsController.php48
-rw-r--r--apps/settings/lib/Controller/MailSettingsController.php122
-rw-r--r--apps/settings/lib/Controller/PersonalSettingsController.php90
-rw-r--r--apps/settings/lib/Controller/ReasonsController.php29
-rw-r--r--apps/settings/lib/Controller/TwoFactorSettingsController.php25
-rw-r--r--apps/settings/lib/Controller/UsersController.php303
-rw-r--r--apps/settings/lib/Controller/WebAuthnController.php75
17 files changed, 957 insertions, 1756 deletions
diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php
new file mode 100644
index 00000000000..114cbf61514
--- /dev/null
+++ b/apps/settings/lib/Controller/AISettingsController.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCA\Settings\Controller;
+
+use OCA\Settings\Settings\Admin\ArtificialIntelligence;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\IAppConfig;
+use OCP\IRequest;
+
+class AISettingsController extends Controller {
+
+ public function __construct(
+ $appName,
+ IRequest $request,
+ private IAppConfig $appConfig,
+ ) {
+ parent::__construct($appName, $request);
+ }
+
+ /**
+ * Sets the email settings
+ *
+ * @param array $settings
+ * @return DataResponse
+ */
+ #[AuthorizedAdminSetting(settings: ArtificialIntelligence::class)]
+ public function update($settings) {
+ $keys = ['ai.stt_provider', 'ai.textprocessing_provider_preferences', 'ai.taskprocessing_provider_preferences','ai.taskprocessing_type_preferences', 'ai.translation_provider_preferences', 'ai.text2image_provider', 'ai.taskprocessing_guests'];
+ foreach ($keys as $key) {
+ if (!isset($settings[$key])) {
+ continue;
+ }
+ $this->appConfig->setValueString('core', $key, json_encode($settings[$key]), lazy: in_array($key, \OC\TaskProcessing\Manager::LAZY_CONFIG_KEYS, true));
+ }
+
+ return new DataResponse();
+ }
+}
diff --git a/apps/settings/lib/Controller/AdminSettingsController.php b/apps/settings/lib/Controller/AdminSettingsController.php
index dfaa26ff695..15e2c392148 100644
--- a/apps/settings/lib/Controller/AdminSettingsController.php
+++ b/apps/settings/lib/Controller/AdminSettingsController.php
@@ -1,42 +1,26 @@
<?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>
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Controller;
-use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
use OCP\Group\ISubAdmin;
use OCP\IGroupManager;
use OCP\INavigationManager;
use OCP\IRequest;
-use OCP\IUser;
use OCP\IUserSession;
+use OCP\Settings\IDeclarativeManager;
use OCP\Settings\IManager as ISettingsManager;
-use OCP\Template;
+#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class AdminSettingsController extends Controller {
use CommonSettingsTrait;
@@ -47,7 +31,9 @@ class AdminSettingsController extends Controller {
ISettingsManager $settingsManager,
IUserSession $userSession,
IGroupManager $groupManager,
- ISubAdmin $subAdmin
+ ISubAdmin $subAdmin,
+ IDeclarativeManager $declarativeSettingsManager,
+ IInitialState $initialState,
) {
parent::__construct($appName, $request);
$this->navigationManager = $navigationManager;
@@ -55,66 +41,21 @@ class AdminSettingsController extends Controller {
$this->userSession = $userSession;
$this->groupManager = $groupManager;
$this->subAdmin = $subAdmin;
+ $this->declarativeSettingsManager = $declarativeSettingsManager;
+ $this->initialState = $initialState;
}
/**
- * @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.
+ * settings for the given section. The user will be greeted by an error message.
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
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();
+ return $this->getIndexResponse(
+ 'admin',
+ $section,
+ );
}
}
diff --git a/apps/settings/lib/Controller/AppSettingsController.php b/apps/settings/lib/Controller/AppSettingsController.php
index a4addfc5b35..a85ee8cc20a 100644
--- a/apps/settings/lib/Controller/AppSettingsController.php
+++ b/apps/settings/lib/Controller/AppSettingsController.php
@@ -1,149 +1,251 @@
<?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>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Settings\Controller;
+use OC\App\AppManager;
use OC\App\AppStore\Bundles\BundleFetcher;
+use OC\App\AppStore\Fetcher\AppDiscoverFetcher;
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 OCA\AppAPI\Service\ExAppsPageService;
+use OCP\App\AppPathNotFoundException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
+use OCP\Files\AppData\IAppDataFactory;
+use OCP\Files\IAppData;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
+use OCP\Http\Client\IClientService;
use OCP\IConfig;
+use OCP\IGroup;
+use OCP\IGroupManager;
use OCP\IL10N;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IURLGenerator;
+use OCP\IUserSession;
use OCP\L10N\IFactory;
+use OCP\Security\RateLimiting\ILimiter;
+use OCP\Server;
+use OCP\Util;
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) {
+ private IAppData $appData;
+
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ IAppDataFactory $appDataFactory,
+ private IL10N $l10n,
+ private IConfig $config,
+ private INavigationManager $navigationManager,
+ private AppManager $appManager,
+ private CategoryFetcher $categoryFetcher,
+ private AppFetcher $appFetcher,
+ private IFactory $l10nFactory,
+ private BundleFetcher $bundleFetcher,
+ private Installer $installer,
+ private IURLGenerator $urlGenerator,
+ private LoggerInterface $logger,
+ private IInitialState $initialState,
+ private AppDiscoverFetcher $discoverFetcher,
+ private IClientService $clientService,
+ ) {
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;
+ $this->appData = $appDataFactory->get('appstore');
}
/**
- * @NoCSRFRequired
+ * @psalm-suppress UndefinedClass AppAPI is shipped since 30.0.1
*
* @return TemplateResponse
*/
+ #[NoCSRFRequired]
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]);
+ $this->initialState->provideInitialState('appstoreEnabled', $this->config->getSystemValueBool('appstoreenabled', true));
+ $this->initialState->provideInitialState('appstoreBundles', $this->getBundles());
+ $this->initialState->provideInitialState('appstoreDeveloperDocs', $this->urlGenerator->linkToDocs('developer-manual'));
+ $this->initialState->provideInitialState('appstoreUpdateCount', count($this->getAppsWithUpdates()));
+
+ if ($this->appManager->isEnabledForAnyone('app_api')) {
+ try {
+ Server::get(ExAppsPageService::class)->provideAppApiState($this->initialState);
+ } catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
+ }
+ }
+
$policy = new ContentSecurityPolicy();
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
+
+ $templateResponse = new TemplateResponse('settings', 'settings/empty', ['pageTitle' => $this->l10n->t('Settings')]);
$templateResponse->setContentSecurityPolicy($policy);
+ Util::addStyle('settings', 'settings');
+ Util::addScript('settings', 'vue-settings-apps-users-management');
+
return $templateResponse;
}
+ /**
+ * Get all active entries for the app discover section
+ */
+ #[NoCSRFRequired]
+ public function getAppDiscoverJSON(): JSONResponse {
+ $data = $this->discoverFetcher->get(true);
+ return new JSONResponse(array_values($data));
+ }
+
+ /**
+ * Get a image for the app discover section - this is proxied for privacy and CSP reasons
+ *
+ * @param string $image
+ * @throws \Exception
+ */
+ #[NoCSRFRequired]
+ public function getAppDiscoverMedia(string $fileName, ILimiter $limiter, IUserSession $session): Response {
+ $getEtag = $this->discoverFetcher->getETag() ?? date('Y-m');
+ $etag = trim($getEtag, '"');
+
+ $folder = null;
+ try {
+ $folder = $this->appData->getFolder('app-discover-cache');
+ $this->cleanUpImageCache($folder, $etag);
+ } catch (\Throwable $e) {
+ $folder = $this->appData->newFolder('app-discover-cache');
+ }
+
+ // Get the current cache folder
+ try {
+ $folder = $folder->getFolder($etag);
+ } catch (NotFoundException $e) {
+ $folder = $folder->newFolder($etag);
+ }
+
+ $info = pathinfo($fileName);
+ $hashName = md5($fileName);
+ $allFiles = $folder->getDirectoryListing();
+ // Try to find the file
+ $file = array_filter($allFiles, function (ISimpleFile $file) use ($hashName) {
+ return str_starts_with($file->getName(), $hashName);
+ });
+ // Get the first entry
+ $file = reset($file);
+ // If not found request from Web
+ if ($file === false) {
+ $user = $session->getUser();
+ // this route is not public thus we can assume a user is logged-in
+ assert($user !== null);
+ // Register a user request to throttle fetching external data
+ // this will prevent using the server for DoS of other systems.
+ $limiter->registerUserRequest(
+ 'settings-discover-media',
+ // allow up to 24 media requests per hour
+ // this should be a sane default when a completely new section is loaded
+ // keep in mind browsers request all files from a source-set
+ 24,
+ 60 * 60,
+ $user,
+ );
+
+ if (!$this->checkCanDownloadMedia($fileName)) {
+ $this->logger->warning('Tried to load media files for app discover section from untrusted source');
+ return new NotFoundResponse(Http::STATUS_BAD_REQUEST);
+ }
+
+ try {
+ $client = $this->clientService->newClient();
+ $fileResponse = $client->get($fileName);
+ $contentType = $fileResponse->getHeader('Content-Type');
+ $extension = $info['extension'] ?? '';
+ $file = $folder->newFile($hashName . '.' . base64_encode($contentType) . '.' . $extension, $fileResponse->getBody());
+ } catch (\Throwable $e) {
+ $this->logger->warning('Could not load media file for app discover section', ['media_src' => $fileName, 'exception' => $e]);
+ return new NotFoundResponse();
+ }
+ } else {
+ // File was found so we can get the content type from the file name
+ $contentType = base64_decode(explode('.', $file->getName())[1] ?? '');
+ }
+
+ $response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $contentType]);
+ // cache for 7 days
+ $response->cacheFor(604800, false, true);
+ return $response;
+ }
+
+ private function checkCanDownloadMedia(string $filename): bool {
+ $urlInfo = parse_url($filename);
+ if (!isset($urlInfo['host']) || !isset($urlInfo['path'])) {
+ return false;
+ }
+
+ // Always allowed hosts
+ if ($urlInfo['host'] === 'nextcloud.com') {
+ return true;
+ }
+
+ // Hosts that need further verification
+ // Github is only allowed if from our organization
+ $ALLOWED_HOSTS = ['github.com', 'raw.githubusercontent.com'];
+ if (!in_array($urlInfo['host'], $ALLOWED_HOSTS)) {
+ return false;
+ }
+
+ if (str_starts_with($urlInfo['path'], '/nextcloud/') || str_starts_with($urlInfo['path'], '/nextcloud-gmbh/')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Remove orphaned folders from the image cache that do not match the current etag
+ * @param ISimpleFolder $folder The folder to clear
+ * @param string $etag The etag (directory name) to keep
+ */
+ private function cleanUpImageCache(ISimpleFolder $folder, string $etag): void {
+ // Cleanup old cache folders
+ $allFiles = $folder->getDirectoryListing();
+ foreach ($allFiles as $dir) {
+ try {
+ if ($dir->getName() !== $etag) {
+ $dir->delete();
+ }
+ } catch (NotPermittedException $e) {
+ // ignore folder for now
+ }
+ }
+ }
+
private function getAppsWithUpdates() {
$appClass = new \OC_App();
$apps = $appClass->listAllApps();
@@ -181,17 +283,21 @@ class AppSettingsController extends Controller {
private function getAllCategories() {
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
- $formattedCategories = [];
$categories = $this->categoryFetcher->get();
- foreach ($categories as $category) {
- $formattedCategories[] = [
- 'id' => $category['id'],
- 'ident' => $category['id'],
- 'displayName' => isset($category['translations'][$currentLanguage]['name']) ? $category['translations'][$currentLanguage]['name'] : $category['translations']['en']['name'],
- ];
- }
+ return array_map(fn ($category) => [
+ 'id' => $category['id'],
+ 'displayName' => $category['translations'][$currentLanguage]['name'] ?? $category['translations']['en']['name'],
+ ], $categories);
+ }
- return $formattedCategories;
+ /**
+ * Convert URL to proxied URL so CSP is no problem
+ */
+ private function createProxyPreviewUrl(string $url): string {
+ if ($url === '') {
+ return '';
+ }
+ return 'https://usercontent.apps.nextcloud.com/' . base64_encode($url);
}
private function fetchApps() {
@@ -199,6 +305,16 @@ class AppSettingsController extends Controller {
$apps = $appClass->listAllApps();
foreach ($apps as $app) {
$app['installed'] = true;
+
+ if (isset($app['screenshot'][0])) {
+ $appScreenshot = $app['screenshot'][0] ?? null;
+ if (is_array($appScreenshot)) {
+ // Screenshot with thumbnail
+ $appScreenshot = $appScreenshot['@value'];
+ }
+
+ $app['screenshot'] = $this->createProxyPreviewUrl($appScreenshot);
+ }
$this->allApps[$app['id']] = $app;
}
@@ -234,6 +350,7 @@ class AppSettingsController extends Controller {
private function getAllApps() {
return $this->allApps;
}
+
/**
* Get all available apps in a category
*
@@ -256,7 +373,7 @@ class AppSettingsController extends Controller {
$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['screenshot'] = $this->createProxyPreviewUrl($appstoreData['screenshots'][0]['url'] ?? '');
$appData['category'] = $appstoreData['categories'];
$appData['releases'] = $appstoreData['releases'];
}
@@ -270,6 +387,10 @@ class AppSettingsController extends Controller {
$groups = [];
if (is_string($appData['groups'])) {
$groups = json_decode($appData['groups']);
+ // ensure 'groups' is an array
+ if (!is_array($groups)) {
+ $groups = [$groups];
+ }
}
$appData['groups'] = $groups;
$appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
@@ -335,7 +456,14 @@ class AppSettingsController extends Controller {
$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
}
$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
- $existsLocally = \OC_App::getAppPath($app['id']) !== false;
+
+ try {
+ $this->appManager->getAppPath($app['id']);
+ $existsLocally = true;
+ } catch (AppPathNotFoundException) {
+ $existsLocally = false;
+ }
+
$phpDependencies = [];
if ($phpVersion->getMinimumVersion() !== '') {
$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
@@ -354,7 +482,7 @@ class AppSettingsController extends Controller {
}
}
- $currentLanguage = substr(\OC::$server->getL10NFactory()->findLanguage(), 0, 2);
+ $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
$groups = null;
if ($enabledValue !== 'no' && $enabledValue !== 'yes') {
@@ -362,20 +490,21 @@ class AppSettingsController extends Controller {
}
$currentVersion = '';
- if ($this->appManager->isInstalled($app['id'])) {
+ if ($this->appManager->isEnabledForAnyone($app['id'])) {
$currentVersion = $this->appManager->getAppVersion($app['id']);
} else {
- $currentLanguage = $app['releases'][0]['version'];
+ $currentVersion = $app['releases'][0]['version'];
}
$formattedApps[] = [
'id' => $app['id'],
- 'name' => isset($app['translations'][$currentLanguage]['name']) ? $app['translations'][$currentLanguage]['name'] : $app['translations']['en']['name'],
- 'description' => isset($app['translations'][$currentLanguage]['description']) ? $app['translations'][$currentLanguage]['description'] : $app['translations']['en']['description'],
- 'summary' => isset($app['translations'][$currentLanguage]['summary']) ? $app['translations'][$currentLanguage]['summary'] : $app['translations']['en']['summary'],
+ 'app_api' => false,
+ '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,
+ 'shipped' => $this->appManager->isShipped($app['id']),
'version' => $currentVersion,
'default_enable' => '',
'types' => [],
@@ -395,7 +524,7 @@ class AppSettingsController extends Controller {
'missingMaxOwnCloudVersion' => false,
'missingMinOwnCloudVersion' => false,
'canInstall' => true,
- 'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/'.base64_encode($app['screenshots'][0]['url']) : '',
+ '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,
@@ -412,12 +541,11 @@ class AppSettingsController extends Controller {
}
/**
- * @PasswordConfirmationRequired
- *
* @param string $appId
* @param array $groups
* @return JSONResponse
*/
+ #[PasswordConfirmationRequired]
public function enableApp(string $appId, array $groups = []): JSONResponse {
return $this->enableApps([$appId], $groups);
}
@@ -427,21 +555,21 @@ class AppSettingsController extends Controller {
*
* apps will be enabled for specific groups only if $groups is defined
*
- * @PasswordConfirmationRequired
* @param array $appIds
* @param array $groups
* @return JSONResponse
*/
+ #[PasswordConfirmationRequired]
public function enableApps(array $appIds, array $groups = []): JSONResponse {
try {
$updateRequired = false;
foreach ($appIds as $appId) {
- $appId = OC_App::cleanAppId($appId);
+ $appId = $this->appManager->cleanAppId($appId);
// Check if app is already downloaded
/** @var Installer $installer */
- $installer = \OC::$server->query(Installer::class);
+ $installer = Server::get(Installer::class);
$isDownloaded = $installer->isDownloaded($appId);
if (!$isDownloaded) {
@@ -460,18 +588,18 @@ class AppSettingsController extends Controller {
}
}
return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
- } catch (\Exception $e) {
+ } catch (\Throwable $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();
+ $groupManager = Server::get(IGroupManager::class);
$groupsList = [];
foreach ($groups as $group) {
$groupItem = $groupManager->get($group);
- if ($groupItem instanceof \OCP\IGroup) {
+ if ($groupItem instanceof IGroup) {
$groupsList[] = $groupManager->get($group);
}
}
@@ -479,25 +607,23 @@ class AppSettingsController extends Controller {
}
/**
- * @PasswordConfirmationRequired
- *
* @param string $appId
* @return JSONResponse
*/
+ #[PasswordConfirmationRequired]
public function disableApp(string $appId): JSONResponse {
return $this->disableApps([$appId]);
}
/**
- * @PasswordConfirmationRequired
- *
* @param array $appIds
* @return JSONResponse
*/
+ #[PasswordConfirmationRequired]
public function disableApps(array $appIds): JSONResponse {
try {
foreach ($appIds as $appId) {
- $appId = OC_App::cleanAppId($appId);
+ $appId = $this->appManager->cleanAppId($appId);
$this->appManager->disableApp($appId);
}
return new JSONResponse([]);
@@ -508,15 +634,16 @@ class AppSettingsController extends Controller {
}
/**
- * @PasswordConfirmationRequired
- *
* @param string $appId
* @return JSONResponse
*/
+ #[PasswordConfirmationRequired]
public function uninstallApp(string $appId): JSONResponse {
- $appId = OC_App::cleanAppId($appId);
+ $appId = $this->appManager->cleanAppId($appId);
$result = $this->installer->removeApp($appId);
if ($result !== false) {
+ // If this app was force enabled, remove the force-enabled-state
+ $this->appManager->removeOverwriteNextcloudRequirement($appId);
$this->appManager->clearAppsCache();
return new JSONResponse(['data' => ['appid' => $appId]]);
}
@@ -528,7 +655,7 @@ class AppSettingsController extends Controller {
* @return JSONResponse
*/
public function updateApp(string $appId): JSONResponse {
- $appId = OC_App::cleanAppId($appId);
+ $appId = $this->appManager->cleanAppId($appId);
$this->config->setSystemValue('maintenance', true);
try {
@@ -555,8 +682,8 @@ class AppSettingsController extends Controller {
}
public function force(string $appId): JSONResponse {
- $appId = OC_App::cleanAppId($appId);
- $this->appManager->ignoreNextcloudRequirementForApp($appId);
+ $appId = $this->appManager->cleanAppId($appId);
+ $this->appManager->overwriteNextcloudRequirement($appId);
return new JSONResponse();
}
}
diff --git a/apps/settings/lib/Controller/AuthSettingsController.php b/apps/settings/lib/Controller/AuthSettingsController.php
index 38db7be1e91..8652a49fb1d 100644
--- a/apps/settings/lib/Controller/AuthSettingsController.php
+++ b/apps/settings/lib/Controller/AuthSettingsController.php
@@ -1,50 +1,29 @@
<?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/>
- *
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Settings\Controller;
use BadMethodCallException;
-use OC\Authentication\Exceptions\ExpiredTokenException;
-use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Exceptions\InvalidTokenException as OcInvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
-use OC\Authentication\Exceptions\WipeTokenException;
use OC\Authentication\Token\INamedToken;
use OC\Authentication\Token\IProvider;
-use OC\Authentication\Token\IToken;
use OC\Authentication\Token\RemoteWipe;
use OCA\Settings\Activity\Provider;
use OCP\Activity\IManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
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;
@@ -53,31 +32,12 @@ use OCP\Session\Exceptions\SessionNotAvailableException;
use Psr\Log\LoggerInterface;
class AuthSettingsController extends Controller {
-
/** @var IProvider */
private $tokenProvider;
- /** @var ISession */
- private $session;
-
- /** IUserSession */
- private $userSession;
-
- /** @var string */
- private $uid;
-
- /** @var ISecureRandom */
- private $random;
-
- /** @var IManager */
- private $activityManager;
-
/** @var RemoteWipe */
private $remoteWipe;
- /** @var LoggerInterface */
- private $logger;
-
/**
* @param string $appName
* @param IRequest $request
@@ -90,35 +50,31 @@ class AuthSettingsController extends Controller {
* @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) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ IProvider $tokenProvider,
+ private ISession $session,
+ private ISecureRandom $random,
+ private ?string $userId,
+ private IUserSession $userSession,
+ private IManager $activityManager,
+ RemoteWipe $remoteWipe,
+ private 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
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
public function create($name) {
if ($this->checkAppToken()) {
return $this->getServiceNotAvailableResponse();
@@ -150,7 +106,7 @@ class AuthSettingsController extends Controller {
}
$token = $this->generateRandomDeviceToken();
- $deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN);
+ $deviceToken = $this->tokenProvider->generateToken($token, $this->userId, $loginName, $password, $name, IToken::PERMANENT_TOKEN);
$tokenData = $deviceToken->jsonSerialize();
$tokenData['canDelete'] = true;
$tokenData['canRename'] = true;
@@ -193,12 +149,12 @@ class AuthSettingsController extends Controller {
}
/**
- * @NoAdminRequired
* @NoSubAdminRequired
*
* @param int $id
* @return array|JSONResponse
*/
+ #[NoAdminRequired]
public function destroy($id) {
if ($this->checkAppToken()) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
@@ -213,13 +169,12 @@ class AuthSettingsController extends Controller {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
- $this->tokenProvider->invalidateTokenById($this->uid, $token->getId());
+ $this->tokenProvider->invalidateTokenById($this->userId, $token->getId());
$this->publishActivity(Provider::APP_TOKEN_DELETED, $token->getId(), ['name' => $token->getName()]);
return [];
}
/**
- * @NoAdminRequired
* @NoSubAdminRequired
*
* @param int $id
@@ -227,6 +182,7 @@ class AuthSettingsController extends Controller {
* @param string $name
* @return array|JSONResponse
*/
+ #[NoAdminRequired]
public function update($id, array $scope, string $name) {
if ($this->checkAppToken()) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
@@ -241,8 +197,8 @@ class AuthSettingsController extends Controller {
$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]);
+ $token->setScope([IToken::SCOPE_FILESYSTEM => $scope[IToken::SCOPE_FILESYSTEM]]);
+ $this->publishActivity($scope[IToken::SCOPE_FILESYSTEM] ? Provider::APP_TOKEN_FILESYSTEM_GRANTED : Provider::APP_TOKEN_FILESYSTEM_REVOKED, $token->getId(), ['name' => $currentName]);
}
if (mb_strlen($name) > 128) {
@@ -267,8 +223,8 @@ class AuthSettingsController extends Controller {
$event = $this->activityManager->generateEvent();
$event->setApp('settings')
->setType('security')
- ->setAffectedUser($this->uid)
- ->setAuthor($this->uid)
+ ->setAffectedUser($this->userId)
+ ->setAuthor($this->userId)
->setSubject($subject, $parameters)
->setObject('app_token', $id, 'App Password');
@@ -292,22 +248,23 @@ class AuthSettingsController extends Controller {
} catch (ExpiredTokenException $e) {
$token = $e->getToken();
}
- if ($token->getUID() !== $this->uid) {
- throw new InvalidTokenException('This token does not belong to you!');
+ if ($token->getUID() !== $this->userId) {
+ /** @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 \OC\Authentication\Exceptions\ExpiredTokenException
+ * @throws ExpiredTokenException
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
public function wipe(int $id): JSONResponse {
if ($this->checkAppToken()) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
diff --git a/apps/settings/lib/Controller/AuthorizedGroupController.php b/apps/settings/lib/Controller/AuthorizedGroupController.php
index dcae5b31332..82a1ca4703e 100644
--- a/apps/settings/lib/Controller/AuthorizedGroupController.php
+++ b/apps/settings/lib/Controller/AuthorizedGroupController.php
@@ -1,43 +1,26 @@
<?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/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
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;
-use OCP\AppFramework\Http\DataResponse;
-use OCP\AppFramework\Controller;
class AuthorizedGroupController extends Controller {
- /** @var AuthorizedGroupService $authorizedGroupService */
- private $authorizedGroupService;
-
- public function __construct(string $AppName, IRequest $request, AuthorizedGroupService $authorizedGroupService) {
+ public function __construct(
+ string $AppName,
+ IRequest $request,
+ private AuthorizedGroupService $authorizedGroupService,
+ ) {
parent::__construct($AppName, $request);
- $this->authorizedGroupService = $authorizedGroupService;
}
/**
@@ -74,7 +57,7 @@ class AuthorizedGroupController extends Controller {
$this->authorizedGroupService->create($groupData['gid'], $class);
}
}
-
+
return new DataResponse(['valid' => true]);
}
}
diff --git a/apps/settings/lib/Controller/ChangePasswordController.php b/apps/settings/lib/Controller/ChangePasswordController.php
index 20ec28220a5..a874a47c16a 100644
--- a/apps/settings/lib/Controller/ChangePasswordController.php
+++ b/apps/settings/lib/Controller/ChangePasswordController.php
@@ -1,33 +1,8 @@
<?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/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
// FIXME: disabled for now to be able to inject IGroupManager and also use
// getSubAdmin()
@@ -37,49 +12,45 @@ namespace OCA\Settings\Controller;
use OC\Group\Manager as GroupManager;
use OC\User\Session;
+use OCA\Encryption\KeyManager;
+use OCA\Encryption\Recovery;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
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;
+use OCP\Server;
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) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private ?string $userId,
+ private IUserManager $userManager,
+ IUserSession $userSession,
+ private GroupManager $groupManager,
+ private IAppManager $appManager,
+ private 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 {
+ #[NoAdminRequired]
+ #[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);
@@ -123,16 +94,14 @@ class ChangePasswordController extends Controller {
]);
}
- /**
- * @NoAdminRequired
- * @PasswordConfirmationRequired
- */
- public function changeUserPassword(string $username = null, string $password = null, string $recoveryPassword = null): JSONResponse {
+ #[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'),
+ 'message' => $this->l->t('No Login supplied'),
],
]);
}
@@ -157,9 +126,9 @@ class ChangePasswordController extends Controller {
$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))
+ if ($currentUser === null || $targetUser === null
+ || !($this->groupManager->isAdmin($this->userId)
+ || $this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $targetUser))
) {
return new JSONResponse([
'status' => 'error',
@@ -171,36 +140,8 @@ class ChangePasswordController extends Controller {
if ($this->appManager->isEnabledForUser('encryption')) {
//handle the recovery case
- $crypt = new \OCA\Encryption\Crypto\Crypt(
- \OC::$server->getLogger(),
- \OC::$server->getUserSession(),
- \OC::$server->getConfig(),
- \OC::$server->getL10N('encryption'));
- $keyStorage = \OC::$server->getEncryptionKeyStorage();
- $util = new \OCA\Encryption\Util(
- new \OC\Files\View(),
- $crypt,
- \OC::$server->getLogger(),
- \OC::$server->getUserSession(),
- \OC::$server->getConfig(),
- \OC::$server->getUserManager());
- $keyManager = new \OCA\Encryption\KeyManager(
- $keyStorage,
- $crypt,
- \OC::$server->getConfig(),
- \OC::$server->getUserSession(),
- new \OCA\Encryption\Session(\OC::$server->getSession()),
- \OC::$server->getLogger(),
- $util,
- \OC::$server->getLockingProvider()
- );
- $recovery = new \OCA\Encryption\Recovery(
- \OC::$server->getUserSession(),
- $crypt,
- $keyManager,
- \OC::$server->getConfig(),
- \OC::$server->getEncryptionFilesHelper(),
- new \OC\Files\View());
+ $keyManager = Server::get(KeyManager::class);
+ $recovery = Server::get(Recovery::class);
$recoveryAdminEnabled = $recovery->isRecoveryKeyEnabled();
$validRecoveryPassword = false;
@@ -214,7 +155,7 @@ class ChangePasswordController extends Controller {
return new JSONResponse([
'status' => 'error',
'data' => [
- 'message' => $this->l->t('Please provide an admin recovery password; otherwise, all user data will be lost.'),
+ 'message' => $this->l->t('Please provide an admin recovery password; otherwise, all account data will be lost.'),
]
]);
} elseif ($recoveryEnabledForUser && ! $validRecoveryPassword) {
@@ -240,7 +181,7 @@ class ChangePasswordController extends Controller {
return new JSONResponse([
'status' => 'error',
'data' => [
- 'message' => $this->l->t('Backend does not support password change, but the user\'s encryption key was updated.'),
+ 'message' => $this->l->t('Backend does not support password change, but the encryption of the account key was updated.'),
]
]);
} elseif (!$result && !$recoveryEnabledForUser) {
diff --git a/apps/settings/lib/Controller/CheckSetupController.php b/apps/settings/lib/Controller/CheckSetupController.php
index a5c158d2602..2a189a37ce6 100644
--- a/apps/settings/lib/Controller/CheckSetupController.php
+++ b/apps/settings/lib/Controller/CheckSetupController.php
@@ -1,408 +1,63 @@
<?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>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Settings\Controller;
-use bantu\IniGetWrapper\IniGetWrapper;
-use DirectoryIterator;
-use Doctrine\DBAL\Exception;
-use Doctrine\DBAL\Platforms\SqlitePlatform;
-use Doctrine\DBAL\TransactionIsolationLevel;
-use GuzzleHttp\Exception\ClientException;
-use OC;
use OC\AppFramework\Http;
-use OC\DB\Connection;
-use OC\DB\MissingColumnInformation;
-use OC\DB\MissingIndexInformation;
-use OC\DB\MissingPrimaryKeyInformation;
-use OC\DB\SchemaWrapper;
use OC\IntegrityCheck\Checker;
-use OC\Lock\NoopLockingProvider;
-use OC\MemoryInfo;
-use OCA\Settings\SetupChecks\CheckUserCertificates;
-use OCA\Settings\SetupChecks\LdapInvalidUuids;
-use OCA\Settings\SetupChecks\LegacySSEKeyFormat;
-use OCA\Settings\SetupChecks\PhpDefaultCharset;
-use OCA\Settings\SetupChecks\PhpOutputBuffering;
-use OCA\Settings\SetupChecks\SupportedDatabase;
-use OCP\App\IAppManager;
+use OCA\Settings\Settings\Admin\Overview;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
-use OCP\DB\Types;
-use OCP\Http\Client\IClientService;
use OCP\IConfig;
-use OCP\IDateTimeFormatter;
-use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IRequest;
-use OCP\IServerContainer;
-use OCP\ITempManager;
use OCP\IURLGenerator;
-use OCP\Lock\ILockingProvider;
-use OCP\Notification\IManager;
-use OCP\Security\ISecureRandom;
+use OCP\SetupCheck\ISetupCheckManager;
use Psr\Log\LoggerInterface;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
+#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class CheckSetupController extends Controller {
- /** @var IConfig */
- private $config;
- /** @var IClientService */
- private $clientService;
- /** @var IURLGenerator */
- private $urlGenerator;
- /** @var IL10N */
- private $l10n;
/** @var Checker */
private $checker;
- /** @var LoggerInterface */
- private $logger;
- /** @var EventDispatcherInterface */
- private $dispatcher;
- /** @var Connection */
- private $db;
- /** @var ILockingProvider */
- private $lockingProvider;
- /** @var IDateTimeFormatter */
- private $dateTimeFormatter;
- /** @var MemoryInfo */
- private $memoryInfo;
- /** @var ISecureRandom */
- private $secureRandom;
- /** @var IniGetWrapper */
- private $iniGetWrapper;
- /** @var IDBConnection */
- private $connection;
- /** @var ITempManager */
- private $tempManager;
- /** @var IManager */
- private $manager;
- /** @var IAppManager */
- private $appManager;
- /** @var IServerContainer */
- private $serverContainer;
- public function __construct($AppName,
- IRequest $request,
- IConfig $config,
- IClientService $clientService,
- IURLGenerator $urlGenerator,
- IL10N $l10n,
- Checker $checker,
- LoggerInterface $logger,
- EventDispatcherInterface $dispatcher,
- Connection $db,
- ILockingProvider $lockingProvider,
- IDateTimeFormatter $dateTimeFormatter,
- MemoryInfo $memoryInfo,
- ISecureRandom $secureRandom,
- IniGetWrapper $iniGetWrapper,
- IDBConnection $connection,
- ITempManager $tempManager,
- IManager $manager,
- IAppManager $appManager,
- IServerContainer $serverContainer
+ public function __construct(
+ $AppName,
+ IRequest $request,
+ private IConfig $config,
+ private IURLGenerator $urlGenerator,
+ private IL10N $l10n,
+ Checker $checker,
+ private LoggerInterface $logger,
+ private ISetupCheckManager $setupCheckManager,
) {
parent::__construct($AppName, $request);
- $this->config = $config;
- $this->clientService = $clientService;
- $this->urlGenerator = $urlGenerator;
- $this->l10n = $l10n;
$this->checker = $checker;
- $this->logger = $logger;
- $this->dispatcher = $dispatcher;
- $this->db = $db;
- $this->lockingProvider = $lockingProvider;
- $this->dateTimeFormatter = $dateTimeFormatter;
- $this->memoryInfo = $memoryInfo;
- $this->secureRandom = $secureRandom;
- $this->iniGetWrapper = $iniGetWrapper;
- $this->connection = $connection;
- $this->tempManager = $tempManager;
- $this->manager = $manager;
- $this->appManager = $appManager;
- $this->serverContainer = $serverContainer;
- }
-
- /**
- * Check if is fair use of free push service
- * @return bool
- */
- private function isFairUseOfFreePushService(): bool {
- return $this->manager->isFairUseOfFreePushService();
- }
-
- /**
- * Checks if the server can connect to the internet using HTTPS and HTTP
- * @return bool
- */
- private function hasInternetConnectivityProblems(): bool {
- if ($this->config->getSystemValue('has_internet_connection', true) === false) {
- return false;
- }
-
- $siteArray = $this->config->getSystemValue('connectivity_check_domains', [
- 'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
- ]);
-
- foreach ($siteArray as $site) {
- if ($this->isSiteReachable($site)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Checks if the Nextcloud server can connect to a specific URL
- * @param string $site site domain or full URL with http/https protocol
- * @return bool
- */
- private function isSiteReachable(string $site): bool {
- try {
- $client = $this->clientService->newClient();
- // if there is no protocol, test http:// AND https://
- if (preg_match('/^https?:\/\//', $site) !== 1) {
- $httpSite = 'http://' . $site . '/';
- $client->get($httpSite);
- $httpsSite = 'https://' . $site . '/';
- $client->get($httpsSite);
- } else {
- $client->get($site);
- }
- } catch (\Exception $e) {
- $this->logger->error('Cannot connect to: ' . $site, [
- 'app' => 'internet_connection_check',
- 'exception' => $e,
- ]);
- return false;
- }
- return true;
- }
-
- /**
- * Checks whether a local memcache is installed or not
- * @return bool
- */
- private function isMemcacheConfigured() {
- return $this->config->getSystemValue('memcache.local', null) !== null;
- }
-
- /**
- * Whether PHP can generate "secure" pseudorandom integers
- *
- * @return bool
- */
- private function isRandomnessSecure() {
- try {
- $this->secureRandom->generate(1);
- } catch (\Exception $ex) {
- return false;
- }
- return true;
- }
-
- /**
- * Public for the sake of unit-testing
- *
- * @return array
- */
- protected function getCurlVersion() {
- return curl_version();
- }
-
- /**
- * Check if the used SSL lib is outdated. Older OpenSSL and NSS versions do
- * have multiple bugs which likely lead to problems in combination with
- * functionality required by ownCloud such as SNI.
- *
- * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
- * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
- * @return string
- */
- private function isUsedTlsLibOutdated() {
- // Don't run check when:
- // 1. Server has `has_internet_connection` set to false
- // 2. AppStore AND S2S is disabled
- if (!$this->config->getSystemValue('has_internet_connection', true)) {
- return '';
- }
- if (!$this->config->getSystemValue('appstoreenabled', true)
- && $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
- && $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
- return '';
- }
-
- $versionString = $this->getCurlVersion();
- if (isset($versionString['ssl_version'])) {
- $versionString = $versionString['ssl_version'];
- } else {
- return '';
- }
-
- $features = $this->l10n->t('installing and updating apps via the App Store or Federated Cloud Sharing');
- if (!$this->config->getSystemValue('appstoreenabled', true)) {
- $features = $this->l10n->t('Federated Cloud Sharing');
- }
-
- // Check if at least OpenSSL after 1.01d or 1.0.2b
- if (strpos($versionString, 'OpenSSL/') === 0) {
- $majorVersion = substr($versionString, 8, 5);
- $patchRelease = substr($versionString, 13, 6);
-
- if (($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
- ($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
- return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
- }
- }
-
- // Check if NSS and perform heuristic check
- if (strpos($versionString, 'NSS/') === 0) {
- try {
- $firstClient = $this->clientService->newClient();
- $firstClient->get('https://nextcloud.com/');
-
- $secondClient = $this->clientService->newClient();
- $secondClient->get('https://nextcloud.com/');
- } catch (ClientException $e) {
- if ($e->getResponse()->getStatusCode() === 400) {
- return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
- }
- } catch (\Exception $e) {
- $this->logger->warning('error checking curl', [
- 'app' => 'settings',
- 'exception' => $e,
- ]);
- return $this->l10n->t('Could not determine if TLS version of cURL is outdated or not because an error happened during the HTTPS request against https://nextcloud.com. Please check the Nextcloud log file for more details.');
- }
- }
-
- return '';
- }
-
- /**
- * Whether the version is outdated
- *
- * @return bool
- */
- protected function isPhpOutdated(): bool {
- return PHP_VERSION_ID < 80000;
}
/**
- * Whether the php version is still supported (at time of release)
- * according to: https://www.php.net/supported-versions.php
- *
- * @return array
- */
- private function isPhpSupported(): array {
- return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
- }
-
- /**
- * Check if the reverse proxy configuration is working as expected
- *
- * @return bool
- */
- private function forwardedForHeadersWorking(): bool {
- $trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
- $remoteAddress = $this->request->getHeader('REMOTE_ADDR');
-
- if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
- return false;
- }
-
- if (\is_array($trustedProxies)) {
- if (\in_array($remoteAddress, $trustedProxies, true) && $remoteAddress !== '127.0.0.1') {
- return $remoteAddress !== $this->request->getRemoteAddress();
- }
- } else {
- return false;
- }
-
- // either not enabled or working correctly
- return true;
- }
-
- /**
- * Checks if the correct memcache module for PHP is installed. Only
- * fails if memcached is configured and the working module is not installed.
- *
- * @return bool
- */
- private function isCorrectMemcachedPHPModuleInstalled() {
- if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
- return true;
- }
-
- // there are two different 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
+ * @return DataResponse
*/
- private function isSettimelimitAvailable() {
- if (function_exists('set_time_limit')
- && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
- return true;
- }
-
- return false;
+ #[NoCSRFRequired]
+ #[NoAdminRequired]
+ public function setupCheckManager(): DataResponse {
+ return new DataResponse($this->setupCheckManager->runAll());
}
/**
* @return RedirectResponse
- * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
*/
+ #[NoCSRFRequired]
+ #[AuthorizedAdminSetting(settings: Overview::class)]
public function rescanFailedIntegrityCheck(): RedirectResponse {
$this->checker->runInstanceVerification();
return new RedirectResponse(
@@ -410,10 +65,8 @@ class CheckSetupController extends Controller {
);
}
- /**
- * @NoCSRFRequired
- * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
- */
+ #[NoCSRFRequired]
+ #[AuthorizedAdminSetting(settings: Overview::class)]
public function getFailedIntegrityCheckFiles(): DataDisplayResponse {
if (!$this->checker->isCodeCheckEnforced()) {
return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
@@ -421,6 +74,10 @@ class CheckSetupController extends Controller {
$completeResults = $this->checker->getResults();
+ if ($completeResults === null) {
+ return new DataDisplayResponse('Integrity checker has not been run. Integrity information not available.');
+ }
+
if (!empty($completeResults)) {
$formattedTextResponse = 'Technical information
=====================
@@ -468,448 +125,13 @@ Raw output
}
/**
- * Checks whether a PHP OPcache is properly set up
- * @return string[] The list of OPcache setup recommendations
- */
- protected function getOpcacheSetupRecommendations(): array {
- // If the module is not loaded, return directly to skip inapplicable checks
- if (!extension_loaded('Zend OPcache')) {
- return [$this->l10n->t('The PHP OPcache module is not loaded. For better performance it is recommended to load it into your PHP installation.')];
- }
-
- $recommendations = [];
-
- // Check whether Nextcloud is allowed to use the OPcache API
- $isPermitted = true;
- $permittedPath = $this->iniGetWrapper->getString('opcache.restrict_api');
- if (isset($permittedPath) && $permittedPath !== '' && !str_starts_with(\OC::$SERVERROOT, rtrim($permittedPath, '/'))) {
- $isPermitted = false;
- }
-
- if (!$this->iniGetWrapper->getBool('opcache.enable')) {
- $recommendations[] = $this->l10n->t('OPcache is disabled. For better performance, it is recommended to apply <code>opcache.enable=1</code> to your PHP configuration.');
-
- // Check for saved comments only when OPcache is currently disabled. If it was enabled, opcache.save_comments=0 would break Nextcloud in the first place.
- if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
- $recommendations[] = $this->l10n->t('OPcache is configured to remove code comments. With OPcache enabled, <code>opcache.save_comments=1</code> must be set for Nextcloud to function.');
- }
-
- if (!$isPermitted) {
- $recommendations[] = $this->l10n->t('Nextcloud is not allowed to use the OPcache API. With OPcache enabled, it is highly recommended to include all Nextcloud directories with <code>opcache.restrict_api</code> or unset this setting to disable OPcache API restrictions, to prevent errors during Nextcloud core or app upgrades.');
- }
- } elseif (!$isPermitted) {
- $recommendations[] = $this->l10n->t('Nextcloud is not allowed to use the OPcache API. It is highly recommended to include all Nextcloud directories with <code>opcache.restrict_api</code> or unset this setting to disable OPcache API restrictions, to prevent errors during Nextcloud core or app upgrades.');
- } else {
- // Check whether opcache_get_status has been explicitly disabled an in case skip usage based checks
- $disabledFunctions = $this->iniGetWrapper->getString('disable_functions');
- if (isset($disabledFunctions) && str_contains($disabledFunctions, 'opcache_get_status')) {
- return [];
- }
-
- $status = opcache_get_status(false);
-
- // Recommend to raise value, if more than 90% of max value is reached
- if (
- empty($status['opcache_statistics']['max_cached_keys']) ||
- ($status['opcache_statistics']['num_cached_keys'] / $status['opcache_statistics']['max_cached_keys'] > 0.9)
- ) {
- $recommendations[] = $this->l10n->t('The maximum number of OPcache keys is nearly exceeded. To assure that all scripts can be kept in the cache, it is recommended to apply <code>opcache.max_accelerated_files</code> to your PHP configuration with a value higher than <code>%s</code>.', [($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') ?: 'currently')]);
- }
-
- if (
- empty($status['memory_usage']['free_memory']) ||
- ($status['memory_usage']['used_memory'] / $status['memory_usage']['free_memory'] > 9)
- ) {
- $recommendations[] = $this->l10n->t('The OPcache buffer is nearly full. To assure that all scripts can be hold in cache, it is recommended to apply <code>opcache.memory_consumption</code> to your PHP configuration with a value higher than <code>%s</code>.', [($this->iniGetWrapper->getNumeric('opcache.memory_consumption') ?: 'currently')]);
- }
-
- if (
- // Do not recommend to raise the interned strings buffer size above a quarter of the total OPcache size
- ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < $this->iniGetWrapper->getNumeric('opcache.memory_consumption') / 4) &&
- (
- empty($status['interned_strings_usage']['free_memory']) ||
- ($status['interned_strings_usage']['used_memory'] / $status['interned_strings_usage']['free_memory'] > 9)
- )
- ) {
- $recommendations[] = $this->l10n->t('The OPcache interned strings buffer is nearly full. To assure that repeating strings can be effectively cached, it is recommended to apply <code>opcache.interned_strings_buffer</code> to your PHP configuration with a value higher than <code>%s</code>.', [($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') ?: 'currently')]);
- }
- }
-
- return $recommendations;
- }
-
- /**
- * Check if the required FreeType functions are present
- * @return bool
- */
- protected function hasFreeTypeSupport() {
- return function_exists('imagettfbbox') && function_exists('imagettftext');
- }
-
- protected function hasMissingIndexes(): array {
- $indexInfo = new MissingIndexInformation();
- // Dispatch event so apps can also hint for pending index updates if needed
- $event = new GenericEvent($indexInfo);
- $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
-
- return $indexInfo->getListOfMissingIndexes();
- }
-
- protected function hasMissingPrimaryKeys(): array {
- $info = new MissingPrimaryKeyInformation();
- // Dispatch event so apps can also hint for pending index updates if needed
- $event = new GenericEvent($info);
- $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);
-
- return $info->getListOfMissingPrimaryKeys();
- }
-
- protected function hasMissingColumns(): array {
- $indexInfo = new MissingColumnInformation();
- // Dispatch event so apps can also hint for pending index updates if needed
- $event = new GenericEvent($indexInfo);
- $this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);
-
- return $indexInfo->getListOfMissingColumns();
- }
-
- protected function isSqliteUsed() {
- return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
- }
-
- protected function isReadOnlyConfig(): bool {
- return \OC_Helper::isReadOnlyConfigEnabled();
- }
-
- protected function wasEmailTestSuccessful(): bool {
- // Handle the case that the configuration was set before the check was introduced or it was only set via command line and not from the UI
- if ($this->config->getAppValue('core', 'emailTestSuccessful', '') === '' && $this->config->getSystemValue('mail_domain', '') === '') {
- return false;
- }
-
- // The mail test was unsuccessful or the config was changed using the UI without verifying with a testmail, hence return false
- if ($this->config->getAppValue('core', 'emailTestSuccessful', '') === '0') {
- return false;
- }
-
- return true;
- }
-
- protected function hasValidTransactionIsolationLevel(): bool {
- try {
- if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
- return true;
- }
-
- return $this->db->getTransactionIsolation() === TransactionIsolationLevel::READ_COMMITTED;
- } catch (Exception $e) {
- // ignore
- }
-
- return true;
- }
-
- protected function hasFileinfoInstalled(): bool {
- return \OC_Util::fileInfoLoaded();
- }
-
- protected function hasWorkingFileLocking(): bool {
- return !($this->lockingProvider instanceof NoopLockingProvider);
- }
-
- protected function getSuggestedOverwriteCliURL(): string {
- $currentOverwriteCliUrl = $this->config->getSystemValue('overwrite.cli.url', '');
- $suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
-
- // Check correctness by checking if it is a valid URL
- if (filter_var($currentOverwriteCliUrl, FILTER_VALIDATE_URL)) {
- $suggestedOverwriteCliUrl = '';
- }
-
- return $suggestedOverwriteCliUrl;
- }
-
- protected function getLastCronInfo(): array {
- $lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
- return [
- 'diffInSeconds' => time() - $lastCronRun,
- 'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
- 'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
- ];
- }
-
- protected function getCronErrors() {
- $errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
-
- if (is_array($errors)) {
- return $errors;
- }
-
- return [];
- }
-
- private function isTemporaryDirectoryWritable(): bool {
- try {
- if (!empty($this->tempManager->getTempBaseDir())) {
- return true;
- }
- } catch (\Exception $e) {
- }
- return false;
- }
-
- /**
- * Iterates through the configured app roots and
- * tests if the subdirectories are owned by the same user than the current user.
- *
- * @return array
- */
- protected function getAppDirsWithDifferentOwner(): array {
- $currentUser = posix_getuid();
- $appDirsWithDifferentOwner = [[]];
-
- foreach (OC::$APPSROOTS as $appRoot) {
- if ($appRoot['writable'] === true) {
- $appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
- }
- }
-
- $appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
- sort($appDirsWithDifferentOwner);
-
- return $appDirsWithDifferentOwner;
- }
-
- /**
- * Tests if the directories for one apps directory are writable by the current user.
- *
- * @param int $currentUser The current user
- * @param array $appRoot The app root config
- * @return string[] The none writable directory paths inside the app root
- */
- private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
- $appDirsWithDifferentOwner = [];
- $appsPath = $appRoot['path'];
- $appsDir = new DirectoryIterator($appRoot['path']);
-
- foreach ($appsDir as $fileInfo) {
- if ($fileInfo->isDir() && !$fileInfo->isDot()) {
- $absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
- $appDirUser = fileowner($absAppPath);
- if ($appDirUser !== $currentUser) {
- $appDirsWithDifferentOwner[] = $absAppPath;
- }
- }
- }
-
- return $appDirsWithDifferentOwner;
- }
-
- /**
- * Checks for potential PHP modules that would improve the instance
- *
- * @return string[] A list of PHP modules that is recommended
- */
- protected function hasRecommendedPHPModules(): array {
- $recommendedPHPModules = [];
-
- if (!extension_loaded('intl')) {
- $recommendedPHPModules[] = 'intl';
- }
-
- if (!extension_loaded('sysvsem')) {
- // used to limit the usage of resources by preview generator
- $recommendedPHPModules[] = 'sysvsem';
- }
-
- if (!defined('PASSWORD_ARGON2I')) {
- // Installing php-sodium on >=php7.4 will provide PASSWORD_ARGON2I
- // on previous version argon2 wasn't part of the "standard" extension
- // and RedHat disabled it so even installing php-sodium won't provide argon2i
- // support in password_hash/password_verify.
- $recommendedPHPModules[] = 'sodium';
- }
-
- return $recommendedPHPModules;
- }
-
- protected function isImagickEnabled(): bool {
- if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
- if (!extension_loaded('imagick')) {
- return false;
- }
- }
- return true;
- }
-
- protected function areWebauthnExtensionsEnabled(): bool {
- if (!extension_loaded('bcmath')) {
- return false;
- }
- if (!extension_loaded('gmp')) {
- return false;
- }
- return true;
- }
-
- protected function is64bit(): bool {
- if (PHP_INT_SIZE < 8) {
- return false;
- } else {
- return true;
- }
- }
-
- protected function isMysqlUsedWithoutUTF8MB4(): bool {
- return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
- }
-
- protected function hasBigIntConversionPendingColumns(): array {
- // copy of ConvertFilecacheBigInt::getColumnsByTable()
- $tables = [
- 'activity' => ['activity_id', 'object_id'],
- 'activity_mq' => ['mail_id'],
- 'authtoken' => ['id'],
- 'bruteforce_attempts' => ['id'],
- 'federated_reshares' => ['share_id'],
- 'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
- 'filecache_extended' => ['fileid'],
- 'files_trash' => ['auto_id'],
- 'file_locks' => ['id'],
- 'file_metadata' => ['id'],
- 'jobs' => ['id'],
- 'mimetypes' => ['id'],
- 'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
- 'share_external' => ['id', 'parent'],
- 'storages' => ['numeric_id'],
- ];
-
- $schema = new SchemaWrapper($this->db);
- $isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
- $pendingColumns = [];
-
- foreach ($tables as $tableName => $columns) {
- if (!$schema->hasTable($tableName)) {
- continue;
- }
-
- $table = $schema->getTable($tableName);
- foreach ($columns as $columnName) {
- $column = $table->getColumn($columnName);
- $isAutoIncrement = $column->getAutoincrement();
- $isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
- if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
- $pendingColumns[] = $tableName . '.' . $columnName;
- }
- }
- }
-
- return $pendingColumns;
- }
-
- protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
- $objectStore = $this->config->getSystemValue('objectstore', null);
- $objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
-
- if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
- return true;
- }
-
- if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
- return true;
- }
-
- if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
- return true;
- }
-
- $tempPath = sys_get_temp_dir();
- if (!is_dir($tempPath)) {
- $this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. 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;
- }
-
- protected function imageMagickLacksSVGSupport(): bool {
- return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
- }
-
- /**
* @return DataResponse
- * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
*/
+ #[AuthorizedAdminSetting(settings: Overview::class)]
public function check() {
- $phpDefaultCharset = new PhpDefaultCharset();
- $phpOutputBuffering = new PhpOutputBuffering();
- $legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
- $checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
- $supportedDatabases = new SupportedDatabase($this->l10n, $this->connection);
- $ldapInvalidUuids = new LdapInvalidUuids($this->appManager, $this->l10n, $this->serverContainer);
-
return new DataResponse(
[
- 'isGetenvServerWorking' => !empty(getenv('PATH')),
- 'isReadOnlyConfig' => $this->isReadOnlyConfig(),
- 'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
- 'wasEmailTestSuccessful' => $this->wasEmailTestSuccessful(),
- 'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
- 'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
- 'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
- 'cronInfo' => $this->getLastCronInfo(),
- 'cronErrors' => $this->getCronErrors(),
- 'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(),
- 'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
- 'isMemcacheConfigured' => $this->isMemcacheConfigured(),
- 'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
- 'isRandomnessSecure' => $this->isRandomnessSecure(),
- 'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
- 'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
- 'phpSupported' => $this->isPhpSupported(),
- 'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
- 'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
- 'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
- 'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
- 'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
- 'OpcacheSetupRecommendations' => $this->getOpcacheSetupRecommendations(),
- 'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
- 'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
- 'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
- 'missingIndexes' => $this->hasMissingIndexes(),
- 'missingColumns' => $this->hasMissingColumns(),
- 'isSqliteUsed' => $this->isSqliteUsed(),
- 'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
- 'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
- 'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
- 'isImagickEnabled' => $this->isImagickEnabled(),
- 'areWebauthnExtensionsEnabled' => $this->areWebauthnExtensionsEnabled(),
- 'is64bit' => $this->is64bit(),
- 'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
- 'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
- 'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
- 'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
- 'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
- 'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
- PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
- PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
- LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
- CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
- 'isDefaultPhoneRegionSet' => $this->config->getSystemValueString('default_phone_region', '') !== '',
- SupportedDatabase::class => ['pass' => $supportedDatabases->run(), 'description' => $supportedDatabases->description(), 'severity' => $supportedDatabases->severity()],
- 'temporaryDirectoryWritable' => $this->isTemporaryDirectoryWritable(),
- LdapInvalidUuids::class => ['pass' => $ldapInvalidUuids->run(), 'description' => $ldapInvalidUuids->description(), 'severity' => $ldapInvalidUuids->severity()],
+ 'generic' => $this->setupCheckManager->runAll(),
]
);
}
diff --git a/apps/settings/lib/Controller/CommonSettingsTrait.php b/apps/settings/lib/Controller/CommonSettingsTrait.php
index 8ca62b9d1b3..75d2b1f2f9e 100644
--- a/apps/settings/lib/Controller/CommonSettingsTrait.php
+++ b/apps/settings/lib/Controller/CommonSettingsTrait.php
@@ -1,42 +1,32 @@
<?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/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
namespace OCA\Settings\Controller;
+use InvalidArgumentException;
+use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
+use OCA\Settings\AppInfo\Application;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
use OCP\Group\ISubAdmin;
use OCP\IGroupManager;
use OCP\INavigationManager;
use OCP\IUserSession;
+use OCP\Settings\IDeclarativeManager;
+use OCP\Settings\IDeclarativeSettingsForm;
use OCP\Settings\IIconSection;
use OCP\Settings\IManager as ISettingsManager;
use OCP\Settings\ISettings;
+use OCP\Util;
+/**
+ * @psalm-import-type DeclarativeSettingsFormSchemaWithValues from IDeclarativeSettingsForm
+ * @psalm-import-type DeclarativeSettingsFormSchemaWithoutValues from IDeclarativeSettingsForm
+ */
trait CommonSettingsTrait {
/** @var ISettingsManager */
@@ -54,28 +44,26 @@ trait CommonSettingsTrait {
/** @var ISubAdmin */
private $subAdmin;
+ private IDeclarativeManager $declarativeSettingsManager;
+
+ /** @var IInitialState */
+ private $initialState;
+
/**
* @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
+ 'forms' => [
+ 'personal' => $this->formatPersonalSections($currentType, $currentSection),
+ 'admin' => $this->formatAdminSections($currentType, $currentSection),
+ ],
];
}
/**
* @param IIconSection[][] $sections
- * @psam-param 'admin'|'personal' $type
+ * @psalm-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 {
@@ -87,7 +75,11 @@ trait CommonSettingsTrait {
} elseif ($type === 'personal') {
$settings = $this->settingsManager->getPersonalSettings($section->getID());
}
- if (empty($settings) && !($section->getID() === 'additional' && count(\OC_App::getForms('admin')) > 0)) {
+
+ /** @psalm-suppress PossiblyNullArgument */
+ $declarativeFormIDs = $this->declarativeSettingsManager->getFormIDs($this->userSession->getUser(), $type, $section->getID());
+
+ if (empty($settings) && empty($declarativeFormIDs)) {
continue;
}
@@ -107,54 +99,93 @@ trait CommonSettingsTrait {
return $templateParameters;
}
- protected function formatPersonalSections(string $currentType, string $currentSections): array {
+ protected function formatPersonalSections(string $currentType, string $currentSection): array {
$sections = $this->settingsManager->getPersonalSections();
- return $this->formatSections($sections, $currentSections, 'personal', $currentType);
+ return $this->formatSections($sections, $currentSection, 'personal', $currentType);
}
- protected function formatAdminSections(string $currentType, string $currentSections): array {
+ protected function formatAdminSections(string $currentType, string $currentSection): array {
$sections = $this->settingsManager->getAdminSections();
- return $this->formatSections($sections, $currentSections, 'admin', $currentType);
+ return $this->formatSections($sections, $currentSection, 'admin', $currentType);
}
/**
- * @param array<int, list<\OCP\Settings\ISettings>> $settings
+ * @param list<ISettings> $settings
+ * @param list<DeclarativeSettingsFormSchemaWithValues> $declarativeSettings
* @return array{content: string}
*/
- private function formatSettings(array $settings): array {
+ private function formatSettings(array $settings, array $declarativeSettings): array {
+ $settings = array_merge($settings, $declarativeSettings);
+
+ usort($settings, function ($first, $second) {
+ $priorityOne = $first instanceof ISettings ? $first->getPriority() : $first['priority'];
+ $priorityTwo = $second instanceof ISettings ? $second->getPriority() : $second['priority'];
+ return $priorityOne - $priorityTwo;
+ });
+
$html = '';
- foreach ($settings as $prioritizedSettings) {
- foreach ($prioritizedSettings as $setting) {
- /** @var ISettings $setting */
+ foreach ($settings as $setting) {
+ if ($setting instanceof ISettings) {
$form = $setting->getForm();
$html .= $form->renderAs('')->render();
+ } else {
+ $html .= '<div id="' . $setting['app'] . '_' . $setting['id'] . '"></div>';
}
}
return ['content' => $html];
}
+ /**
+ * @psalm-param 'admin'|'personal' $type
+ */
private function getIndexResponse(string $type, string $section): TemplateResponse {
+ $user = $this->userSession->getUser();
+ assert($user !== null, 'No user logged in for settings');
+
+ $this->declarativeSettingsManager->loadSchemas();
+ $declarativeSettings = $this->declarativeSettingsManager->getFormsWithValues($user, $type, $section);
+
+ foreach ($declarativeSettings as &$form) {
+ foreach ($form['fields'] as &$field) {
+ if (isset($field['sensitive']) && $field['sensitive'] === true && !empty($field['value'])) {
+ $field['value'] = 'dummySecret';
+ }
+ }
+ }
+
if ($type === 'personal') {
+ $settings = array_values($this->settingsManager->getPersonalSettings($section));
if ($section === 'theming') {
$this->navigationManager->setActiveEntry('accessibility_settings');
} else {
$this->navigationManager->setActiveEntry('settings');
}
} elseif ($type === 'admin') {
+ $settings = array_values($this->settingsManager->getAllowedAdminSettings($section, $user));
+ if (empty($settings) && empty($declarativeSettings)) {
+ throw new NotAdminException('Logged in user does not have permission to access these settings.');
+ }
$this->navigationManager->setActiveEntry('admin_settings');
+ } else {
+ throw new InvalidArgumentException('$type must be either "admin" or "personal"');
+ }
+
+ if (!empty($declarativeSettings)) {
+ Util::addScript(Application::APP_ID, 'declarative-settings-forms');
+ $this->initialState->provideInitialState('declarative-settings-forms', $declarativeSettings);
}
- $templateParams = [];
+ $settings = array_merge(...$settings);
+ $templateParams = $this->formatSettings($settings, $declarativeSettings);
$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);
}
diff --git a/apps/settings/lib/Controller/DeclarativeSettingsController.php b/apps/settings/lib/Controller/DeclarativeSettingsController.php
new file mode 100644
index 00000000000..4e4bee4043c
--- /dev/null
+++ b/apps/settings/lib/Controller/DeclarativeSettingsController.php
@@ -0,0 +1,131 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Settings\Controller;
+
+use Exception;
+use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
+use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
+use OCA\Settings\ResponseDefinitions;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCS\OCSBadRequestException;
+use OCP\AppFramework\OCSController;
+use OCP\IRequest;
+use OCP\IUserSession;
+use OCP\Settings\IDeclarativeManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @psalm-import-type SettingsDeclarativeForm from ResponseDefinitions
+ */
+class DeclarativeSettingsController extends OCSController {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private IUserSession $userSession,
+ private IDeclarativeManager $declarativeManager,
+ private LoggerInterface $logger,
+ ) {
+ parent::__construct($appName, $request);
+ }
+
+ /**
+ * Sets a declarative settings value
+ *
+ * @param string $app ID of the app
+ * @param string $formId ID of the form
+ * @param string $fieldId ID of the field
+ * @param mixed $value Value to be saved
+ * @return DataResponse<Http::STATUS_OK, null, array{}>
+ * @throws NotLoggedInException Not logged in or not an admin user
+ * @throws NotAdminException Not logged in or not an admin user
+ * @throws OCSBadRequestException Invalid arguments to save value
+ *
+ * 200: Value set successfully
+ */
+ #[NoAdminRequired]
+ public function setValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
+ return $this->saveValue($app, $formId, $fieldId, $value);
+ }
+
+ /**
+ * Sets a declarative settings value.
+ * Password confirmation is required for sensitive values.
+ *
+ * @param string $app ID of the app
+ * @param string $formId ID of the form
+ * @param string $fieldId ID of the field
+ * @param mixed $value Value to be saved
+ * @return DataResponse<Http::STATUS_OK, null, array{}>
+ * @throws NotLoggedInException Not logged in or not an admin user
+ * @throws NotAdminException Not logged in or not an admin user
+ * @throws OCSBadRequestException Invalid arguments to save value
+ *
+ * 200: Value set successfully
+ */
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
+ public function setSensitiveValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
+ return $this->saveValue($app, $formId, $fieldId, $value);
+ }
+
+ /**
+ * Sets a declarative settings value.
+ *
+ * @param string $app ID of the app
+ * @param string $formId ID of the form
+ * @param string $fieldId ID of the field
+ * @param mixed $value Value to be saved
+ * @return DataResponse<Http::STATUS_OK, null, array{}>
+ * @throws NotLoggedInException Not logged in or not an admin user
+ * @throws NotAdminException Not logged in or not an admin user
+ * @throws OCSBadRequestException Invalid arguments to save value
+ *
+ * 200: Value set successfully
+ */
+ private function saveValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
+ $user = $this->userSession->getUser();
+ if ($user === null) {
+ throw new NotLoggedInException();
+ }
+
+ try {
+ $this->declarativeManager->loadSchemas();
+ $this->declarativeManager->setValue($user, $app, $formId, $fieldId, $value);
+ return new DataResponse(null);
+ } catch (NotAdminException $e) {
+ throw $e;
+ } catch (Exception $e) {
+ $this->logger->error('Failed to set declarative settings value: ' . $e->getMessage());
+ throw new OCSBadRequestException();
+ }
+ }
+
+ /**
+ * Gets all declarative forms with the values prefilled.
+ *
+ * @return DataResponse<Http::STATUS_OK, list<SettingsDeclarativeForm>, array{}>
+ * @throws NotLoggedInException
+ * @NoSubAdminRequired
+ *
+ * 200: Forms returned
+ */
+ #[NoAdminRequired]
+ public function getForms(): DataResponse {
+ $user = $this->userSession->getUser();
+ if ($user === null) {
+ throw new NotLoggedInException();
+ }
+ $this->declarativeManager->loadSchemas();
+ return new DataResponse($this->declarativeManager->getFormsWithValues($user, null, null));
+ }
+}
diff --git a/apps/settings/lib/Controller/HelpController.php b/apps/settings/lib/Controller/HelpController.php
index 38ce84843ed..05bff158ee6 100644
--- a/apps/settings/lib/Controller/HelpController.php
+++ b/apps/settings/lib/Controller/HelpController.php
@@ -3,79 +3,50 @@
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>
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Controller;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IAppConfig;
+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;
-
public function __construct(
string $appName,
IRequest $request,
- INavigationManager $navigationManager,
- IURLGenerator $urlGenerator,
- ?string $userId,
- IGroupManager $groupManager,
- IL10N $l10n
+ private INavigationManager $navigationManager,
+ private IURLGenerator $urlGenerator,
+ /** @var string */
+ private ?string $userId,
+ private IGroupManager $groupManager,
+ private IL10N $l10n,
+ private IConfig $config,
+ private IAppConfig $appConfig,
) {
parent::__construct($appName, $request);
- $this->navigationManager = $navigationManager;
- $this->urlGenerator = $urlGenerator;
- $this->userId = $userId;
- $this->groupManager = $groupManager;
- $this->l10n = $l10n;
}
/**
* @return TemplateResponse
*
- * @NoCSRFRequired
- * @NoAdminRequired
* @NoSubAdminRequired
*/
+ #[NoCSRFRequired]
+ #[NoAdminRequired]
public function help(string $mode = 'user'): TemplateResponse {
$this->navigationManager->setActiveEntry('help');
$pageTitle = $this->l10n->t('Administrator documentation');
@@ -91,6 +62,16 @@ class HelpController extends Controller {
$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');
+ }
+
+ $legalNoticeUrl = $this->appConfig->getValueString('theming', 'imprintUrl');
+ $privacyUrl = $this->appConfig->getValueString('theming', 'privacyUrl');
+
$response = new TemplateResponse('settings', 'help', [
'admin' => $this->groupManager->isAdmin($this->userId),
'url' => $documentationUrl,
@@ -98,6 +79,9 @@ class HelpController extends Controller {
'urlAdminDocs' => $urlAdminDocs,
'mode' => $mode,
'pageTitle' => $pageTitle,
+ 'knowledgebaseEmbedded' => $knowledgebaseEmbedded,
+ 'legalNoticeUrl' => $legalNoticeUrl,
+ 'privacyUrl' => $privacyUrl,
]);
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain('\'self\'');
diff --git a/apps/settings/lib/Controller/LogSettingsController.php b/apps/settings/lib/Controller/LogSettingsController.php
index 08c18189d03..90cf4549d2f 100644
--- a/apps/settings/lib/Controller/LogSettingsController.php
+++ b/apps/settings/lib/Controller/LogSettingsController.php
@@ -1,33 +1,17 @@
<?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>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Settings\Controller;
use OC\Log;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\StreamResponse;
use OCP\IRequest;
@@ -44,17 +28,23 @@ class LogSettingsController extends Controller {
/**
* download logfile
*
- * @NoCSRFRequired
+ * @return StreamResponse<Http::STATUS_OK, array{Content-Type: 'application/octet-stream', 'Content-Disposition': 'attachment; filename="nextcloud.log"'}>
*
- * @return StreamResponse
+ * 200: Logfile returned
*/
+ #[NoCSRFRequired]
+ #[OpenAPI(scope: OpenAPI::SCOPE_ADMINISTRATION)]
public function download() {
if (!$this->log instanceof Log) {
throw new \UnexpectedValueException('Log file not available');
}
- $resp = new StreamResponse($this->log->getLogPath());
- $resp->addHeader('Content-Type', 'application/octet-stream');
- $resp->addHeader('Content-Disposition', 'attachment; filename="nextcloud.log"');
- return $resp;
+ return new StreamResponse(
+ $this->log->getLogPath(),
+ Http::STATUS_OK,
+ [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Disposition' => 'attachment; filename="nextcloud.log"',
+ ],
+ );
}
}
diff --git a/apps/settings/lib/Controller/MailSettingsController.php b/apps/settings/lib/Controller/MailSettingsController.php
index 2df79b67731..f1e3b8032dc 100644
--- a/apps/settings/lib/Controller/MailSettingsController.php
+++ b/apps/settings/lib/Controller/MailSettingsController.php
@@ -1,34 +1,17 @@
<?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/>
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Settings\Controller;
+use OCA\Settings\Settings\Admin\Overview;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IConfig;
use OCP\IL10N;
@@ -39,17 +22,6 @@ 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
@@ -59,52 +31,51 @@ class MailSettingsController extends Controller {
* @param IURLGenerator $urlGenerator,
* @param IMailer $mailer
*/
- public function __construct($appName,
- IRequest $request,
- IL10N $l10n,
- IConfig $config,
- IUserSession $userSession,
- IURLGenerator $urlGenerator,
- IMailer $mailer) {
+ public function __construct(
+ $appName,
+ IRequest $request,
+ private IL10N $l10n,
+ private IConfig $config,
+ private IUserSession $userSession,
+ private IURLGenerator $urlGenerator,
+ private 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) {
+ #[AuthorizedAdminSetting(settings: Overview::class)]
+ #[PasswordConfirmationRequired]
+ public function setMailSettings(
+ string $mail_domain,
+ string $mail_from_address,
+ string $mail_smtpmode,
+ string $mail_smtpsecure,
+ string $mail_smtphost,
+ ?string $mail_smtpauth,
+ string $mail_smtpport,
+ string $mail_sendmailmode,
+ ): DataResponse {
+ $mail_smtpauth = $mail_smtpauth == '1';
+
+ $configs = [
+ 'mail_domain' => $mail_domain,
+ 'mail_from_address' => $mail_from_address,
+ 'mail_smtpmode' => $mail_smtpmode,
+ 'mail_smtpsecure' => $mail_smtpsecure,
+ 'mail_smtphost' => $mail_smtphost,
+ 'mail_smtpauth' => $mail_smtpauth,
+ 'mail_smtpport' => $mail_smtpport,
+ 'mail_sendmailmode' => $mail_sendmailmode,
+ ];
+ foreach ($configs as $key => $value) {
$configs[$key] = empty($value) ? null : $value;
}
// Delete passwords from config in case no auth is specified
- if ($params['mail_smtpauth'] !== 1) {
+ if (!$mail_smtpauth) {
$configs['mail_smtpname'] = null;
$configs['mail_smtppassword'] = null;
}
@@ -119,13 +90,12 @@ class MailSettingsController extends Controller {
/**
* 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
*/
+ #[AuthorizedAdminSetting(settings: Overview::class)]
+ #[PasswordConfirmationRequired]
public function storeCredentials($mail_smtpname, $mail_smtppassword) {
if ($mail_smtppassword === '********') {
return new DataResponse($this->l10n->t('Invalid SMTP password.'), Http::STATUS_BAD_REQUEST);
@@ -143,9 +113,9 @@ class MailSettingsController extends Controller {
/**
* Send a mail to test the settings
- * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
* @return DataResponse
*/
+ #[AuthorizedAdminSetting(settings: Overview::class)]
public function sendTestMail() {
$email = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'email', '');
if (!empty($email)) {
@@ -180,6 +150,6 @@ class MailSettingsController extends Controller {
}
$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);
+ return new DataResponse($this->l10n->t('You need to set your account email before being able to send test emails. Go to %s for that.', [$this->urlGenerator->linkToRouteAbsolute('settings.PersonalSettings.index')]), Http::STATUS_BAD_REQUEST);
}
}
diff --git a/apps/settings/lib/Controller/PersonalSettingsController.php b/apps/settings/lib/Controller/PersonalSettingsController.php
index 8781ecd214e..340ca3f93eb 100644
--- a/apps/settings/lib/Controller/PersonalSettingsController.php
+++ b/apps/settings/lib/Controller/PersonalSettingsController.php
@@ -1,40 +1,26 @@
<?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>
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Controller;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
use OCP\Group\ISubAdmin;
use OCP\IGroupManager;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IUserSession;
+use OCP\Settings\IDeclarativeManager;
use OCP\Settings\IManager as ISettingsManager;
-use OCP\Template;
+#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class PersonalSettingsController extends Controller {
use CommonSettingsTrait;
@@ -45,7 +31,9 @@ class PersonalSettingsController extends Controller {
ISettingsManager $settingsManager,
IUserSession $userSession,
IGroupManager $groupManager,
- ISubAdmin $subAdmin
+ ISubAdmin $subAdmin,
+ IDeclarativeManager $declarativeSettingsManager,
+ IInitialState $initialState,
) {
parent::__construct($appName, $request);
$this->navigationManager = $navigationManager;
@@ -53,57 +41,19 @@ class PersonalSettingsController extends Controller {
$this->userSession = $userSession;
$this->subAdmin = $subAdmin;
$this->groupManager = $groupManager;
+ $this->declarativeSettingsManager = $declarativeSettingsManager;
+ $this->initialState = $initialState;
}
/**
- * @NoCSRFRequired
- * @NoAdminRequired
* @NoSubAdminRequired
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
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();
+ return $this->getIndexResponse(
+ 'personal',
+ $section,
+ );
}
}
diff --git a/apps/settings/lib/Controller/ReasonsController.php b/apps/settings/lib/Controller/ReasonsController.php
index 2ceb7005407..91d0a8640d1 100644
--- a/apps/settings/lib/Controller/ReasonsController.php
+++ b/apps/settings/lib/Controller/ReasonsController.php
@@ -3,38 +3,25 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Jan C. Borchardt <hey@jancborchardt.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Controller;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataDisplayResponse;
+#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class ReasonsController extends Controller {
/**
- * @NoCSRFRequired
- * @NoAdminRequired
* @NoSubAdminRequired
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
public function getPdf() {
$data = file_get_contents(__DIR__ . '/../../data/Reasons to use Nextcloud.pdf');
diff --git a/apps/settings/lib/Controller/TwoFactorSettingsController.php b/apps/settings/lib/Controller/TwoFactorSettingsController.php
index 45722767cda..e08fca8ec6c 100644
--- a/apps/settings/lib/Controller/TwoFactorSettingsController.php
+++ b/apps/settings/lib/Controller/TwoFactorSettingsController.php
@@ -3,25 +3,8 @@
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/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Controller;
@@ -37,8 +20,8 @@ class TwoFactorSettingsController extends Controller {
private $mandatoryTwoFactor;
public function __construct(string $appName,
- IRequest $request,
- MandatoryTwoFactor $mandatoryTwoFactor) {
+ IRequest $request,
+ MandatoryTwoFactor $mandatoryTwoFactor) {
parent::__construct($appName, $request);
$this->mandatoryTwoFactor = $mandatoryTwoFactor;
diff --git a/apps/settings/lib/Controller/UsersController.php b/apps/settings/lib/Controller/UsersController.php
index 217abf0e156..8efd3eeb8ca 100644
--- a/apps/settings/lib/Controller/UsersController.php
+++ b/apps/settings/lib/Controller/UsersController.php
@@ -3,37 +3,10 @@
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>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
-// FIXME: disabled for now to be able to inject IGroupManager and also use
-// getSubAdmin()
namespace OCA\Settings\Controller;
@@ -41,145 +14,112 @@ use InvalidArgumentException;
use OC\AppFramework\Http;
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
use OC\ForbiddenException;
-use OC\Group\Manager as GroupManager;
+use OC\Group\MetaData;
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\Settings\Settings\Admin\Users;
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\AuthorizedAdminSetting;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
use OCP\BackgroundJob\IJobList;
use OCP\Encryption\IManager;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Group\ISubAdmin;
use OCP\IConfig;
+use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IL10N;
+use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IUser;
-use OCP\IUserManager;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\Mail\IMailer;
+use OCP\Util;
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;
-
+ /** Limit for counting users for subadmins, to avoid spending too much time */
+ private const COUNT_LIMIT_FOR_SUBADMINS = 999;
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
+ private UserManager $userManager,
+ private IGroupManager $groupManager,
+ private IUserSession $userSession,
+ private IConfig $config,
+ private IL10N $l10n,
+ private IMailer $mailer,
+ private IFactory $l10nFactory,
+ private IAppManager $appManager,
+ private IAccountManager $accountManager,
+ private Manager $keyManager,
+ private IJobList $jobList,
+ private IManager $encryptionManager,
+ private KnownUserService $knownUserService,
+ private IEventDispatcher $dispatcher,
+ private IInitialState $initialState,
) {
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();
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
+ public function usersListByGroup(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
+ return $this->usersList($navigationManager, $subAdmin);
}
/**
- * @NoCSRFRequired
- * @NoAdminRequired
- *
* Display users list template
*
* @return TemplateResponse
*/
- public function usersList(): TemplateResponse {
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
+ public function usersList(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
$user = $this->userSession->getUser();
$uid = $user->getUID();
+ $isAdmin = $this->groupManager->isAdmin($uid);
+ $isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($uid);
- \OC::$server->getNavigationManager()->setActiveEntry('core_users');
+ $navigationManager->setActiveEntry('core_users');
/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
- $sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
+ $sortGroupsBy = MetaData::SORT_USERCOUNT;
$isLDAPUsed = false;
- if ($this->config->getSystemValue('sort_groups_by_name', false)) {
- $sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
+ if ($this->config->getSystemValueBool('sort_groups_by_name', false)) {
+ $sortGroupsBy = MetaData::SORT_GROUPNAME;
} else {
if ($this->appManager->isEnabledForUser('user_ldap')) {
- $isLDAPUsed =
- $this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
+ $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;
+ $sortGroupsBy = MetaData::SORT_GROUPNAME;
}
}
}
@@ -187,15 +127,23 @@ class UsersController extends Controller {
$canChangePassword = $this->canAdminChangeUserPasswords();
/* GROUPS */
- $groupsInfo = new \OC\Group\MetaData(
+ $groupsInfo = new MetaData(
$uid,
- $this->isAdmin,
+ $isAdmin,
+ $isDelegatedAdmin,
$this->groupManager,
$this->userSession
);
- $groupsInfo->setSorting($sortGroupsBy);
- [$adminGroup, $groups] = $groupsInfo->get();
+ $adminGroup = $this->groupManager->get('admin');
+ $adminGroupData = [
+ 'id' => $adminGroup->getGID(),
+ 'name' => $adminGroup->getDisplayName(),
+ 'usercount' => $sortGroupsBy === MetaData::SORT_USERCOUNT ? $adminGroup->count() : 0,
+ 'disabled' => $adminGroup->countDisabled(),
+ 'canAdd' => $adminGroup->canAddUser(),
+ 'canRemove' => $adminGroup->canRemoveUser(),
+ ];
if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
@@ -207,40 +155,41 @@ class UsersController extends Controller {
$userCount = 0;
if (!$isLDAPUsed) {
- if ($this->isAdmin) {
+ if ($isAdmin || $isDelegatedAdmin) {
$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'];
- array_push($groupsNames, $group['name']);
- // we prevent subadmins from looking up themselves
- // so we lower the count of the groups he belongs to
- if (array_key_exists($group['id'], $userGroups)) {
- $groups[$key]['usercount']--;
- $userCount -= 1; // we also lower from one the total count
- }
- }
- $userCount += $this->userManager->countUsersOfGroups($groupsInfo->getGroups());
- $disabledUsers = $this->userManager->countDisabledUsersOfGroups($groupsNames);
+ [$userCount,$disabledUsers] = $this->userManager->countUsersAndDisabledUsersOfGroups($groupsInfo->getGroups(), self::COUNT_LIMIT_FOR_SUBADMINS);
}
- $userCount -= $disabledUsers;
+ if ($disabledUsers > 0) {
+ $userCount -= $disabledUsers;
+ }
}
+ $recentUsersGroup = [
+ 'id' => '__nc_internal_recent',
+ 'name' => $this->l10n->t('Recently active'),
+ 'usercount' => $this->userManager->countSeenUsers(),
+ ];
+
$disabledUsersGroup = [
'id' => 'disabled',
- 'name' => 'Disabled users',
+ 'name' => $this->l10n->t('Disabled accounts'),
'usercount' => $disabledUsers
];
+ if (!$isAdmin && !$isDelegatedAdmin) {
+ $subAdminGroups = array_map(
+ fn (IGroup $group) => ['id' => $group->getGID(), 'name' => $group->getDisplayName()],
+ $subAdmin->getSubAdminsGroups($user),
+ );
+ $subAdminGroups = array_values($subAdminGroups);
+ }
+
/* 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';
@@ -257,13 +206,21 @@ class UsersController extends Controller {
/* LANGUAGES */
$languages = $this->l10nFactory->getLanguages();
+ /** Using LDAP or admins (system config) can enfore sorting by group name, in this case the frontend setting is overwritten */
+ $forceSortGroupByName = $sortGroupsBy === MetaData::SORT_GROUPNAME;
+
/* FINAL DATA */
$serverData = [];
// groups
- $serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
+ $serverData['systemGroups'] = [$adminGroupData, $recentUsersGroup, $disabledUsersGroup];
+ $serverData['subAdminGroups'] = $subAdminGroups ?? [];
// Various data
- $serverData['isAdmin'] = $this->isAdmin;
- $serverData['sortGroups'] = $sortGroupsBy;
+ $serverData['isAdmin'] = $isAdmin;
+ $serverData['isDelegatedAdmin'] = $isDelegatedAdmin;
+ $serverData['sortGroups'] = $forceSortGroupByName
+ ? MetaData::SORT_GROUPNAME
+ : (int)$this->config->getAppValue('core', 'group.sortBy', (string)MetaData::SORT_USERCOUNT);
+ $serverData['forceSortGroupByName'] = $forceSortGroupByName;
$serverData['quotaPreset'] = $quotaPreset;
$serverData['allowUnlimitedQuota'] = $allowUnlimitedQuota;
$serverData['userCount'] = $userCount;
@@ -277,7 +234,12 @@ class UsersController extends Controller {
$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]);
+ $this->initialState->provideInitialState('usersSettings', $serverData);
+
+ Util::addStyle('settings', 'settings');
+ Util::addScript('settings', 'vue-settings-apps-users-management');
+
+ return new TemplateResponse('settings', 'settings/empty', ['pageTitle' => $this->l10n->t('Settings')]);
}
/**
@@ -286,8 +248,9 @@ class UsersController extends Controller {
*
* @return JSONResponse
*/
+ #[AuthorizedAdminSetting(settings:Users::class)]
public function setPreference(string $key, string $value): JSONResponse {
- $allowed = ['newUser.sendEmail'];
+ $allowed = ['newUser.sendEmail', 'group.sortBy'];
if (!in_array($key, $allowed, true)) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
@@ -341,9 +304,7 @@ class UsersController extends Controller {
}
/**
- * @NoAdminRequired
* @NoSubAdminRequired
- * @PasswordConfirmationRequired
*
* @param string|null $avatarScope
* @param string|null $displayname
@@ -358,26 +319,39 @@ class UsersController extends Controller {
* @param string|null $addressScope
* @param string|null $twitter
* @param string|null $twitterScope
+ * @param string|null $bluesky
+ * @param string|null $blueskyScope
* @param string|null $fediverse
* @param string|null $fediverseScope
+ * @param string|null $birthdate
+ * @param string|null $birthdateScope
*
* @return DataResponse
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
+ #[UserRateLimit(limit: 5, period: 60)]
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
+ ?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 $bluesky = null,
+ ?string $blueskyScope = null,
+ ?string $fediverse = null,
+ ?string $fediverseScope = null,
+ ?string $birthdate = null,
+ ?string $birthdateScope = null,
+ ?string $pronouns = null,
+ ?string $pronounsScope = null,
) {
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
@@ -385,7 +359,7 @@ class UsersController extends Controller {
[
'status' => 'error',
'data' => [
- 'message' => $this->l10n->t('Invalid user')
+ 'message' => $this->l10n->t('Invalid account')
]
],
Http::STATUS_UNAUTHORIZED
@@ -416,7 +390,10 @@ class UsersController extends Controller {
IAccountManager::PROPERTY_ADDRESS => ['value' => $address, 'scope' => $addressScope],
IAccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
+ IAccountManager::PROPERTY_BLUESKY => ['value' => $bluesky, 'scope' => $blueskyScope],
IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
+ IAccountManager::PROPERTY_BIRTHDATE => ['value' => $birthdate, 'scope' => $birthdateScope],
+ IAccountManager::PROPERTY_PRONOUNS => ['value' => $pronouns, 'scope' => $pronounsScope],
];
$allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
foreach ($updatable as $property => $data) {
@@ -425,10 +402,10 @@ class UsersController extends Controller {
continue;
}
$property = $userAccount->getProperty($property);
- if (null !== $data['value']) {
+ if ($data['value'] !== null) {
$property->setValue($data['value']);
}
- if (null !== $data['scope']) {
+ if ($data['scope'] !== null) {
$property->setScope($data['scope']);
}
}
@@ -456,14 +433,20 @@ class UsersController extends Controller {
'addressScope' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getScope(),
'twitter' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(),
'twitterScope' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
+ 'bluesky' => $userAccount->getProperty(IAccountManager::PROPERTY_BLUESKY)->getValue(),
+ 'blueskyScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BLUESKY)->getScope(),
'fediverse' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue(),
'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
+ 'birthdate' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getValue(),
+ 'birthdateScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getScope(),
+ 'pronouns' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getValue(),
+ 'pronounsScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getScope(),
'message' => $this->l10n->t('Settings saved'),
],
],
Http::STATUS_OK
);
- } catch (ForbiddenException | InvalidArgumentException | PropertyDoesNotExistException $e) {
+ } catch (ForbiddenException|InvalidArgumentException|PropertyDoesNotExistException $e) {
return new DataResponse([
'status' => 'error',
'data' => [
@@ -516,14 +499,14 @@ class UsersController extends Controller {
/**
* 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
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
$user = $this->userSession->getUser();
diff --git a/apps/settings/lib/Controller/WebAuthnController.php b/apps/settings/lib/Controller/WebAuthnController.php
index 2692882301d..495b58e6a4b 100644
--- a/apps/settings/lib/Controller/WebAuthnController.php
+++ b/apps/settings/lib/Controller/WebAuthnController.php
@@ -3,26 +3,8 @@
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>
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Controller;
@@ -30,46 +12,39 @@ use OC\Authentication\WebAuthn\Manager;
use OCA\Settings\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
+use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\JSONResponse;
-use OCP\ILogger;
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';
- /** @var Manager */
- private $manager;
-
- /** @var IUserSession */
- private $userSession;
- /**
- * @var ISession
- */
- private $session;
- /**
- * @var ILogger
- */
- private $logger;
-
- public function __construct(IRequest $request, ILogger $logger, Manager $webAuthnManager, IUserSession $userSession, ISession $session) {
+ public function __construct(
+ IRequest $request,
+ private LoggerInterface $logger,
+ private Manager $manager,
+ private IUserSession $userSession,
+ private ISession $session,
+ ) {
parent::__construct(Application::APP_ID, $request);
-
- $this->manager = $webAuthnManager;
- $this->userSession = $userSession;
- $this->session = $session;
- $this->logger = $logger;
}
/**
- * @NoAdminRequired
* @NoSubAdminRequired
- * @PasswordConfirmationRequired
- * @UseSession
- * @NoCSRFRequired
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
+ #[UseSession]
+ #[NoCSRFRequired]
public function startRegistration(): JSONResponse {
$this->logger->debug('Starting WebAuthn registration');
@@ -82,11 +57,11 @@ class WebAuthnController extends Controller {
}
/**
- * @NoAdminRequired
* @NoSubAdminRequired
- * @PasswordConfirmationRequired
- * @UseSession
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
+ #[UseSession]
public function finishRegistration(string $name, string $data): JSONResponse {
$this->logger->debug('Finishing WebAuthn registration');
@@ -104,10 +79,10 @@ class WebAuthnController extends Controller {
}
/**
- * @NoAdminRequired
* @NoSubAdminRequired
- * @PasswordConfirmationRequired
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
public function deleteRegistration(int $id): JSONResponse {
$this->logger->debug('Finishing WebAuthn registration');