diff options
Diffstat (limited to 'apps/settings/lib/Controller')
11 files changed, 198 insertions, 73 deletions
diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php index e08ec616e80..114cbf61514 100644 --- a/apps/settings/lib/Controller/AISettingsController.php +++ b/apps/settings/lib/Controller/AISettingsController.php @@ -12,20 +12,15 @@ use OCA\Settings\Settings\Admin\ArtificialIntelligence; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; use OCP\AppFramework\Http\DataResponse; -use OCP\IConfig; +use OCP\IAppConfig; use OCP\IRequest; class AISettingsController extends Controller { - /** - * @param string $appName - * @param IRequest $request - * @param IConfig $config - */ public function __construct( $appName, IRequest $request, - private IConfig $config, + private IAppConfig $appConfig, ) { parent::__construct($appName, $request); } @@ -38,12 +33,12 @@ 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; } - $this->config->setAppValue('core', $key, json_encode($settings[$key])); + $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 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 4d34b7678d7..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. @@ -15,13 +16,13 @@ use OC\App\AppStore\Version\VersionParser; use OC\App\DependencyAnalyzer; use OC\App\Platform; use OC\Installer; +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\Attribute\PublicPage; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\FileDisplayResponse; use OCP\AppFramework\Http\JSONResponse; @@ -38,11 +39,14 @@ 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; @@ -92,9 +96,9 @@ class AppSettingsController extends Controller { $this->initialState->provideInitialState('appstoreDeveloperDocs', $this->urlGenerator->linkToDocs('developer-manual')); $this->initialState->provideInitialState('appstoreUpdateCount', count($this->getAppsWithUpdates())); - if ($this->appManager->isInstalled('app_api')) { + if ($this->appManager->isEnabledForAnyone('app_api')) { try { - Server::get(\OCA\AppAPI\Service\ExAppsPageService::class)->provideAppApiState($this->initialState); + Server::get(ExAppsPageService::class)->provideAppApiState($this->initialState); } catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) { } } @@ -126,10 +130,11 @@ class AppSettingsController extends Controller { * @param string $image * @throws \Exception */ - #[PublicPage] #[NoCSRFRequired] - public function getAppDiscoverMedia(string $fileName): Response { - $etag = $this->discoverFetcher->getETag() ?? date('Y-m'); + 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'); @@ -156,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); @@ -177,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 @@ -440,7 +490,7 @@ class AppSettingsController extends Controller { } $currentVersion = ''; - if ($this->appManager->isInstalled($app['id'])) { + if ($this->appManager->isEnabledForAnyone($app['id'])) { $currentVersion = $this->appManager->getAppVersion($app['id']); } else { $currentVersion = $app['releases'][0]['version']; @@ -519,7 +569,7 @@ class AppSettingsController extends Controller { // Check if app is already downloaded /** @var Installer $installer */ - $installer = \OC::$server->get(Installer::class); + $installer = Server::get(Installer::class); $isDownloaded = $installer->isDownloaded($appId); if (!$isDownloaded) { @@ -545,7 +595,7 @@ class AppSettingsController extends Controller { } private function getGroupList(array $groups) { - $groupManager = \OC::$server->getGroupManager(); + $groupManager = Server::get(IGroupManager::class); $groupsList = []; foreach ($groups as $group) { $groupItem = $groupManager->get($group); 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/LogSettingsController.php b/apps/settings/lib/Controller/LogSettingsController.php index aa5ac9b2cc9..90cf4549d2f 100644 --- a/apps/settings/lib/Controller/LogSettingsController.php +++ b/apps/settings/lib/Controller/LogSettingsController.php @@ -11,6 +11,7 @@ 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; @@ -27,22 +28,23 @@ class LogSettingsController extends Controller { /** * download logfile * - * @psalm-suppress MoreSpecificReturnType The value of Content-Disposition is not relevant - * @psalm-suppress LessSpecificReturnStatement The value of Content-Disposition is not relevant - * @return StreamResponse<Http::STATUS_OK, array{Content-Type: 'application/octet-stream', 'Content-Disposition': string}> + * @return StreamResponse<Http::STATUS_OK, array{Content-Type: 'application/octet-stream', 'Content-Disposition': 'attachment; filename="nextcloud.log"'}> * * 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->setHeaders([ - 'Content-Type' => 'application/octet-stream', - '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 ca037f409b3..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. @@ -44,34 +45,37 @@ class MailSettingsController extends Controller { /** * Sets the email settings - * - * @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 */ #[AuthorizedAdminSetting(settings: Overview::class)] #[PasswordConfirmationRequired] - 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) { + 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; } 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 5af16878e8e..8efd3eeb8ca 100644 --- a/apps/settings/lib/Controller/UsersController.php +++ b/apps/settings/lib/Controller/UsersController.php @@ -32,6 +32,7 @@ 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; @@ -39,9 +40,12 @@ 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\IUserSession; @@ -52,6 +56,8 @@ use function in_array; #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] class UsersController extends Controller { + /** Limit for counting users for subadmins, to avoid spending too much time */ + private const COUNT_LIMIT_FOR_SUBADMINS = 999; public function __construct( string $appName, @@ -83,8 +89,8 @@ class UsersController extends Controller { */ #[NoAdminRequired] #[NoCSRFRequired] - public function usersListByGroup(): TemplateResponse { - return $this->usersList(); + public function usersListByGroup(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse { + return $this->usersList($navigationManager, $subAdmin); } /** @@ -94,13 +100,13 @@ class UsersController extends Controller { */ #[NoAdminRequired] #[NoCSRFRequired] - public function usersList(): TemplateResponse { + 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 = MetaData::SORT_USERCOUNT; @@ -109,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; @@ -129,8 +135,15 @@ class UsersController extends Controller { $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) { @@ -149,20 +162,12 @@ class UsersController extends Controller { }, 0); } else { // User is subadmin ! - // Map group list to ids to retrieve the countDisabledUsersOfGroups - $userGroups = $this->groupManager->getUserGroups($user); - $groupsIds = []; - - foreach ($groups as $key => $group) { - // $userCount += (int)$group['usercount']; - $groupsIds[] = $group['id']; - } - - $userCount += $this->userManager->countUsersOfGroups($groupsInfo->getGroups()); - $disabledUsers = $this->userManager->countDisabledUsersOfGroups($groupsIds); + [$userCount,$disabledUsers] = $this->userManager->countUsersAndDisabledUsersOfGroups($groupsInfo->getGroups(), self::COUNT_LIMIT_FOR_SUBADMINS); } - $userCount -= $disabledUsers; + if ($disabledUsers > 0) { + $userCount -= $disabledUsers; + } } $recentUsersGroup = [ @@ -177,6 +182,14 @@ class UsersController extends Controller { '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'; @@ -199,7 +212,8 @@ class UsersController extends Controller { /* FINAL DATA */ $serverData = []; // groups - $serverData['groups'] = array_merge_recursive($adminGroup, [$recentUsersGroup, $disabledUsersGroup], $groups); + $serverData['systemGroups'] = [$adminGroupData, $recentUsersGroup, $disabledUsersGroup]; + $serverData['subAdminGroups'] = $subAdminGroups ?? []; // Various data $serverData['isAdmin'] = $isAdmin; $serverData['isDelegatedAdmin'] = $isDelegatedAdmin; @@ -305,6 +319,8 @@ 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 @@ -314,6 +330,7 @@ class UsersController extends Controller { */ #[NoAdminRequired] #[PasswordConfirmationRequired] + #[UserRateLimit(limit: 5, period: 60)] public function setUserSettings(?string $avatarScope = null, ?string $displayname = null, ?string $displaynameScope = null, @@ -327,6 +344,8 @@ class UsersController extends Controller { ?string $addressScope = null, ?string $twitter = null, ?string $twitterScope = null, + ?string $bluesky = null, + ?string $blueskyScope = null, ?string $fediverse = null, ?string $fediverseScope = null, ?string $birthdate = null, @@ -371,6 +390,7 @@ 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], @@ -413,6 +433,8 @@ 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(), |