diff options
Diffstat (limited to 'apps/settings/lib')
39 files changed, 232 insertions, 57 deletions
diff --git a/apps/settings/lib/Activity/GroupProvider.php b/apps/settings/lib/Activity/GroupProvider.php index 72c9ca0d719..2d492265cf4 100644 --- a/apps/settings/lib/Activity/GroupProvider.php +++ b/apps/settings/lib/Activity/GroupProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Activity/GroupSetting.php b/apps/settings/lib/Activity/GroupSetting.php index 9433bdb25d2..917f4a7ef26 100644 --- a/apps/settings/lib/Activity/GroupSetting.php +++ b/apps/settings/lib/Activity/GroupSetting.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Activity/SecurityFilter.php b/apps/settings/lib/Activity/SecurityFilter.php index 5ff6685f449..9a32e82a984 100644 --- a/apps/settings/lib/Activity/SecurityFilter.php +++ b/apps/settings/lib/Activity/SecurityFilter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Activity/SecuritySetting.php b/apps/settings/lib/Activity/SecuritySetting.php index 997e63a97b1..9226b5aea5b 100644 --- a/apps/settings/lib/Activity/SecuritySetting.php +++ b/apps/settings/lib/Activity/SecuritySetting.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Activity/Setting.php b/apps/settings/lib/Activity/Setting.php index 621dadda11d..f9c659594d6 100644 --- a/apps/settings/lib/Activity/Setting.php +++ b/apps/settings/lib/Activity/Setting.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php index f4737468716..6e59e56fe82 100644 --- a/apps/settings/lib/AppInfo/Application.php +++ b/apps/settings/lib/AppInfo/Application.php @@ -70,6 +70,7 @@ use OCA\Settings\SetupChecks\SchedulingTableSize; use OCA\Settings\SetupChecks\SecurityHeaders; use OCA\Settings\SetupChecks\SupportedDatabase; use OCA\Settings\SetupChecks\SystemIs64bit; +use OCA\Settings\SetupChecks\TaskProcessingPickupSpeed; use OCA\Settings\SetupChecks\TempSpaceAvailable; use OCA\Settings\SetupChecks\TransactionIsolation; use OCA\Settings\SetupChecks\WellKnownUrls; @@ -206,6 +207,7 @@ class Application extends App implements IBootstrap { $context->registerSetupCheck(SchedulingTableSize::class); $context->registerSetupCheck(SupportedDatabase::class); $context->registerSetupCheck(SystemIs64bit::class); + $context->registerSetupCheck(TaskProcessingPickupSpeed::class); $context->registerSetupCheck(TempSpaceAvailable::class); $context->registerSetupCheck(TransactionIsolation::class); $context->registerSetupCheck(PushService::class); diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php index e08ec616e80..a3b0a874987 100644 --- a/apps/settings/lib/Controller/AISettingsController.php +++ b/apps/settings/lib/Controller/AISettingsController.php @@ -38,7 +38,7 @@ class AISettingsController extends Controller { */ #[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']; + $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; diff --git a/apps/settings/lib/Controller/AdminSettingsController.php b/apps/settings/lib/Controller/AdminSettingsController.php index 2b731c5cdde..15e2c392148 100644 --- a/apps/settings/lib/Controller/AdminSettingsController.php +++ b/apps/settings/lib/Controller/AdminSettingsController.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Controller/AppSettingsController.php b/apps/settings/lib/Controller/AppSettingsController.php index df563ac46b7..a85ee8cc20a 100644 --- a/apps/settings/lib/Controller/AppSettingsController.php +++ b/apps/settings/lib/Controller/AppSettingsController.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -22,7 +23,6 @@ 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\Attribute\PublicPage; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\FileDisplayResponse; use OCP\AppFramework\Http\JSONResponse; @@ -44,7 +44,9 @@ 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; @@ -128,9 +130,8 @@ class AppSettingsController extends Controller { * @param string $image * @throws \Exception */ - #[PublicPage] #[NoCSRFRequired] - public function getAppDiscoverMedia(string $fileName): Response { + public function getAppDiscoverMedia(string $fileName, ILimiter $limiter, IUserSession $session): Response { $getEtag = $this->discoverFetcher->getETag() ?? date('Y-m'); $etag = trim($getEtag, '"'); @@ -160,6 +161,26 @@ class AppSettingsController extends Controller { $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); @@ -181,6 +202,31 @@ class AppSettingsController extends Controller { 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 diff --git a/apps/settings/lib/Controller/AuthorizedGroupController.php b/apps/settings/lib/Controller/AuthorizedGroupController.php index ad01b590440..82a1ca4703e 100644 --- a/apps/settings/lib/Controller/AuthorizedGroupController.php +++ b/apps/settings/lib/Controller/AuthorizedGroupController.php @@ -57,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 2154a9ab11b..a874a47c16a 100644 --- a/apps/settings/lib/Controller/ChangePasswordController.php +++ b/apps/settings/lib/Controller/ChangePasswordController.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -125,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', diff --git a/apps/settings/lib/Controller/CommonSettingsTrait.php b/apps/settings/lib/Controller/CommonSettingsTrait.php index 56760c10f81..75d2b1f2f9e 100644 --- a/apps/settings/lib/Controller/CommonSettingsTrait.php +++ b/apps/settings/lib/Controller/CommonSettingsTrait.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -144,6 +145,14 @@ trait CommonSettingsTrait { $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') { diff --git a/apps/settings/lib/Controller/DeclarativeSettingsController.php b/apps/settings/lib/Controller/DeclarativeSettingsController.php index eb9d45839de..4e4bee4043c 100644 --- a/apps/settings/lib/Controller/DeclarativeSettingsController.php +++ b/apps/settings/lib/Controller/DeclarativeSettingsController.php @@ -15,6 +15,7 @@ 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; @@ -53,6 +54,45 @@ class DeclarativeSettingsController extends OCSController { */ #[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(); diff --git a/apps/settings/lib/Controller/MailSettingsController.php b/apps/settings/lib/Controller/MailSettingsController.php index 13871bc13b6..f1e3b8032dc 100644 --- a/apps/settings/lib/Controller/MailSettingsController.php +++ b/apps/settings/lib/Controller/MailSettingsController.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/apps/settings/lib/Controller/PersonalSettingsController.php b/apps/settings/lib/Controller/PersonalSettingsController.php index 0a87181c7d7..340ca3f93eb 100644 --- a/apps/settings/lib/Controller/PersonalSettingsController.php +++ b/apps/settings/lib/Controller/PersonalSettingsController.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Controller/UsersController.php b/apps/settings/lib/Controller/UsersController.php index a720425ab3c..6cd596d6cc8 100644 --- a/apps/settings/lib/Controller/UsersController.php +++ b/apps/settings/lib/Controller/UsersController.php @@ -115,8 +115,8 @@ class UsersController extends Controller { $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 = MetaData::SORT_GROUPNAME; diff --git a/apps/settings/lib/Hooks.php b/apps/settings/lib/Hooks.php index d4ee93597e2..f59013ca5e1 100644 --- a/apps/settings/lib/Hooks.php +++ b/apps/settings/lib/Hooks.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -111,8 +112,8 @@ class Hooks { * @throws \BadMethodCallException */ public function onChangeEmail(IUser $user, $oldMailAddress) { - if ($oldMailAddress === $user->getEMailAddress() || - $user->getLastLogin() === 0) { + if ($oldMailAddress === $user->getEMailAddress() + || $user->getLastLogin() === 0) { // Email didn't really change or user didn't login, // so don't create activities and emails. return; diff --git a/apps/settings/lib/Listener/MailProviderListener.php b/apps/settings/lib/Listener/MailProviderListener.php index 974378c0006..61446f1e6cb 100644 --- a/apps/settings/lib/Listener/MailProviderListener.php +++ b/apps/settings/lib/Listener/MailProviderListener.php @@ -38,11 +38,11 @@ class MailProviderListener implements IEventListener { $this->handleSetValue($event); return; } - + } private function handleGetValue(DeclarativeSettingsGetValueEvent $event): void { - + if ($event->getFieldId() === 'mail_providers_enabled') { $event->setValue((int)$this->config->getValueBool('core', 'mail_providers_enabled', true)); } diff --git a/apps/settings/lib/Mailer/NewUserMailHelper.php b/apps/settings/lib/Mailer/NewUserMailHelper.php index bd4de2b2087..202495a020e 100644 --- a/apps/settings/lib/Mailer/NewUserMailHelper.php +++ b/apps/settings/lib/Mailer/NewUserMailHelper.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/ResponseDefinitions.php b/apps/settings/lib/ResponseDefinitions.php index 0f3d6bffccf..12adefda91f 100644 --- a/apps/settings/lib/ResponseDefinitions.php +++ b/apps/settings/lib/ResponseDefinitions.php @@ -20,6 +20,7 @@ namespace OCA\Settings; * default: mixed, * options?: list<string|array{name: string, value: mixed}>, * value: string|int|float|bool|list<string>, + * sensitive?: boolean, * } * * @psalm-type SettingsDeclarativeForm = array{ diff --git a/apps/settings/lib/Search/AppSearch.php b/apps/settings/lib/Search/AppSearch.php index fbc799e6f04..19c2bce5451 100644 --- a/apps/settings/lib/Search/AppSearch.php +++ b/apps/settings/lib/Search/AppSearch.php @@ -47,8 +47,8 @@ class AppSearch implements IProvider { $result = []; foreach ($entries as $entry) { if ( - stripos($entry['name'], $term) === false && - stripos($entry['id'], $term) === false + stripos($entry['name'], $term) === false + && stripos($entry['id'], $term) === false ) { continue; } diff --git a/apps/settings/lib/Search/SectionSearch.php b/apps/settings/lib/Search/SectionSearch.php index 08a6f9f76b0..52f0c9b08db 100644 --- a/apps/settings/lib/Search/SectionSearch.php +++ b/apps/settings/lib/Search/SectionSearch.php @@ -110,8 +110,8 @@ class SectionSearch implements IProvider { foreach ($sections as $priority => $sectionsByPriority) { foreach ($sectionsByPriority as $section) { if ( - stripos($section->getName(), $query->getTerm()) === false && - stripos($section->getID(), $query->getTerm()) === false + stripos($section->getName(), $query->getTerm()) === false + && stripos($section->getID(), $query->getTerm()) === false ) { continue; } diff --git a/apps/settings/lib/Sections/Admin/Delegation.php b/apps/settings/lib/Sections/Admin/Delegation.php index 2a455f1e549..0dd3b48c20b 100644 --- a/apps/settings/lib/Sections/Admin/Delegation.php +++ b/apps/settings/lib/Sections/Admin/Delegation.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Service/AuthorizedGroupService.php b/apps/settings/lib/Service/AuthorizedGroupService.php index 20966446d61..15aca94198a 100644 --- a/apps/settings/lib/Service/AuthorizedGroupService.php +++ b/apps/settings/lib/Service/AuthorizedGroupService.php @@ -42,8 +42,8 @@ class AuthorizedGroupService { * @throws NotFoundException */ private function handleException(\Exception $e): void { - if ($e instanceof DoesNotExistException || - $e instanceof MultipleObjectsReturnedException) { + if ($e instanceof DoesNotExistException + || $e instanceof MultipleObjectsReturnedException) { throw new NotFoundException('AuthorizedGroup not found'); } else { throw $e; diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php index 555d3c27313..78bec73382d 100644 --- a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -125,6 +125,7 @@ class ArtificialIntelligence implements IDelegatedSettings { $taskProcessingTypeSettings[$taskTypeId] = true; } + $this->initialState->provideInitialState('ai-stt-providers', $sttProviders); $this->initialState->provideInitialState('ai-translation-providers', $translationProviders); $this->initialState->provideInitialState('ai-text-processing-providers', $textProcessingProviders); @@ -140,6 +141,7 @@ class ArtificialIntelligence implements IDelegatedSettings { 'ai.text2image_provider' => count($text2imageProviders) > 0 ? $text2imageProviders[0]['id'] : null, 'ai.taskprocessing_provider_preferences' => $taskProcessingSettings, 'ai.taskprocessing_type_preferences' => $taskProcessingTypeSettings, + 'ai.taskprocessing_guests' => false, ]; foreach ($settings as $key => $defaultValue) { $value = $defaultValue; @@ -158,7 +160,7 @@ class ArtificialIntelligence implements IDelegatedSettings { } continue; } - + switch ($key) { case 'ai.taskprocessing_provider_preferences': case 'ai.taskprocessing_type_preferences': diff --git a/apps/settings/lib/Settings/Admin/Mail.php b/apps/settings/lib/Settings/Admin/Mail.php index 728be01f3d8..8bf2342a59c 100644 --- a/apps/settings/lib/Settings/Admin/Mail.php +++ b/apps/settings/lib/Settings/Admin/Mail.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -9,6 +10,7 @@ use OCP\AppFramework\Http\TemplateResponse; use OCP\IBinaryFinder; use OCP\IConfig; use OCP\IL10N; +use OCP\Server; use OCP\Settings\IDelegatedSettings; class Mail implements IDelegatedSettings { @@ -26,7 +28,7 @@ class Mail implements IDelegatedSettings { * @return TemplateResponse */ public function getForm() { - $finder = \OCP\Server::get(IBinaryFinder::class); + $finder = Server::get(IBinaryFinder::class); $parameters = [ // Mail diff --git a/apps/settings/lib/Settings/Admin/Overview.php b/apps/settings/lib/Settings/Admin/Overview.php index 5db9d9533c4..355200372f1 100644 --- a/apps/settings/lib/Settings/Admin/Overview.php +++ b/apps/settings/lib/Settings/Admin/Overview.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Settings/Admin/Security.php b/apps/settings/lib/Settings/Admin/Security.php index 72485b8f8f3..c4efdb478c7 100644 --- a/apps/settings/lib/Settings/Admin/Security.php +++ b/apps/settings/lib/Settings/Admin/Security.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Settings/Admin/Sharing.php b/apps/settings/lib/Settings/Admin/Sharing.php index e001a7d00ea..e038b2a6231 100644 --- a/apps/settings/lib/Settings/Admin/Sharing.php +++ b/apps/settings/lib/Settings/Admin/Sharing.php @@ -1,14 +1,17 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Settings\Settings\Admin; +use OC\Core\AppInfo\ConfigLexicon; use OCP\App\IAppManager; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; use OCP\Constants; +use OCP\IAppConfig; use OCP\IConfig; use OCP\IL10N; use OCP\IURLGenerator; @@ -19,6 +22,7 @@ use OCP\Util; class Sharing implements IDelegatedSettings { public function __construct( private IConfig $config, + private IAppConfig $appConfig, private IL10N $l, private IManager $shareManager, private IAppManager $appManager, @@ -46,6 +50,7 @@ class Sharing implements IDelegatedSettings { 'allowPublicUpload' => $this->getHumanBooleanConfig('core', 'shareapi_allow_public_upload', true), 'allowResharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_resharing', true), 'allowShareDialogUserEnumeration' => $this->getHumanBooleanConfig('core', 'shareapi_allow_share_dialog_user_enumeration', true), + 'allowFederationOnPublicShares' => $this->appConfig->getValueBool('core', ConfigLexicon::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES), 'restrictUserEnumerationToGroup' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_group'), 'restrictUserEnumerationToPhone' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_phone'), 'restrictUserEnumerationFullMatch' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match', true), @@ -72,6 +77,7 @@ class Sharing implements IDelegatedSettings { 'remoteExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7'), 'enforceRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_remote_expire_date'), 'allowCustomTokens' => $this->shareManager->allowCustomTokens(), + 'allowViewWithoutDownload' => $this->shareManager->allowViewWithoutDownload(), ]; $this->initialState->provideInitialState('sharingAppEnabled', $this->appManager->isEnabledForUser('files_sharing')); diff --git a/apps/settings/lib/Settings/Personal/Additional.php b/apps/settings/lib/Settings/Personal/Additional.php index c8b3fc66450..58fe08a63b7 100644 --- a/apps/settings/lib/Settings/Personal/Additional.php +++ b/apps/settings/lib/Settings/Personal/Additional.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php index 1032f97361b..84b379f2e87 100644 --- a/apps/settings/lib/Settings/Personal/PersonalInfo.php +++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php @@ -29,6 +29,7 @@ use OCP\L10N\IFactory; use OCP\Notification\IManager; use OCP\Server; use OCP\Settings\ISettings; +use OCP\Util; class PersonalInfo implements ISettings { @@ -71,7 +72,7 @@ class PersonalInfo implements ISettings { if ($storageInfo['quota'] === FileInfo::SPACE_UNLIMITED) { $totalSpace = $this->l->t('Unlimited'); } else { - $totalSpace = \OC_Helper::humanFileSize($storageInfo['total']); + $totalSpace = Util::humanFileSize($storageInfo['total']); } $messageParameters = $this->getMessageParameters($account); @@ -88,7 +89,7 @@ class PersonalInfo implements ISettings { 'groups' => $this->getGroups($user), 'quota' => $storageInfo['quota'], 'totalSpace' => $totalSpace, - 'usage' => \OC_Helper::humanFileSize($storageInfo['used']), + 'usage' => Util::humanFileSize($storageInfo['used']), 'usageRelative' => round($storageInfo['relative']), 'displayName' => $this->getProperty($account, IAccountManager::PROPERTY_DISPLAYNAME), 'emailMap' => $this->getEmailMap($account), diff --git a/apps/settings/lib/Settings/Personal/ServerDevNotice.php b/apps/settings/lib/Settings/Personal/ServerDevNotice.php index 71c83740b92..c9993484abd 100644 --- a/apps/settings/lib/Settings/Personal/ServerDevNotice.php +++ b/apps/settings/lib/Settings/Personal/ServerDevNotice.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/settings/lib/SetupChecks/DataDirectoryProtected.php b/apps/settings/lib/SetupChecks/DataDirectoryProtected.php index 4280457ced0..e572c345079 100644 --- a/apps/settings/lib/SetupChecks/DataDirectoryProtected.php +++ b/apps/settings/lib/SetupChecks/DataDirectoryProtected.php @@ -66,6 +66,6 @@ class DataDirectoryProtected implements ISetupCheck { return SetupResult::warning($this->l10n->t('Could not check that the data directory is protected. Please check manually that your server does not allow access to the data directory.') . "\n" . $this->serverConfigHelp()); } return SetupResult::success(); - + } } diff --git a/apps/settings/lib/SetupChecks/JavaScriptModules.php b/apps/settings/lib/SetupChecks/JavaScriptModules.php index e09dc459dc8..72f58405811 100644 --- a/apps/settings/lib/SetupChecks/JavaScriptModules.php +++ b/apps/settings/lib/SetupChecks/JavaScriptModules.php @@ -55,6 +55,6 @@ class JavaScriptModules implements ISetupCheck { return SetupResult::warning($this->l10n->t('Unable to run check for JavaScript support. Please remedy or confirm manually if your webserver serves `.mjs` files using the JavaScript MIME type.') . "\n" . $this->serverConfigHelp()); } return SetupResult::error($this->l10n->t('Your webserver does not serve `.mjs` files using the JavaScript MIME type. This will break some apps by preventing browsers from executing the JavaScript files. You should configure your webserver to serve `.mjs` files with either the `text/javascript` or `application/javascript` MIME type.')); - + } } diff --git a/apps/settings/lib/SetupChecks/MimeTypeMigrationAvailable.php b/apps/settings/lib/SetupChecks/MimeTypeMigrationAvailable.php index 98c8eacbfda..cf237f68670 100644 --- a/apps/settings/lib/SetupChecks/MimeTypeMigrationAvailable.php +++ b/apps/settings/lib/SetupChecks/MimeTypeMigrationAvailable.php @@ -10,18 +10,15 @@ namespace OCA\Settings\SetupChecks; use OC\Repair\RepairMimeTypes; use OCP\IL10N; -use OCP\L10N\IFactory; use OCP\SetupCheck\ISetupCheck; use OCP\SetupCheck\SetupResult; class MimeTypeMigrationAvailable implements ISetupCheck { - private IL10N $l10n; public function __construct( - IFactory $l10nFactory, private RepairMimeTypes $repairMimeTypes, + private IL10N $l10n, ) { - $this->l10n = $l10nFactory->get('core'); } public function getCategory(): string { diff --git a/apps/settings/lib/SetupChecks/PhpOpcacheSetup.php b/apps/settings/lib/SetupChecks/PhpOpcacheSetup.php index 22605012058..83b7be1c390 100644 --- a/apps/settings/lib/SetupChecks/PhpOpcacheSetup.php +++ b/apps/settings/lib/SetupChecks/PhpOpcacheSetup.php @@ -57,7 +57,7 @@ class PhpOpcacheSetup implements ISetupCheck { } elseif ($this->iniGetWrapper->getBool('opcache.file_cache_only')) { $recommendations[] = $this->l10n->t('The shared memory based OPcache is disabled. For better performance, it is recommended to apply "opcache.file_cache_only=0" to your PHP configuration and use the file cache as second level cache only.'); } else { - // Check whether opcache_get_status has been explicitly disabled an in case skip usage based checks + // Check whether opcache_get_status has been explicitly disabled and in case skip usage based checks $disabledFunctions = $this->iniGetWrapper->getString('disable_functions'); if (isset($disabledFunctions) && str_contains($disabledFunctions, 'opcache_get_status')) { return [$level, $recommendations]; @@ -70,29 +70,27 @@ class PhpOpcacheSetup implements ISetupCheck { $level = 'error'; } - // 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 "opcache.max_accelerated_files" to your PHP configuration with a value higher than "%s".', [($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 "opcache.memory_consumption" to your PHP configuration with a value higher than "%s".', [($this->iniGetWrapper->getNumeric('opcache.memory_consumption') ?: 'currently')]); + // Check whether OPcache is full, which can be either the overall OPcache size or limit of cached keys reached. + // If the limit of cached keys has been reached, num_cached_keys equals max_cached_keys. The recommendation contains this value instead of opcache.max_accelerated_files, since the effective limit is a next higher prime number: https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.max-accelerated-files + // Else, the remaining $status['memory_usage']['free_memory'] was too low to store another script. Aside of used_memory, this can be also due to wasted_memory, remaining cache keys from scripts changed on disk. + // Wasted memory is cleared only via opcache_reset(), or if $status['memory_usage']['current_wasted_percentage'] reached opcache.max_wasted_percentage, which triggers an engine restart and hence OPcache reset. Due to this complexity, we check for $status['cache_full'] only. + if ($status['cache_full'] === true) { + if ($status['opcache_statistics']['num_cached_keys'] === $status['opcache_statistics']['max_cached_keys']) { + $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 "opcache.max_accelerated_files" to your PHP configuration with a value higher than "%s".', [($status['opcache_statistics']['max_cached_keys'] ?: 'currently')]); + } else { + $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 "opcache.memory_consumption" to your PHP configuration with a value higher than "%s".', [($this->iniGetWrapper->getNumeric('opcache.memory_consumption') ?: 'currently')]); + } } + // Interned strings buffer: recommend to raise size if more than 90% is used $interned_strings_buffer = $this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') ?? 0; $memory_consumption = $this->iniGetWrapper->getNumeric('opcache.memory_consumption') ?? 0; if ( // Do not recommend to raise the interned strings buffer size above a quarter of the total OPcache size - ($interned_strings_buffer < ($memory_consumption / 4)) && - ( - empty($status['interned_strings_usage']['free_memory']) || - ($status['interned_strings_usage']['used_memory'] / $status['interned_strings_usage']['free_memory'] > 9) + ($interned_strings_buffer < ($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 "opcache.interned_strings_buffer" to your PHP configuration with a value higher than "%s".', [($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') ?: 'currently')]); diff --git a/apps/settings/lib/SetupChecks/SecurityHeaders.php b/apps/settings/lib/SetupChecks/SecurityHeaders.php index ed4e56218da..9cc6856a170 100644 --- a/apps/settings/lib/SetupChecks/SecurityHeaders.php +++ b/apps/settings/lib/SetupChecks/SecurityHeaders.php @@ -72,11 +72,6 @@ class SecurityHeaders implements ISetupCheck { } } - $xssFields = array_map('trim', explode(';', $response->getHeader('X-XSS-Protection'))); - if (!in_array('1', $xssFields) || !in_array('mode=block', $xssFields)) { - $msg .= $this->l10n->t('- The `%1$s` HTTP header does not contain `%2$s`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', ['X-XSS-Protection', '1; mode=block']) . "\n"; - } - $referrerPolicy = $response->getHeader('Referrer-Policy'); if (!preg_match('/(no-referrer(-when-downgrade)?|strict-origin(-when-cross-origin)?|same-origin)(,|$)/', $referrerPolicy)) { $msg .= $this->l10n->t( diff --git a/apps/settings/lib/SetupChecks/TaskProcessingPickupSpeed.php b/apps/settings/lib/SetupChecks/TaskProcessingPickupSpeed.php new file mode 100644 index 00000000000..83168ac0f3e --- /dev/null +++ b/apps/settings/lib/SetupChecks/TaskProcessingPickupSpeed.php @@ -0,0 +1,63 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Settings\SetupChecks; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IL10N; +use OCP\SetupCheck\ISetupCheck; +use OCP\SetupCheck\SetupResult; +use OCP\TaskProcessing\IManager; + +class TaskProcessingPickupSpeed implements ISetupCheck { + public const MAX_SLOW_PERCENTAGE = 0.2; + public const TIME_SPAN = 24; + + public function __construct( + private IL10N $l10n, + private IManager $taskProcessingManager, + private ITimeFactory $timeFactory, + ) { + } + + public function getCategory(): string { + return 'ai'; + } + + public function getName(): string { + return $this->l10n->t('Task Processing pickup speed'); + } + + public function run(): SetupResult { + $tasks = $this->taskProcessingManager->getTasks(userId: '', scheduleAfter: $this->timeFactory->now()->getTimestamp() - 60 * 60 * self::TIME_SPAN); // userId: '' means no filter, whereas null would mean guest + $taskCount = count($tasks); + if ($taskCount === 0) { + return SetupResult::success($this->l10n->n('No scheduled tasks in the last %n hour.', 'No scheduled tasks in the last %n hours.', self::TIME_SPAN)); + } + $slowCount = 0; + foreach ($tasks as $task) { + if ($task->getStartedAt() === null) { + continue; // task was not picked up yet + } + if ($task->getScheduledAt() === null) { + continue; // task was not scheduled yet -- should not happen, but the API specifies null as return value + } + $pickupDelay = $task->getScheduledAt() - $task->getStartedAt(); + if ($pickupDelay > 60 * 4) { + $slowCount++; // task pickup took longer than 4 minutes + } + } + + if ($slowCount / $taskCount < self::MAX_SLOW_PERCENTAGE) { + return SetupResult::success($this->l10n->n('The task pickup speed has been ok in the last %n hour.', 'The task pickup speed has been ok in the last %n hours.', self::TIME_SPAN)); + } else { + return SetupResult::warning($this->l10n->n('The task pickup speed has been slow in the last %n hour. Many tasks took longer than 4 minutes to be picked up. Consider setting up a worker to process tasks in the background.', 'The task pickup speed has been slow in the last %n hours. Many tasks took longer than 4 minutes to be picked up. Consider setting up a worker to process tasks in the background.', self::TIME_SPAN), 'https://docs.nextcloud.com/server/latest/admin_manual/ai/overview.html#improve-ai-task-pickup-speed'); + } + } +} diff --git a/apps/settings/lib/UserMigration/AccountMigrator.php b/apps/settings/lib/UserMigration/AccountMigrator.php index 15ac06687b6..1c51aec5104 100644 --- a/apps/settings/lib/UserMigration/AccountMigrator.php +++ b/apps/settings/lib/UserMigration/AccountMigrator.php @@ -35,8 +35,6 @@ class AccountMigrator implements IMigrator, ISizeEstimationMigrator { private ProfileManager $profileManager; - private ProfileConfigMapper $configMapper; - private const PATH_ROOT = Application::APP_ID . '/'; private const PATH_ACCOUNT_FILE = AccountMigrator::PATH_ROOT . 'account.json'; @@ -49,11 +47,10 @@ class AccountMigrator implements IMigrator, ISizeEstimationMigrator { private IAccountManager $accountManager, private IAvatarManager $avatarManager, ProfileManager $profileManager, - ProfileConfigMapper $configMapper, + private ProfileConfigMapper $configMapper, private IL10N $l10n, ) { $this->profileManager = $profileManager; - $this->configMapper = $configMapper; } /** |