Completed
Push — master ( 294057...9019c5 )
by
unknown
29:23
created
apps/settings/lib/ConfigLexicon.php 1 patch
Indentation   +66 added lines, -66 removed lines patch added patch discarded remove patch
@@ -20,73 +20,73 @@
 block discarded – undo
20 20
  * Please Add & Manage your Config Keys in that file and keep the Lexicon up to date!
21 21
  */
22 22
 class ConfigLexicon implements ILexicon {
23
-	public const USER_SETTINGS_EMAIL = 'email';
24
-	public const USER_LIST_SHOW_STORAGE_PATH = 'user_list_show_storage_path';
25
-	public const USER_LIST_SHOW_USER_BACKEND = 'user_list_show_user_backend';
26
-	public const USER_LIST_SHOW_LAST_LOGIN = 'user_list_show_last_login';
27
-	public const USER_LIST_SHOW_FIRST_LOGIN = 'user_list_show_first_login';
28
-	public const USER_LIST_SHOW_NEW_USER_FORM = 'user_list_show_new_user_form';
29
-	public const USER_LIST_SHOW_LANGUAGES = 'user_list_show_languages';
23
+    public const USER_SETTINGS_EMAIL = 'email';
24
+    public const USER_LIST_SHOW_STORAGE_PATH = 'user_list_show_storage_path';
25
+    public const USER_LIST_SHOW_USER_BACKEND = 'user_list_show_user_backend';
26
+    public const USER_LIST_SHOW_LAST_LOGIN = 'user_list_show_last_login';
27
+    public const USER_LIST_SHOW_FIRST_LOGIN = 'user_list_show_first_login';
28
+    public const USER_LIST_SHOW_NEW_USER_FORM = 'user_list_show_new_user_form';
29
+    public const USER_LIST_SHOW_LANGUAGES = 'user_list_show_languages';
30 30
 
31
-	public function getStrictness(): Strictness {
32
-		return Strictness::IGNORE;
33
-	}
31
+    public function getStrictness(): Strictness {
32
+        return Strictness::IGNORE;
33
+    }
34 34
 
35
-	public function getAppConfigs(): array {
36
-		return [];
37
-	}
35
+    public function getAppConfigs(): array {
36
+        return [];
37
+    }
38 38
 
39
-	public function getUserConfigs(): array {
40
-		return [
41
-			new Entry(
42
-				key: self::USER_SETTINGS_EMAIL,
43
-				type: ValueType::STRING,
44
-				defaultRaw: '',
45
-				definition: 'account mail address',
46
-				flags: IUserConfig::FLAG_INDEXED,
47
-			),
48
-			new Entry(
49
-				key: self::USER_LIST_SHOW_STORAGE_PATH,
50
-				type: ValueType::BOOL,
51
-				defaultRaw: false,
52
-				definition: 'Show storage path column in user list',
53
-				lazy: true,
54
-			),
55
-			new Entry(
56
-				key: self::USER_LIST_SHOW_USER_BACKEND,
57
-				type: ValueType::BOOL,
58
-				defaultRaw: false,
59
-				definition: 'Show user account backend column in user list',
60
-				lazy: true,
61
-			),
62
-			new Entry(
63
-				key: self::USER_LIST_SHOW_LAST_LOGIN,
64
-				type: ValueType::BOOL,
65
-				defaultRaw: false,
66
-				definition: 'Show last login date column in user list',
67
-				lazy: true,
68
-			),
69
-			new Entry(
70
-				key: self::USER_LIST_SHOW_FIRST_LOGIN,
71
-				type: ValueType::BOOL,
72
-				defaultRaw: false,
73
-				definition: 'Show first login date column in user list',
74
-				lazy: true,
75
-			),
76
-			new Entry(
77
-				key: self::USER_LIST_SHOW_NEW_USER_FORM,
78
-				type: ValueType::BOOL,
79
-				defaultRaw: false,
80
-				definition: 'Show new user form in user list',
81
-				lazy: true,
82
-			),
83
-			new Entry(
84
-				key: self::USER_LIST_SHOW_LANGUAGES,
85
-				type: ValueType::BOOL,
86
-				defaultRaw: false,
87
-				definition: 'Show languages in user list',
88
-				lazy: true,
89
-			),
90
-		];
91
-	}
39
+    public function getUserConfigs(): array {
40
+        return [
41
+            new Entry(
42
+                key: self::USER_SETTINGS_EMAIL,
43
+                type: ValueType::STRING,
44
+                defaultRaw: '',
45
+                definition: 'account mail address',
46
+                flags: IUserConfig::FLAG_INDEXED,
47
+            ),
48
+            new Entry(
49
+                key: self::USER_LIST_SHOW_STORAGE_PATH,
50
+                type: ValueType::BOOL,
51
+                defaultRaw: false,
52
+                definition: 'Show storage path column in user list',
53
+                lazy: true,
54
+            ),
55
+            new Entry(
56
+                key: self::USER_LIST_SHOW_USER_BACKEND,
57
+                type: ValueType::BOOL,
58
+                defaultRaw: false,
59
+                definition: 'Show user account backend column in user list',
60
+                lazy: true,
61
+            ),
62
+            new Entry(
63
+                key: self::USER_LIST_SHOW_LAST_LOGIN,
64
+                type: ValueType::BOOL,
65
+                defaultRaw: false,
66
+                definition: 'Show last login date column in user list',
67
+                lazy: true,
68
+            ),
69
+            new Entry(
70
+                key: self::USER_LIST_SHOW_FIRST_LOGIN,
71
+                type: ValueType::BOOL,
72
+                defaultRaw: false,
73
+                definition: 'Show first login date column in user list',
74
+                lazy: true,
75
+            ),
76
+            new Entry(
77
+                key: self::USER_LIST_SHOW_NEW_USER_FORM,
78
+                type: ValueType::BOOL,
79
+                defaultRaw: false,
80
+                definition: 'Show new user form in user list',
81
+                lazy: true,
82
+            ),
83
+            new Entry(
84
+                key: self::USER_LIST_SHOW_LANGUAGES,
85
+                type: ValueType::BOOL,
86
+                defaultRaw: false,
87
+                definition: 'Show languages in user list',
88
+                lazy: true,
89
+            ),
90
+        ];
91
+    }
92 92
 }
Please login to merge, or discard this patch.
apps/settings/lib/Controller/UsersController.php 2 patches
Indentation   +549 added lines, -549 removed lines patch added patch discarded remove patch
@@ -59,553 +59,553 @@
 block discarded – undo
59 59
 
60 60
 #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
61 61
 class UsersController extends Controller {
62
-	/** Limit for counting users for subadmins, to avoid spending too much time */
63
-	private const COUNT_LIMIT_FOR_SUBADMINS = 999;
64
-
65
-	public const ALLOWED_USER_PREFERENCES = [
66
-		ConfigLexicon::USER_LIST_SHOW_STORAGE_PATH,
67
-		ConfigLexicon::USER_LIST_SHOW_USER_BACKEND,
68
-		ConfigLexicon::USER_LIST_SHOW_FIRST_LOGIN,
69
-		ConfigLexicon::USER_LIST_SHOW_LAST_LOGIN,
70
-		ConfigLexicon::USER_LIST_SHOW_NEW_USER_FORM,
71
-		ConfigLexicon::USER_LIST_SHOW_LANGUAGES,
72
-	];
73
-
74
-	public function __construct(
75
-		string $appName,
76
-		IRequest $request,
77
-		private UserManager $userManager,
78
-		private IGroupManager $groupManager,
79
-		private IUserSession $userSession,
80
-		private IConfig $config,
81
-		private IAppConfig $appConfig,
82
-		private IUserConfig $userConfig,
83
-		private IL10N $l10n,
84
-		private IMailer $mailer,
85
-		private IFactory $l10nFactory,
86
-		private IAppManager $appManager,
87
-		private IAccountManager $accountManager,
88
-		private Manager $keyManager,
89
-		private IJobList $jobList,
90
-		private IManager $encryptionManager,
91
-		private KnownUserService $knownUserService,
92
-		private IEventDispatcher $dispatcher,
93
-		private IInitialState $initialState,
94
-	) {
95
-		parent::__construct($appName, $request);
96
-	}
97
-
98
-
99
-	/**
100
-	 * Display users list template
101
-	 *
102
-	 * @return TemplateResponse
103
-	 */
104
-	#[NoAdminRequired]
105
-	#[NoCSRFRequired]
106
-	public function usersListByGroup(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
107
-		return $this->usersList($navigationManager, $subAdmin);
108
-	}
109
-
110
-	/**
111
-	 * Display users list template
112
-	 *
113
-	 * @return TemplateResponse
114
-	 */
115
-	#[NoAdminRequired]
116
-	#[NoCSRFRequired]
117
-	public function usersList(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
118
-		$user = $this->userSession->getUser();
119
-		$uid = $user->getUID();
120
-		$isAdmin = $this->groupManager->isAdmin($uid);
121
-		$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($uid);
122
-
123
-		$navigationManager->setActiveEntry('core_users');
124
-
125
-		/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
126
-		$sortGroupsBy = MetaData::SORT_USERCOUNT;
127
-		$isLDAPUsed = false;
128
-		if ($this->config->getSystemValueBool('sort_groups_by_name', false)) {
129
-			$sortGroupsBy = MetaData::SORT_GROUPNAME;
130
-		} else {
131
-			if ($this->appManager->isEnabledForUser('user_ldap')) {
132
-				$isLDAPUsed
133
-					= $this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
134
-				if ($isLDAPUsed) {
135
-					// LDAP user count can be slow, so we sort by group name here
136
-					$sortGroupsBy = MetaData::SORT_GROUPNAME;
137
-				}
138
-			}
139
-		}
140
-
141
-		$canChangePassword = $this->canAdminChangeUserPasswords();
142
-
143
-		/* GROUPS */
144
-		$groupsInfo = new MetaData(
145
-			$uid,
146
-			$isAdmin,
147
-			$isDelegatedAdmin,
148
-			$this->groupManager,
149
-			$this->userSession
150
-		);
151
-
152
-		$adminGroup = $this->groupManager->get('admin');
153
-		$adminGroupData = [
154
-			'id' => $adminGroup->getGID(),
155
-			'name' => $adminGroup->getDisplayName(),
156
-			'usercount' => $sortGroupsBy === MetaData::SORT_USERCOUNT ? $adminGroup->count() : 0,
157
-			'disabled' => $adminGroup->countDisabled(),
158
-			'canAdd' => $adminGroup->canAddUser(),
159
-			'canRemove' => $adminGroup->canRemoveUser(),
160
-		];
161
-
162
-		if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
163
-			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
164
-				return $ldapFound || $backend instanceof User_Proxy;
165
-			});
166
-		}
167
-
168
-		$disabledUsers = -1;
169
-		$userCount = 0;
170
-
171
-		if (!$isLDAPUsed) {
172
-			if ($isAdmin || $isDelegatedAdmin) {
173
-				$disabledUsers = $this->userManager->countDisabledUsers();
174
-				$userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
175
-					return $v + (int)$w;
176
-				}, 0);
177
-			} else {
178
-				// User is subadmin !
179
-				[$userCount,$disabledUsers] = $this->userManager->countUsersAndDisabledUsersOfGroups($groupsInfo->getGroups(), self::COUNT_LIMIT_FOR_SUBADMINS);
180
-			}
181
-
182
-			if ($disabledUsers > 0) {
183
-				$userCount -= $disabledUsers;
184
-			}
185
-		}
186
-
187
-		$recentUsersGroup = [
188
-			'id' => '__nc_internal_recent',
189
-			'name' => $this->l10n->t('Recently active'),
190
-			'usercount' => $this->userManager->countSeenUsers(),
191
-		];
192
-
193
-		$disabledUsersGroup = [
194
-			'id' => 'disabled',
195
-			'name' => $this->l10n->t('Disabled accounts'),
196
-			'usercount' => $disabledUsers
197
-		];
198
-
199
-		if (!$isAdmin && !$isDelegatedAdmin) {
200
-			$subAdminGroups = array_map(
201
-				fn (IGroup $group) => ['id' => $group->getGID(), 'name' => $group->getDisplayName()],
202
-				$subAdmin->getSubAdminsGroups($user),
203
-			);
204
-			$subAdminGroups = array_values($subAdminGroups);
205
-		}
206
-
207
-		/* QUOTAS PRESETS */
208
-		$quotaPreset = $this->parseQuotaPreset($this->appConfig->getValueString('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
209
-		$allowUnlimitedQuota = $this->appConfig->getValueBool('files', 'allow_unlimited_quota', true);
210
-		if (!$allowUnlimitedQuota && count($quotaPreset) > 0) {
211
-			$defaultQuota = $this->appConfig->getValueString('files', 'default_quota', $quotaPreset[0]);
212
-		} else {
213
-			$defaultQuota = $this->appConfig->getValueString('files', 'default_quota', 'none');
214
-		}
215
-
216
-		$event = new BeforeTemplateRenderedEvent();
217
-		$this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
218
-		$this->dispatcher->dispatchTyped($event);
219
-
220
-		/* LANGUAGES */
221
-		$languages = $this->l10nFactory->getLanguages();
222
-
223
-		/** Using LDAP or admins (system config) can enfore sorting by group name, in this case the frontend setting is overwritten */
224
-		$forceSortGroupByName = $sortGroupsBy === MetaData::SORT_GROUPNAME;
225
-
226
-		/* FINAL DATA */
227
-		$serverData = [];
228
-		// groups
229
-		$serverData['systemGroups'] = [$adminGroupData, $recentUsersGroup, $disabledUsersGroup];
230
-		$serverData['subAdminGroups'] = $subAdminGroups ?? [];
231
-		// Various data
232
-		$serverData['isAdmin'] = $isAdmin;
233
-		$serverData['isDelegatedAdmin'] = $isDelegatedAdmin;
234
-		$serverData['sortGroups'] = $forceSortGroupByName
235
-			? MetaData::SORT_GROUPNAME
236
-			: (int)$this->appConfig->getValueString('core', 'group.sortBy', (string)MetaData::SORT_USERCOUNT);
237
-		$serverData['forceSortGroupByName'] = $forceSortGroupByName;
238
-		$serverData['quotaPreset'] = $quotaPreset;
239
-		$serverData['allowUnlimitedQuota'] = $allowUnlimitedQuota;
240
-		$serverData['userCount'] = $userCount;
241
-		$serverData['languages'] = $languages;
242
-		$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
243
-		$serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
244
-		// Settings
245
-		$serverData['defaultQuota'] = $defaultQuota;
246
-		$serverData['canChangePassword'] = $canChangePassword;
247
-		$serverData['newUserGenerateUserID'] = $this->appConfig->getValueBool('core', 'newUser.generateUserID', false);
248
-		$serverData['newUserRequireEmail'] = $this->appConfig->getValueBool('core', 'newUser.requireEmail', false);
249
-		$serverData['newUserSendEmail'] = $this->appConfig->getValueBool('core', 'newUser.sendEmail', true);
250
-		$serverData['showConfig'] = [];
251
-		foreach (self::ALLOWED_USER_PREFERENCES as $key) {
252
-			$serverData['showConfig'][$key] = $this->userConfig->getValueBool($uid, $this->appName, $key, false);
253
-		}
254
-
255
-		$this->initialState->provideInitialState('usersSettings', $serverData);
256
-
257
-		Util::addStyle('settings', 'settings');
258
-		Util::addScript('settings', 'vue-settings-apps-users-management');
259
-
260
-		return new TemplateResponse('settings', 'settings/empty', ['pageTitle' => $this->l10n->t('Settings')]);
261
-	}
262
-
263
-	/**
264
-	 * @param string $key
265
-	 * @param string $value
266
-	 *
267
-	 * @return JSONResponse
268
-	 */
269
-	#[AuthorizedAdminSetting(settings:Users::class)]
270
-	public function setPreference(string $key, string $value): JSONResponse {
271
-		switch ($key) {
272
-			case 'newUser.sendEmail':
273
-				$this->appConfig->setValueBool('core', $key, $value === 'yes');
274
-				break;
275
-			case 'group.sortBy':
276
-				$this->appConfig->setValueString('core', $key, $value);
277
-				break;
278
-			default:
279
-				if (in_array($key, self::ALLOWED_USER_PREFERENCES, true)) {
280
-					$this->userConfig->setValueBool($this->userSession->getUser()->getUID(), $this->appName, $key, $value === 'true');
281
-				} else {
282
-					return new JSONResponse([], Http::STATUS_FORBIDDEN);
283
-				}
284
-				break;
285
-		}
286
-
287
-		return new JSONResponse([]);
288
-	}
289
-
290
-	/**
291
-	 * Parse the app value for quota_present
292
-	 *
293
-	 * @param string $quotaPreset
294
-	 * @return array
295
-	 */
296
-	protected function parseQuotaPreset(string $quotaPreset): array {
297
-		// 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
298
-		$presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
299
-		// Drop default and none, Make array indexes numerically
300
-		return array_values(array_diff($presets, ['default', 'none']));
301
-	}
302
-
303
-	/**
304
-	 * check if the admin can change the users password
305
-	 *
306
-	 * The admin can change the passwords if:
307
-	 *
308
-	 *   - no encryption module is loaded and encryption is disabled
309
-	 *   - encryption module is loaded but it doesn't require per user keys
310
-	 *
311
-	 * The admin can not change the passwords if:
312
-	 *
313
-	 *   - an encryption module is loaded and it uses per-user keys
314
-	 *   - encryption is enabled but no encryption modules are loaded
315
-	 *
316
-	 * @return bool
317
-	 */
318
-	protected function canAdminChangeUserPasswords(): bool {
319
-		$isEncryptionEnabled = $this->encryptionManager->isEnabled();
320
-		try {
321
-			$noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
322
-			$isEncryptionModuleLoaded = true;
323
-		} catch (ModuleDoesNotExistsException $e) {
324
-			$noUserSpecificEncryptionKeys = true;
325
-			$isEncryptionModuleLoaded = false;
326
-		}
327
-		$canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
328
-			|| (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
329
-
330
-		return $canChangePassword;
331
-	}
332
-
333
-	/**
334
-	 * @NoSubAdminRequired
335
-	 *
336
-	 * @param string|null $avatarScope
337
-	 * @param string|null $displayname
338
-	 * @param string|null $displaynameScope
339
-	 * @param string|null $phone
340
-	 * @param string|null $phoneScope
341
-	 * @param string|null $email
342
-	 * @param string|null $emailScope
343
-	 * @param string|null $website
344
-	 * @param string|null $websiteScope
345
-	 * @param string|null $address
346
-	 * @param string|null $addressScope
347
-	 * @param string|null $twitter
348
-	 * @param string|null $twitterScope
349
-	 * @param string|null $bluesky
350
-	 * @param string|null $blueskyScope
351
-	 * @param string|null $fediverse
352
-	 * @param string|null $fediverseScope
353
-	 * @param string|null $birthdate
354
-	 * @param string|null $birthdateScope
355
-	 *
356
-	 * @return DataResponse
357
-	 */
358
-	#[NoAdminRequired]
359
-	#[PasswordConfirmationRequired]
360
-	#[UserRateLimit(limit: 5, period: 60)]
361
-	public function setUserSettings(?string $avatarScope = null,
362
-		?string $displayname = null,
363
-		?string $displaynameScope = null,
364
-		?string $phone = null,
365
-		?string $phoneScope = null,
366
-		?string $email = null,
367
-		?string $emailScope = null,
368
-		?string $website = null,
369
-		?string $websiteScope = null,
370
-		?string $address = null,
371
-		?string $addressScope = null,
372
-		?string $twitter = null,
373
-		?string $twitterScope = null,
374
-		?string $bluesky = null,
375
-		?string $blueskyScope = null,
376
-		?string $fediverse = null,
377
-		?string $fediverseScope = null,
378
-		?string $birthdate = null,
379
-		?string $birthdateScope = null,
380
-		?string $pronouns = null,
381
-		?string $pronounsScope = null,
382
-	) {
383
-		$user = $this->userSession->getUser();
384
-		if (!$user instanceof IUser) {
385
-			return new DataResponse(
386
-				[
387
-					'status' => 'error',
388
-					'data' => [
389
-						'message' => $this->l10n->t('Invalid account')
390
-					]
391
-				],
392
-				Http::STATUS_UNAUTHORIZED
393
-			);
394
-		}
395
-
396
-		$email = !is_null($email) ? strtolower($email) : $email;
397
-		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
398
-			return new DataResponse(
399
-				[
400
-					'status' => 'error',
401
-					'data' => [
402
-						'message' => $this->l10n->t('Invalid mail address')
403
-					]
404
-				],
405
-				Http::STATUS_UNPROCESSABLE_ENTITY
406
-			);
407
-		}
408
-
409
-		$userAccount = $this->accountManager->getAccount($user);
410
-		$oldPhoneValue = $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
411
-
412
-		$updatable = [
413
-			IAccountManager::PROPERTY_AVATAR => ['value' => null, 'scope' => $avatarScope],
414
-			IAccountManager::PROPERTY_DISPLAYNAME => ['value' => $displayname, 'scope' => $displaynameScope],
415
-			IAccountManager::PROPERTY_EMAIL => ['value' => $email, 'scope' => $emailScope],
416
-			IAccountManager::PROPERTY_WEBSITE => ['value' => $website, 'scope' => $websiteScope],
417
-			IAccountManager::PROPERTY_ADDRESS => ['value' => $address, 'scope' => $addressScope],
418
-			IAccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
419
-			IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
420
-			IAccountManager::PROPERTY_BLUESKY => ['value' => $bluesky, 'scope' => $blueskyScope],
421
-			IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
422
-			IAccountManager::PROPERTY_BIRTHDATE => ['value' => $birthdate, 'scope' => $birthdateScope],
423
-			IAccountManager::PROPERTY_PRONOUNS => ['value' => $pronouns, 'scope' => $pronounsScope],
424
-		];
425
-		$allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
426
-		foreach ($updatable as $property => $data) {
427
-			if ($allowUserToChangeDisplayName === false
428
-				&& in_array($property, [IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::PROPERTY_EMAIL], true)) {
429
-				continue;
430
-			}
431
-			$property = $userAccount->getProperty($property);
432
-			if ($data['value'] !== null) {
433
-				$property->setValue($data['value']);
434
-			}
435
-			if ($data['scope'] !== null) {
436
-				$property->setScope($data['scope']);
437
-			}
438
-		}
439
-
440
-		try {
441
-			$this->saveUserSettings($userAccount);
442
-			if ($oldPhoneValue !== $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue()) {
443
-				$this->knownUserService->deleteByContactUserId($user->getUID());
444
-			}
445
-			return new DataResponse(
446
-				[
447
-					'status' => 'success',
448
-					'data' => [
449
-						'userId' => $user->getUID(),
450
-						'avatarScope' => $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(),
451
-						'displayname' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(),
452
-						'displaynameScope' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(),
453
-						'phone' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(),
454
-						'phoneScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getScope(),
455
-						'email' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
456
-						'emailScope' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
457
-						'website' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(),
458
-						'websiteScope' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getScope(),
459
-						'address' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getValue(),
460
-						'addressScope' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getScope(),
461
-						'twitter' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(),
462
-						'twitterScope' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
463
-						'bluesky' => $userAccount->getProperty(IAccountManager::PROPERTY_BLUESKY)->getValue(),
464
-						'blueskyScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BLUESKY)->getScope(),
465
-						'fediverse' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue(),
466
-						'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
467
-						'birthdate' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getValue(),
468
-						'birthdateScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getScope(),
469
-						'pronouns' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getValue(),
470
-						'pronounsScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getScope(),
471
-						'message' => $this->l10n->t('Settings saved'),
472
-					],
473
-				],
474
-				Http::STATUS_OK
475
-			);
476
-		} catch (ForbiddenException|InvalidArgumentException|PropertyDoesNotExistException $e) {
477
-			return new DataResponse([
478
-				'status' => 'error',
479
-				'data' => [
480
-					'message' => $e->getMessage()
481
-				],
482
-			]);
483
-		}
484
-	}
485
-	/**
486
-	 * update account manager with new user data
487
-	 *
488
-	 * @throws ForbiddenException
489
-	 * @throws InvalidArgumentException
490
-	 */
491
-	protected function saveUserSettings(IAccount $userAccount): void {
492
-		// keep the user back-end up-to-date with the latest display name and email
493
-		// address
494
-		$oldDisplayName = $userAccount->getUser()->getDisplayName();
495
-		if ($oldDisplayName !== $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue()) {
496
-			$result = $userAccount->getUser()->setDisplayName($userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue());
497
-			if ($result === false) {
498
-				throw new ForbiddenException($this->l10n->t('Unable to change full name'));
499
-			}
500
-		}
501
-
502
-		$oldEmailAddress = $userAccount->getUser()->getSystemEMailAddress();
503
-		$oldEmailAddress = strtolower((string)$oldEmailAddress);
504
-		if ($oldEmailAddress !== strtolower($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue())) {
505
-			// this is the only permission a backend provides and is also used
506
-			// for the permission of setting a email address
507
-			if (!$userAccount->getUser()->canChangeDisplayName()) {
508
-				throw new ForbiddenException($this->l10n->t('Unable to change email address'));
509
-			}
510
-			$userAccount->getUser()->setSystemEMailAddress($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue());
511
-		}
512
-
513
-		try {
514
-			$this->accountManager->updateAccount($userAccount);
515
-		} catch (InvalidArgumentException $e) {
516
-			if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
517
-				throw new InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
518
-			}
519
-			if ($e->getMessage() === IAccountManager::PROPERTY_WEBSITE) {
520
-				throw new InvalidArgumentException($this->l10n->t('Unable to set invalid website'));
521
-			}
522
-			throw new InvalidArgumentException($this->l10n->t('Some account data was invalid'));
523
-		}
524
-	}
525
-
526
-	/**
527
-	 * Set the mail address of a user
528
-	 *
529
-	 * @NoSubAdminRequired
530
-	 *
531
-	 * @param string $account
532
-	 * @param bool $onlyVerificationCode only return verification code without updating the data
533
-	 * @return DataResponse
534
-	 */
535
-	#[NoAdminRequired]
536
-	#[PasswordConfirmationRequired]
537
-	public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
538
-		$user = $this->userSession->getUser();
539
-
540
-		if ($user === null) {
541
-			return new DataResponse([], Http::STATUS_BAD_REQUEST);
542
-		}
543
-
544
-		$userAccount = $this->accountManager->getAccount($user);
545
-		$cloudId = $user->getCloudId();
546
-		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
547
-		$signature = $this->signMessage($user, $message);
548
-
549
-		$code = $message . ' ' . $signature;
550
-		$codeMd5 = $message . ' ' . md5($signature);
551
-
552
-		switch ($account) {
553
-			case 'verify-twitter':
554
-				$msg = $this->l10n->t('In order to verify your Twitter account, post the following tweet on Twitter (please make sure to post it without any line breaks):');
555
-				$code = $codeMd5;
556
-				$type = IAccountManager::PROPERTY_TWITTER;
557
-				break;
558
-			case 'verify-website':
559
-				$msg = $this->l10n->t('In order to verify your Website, store the following content in your web-root at \'.well-known/CloudIdVerificationCode.txt\' (please make sure that the complete text is in one line):');
560
-				$type = IAccountManager::PROPERTY_WEBSITE;
561
-				break;
562
-			default:
563
-				return new DataResponse([], Http::STATUS_BAD_REQUEST);
564
-		}
565
-
566
-		$userProperty = $userAccount->getProperty($type);
567
-		$userProperty
568
-			->setVerified(IAccountManager::VERIFICATION_IN_PROGRESS)
569
-			->setVerificationData($signature);
570
-
571
-		if ($onlyVerificationCode === false) {
572
-			$this->accountManager->updateAccount($userAccount);
573
-
574
-			$this->jobList->add(VerifyUserData::class,
575
-				[
576
-					'verificationCode' => $code,
577
-					'data' => $userProperty->getValue(),
578
-					'type' => $type,
579
-					'uid' => $user->getUID(),
580
-					'try' => 0,
581
-					'lastRun' => $this->getCurrentTime()
582
-				]
583
-			);
584
-		}
585
-
586
-		return new DataResponse(['msg' => $msg, 'code' => $code]);
587
-	}
588
-
589
-	/**
590
-	 * get current timestamp
591
-	 *
592
-	 * @return int
593
-	 */
594
-	protected function getCurrentTime(): int {
595
-		return time();
596
-	}
597
-
598
-	/**
599
-	 * sign message with users private key
600
-	 *
601
-	 * @param IUser $user
602
-	 * @param string $message
603
-	 *
604
-	 * @return string base64 encoded signature
605
-	 */
606
-	protected function signMessage(IUser $user, string $message): string {
607
-		$privateKey = $this->keyManager->getKey($user)->getPrivate();
608
-		openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
609
-		return base64_encode($signature);
610
-	}
62
+    /** Limit for counting users for subadmins, to avoid spending too much time */
63
+    private const COUNT_LIMIT_FOR_SUBADMINS = 999;
64
+
65
+    public const ALLOWED_USER_PREFERENCES = [
66
+        ConfigLexicon::USER_LIST_SHOW_STORAGE_PATH,
67
+        ConfigLexicon::USER_LIST_SHOW_USER_BACKEND,
68
+        ConfigLexicon::USER_LIST_SHOW_FIRST_LOGIN,
69
+        ConfigLexicon::USER_LIST_SHOW_LAST_LOGIN,
70
+        ConfigLexicon::USER_LIST_SHOW_NEW_USER_FORM,
71
+        ConfigLexicon::USER_LIST_SHOW_LANGUAGES,
72
+    ];
73
+
74
+    public function __construct(
75
+        string $appName,
76
+        IRequest $request,
77
+        private UserManager $userManager,
78
+        private IGroupManager $groupManager,
79
+        private IUserSession $userSession,
80
+        private IConfig $config,
81
+        private IAppConfig $appConfig,
82
+        private IUserConfig $userConfig,
83
+        private IL10N $l10n,
84
+        private IMailer $mailer,
85
+        private IFactory $l10nFactory,
86
+        private IAppManager $appManager,
87
+        private IAccountManager $accountManager,
88
+        private Manager $keyManager,
89
+        private IJobList $jobList,
90
+        private IManager $encryptionManager,
91
+        private KnownUserService $knownUserService,
92
+        private IEventDispatcher $dispatcher,
93
+        private IInitialState $initialState,
94
+    ) {
95
+        parent::__construct($appName, $request);
96
+    }
97
+
98
+
99
+    /**
100
+     * Display users list template
101
+     *
102
+     * @return TemplateResponse
103
+     */
104
+    #[NoAdminRequired]
105
+    #[NoCSRFRequired]
106
+    public function usersListByGroup(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
107
+        return $this->usersList($navigationManager, $subAdmin);
108
+    }
109
+
110
+    /**
111
+     * Display users list template
112
+     *
113
+     * @return TemplateResponse
114
+     */
115
+    #[NoAdminRequired]
116
+    #[NoCSRFRequired]
117
+    public function usersList(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
118
+        $user = $this->userSession->getUser();
119
+        $uid = $user->getUID();
120
+        $isAdmin = $this->groupManager->isAdmin($uid);
121
+        $isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($uid);
122
+
123
+        $navigationManager->setActiveEntry('core_users');
124
+
125
+        /* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
126
+        $sortGroupsBy = MetaData::SORT_USERCOUNT;
127
+        $isLDAPUsed = false;
128
+        if ($this->config->getSystemValueBool('sort_groups_by_name', false)) {
129
+            $sortGroupsBy = MetaData::SORT_GROUPNAME;
130
+        } else {
131
+            if ($this->appManager->isEnabledForUser('user_ldap')) {
132
+                $isLDAPUsed
133
+                    = $this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
134
+                if ($isLDAPUsed) {
135
+                    // LDAP user count can be slow, so we sort by group name here
136
+                    $sortGroupsBy = MetaData::SORT_GROUPNAME;
137
+                }
138
+            }
139
+        }
140
+
141
+        $canChangePassword = $this->canAdminChangeUserPasswords();
142
+
143
+        /* GROUPS */
144
+        $groupsInfo = new MetaData(
145
+            $uid,
146
+            $isAdmin,
147
+            $isDelegatedAdmin,
148
+            $this->groupManager,
149
+            $this->userSession
150
+        );
151
+
152
+        $adminGroup = $this->groupManager->get('admin');
153
+        $adminGroupData = [
154
+            'id' => $adminGroup->getGID(),
155
+            'name' => $adminGroup->getDisplayName(),
156
+            'usercount' => $sortGroupsBy === MetaData::SORT_USERCOUNT ? $adminGroup->count() : 0,
157
+            'disabled' => $adminGroup->countDisabled(),
158
+            'canAdd' => $adminGroup->canAddUser(),
159
+            'canRemove' => $adminGroup->canRemoveUser(),
160
+        ];
161
+
162
+        if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
163
+            $isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
164
+                return $ldapFound || $backend instanceof User_Proxy;
165
+            });
166
+        }
167
+
168
+        $disabledUsers = -1;
169
+        $userCount = 0;
170
+
171
+        if (!$isLDAPUsed) {
172
+            if ($isAdmin || $isDelegatedAdmin) {
173
+                $disabledUsers = $this->userManager->countDisabledUsers();
174
+                $userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
175
+                    return $v + (int)$w;
176
+                }, 0);
177
+            } else {
178
+                // User is subadmin !
179
+                [$userCount,$disabledUsers] = $this->userManager->countUsersAndDisabledUsersOfGroups($groupsInfo->getGroups(), self::COUNT_LIMIT_FOR_SUBADMINS);
180
+            }
181
+
182
+            if ($disabledUsers > 0) {
183
+                $userCount -= $disabledUsers;
184
+            }
185
+        }
186
+
187
+        $recentUsersGroup = [
188
+            'id' => '__nc_internal_recent',
189
+            'name' => $this->l10n->t('Recently active'),
190
+            'usercount' => $this->userManager->countSeenUsers(),
191
+        ];
192
+
193
+        $disabledUsersGroup = [
194
+            'id' => 'disabled',
195
+            'name' => $this->l10n->t('Disabled accounts'),
196
+            'usercount' => $disabledUsers
197
+        ];
198
+
199
+        if (!$isAdmin && !$isDelegatedAdmin) {
200
+            $subAdminGroups = array_map(
201
+                fn (IGroup $group) => ['id' => $group->getGID(), 'name' => $group->getDisplayName()],
202
+                $subAdmin->getSubAdminsGroups($user),
203
+            );
204
+            $subAdminGroups = array_values($subAdminGroups);
205
+        }
206
+
207
+        /* QUOTAS PRESETS */
208
+        $quotaPreset = $this->parseQuotaPreset($this->appConfig->getValueString('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
209
+        $allowUnlimitedQuota = $this->appConfig->getValueBool('files', 'allow_unlimited_quota', true);
210
+        if (!$allowUnlimitedQuota && count($quotaPreset) > 0) {
211
+            $defaultQuota = $this->appConfig->getValueString('files', 'default_quota', $quotaPreset[0]);
212
+        } else {
213
+            $defaultQuota = $this->appConfig->getValueString('files', 'default_quota', 'none');
214
+        }
215
+
216
+        $event = new BeforeTemplateRenderedEvent();
217
+        $this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
218
+        $this->dispatcher->dispatchTyped($event);
219
+
220
+        /* LANGUAGES */
221
+        $languages = $this->l10nFactory->getLanguages();
222
+
223
+        /** Using LDAP or admins (system config) can enfore sorting by group name, in this case the frontend setting is overwritten */
224
+        $forceSortGroupByName = $sortGroupsBy === MetaData::SORT_GROUPNAME;
225
+
226
+        /* FINAL DATA */
227
+        $serverData = [];
228
+        // groups
229
+        $serverData['systemGroups'] = [$adminGroupData, $recentUsersGroup, $disabledUsersGroup];
230
+        $serverData['subAdminGroups'] = $subAdminGroups ?? [];
231
+        // Various data
232
+        $serverData['isAdmin'] = $isAdmin;
233
+        $serverData['isDelegatedAdmin'] = $isDelegatedAdmin;
234
+        $serverData['sortGroups'] = $forceSortGroupByName
235
+            ? MetaData::SORT_GROUPNAME
236
+            : (int)$this->appConfig->getValueString('core', 'group.sortBy', (string)MetaData::SORT_USERCOUNT);
237
+        $serverData['forceSortGroupByName'] = $forceSortGroupByName;
238
+        $serverData['quotaPreset'] = $quotaPreset;
239
+        $serverData['allowUnlimitedQuota'] = $allowUnlimitedQuota;
240
+        $serverData['userCount'] = $userCount;
241
+        $serverData['languages'] = $languages;
242
+        $serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
243
+        $serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
244
+        // Settings
245
+        $serverData['defaultQuota'] = $defaultQuota;
246
+        $serverData['canChangePassword'] = $canChangePassword;
247
+        $serverData['newUserGenerateUserID'] = $this->appConfig->getValueBool('core', 'newUser.generateUserID', false);
248
+        $serverData['newUserRequireEmail'] = $this->appConfig->getValueBool('core', 'newUser.requireEmail', false);
249
+        $serverData['newUserSendEmail'] = $this->appConfig->getValueBool('core', 'newUser.sendEmail', true);
250
+        $serverData['showConfig'] = [];
251
+        foreach (self::ALLOWED_USER_PREFERENCES as $key) {
252
+            $serverData['showConfig'][$key] = $this->userConfig->getValueBool($uid, $this->appName, $key, false);
253
+        }
254
+
255
+        $this->initialState->provideInitialState('usersSettings', $serverData);
256
+
257
+        Util::addStyle('settings', 'settings');
258
+        Util::addScript('settings', 'vue-settings-apps-users-management');
259
+
260
+        return new TemplateResponse('settings', 'settings/empty', ['pageTitle' => $this->l10n->t('Settings')]);
261
+    }
262
+
263
+    /**
264
+     * @param string $key
265
+     * @param string $value
266
+     *
267
+     * @return JSONResponse
268
+     */
269
+    #[AuthorizedAdminSetting(settings:Users::class)]
270
+    public function setPreference(string $key, string $value): JSONResponse {
271
+        switch ($key) {
272
+            case 'newUser.sendEmail':
273
+                $this->appConfig->setValueBool('core', $key, $value === 'yes');
274
+                break;
275
+            case 'group.sortBy':
276
+                $this->appConfig->setValueString('core', $key, $value);
277
+                break;
278
+            default:
279
+                if (in_array($key, self::ALLOWED_USER_PREFERENCES, true)) {
280
+                    $this->userConfig->setValueBool($this->userSession->getUser()->getUID(), $this->appName, $key, $value === 'true');
281
+                } else {
282
+                    return new JSONResponse([], Http::STATUS_FORBIDDEN);
283
+                }
284
+                break;
285
+        }
286
+
287
+        return new JSONResponse([]);
288
+    }
289
+
290
+    /**
291
+     * Parse the app value for quota_present
292
+     *
293
+     * @param string $quotaPreset
294
+     * @return array
295
+     */
296
+    protected function parseQuotaPreset(string $quotaPreset): array {
297
+        // 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
298
+        $presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
299
+        // Drop default and none, Make array indexes numerically
300
+        return array_values(array_diff($presets, ['default', 'none']));
301
+    }
302
+
303
+    /**
304
+     * check if the admin can change the users password
305
+     *
306
+     * The admin can change the passwords if:
307
+     *
308
+     *   - no encryption module is loaded and encryption is disabled
309
+     *   - encryption module is loaded but it doesn't require per user keys
310
+     *
311
+     * The admin can not change the passwords if:
312
+     *
313
+     *   - an encryption module is loaded and it uses per-user keys
314
+     *   - encryption is enabled but no encryption modules are loaded
315
+     *
316
+     * @return bool
317
+     */
318
+    protected function canAdminChangeUserPasswords(): bool {
319
+        $isEncryptionEnabled = $this->encryptionManager->isEnabled();
320
+        try {
321
+            $noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
322
+            $isEncryptionModuleLoaded = true;
323
+        } catch (ModuleDoesNotExistsException $e) {
324
+            $noUserSpecificEncryptionKeys = true;
325
+            $isEncryptionModuleLoaded = false;
326
+        }
327
+        $canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
328
+            || (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
329
+
330
+        return $canChangePassword;
331
+    }
332
+
333
+    /**
334
+     * @NoSubAdminRequired
335
+     *
336
+     * @param string|null $avatarScope
337
+     * @param string|null $displayname
338
+     * @param string|null $displaynameScope
339
+     * @param string|null $phone
340
+     * @param string|null $phoneScope
341
+     * @param string|null $email
342
+     * @param string|null $emailScope
343
+     * @param string|null $website
344
+     * @param string|null $websiteScope
345
+     * @param string|null $address
346
+     * @param string|null $addressScope
347
+     * @param string|null $twitter
348
+     * @param string|null $twitterScope
349
+     * @param string|null $bluesky
350
+     * @param string|null $blueskyScope
351
+     * @param string|null $fediverse
352
+     * @param string|null $fediverseScope
353
+     * @param string|null $birthdate
354
+     * @param string|null $birthdateScope
355
+     *
356
+     * @return DataResponse
357
+     */
358
+    #[NoAdminRequired]
359
+    #[PasswordConfirmationRequired]
360
+    #[UserRateLimit(limit: 5, period: 60)]
361
+    public function setUserSettings(?string $avatarScope = null,
362
+        ?string $displayname = null,
363
+        ?string $displaynameScope = null,
364
+        ?string $phone = null,
365
+        ?string $phoneScope = null,
366
+        ?string $email = null,
367
+        ?string $emailScope = null,
368
+        ?string $website = null,
369
+        ?string $websiteScope = null,
370
+        ?string $address = null,
371
+        ?string $addressScope = null,
372
+        ?string $twitter = null,
373
+        ?string $twitterScope = null,
374
+        ?string $bluesky = null,
375
+        ?string $blueskyScope = null,
376
+        ?string $fediverse = null,
377
+        ?string $fediverseScope = null,
378
+        ?string $birthdate = null,
379
+        ?string $birthdateScope = null,
380
+        ?string $pronouns = null,
381
+        ?string $pronounsScope = null,
382
+    ) {
383
+        $user = $this->userSession->getUser();
384
+        if (!$user instanceof IUser) {
385
+            return new DataResponse(
386
+                [
387
+                    'status' => 'error',
388
+                    'data' => [
389
+                        'message' => $this->l10n->t('Invalid account')
390
+                    ]
391
+                ],
392
+                Http::STATUS_UNAUTHORIZED
393
+            );
394
+        }
395
+
396
+        $email = !is_null($email) ? strtolower($email) : $email;
397
+        if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
398
+            return new DataResponse(
399
+                [
400
+                    'status' => 'error',
401
+                    'data' => [
402
+                        'message' => $this->l10n->t('Invalid mail address')
403
+                    ]
404
+                ],
405
+                Http::STATUS_UNPROCESSABLE_ENTITY
406
+            );
407
+        }
408
+
409
+        $userAccount = $this->accountManager->getAccount($user);
410
+        $oldPhoneValue = $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
411
+
412
+        $updatable = [
413
+            IAccountManager::PROPERTY_AVATAR => ['value' => null, 'scope' => $avatarScope],
414
+            IAccountManager::PROPERTY_DISPLAYNAME => ['value' => $displayname, 'scope' => $displaynameScope],
415
+            IAccountManager::PROPERTY_EMAIL => ['value' => $email, 'scope' => $emailScope],
416
+            IAccountManager::PROPERTY_WEBSITE => ['value' => $website, 'scope' => $websiteScope],
417
+            IAccountManager::PROPERTY_ADDRESS => ['value' => $address, 'scope' => $addressScope],
418
+            IAccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
419
+            IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
420
+            IAccountManager::PROPERTY_BLUESKY => ['value' => $bluesky, 'scope' => $blueskyScope],
421
+            IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
422
+            IAccountManager::PROPERTY_BIRTHDATE => ['value' => $birthdate, 'scope' => $birthdateScope],
423
+            IAccountManager::PROPERTY_PRONOUNS => ['value' => $pronouns, 'scope' => $pronounsScope],
424
+        ];
425
+        $allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
426
+        foreach ($updatable as $property => $data) {
427
+            if ($allowUserToChangeDisplayName === false
428
+                && in_array($property, [IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::PROPERTY_EMAIL], true)) {
429
+                continue;
430
+            }
431
+            $property = $userAccount->getProperty($property);
432
+            if ($data['value'] !== null) {
433
+                $property->setValue($data['value']);
434
+            }
435
+            if ($data['scope'] !== null) {
436
+                $property->setScope($data['scope']);
437
+            }
438
+        }
439
+
440
+        try {
441
+            $this->saveUserSettings($userAccount);
442
+            if ($oldPhoneValue !== $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue()) {
443
+                $this->knownUserService->deleteByContactUserId($user->getUID());
444
+            }
445
+            return new DataResponse(
446
+                [
447
+                    'status' => 'success',
448
+                    'data' => [
449
+                        'userId' => $user->getUID(),
450
+                        'avatarScope' => $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(),
451
+                        'displayname' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(),
452
+                        'displaynameScope' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(),
453
+                        'phone' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(),
454
+                        'phoneScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getScope(),
455
+                        'email' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
456
+                        'emailScope' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
457
+                        'website' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(),
458
+                        'websiteScope' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getScope(),
459
+                        'address' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getValue(),
460
+                        'addressScope' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getScope(),
461
+                        'twitter' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(),
462
+                        'twitterScope' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
463
+                        'bluesky' => $userAccount->getProperty(IAccountManager::PROPERTY_BLUESKY)->getValue(),
464
+                        'blueskyScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BLUESKY)->getScope(),
465
+                        'fediverse' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue(),
466
+                        'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
467
+                        'birthdate' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getValue(),
468
+                        'birthdateScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getScope(),
469
+                        'pronouns' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getValue(),
470
+                        'pronounsScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getScope(),
471
+                        'message' => $this->l10n->t('Settings saved'),
472
+                    ],
473
+                ],
474
+                Http::STATUS_OK
475
+            );
476
+        } catch (ForbiddenException|InvalidArgumentException|PropertyDoesNotExistException $e) {
477
+            return new DataResponse([
478
+                'status' => 'error',
479
+                'data' => [
480
+                    'message' => $e->getMessage()
481
+                ],
482
+            ]);
483
+        }
484
+    }
485
+    /**
486
+     * update account manager with new user data
487
+     *
488
+     * @throws ForbiddenException
489
+     * @throws InvalidArgumentException
490
+     */
491
+    protected function saveUserSettings(IAccount $userAccount): void {
492
+        // keep the user back-end up-to-date with the latest display name and email
493
+        // address
494
+        $oldDisplayName = $userAccount->getUser()->getDisplayName();
495
+        if ($oldDisplayName !== $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue()) {
496
+            $result = $userAccount->getUser()->setDisplayName($userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue());
497
+            if ($result === false) {
498
+                throw new ForbiddenException($this->l10n->t('Unable to change full name'));
499
+            }
500
+        }
501
+
502
+        $oldEmailAddress = $userAccount->getUser()->getSystemEMailAddress();
503
+        $oldEmailAddress = strtolower((string)$oldEmailAddress);
504
+        if ($oldEmailAddress !== strtolower($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue())) {
505
+            // this is the only permission a backend provides and is also used
506
+            // for the permission of setting a email address
507
+            if (!$userAccount->getUser()->canChangeDisplayName()) {
508
+                throw new ForbiddenException($this->l10n->t('Unable to change email address'));
509
+            }
510
+            $userAccount->getUser()->setSystemEMailAddress($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue());
511
+        }
512
+
513
+        try {
514
+            $this->accountManager->updateAccount($userAccount);
515
+        } catch (InvalidArgumentException $e) {
516
+            if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
517
+                throw new InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
518
+            }
519
+            if ($e->getMessage() === IAccountManager::PROPERTY_WEBSITE) {
520
+                throw new InvalidArgumentException($this->l10n->t('Unable to set invalid website'));
521
+            }
522
+            throw new InvalidArgumentException($this->l10n->t('Some account data was invalid'));
523
+        }
524
+    }
525
+
526
+    /**
527
+     * Set the mail address of a user
528
+     *
529
+     * @NoSubAdminRequired
530
+     *
531
+     * @param string $account
532
+     * @param bool $onlyVerificationCode only return verification code without updating the data
533
+     * @return DataResponse
534
+     */
535
+    #[NoAdminRequired]
536
+    #[PasswordConfirmationRequired]
537
+    public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
538
+        $user = $this->userSession->getUser();
539
+
540
+        if ($user === null) {
541
+            return new DataResponse([], Http::STATUS_BAD_REQUEST);
542
+        }
543
+
544
+        $userAccount = $this->accountManager->getAccount($user);
545
+        $cloudId = $user->getCloudId();
546
+        $message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
547
+        $signature = $this->signMessage($user, $message);
548
+
549
+        $code = $message . ' ' . $signature;
550
+        $codeMd5 = $message . ' ' . md5($signature);
551
+
552
+        switch ($account) {
553
+            case 'verify-twitter':
554
+                $msg = $this->l10n->t('In order to verify your Twitter account, post the following tweet on Twitter (please make sure to post it without any line breaks):');
555
+                $code = $codeMd5;
556
+                $type = IAccountManager::PROPERTY_TWITTER;
557
+                break;
558
+            case 'verify-website':
559
+                $msg = $this->l10n->t('In order to verify your Website, store the following content in your web-root at \'.well-known/CloudIdVerificationCode.txt\' (please make sure that the complete text is in one line):');
560
+                $type = IAccountManager::PROPERTY_WEBSITE;
561
+                break;
562
+            default:
563
+                return new DataResponse([], Http::STATUS_BAD_REQUEST);
564
+        }
565
+
566
+        $userProperty = $userAccount->getProperty($type);
567
+        $userProperty
568
+            ->setVerified(IAccountManager::VERIFICATION_IN_PROGRESS)
569
+            ->setVerificationData($signature);
570
+
571
+        if ($onlyVerificationCode === false) {
572
+            $this->accountManager->updateAccount($userAccount);
573
+
574
+            $this->jobList->add(VerifyUserData::class,
575
+                [
576
+                    'verificationCode' => $code,
577
+                    'data' => $userProperty->getValue(),
578
+                    'type' => $type,
579
+                    'uid' => $user->getUID(),
580
+                    'try' => 0,
581
+                    'lastRun' => $this->getCurrentTime()
582
+                ]
583
+            );
584
+        }
585
+
586
+        return new DataResponse(['msg' => $msg, 'code' => $code]);
587
+    }
588
+
589
+    /**
590
+     * get current timestamp
591
+     *
592
+     * @return int
593
+     */
594
+    protected function getCurrentTime(): int {
595
+        return time();
596
+    }
597
+
598
+    /**
599
+     * sign message with users private key
600
+     *
601
+     * @param IUser $user
602
+     * @param string $message
603
+     *
604
+     * @return string base64 encoded signature
605
+     */
606
+    protected function signMessage(IUser $user, string $message): string {
607
+        $privateKey = $this->keyManager->getKey($user)->getPrivate();
608
+        openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
609
+        return base64_encode($signature);
610
+    }
611 611
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -160,7 +160,7 @@  discard block
 block discarded – undo
160 160
 		];
161 161
 
162 162
 		if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
163
-			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
163
+			$isLDAPUsed = (bool) array_reduce($this->userManager->getBackends(), function($ldapFound, $backend) {
164 164
 				return $ldapFound || $backend instanceof User_Proxy;
165 165
 			});
166 166
 		}
@@ -171,12 +171,12 @@  discard block
 block discarded – undo
171 171
 		if (!$isLDAPUsed) {
172 172
 			if ($isAdmin || $isDelegatedAdmin) {
173 173
 				$disabledUsers = $this->userManager->countDisabledUsers();
174
-				$userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
175
-					return $v + (int)$w;
174
+				$userCount = array_reduce($this->userManager->countUsers(), function($v, $w) {
175
+					return $v + (int) $w;
176 176
 				}, 0);
177 177
 			} else {
178 178
 				// User is subadmin !
179
-				[$userCount,$disabledUsers] = $this->userManager->countUsersAndDisabledUsersOfGroups($groupsInfo->getGroups(), self::COUNT_LIMIT_FOR_SUBADMINS);
179
+				[$userCount, $disabledUsers] = $this->userManager->countUsersAndDisabledUsersOfGroups($groupsInfo->getGroups(), self::COUNT_LIMIT_FOR_SUBADMINS);
180 180
 			}
181 181
 
182 182
 			if ($disabledUsers > 0) {
@@ -233,7 +233,7 @@  discard block
 block discarded – undo
233 233
 		$serverData['isDelegatedAdmin'] = $isDelegatedAdmin;
234 234
 		$serverData['sortGroups'] = $forceSortGroupByName
235 235
 			? MetaData::SORT_GROUPNAME
236
-			: (int)$this->appConfig->getValueString('core', 'group.sortBy', (string)MetaData::SORT_USERCOUNT);
236
+			: (int) $this->appConfig->getValueString('core', 'group.sortBy', (string) MetaData::SORT_USERCOUNT);
237 237
 		$serverData['forceSortGroupByName'] = $forceSortGroupByName;
238 238
 		$serverData['quotaPreset'] = $quotaPreset;
239 239
 		$serverData['allowUnlimitedQuota'] = $allowUnlimitedQuota;
@@ -473,7 +473,7 @@  discard block
 block discarded – undo
473 473
 				],
474 474
 				Http::STATUS_OK
475 475
 			);
476
-		} catch (ForbiddenException|InvalidArgumentException|PropertyDoesNotExistException $e) {
476
+		} catch (ForbiddenException | InvalidArgumentException | PropertyDoesNotExistException $e) {
477 477
 			return new DataResponse([
478 478
 				'status' => 'error',
479 479
 				'data' => [
@@ -500,7 +500,7 @@  discard block
 block discarded – undo
500 500
 		}
501 501
 
502 502
 		$oldEmailAddress = $userAccount->getUser()->getSystemEMailAddress();
503
-		$oldEmailAddress = strtolower((string)$oldEmailAddress);
503
+		$oldEmailAddress = strtolower((string) $oldEmailAddress);
504 504
 		if ($oldEmailAddress !== strtolower($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue())) {
505 505
 			// this is the only permission a backend provides and is also used
506 506
 			// for the permission of setting a email address
@@ -543,11 +543,11 @@  discard block
 block discarded – undo
543 543
 
544 544
 		$userAccount = $this->accountManager->getAccount($user);
545 545
 		$cloudId = $user->getCloudId();
546
-		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
546
+		$message = 'Use my Federated Cloud ID to share with me: '.$cloudId;
547 547
 		$signature = $this->signMessage($user, $message);
548 548
 
549
-		$code = $message . ' ' . $signature;
550
-		$codeMd5 = $message . ' ' . md5($signature);
549
+		$code = $message.' '.$signature;
550
+		$codeMd5 = $message.' '.md5($signature);
551 551
 
552 552
 		switch ($account) {
553 553
 			case 'verify-twitter':
Please login to merge, or discard this patch.
apps/settings/tests/Controller/UsersControllerTest.php 1 patch
Indentation   +1011 added lines, -1011 removed lines patch added patch discarded remove patch
@@ -44,1015 +44,1015 @@
 block discarded – undo
44 44
  */
45 45
 #[\PHPUnit\Framework\Attributes\Group('DB')]
46 46
 class UsersControllerTest extends \Test\TestCase {
47
-	private IGroupManager&MockObject $groupManager;
48
-	private UserManager&MockObject $userManager;
49
-	private IUserSession&MockObject $userSession;
50
-	private IConfig&MockObject $config;
51
-	private IAppConfig&MockObject $appConfig;
52
-	private IUserConfig&MockObject $userConfig;
53
-	private IMailer&MockObject $mailer;
54
-	private IFactory&MockObject $l10nFactory;
55
-	private IAppManager&MockObject $appManager;
56
-	private IL10N&MockObject $l;
57
-	private AccountManager&MockObject $accountManager;
58
-	private IJobList&MockObject $jobList;
59
-	private \OC\Security\IdentityProof\Manager&MockObject $securityManager;
60
-	private IManager&MockObject $encryptionManager;
61
-	private KnownUserService&MockObject $knownUserService;
62
-	private IEncryptionModule&MockObject $encryptionModule;
63
-	private IEventDispatcher&MockObject $dispatcher;
64
-	private IInitialState&MockObject $initialState;
65
-
66
-	protected function setUp(): void {
67
-		parent::setUp();
68
-
69
-		$this->userManager = $this->createMock(UserManager::class);
70
-		$this->groupManager = $this->createMock(Manager::class);
71
-		$this->userSession = $this->createMock(IUserSession::class);
72
-		$this->config = $this->createMock(IConfig::class);
73
-		$this->appConfig = $this->createMock(IAppConfig::class);
74
-		$this->userConfig = $this->createMock(IUserConfig::class);
75
-		$this->l = $this->createMock(IL10N::class);
76
-		$this->mailer = $this->createMock(IMailer::class);
77
-		$this->l10nFactory = $this->createMock(IFactory::class);
78
-		$this->appManager = $this->createMock(IAppManager::class);
79
-		$this->accountManager = $this->createMock(AccountManager::class);
80
-		$this->securityManager = $this->createMock(\OC\Security\IdentityProof\Manager::class);
81
-		$this->jobList = $this->createMock(IJobList::class);
82
-		$this->encryptionManager = $this->createMock(IManager::class);
83
-		$this->knownUserService = $this->createMock(KnownUserService::class);
84
-		$this->dispatcher = $this->createMock(IEventDispatcher::class);
85
-		$this->initialState = $this->createMock(IInitialState::class);
86
-
87
-		$this->l->method('t')
88
-			->willReturnCallback(function ($text, $parameters = []) {
89
-				return vsprintf($text, $parameters);
90
-			});
91
-
92
-		$this->encryptionModule = $this->createMock(IEncryptionModule::class);
93
-		$this->encryptionManager->expects($this->any())->method('getEncryptionModules')
94
-			->willReturn(['encryptionModule' => ['callback' => function () {
95
-				return $this->encryptionModule;
96
-			}]]);
97
-	}
98
-
99
-	/**
100
-	 * @param bool $isAdmin
101
-	 * @return UsersController|MockObject
102
-	 */
103
-	protected function getController(bool $isAdmin = false, array $mockedMethods = []) {
104
-		$this->groupManager->expects($this->any())
105
-			->method('isAdmin')
106
-			->willReturn($isAdmin);
107
-
108
-		if (empty($mockedMethods)) {
109
-			return new UsersController(
110
-				'settings',
111
-				$this->createMock(IRequest::class),
112
-				$this->userManager,
113
-				$this->groupManager,
114
-				$this->userSession,
115
-				$this->config,
116
-				$this->appConfig,
117
-				$this->userConfig,
118
-				$this->l,
119
-				$this->mailer,
120
-				$this->l10nFactory,
121
-				$this->appManager,
122
-				$this->accountManager,
123
-				$this->securityManager,
124
-				$this->jobList,
125
-				$this->encryptionManager,
126
-				$this->knownUserService,
127
-				$this->dispatcher,
128
-				$this->initialState,
129
-			);
130
-		} else {
131
-			return $this->getMockBuilder(UsersController::class)
132
-				->setConstructorArgs(
133
-					[
134
-						'settings',
135
-						$this->createMock(IRequest::class),
136
-						$this->userManager,
137
-						$this->groupManager,
138
-						$this->userSession,
139
-						$this->config,
140
-						$this->appConfig,
141
-						$this->userConfig,
142
-						$this->l,
143
-						$this->mailer,
144
-						$this->l10nFactory,
145
-						$this->appManager,
146
-						$this->accountManager,
147
-						$this->securityManager,
148
-						$this->jobList,
149
-						$this->encryptionManager,
150
-						$this->knownUserService,
151
-						$this->dispatcher,
152
-						$this->initialState,
153
-					]
154
-				)
155
-				->onlyMethods($mockedMethods)
156
-				->getMock();
157
-		}
158
-	}
159
-
160
-	protected function buildPropertyMock(string $name, string $value, string $scope, string $verified = IAccountManager::VERIFIED): MockObject {
161
-		$property = $this->createMock(IAccountProperty::class);
162
-		$property->expects($this->any())
163
-			->method('getName')
164
-			->willReturn($name);
165
-		$property->expects($this->any())
166
-			->method('getValue')
167
-			->willReturn($value);
168
-		$property->expects($this->any())
169
-			->method('getScope')
170
-			->willReturn($scope);
171
-		$property->expects($this->any())
172
-			->method('getVerified')
173
-			->willReturn($verified);
174
-
175
-		return $property;
176
-	}
177
-
178
-	protected function getDefaultAccountMock(): MockObject {
179
-		$propertyMocks = [
180
-			IAccountManager::PROPERTY_DISPLAYNAME => $this->buildPropertyMock(
181
-				IAccountManager::PROPERTY_DISPLAYNAME,
182
-				'Default display name',
183
-				IAccountManager::SCOPE_FEDERATED,
184
-			),
185
-			IAccountManager::PROPERTY_ADDRESS => $this->buildPropertyMock(
186
-				IAccountManager::PROPERTY_ADDRESS,
187
-				'Default address',
188
-				IAccountManager::SCOPE_LOCAL,
189
-			),
190
-			IAccountManager::PROPERTY_WEBSITE => $this->buildPropertyMock(
191
-				IAccountManager::PROPERTY_WEBSITE,
192
-				'Default website',
193
-				IAccountManager::SCOPE_LOCAL,
194
-			),
195
-			IAccountManager::PROPERTY_EMAIL => $this->buildPropertyMock(
196
-				IAccountManager::PROPERTY_EMAIL,
197
-				'Default email',
198
-				IAccountManager::SCOPE_FEDERATED,
199
-			),
200
-			IAccountManager::PROPERTY_AVATAR => $this->buildPropertyMock(
201
-				IAccountManager::PROPERTY_AVATAR,
202
-				'',
203
-				IAccountManager::SCOPE_FEDERATED,
204
-			),
205
-			IAccountManager::PROPERTY_PHONE => $this->buildPropertyMock(
206
-				IAccountManager::PROPERTY_PHONE,
207
-				'Default phone',
208
-				IAccountManager::SCOPE_LOCAL,
209
-			),
210
-			IAccountManager::PROPERTY_TWITTER => $this->buildPropertyMock(
211
-				IAccountManager::PROPERTY_TWITTER,
212
-				'Default twitter',
213
-				IAccountManager::SCOPE_LOCAL,
214
-			),
215
-			IAccountManager::PROPERTY_BLUESKY => $this->buildPropertyMock(
216
-				IAccountManager::PROPERTY_BLUESKY,
217
-				'Default bluesky',
218
-				IAccountManager::SCOPE_LOCAL,
219
-			),
220
-			IAccountManager::PROPERTY_FEDIVERSE => $this->buildPropertyMock(
221
-				IAccountManager::PROPERTY_FEDIVERSE,
222
-				'Default fediverse',
223
-				IAccountManager::SCOPE_LOCAL,
224
-			),
225
-			IAccountManager::PROPERTY_BIRTHDATE => $this->buildPropertyMock(
226
-				IAccountManager::PROPERTY_BIRTHDATE,
227
-				'Default birthdate',
228
-				IAccountManager::SCOPE_LOCAL,
229
-			),
230
-			IAccountManager::PROPERTY_PRONOUNS => $this->buildPropertyMock(
231
-				IAccountManager::PROPERTY_PRONOUNS,
232
-				'Default pronouns',
233
-				IAccountManager::SCOPE_LOCAL,
234
-			),
235
-		];
236
-
237
-		$account = $this->createMock(IAccount::class);
238
-		$account->expects($this->any())
239
-			->method('getProperty')
240
-			->willReturnCallback(function (string $propertyName) use ($propertyMocks) {
241
-				if (isset($propertyMocks[$propertyName])) {
242
-					return $propertyMocks[$propertyName];
243
-				}
244
-				throw new PropertyDoesNotExistException($propertyName);
245
-			});
246
-		$account->expects($this->any())
247
-			->method('getProperties')
248
-			->willReturn($propertyMocks);
249
-
250
-		return $account;
251
-	}
252
-
253
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestSetUserSettings')]
254
-	public function testSetUserSettings(string $email, bool $validEmail, int $expectedStatus): void {
255
-		$controller = $this->getController(false, ['saveUserSettings']);
256
-		$user = $this->createMock(IUser::class);
257
-		$user->method('getUID')->willReturn('johndoe');
258
-
259
-		$this->userSession->method('getUser')->willReturn($user);
260
-
261
-		if (!empty($email) && $validEmail) {
262
-			$this->mailer->expects($this->once())->method('validateMailAddress')
263
-				->willReturn($validEmail);
264
-		}
265
-
266
-		$saveData = (!empty($email) && $validEmail) || empty($email);
267
-
268
-		if ($saveData) {
269
-			$this->accountManager->expects($this->once())
270
-				->method('getAccount')
271
-				->with($user)
272
-				->willReturn($this->getDefaultAccountMock());
273
-
274
-			$controller->expects($this->once())
275
-				->method('saveUserSettings');
276
-		} else {
277
-			$controller->expects($this->never())->method('saveUserSettings');
278
-		}
279
-
280
-		$result = $controller->setUserSettings(
281
-			AccountManager::SCOPE_FEDERATED,
282
-			'displayName',
283
-			AccountManager::SCOPE_FEDERATED,
284
-			'47658468',
285
-			AccountManager::SCOPE_FEDERATED,
286
-			$email,
287
-			AccountManager::SCOPE_FEDERATED,
288
-			'nextcloud.com',
289
-			AccountManager::SCOPE_FEDERATED,
290
-			'street and city',
291
-			AccountManager::SCOPE_FEDERATED,
292
-			'@nextclouders',
293
-			AccountManager::SCOPE_FEDERATED,
294
-			'@nextclouders',
295
-			AccountManager::SCOPE_FEDERATED,
296
-			'2020-01-01',
297
-			AccountManager::SCOPE_FEDERATED,
298
-			'they/them',
299
-			AccountManager::SCOPE_FEDERATED,
300
-		);
301
-
302
-		$this->assertSame($expectedStatus, $result->getStatus());
303
-	}
304
-
305
-	public static function dataTestSetUserSettings(): array {
306
-		return [
307
-			['', true, Http::STATUS_OK],
308
-			['', false, Http::STATUS_OK],
309
-			['example.com', false, Http::STATUS_UNPROCESSABLE_ENTITY],
310
-			['[email protected]', true, Http::STATUS_OK],
311
-		];
312
-	}
313
-
314
-	public function testSetUserSettingsWhenUserDisplayNameChangeNotAllowed(): void {
315
-		$controller = $this->getController(false, ['saveUserSettings']);
316
-
317
-		$avatarScope = IAccountManager::SCOPE_PUBLISHED;
318
-		$displayName = 'Display name';
319
-		$displayNameScope = IAccountManager::SCOPE_PUBLISHED;
320
-		$phone = '47658468';
321
-		$phoneScope = IAccountManager::SCOPE_PUBLISHED;
322
-		$email = '[email protected]';
323
-		$emailScope = IAccountManager::SCOPE_PUBLISHED;
324
-		$website = 'nextcloud.com';
325
-		$websiteScope = IAccountManager::SCOPE_PUBLISHED;
326
-		$address = 'street and city';
327
-		$addressScope = IAccountManager::SCOPE_PUBLISHED;
328
-		$twitter = '@nextclouders';
329
-		$twitterScope = IAccountManager::SCOPE_PUBLISHED;
330
-		$fediverse = '@[email protected]';
331
-		$fediverseScope = IAccountManager::SCOPE_PUBLISHED;
332
-		$birtdate = '2020-01-01';
333
-		$birthdateScope = IAccountManager::SCOPE_PUBLISHED;
334
-		$pronouns = 'she/her';
335
-		$pronounsScope = IAccountManager::SCOPE_PUBLISHED;
336
-
337
-		$user = $this->createMock(IUser::class);
338
-		$user->method('getUID')->willReturn('johndoe');
339
-
340
-		$this->userSession->method('getUser')->willReturn($user);
341
-
342
-		/** @var MockObject|IAccount $userAccount */
343
-		$userAccount = $this->getDefaultAccountMock();
344
-		$this->accountManager->expects($this->once())
345
-			->method('getAccount')
346
-			->with($user)
347
-			->willReturn($userAccount);
348
-
349
-		/** @var MockObject|IAccountProperty $avatarProperty */
350
-		$avatarProperty = $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR);
351
-		$avatarProperty->expects($this->atLeastOnce())
352
-			->method('setScope')
353
-			->with($avatarScope)
354
-			->willReturnSelf();
355
-
356
-		/** @var MockObject|IAccountProperty $avatarProperty */
357
-		$avatarProperty = $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS);
358
-		$avatarProperty->expects($this->atLeastOnce())
359
-			->method('setScope')
360
-			->with($addressScope)
361
-			->willReturnSelf();
362
-		$avatarProperty->expects($this->atLeastOnce())
363
-			->method('setValue')
364
-			->with($address)
365
-			->willReturnSelf();
366
-
367
-		/** @var MockObject|IAccountProperty $emailProperty */
368
-		$emailProperty = $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL);
369
-		$emailProperty->expects($this->never())
370
-			->method('setValue');
371
-		$emailProperty->expects($this->never())
372
-			->method('setScope');
373
-
374
-		/** @var MockObject|IAccountProperty $emailProperty */
375
-		$emailProperty = $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
376
-		$emailProperty->expects($this->never())
377
-			->method('setValue');
378
-		$emailProperty->expects($this->never())
379
-			->method('setScope');
380
-
381
-		$this->config->expects($this->once())
382
-			->method('getSystemValueBool')
383
-			->with('allow_user_to_change_display_name')
384
-			->willReturn(false);
385
-
386
-		$this->appManager->expects($this->any())
387
-			->method('isEnabledForUser')
388
-			->with('federatedfilesharing')
389
-			->willReturn(true);
390
-
391
-		$this->mailer->expects($this->once())->method('validateMailAddress')
392
-			->willReturn(true);
393
-
394
-		$controller->expects($this->once())
395
-			->method('saveUserSettings');
396
-
397
-		$controller->setUserSettings(
398
-			$avatarScope,
399
-			$displayName,
400
-			$displayNameScope,
401
-			$phone,
402
-			$phoneScope,
403
-			$email,
404
-			$emailScope,
405
-			$website,
406
-			$websiteScope,
407
-			$address,
408
-			$addressScope,
409
-			$twitter,
410
-			$twitterScope,
411
-			$fediverse,
412
-			$fediverseScope,
413
-			$birtdate,
414
-			$birthdateScope,
415
-			$pronouns,
416
-			$pronounsScope,
417
-		);
418
-	}
419
-
420
-	public function testSetUserSettingsWhenFederatedFilesharingNotEnabled(): void {
421
-		$controller = $this->getController(false, ['saveUserSettings']);
422
-		$user = $this->createMock(IUser::class);
423
-		$user->method('getUID')->willReturn('johndoe');
424
-
425
-		$this->userSession->method('getUser')->willReturn($user);
426
-
427
-		$defaultProperties = []; //$this->getDefaultAccountMock();
428
-
429
-		$userAccount = $this->getDefaultAccountMock();
430
-		$this->accountManager->expects($this->once())
431
-			->method('getAccount')
432
-			->with($user)
433
-			->willReturn($userAccount);
434
-
435
-		$this->appManager->expects($this->any())
436
-			->method('isEnabledForUser')
437
-			->with('federatedfilesharing')
438
-			->willReturn(false);
439
-
440
-		$avatarScope = IAccountManager::SCOPE_PUBLISHED;
441
-		$displayName = 'Display name';
442
-		$displayNameScope = IAccountManager::SCOPE_PUBLISHED;
443
-		$phone = '47658468';
444
-		$phoneScope = IAccountManager::SCOPE_PUBLISHED;
445
-		$email = '[email protected]';
446
-		$emailScope = IAccountManager::SCOPE_PUBLISHED;
447
-		$website = 'nextcloud.com';
448
-		$websiteScope = IAccountManager::SCOPE_PUBLISHED;
449
-		$address = 'street and city';
450
-		$addressScope = IAccountManager::SCOPE_PUBLISHED;
451
-		$twitter = '@nextclouders';
452
-		$twitterScope = IAccountManager::SCOPE_PUBLISHED;
453
-		$bluesky = 'nextclouders.net';
454
-		$blueskyScope = IAccountManager::SCOPE_PUBLISHED;
455
-		$fediverse = '@[email protected]';
456
-		$fediverseScope = IAccountManager::SCOPE_PUBLISHED;
457
-		$birthdate = '2020-01-01';
458
-		$birthdateScope = IAccountManager::SCOPE_PUBLISHED;
459
-		$pronouns = 'she/her';
460
-		$pronounsScope = IAccountManager::SCOPE_PUBLISHED;
461
-
462
-		// All settings are changed (in the past phone, website, address and
463
-		// twitter were not changed).
464
-		$expectedProperties = $defaultProperties;
465
-		$expectedProperties[IAccountManager::PROPERTY_AVATAR]['scope'] = $avatarScope;
466
-		$expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayName;
467
-		$expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['scope'] = $displayNameScope;
468
-		$expectedProperties[IAccountManager::PROPERTY_EMAIL]['value'] = $email;
469
-		$expectedProperties[IAccountManager::PROPERTY_EMAIL]['scope'] = $emailScope;
470
-		$expectedProperties[IAccountManager::PROPERTY_PHONE]['value'] = $phone;
471
-		$expectedProperties[IAccountManager::PROPERTY_PHONE]['scope'] = $phoneScope;
472
-		$expectedProperties[IAccountManager::PROPERTY_WEBSITE]['value'] = $website;
473
-		$expectedProperties[IAccountManager::PROPERTY_WEBSITE]['scope'] = $websiteScope;
474
-		$expectedProperties[IAccountManager::PROPERTY_ADDRESS]['value'] = $address;
475
-		$expectedProperties[IAccountManager::PROPERTY_ADDRESS]['scope'] = $addressScope;
476
-		$expectedProperties[IAccountManager::PROPERTY_TWITTER]['value'] = $twitter;
477
-		$expectedProperties[IAccountManager::PROPERTY_TWITTER]['scope'] = $twitterScope;
478
-		$expectedProperties[IAccountManager::PROPERTY_BLUESKY]['value'] = $bluesky;
479
-		$expectedProperties[IAccountManager::PROPERTY_BLUESKY]['scope'] = $blueskyScope;
480
-		$expectedProperties[IAccountManager::PROPERTY_FEDIVERSE]['value'] = $fediverse;
481
-		$expectedProperties[IAccountManager::PROPERTY_FEDIVERSE]['scope'] = $fediverseScope;
482
-		$expectedProperties[IAccountManager::PROPERTY_BIRTHDATE]['value'] = $birthdate;
483
-		$expectedProperties[IAccountManager::PROPERTY_BIRTHDATE]['scope'] = $birthdateScope;
484
-		$expectedProperties[IAccountManager::PROPERTY_PRONOUNS]['value'] = $pronouns;
485
-		$expectedProperties[IAccountManager::PROPERTY_PRONOUNS]['scope'] = $pronounsScope;
486
-
487
-		$this->mailer->expects($this->once())->method('validateMailAddress')
488
-			->willReturn(true);
489
-
490
-		$controller->expects($this->once())
491
-			->method('saveUserSettings')
492
-			->with($userAccount);
493
-
494
-		$controller->setUserSettings(
495
-			$avatarScope,
496
-			$displayName,
497
-			$displayNameScope,
498
-			$phone,
499
-			$phoneScope,
500
-			$email,
501
-			$emailScope,
502
-			$website,
503
-			$websiteScope,
504
-			$address,
505
-			$addressScope,
506
-			$twitter,
507
-			$twitterScope,
508
-			$bluesky,
509
-			$blueskyScope,
510
-			$fediverse,
511
-			$fediverseScope,
512
-			$birthdate,
513
-			$birthdateScope,
514
-			$pronouns,
515
-			$pronounsScope,
516
-		);
517
-	}
518
-
519
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestSetUserSettingsSubset')]
520
-	public function testSetUserSettingsSubset(string $property, string $propertyValue): void {
521
-		$controller = $this->getController(false, ['saveUserSettings']);
522
-		$user = $this->createMock(IUser::class);
523
-		$user->method('getUID')->willReturn('johndoe');
524
-
525
-		$this->userSession->method('getUser')->willReturn($user);
526
-
527
-		/** @var IAccount&MockObject $userAccount */
528
-		$userAccount = $this->getDefaultAccountMock();
529
-
530
-		$this->accountManager->expects($this->once())
531
-			->method('getAccount')
532
-			->with($user)
533
-			->willReturn($userAccount);
534
-
535
-		$avatarScope = ($property === 'avatarScope') ? $propertyValue : null;
536
-		$displayName = ($property === 'displayName') ? $propertyValue : null;
537
-		$displayNameScope = ($property === 'displayNameScope') ? $propertyValue : null;
538
-		$phone = ($property === 'phone') ? $propertyValue : null;
539
-		$phoneScope = ($property === 'phoneScope') ? $propertyValue : null;
540
-		$email = ($property === 'email') ? $propertyValue : null;
541
-		$emailScope = ($property === 'emailScope') ? $propertyValue : null;
542
-		$website = ($property === 'website') ? $propertyValue : null;
543
-		$websiteScope = ($property === 'websiteScope') ? $propertyValue : null;
544
-		$address = ($property === 'address') ? $propertyValue : null;
545
-		$addressScope = ($property === 'addressScope') ? $propertyValue : null;
546
-		$twitter = ($property === 'twitter') ? $propertyValue : null;
547
-		$twitterScope = ($property === 'twitterScope') ? $propertyValue : null;
548
-		$bluesky = ($property === 'bluesky') ? $propertyValue : null;
549
-		$blueskyScope = ($property === 'blueskyScope') ? $propertyValue : null;
550
-		$fediverse = ($property === 'fediverse') ? $propertyValue : null;
551
-		$fediverseScope = ($property === 'fediverseScope') ? $propertyValue : null;
552
-		$birthdate = ($property === 'birthdate') ? $propertyValue : null;
553
-		$birthdateScope = ($property === 'birthdateScope') ? $propertyValue : null;
554
-		$pronouns = ($property === 'pronouns') ? $propertyValue : null;
555
-		$pronounsScope = ($property === 'pronounsScope') ? $propertyValue : null;
556
-
557
-		/** @var IAccountProperty[]&MockObject[] $expectedProperties */
558
-		$expectedProperties = $userAccount->getProperties();
559
-		$isScope = strrpos($property, 'Scope') === strlen($property) - strlen('5');
560
-		switch ($property) {
561
-			case 'avatarScope':
562
-				$propertyId = IAccountManager::PROPERTY_AVATAR;
563
-				break;
564
-			case 'displayName':
565
-			case 'displayNameScope':
566
-				$propertyId = IAccountManager::PROPERTY_DISPLAYNAME;
567
-				break;
568
-			case 'phone':
569
-			case 'phoneScope':
570
-				$propertyId = IAccountManager::PROPERTY_PHONE;
571
-				break;
572
-			case 'email':
573
-			case 'emailScope':
574
-				$propertyId = IAccountManager::PROPERTY_EMAIL;
575
-				break;
576
-			case 'website':
577
-			case 'websiteScope':
578
-				$propertyId = IAccountManager::PROPERTY_WEBSITE;
579
-				break;
580
-			case 'address':
581
-			case 'addressScope':
582
-				$propertyId = IAccountManager::PROPERTY_ADDRESS;
583
-				break;
584
-			case 'twitter':
585
-			case 'twitterScope':
586
-				$propertyId = IAccountManager::PROPERTY_TWITTER;
587
-				break;
588
-			case 'bluesky':
589
-			case 'blueskyScope':
590
-				$propertyId = IAccountManager::PROPERTY_BLUESKY;
591
-				break;
592
-			case 'fediverse':
593
-			case 'fediverseScope':
594
-				$propertyId = IAccountManager::PROPERTY_FEDIVERSE;
595
-				break;
596
-			case 'birthdate':
597
-			case 'birthdateScope':
598
-				$propertyId = IAccountManager::PROPERTY_BIRTHDATE;
599
-				break;
600
-			case 'pronouns':
601
-			case 'pronounsScope':
602
-				$propertyId = IAccountManager::PROPERTY_PRONOUNS;
603
-				break;
604
-			default:
605
-				$propertyId = '404';
606
-		}
607
-		$expectedProperties[$propertyId]->expects($this->any())
608
-			->method($isScope ? 'getScope' : 'getValue')
609
-			->willReturn($propertyValue);
610
-
611
-		if (!empty($email)) {
612
-			$this->mailer->expects($this->once())->method('validateMailAddress')
613
-				->willReturn(true);
614
-		}
615
-
616
-		$controller->expects($this->once())
617
-			->method('saveUserSettings')
618
-			->with($userAccount);
619
-
620
-		$controller->setUserSettings(
621
-			$avatarScope,
622
-			$displayName,
623
-			$displayNameScope,
624
-			$phone,
625
-			$phoneScope,
626
-			$email,
627
-			$emailScope,
628
-			$website,
629
-			$websiteScope,
630
-			$address,
631
-			$addressScope,
632
-			$twitter,
633
-			$twitterScope,
634
-			$bluesky,
635
-			$blueskyScope,
636
-			$fediverse,
637
-			$fediverseScope,
638
-			$birthdate,
639
-			$birthdateScope,
640
-			$pronouns,
641
-			$pronounsScope,
642
-		);
643
-	}
644
-
645
-	public static function dataTestSetUserSettingsSubset(): array {
646
-		return [
647
-			['avatarScope', IAccountManager::SCOPE_PUBLISHED],
648
-			['displayName', 'Display name'],
649
-			['displayNameScope', IAccountManager::SCOPE_PUBLISHED],
650
-			['phone', '47658468'],
651
-			['phoneScope', IAccountManager::SCOPE_PUBLISHED],
652
-			['email', '[email protected]'],
653
-			['emailScope', IAccountManager::SCOPE_PUBLISHED],
654
-			['website', 'nextcloud.com'],
655
-			['websiteScope', IAccountManager::SCOPE_PUBLISHED],
656
-			['address', 'street and city'],
657
-			['addressScope', IAccountManager::SCOPE_PUBLISHED],
658
-			['twitter', '@nextclouders'],
659
-			['twitterScope', IAccountManager::SCOPE_PUBLISHED],
660
-			['bluesky', 'nextclouders.net'],
661
-			['blueskyScope', IAccountManager::SCOPE_PUBLISHED],
662
-			['fediverse', '@[email protected]'],
663
-			['fediverseScope', IAccountManager::SCOPE_PUBLISHED],
664
-			['birthdate', '2020-01-01'],
665
-			['birthdateScope', IAccountManager::SCOPE_PUBLISHED],
666
-			['pronouns', 'he/him'],
667
-			['pronounsScope', IAccountManager::SCOPE_PUBLISHED],
668
-		];
669
-	}
670
-
671
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestSaveUserSettings')]
672
-	public function testSaveUserSettings(array $data, ?string $oldEmailAddress, ?string $oldDisplayName): void {
673
-		$controller = $this->getController();
674
-		$user = $this->createMock(IUser::class);
675
-
676
-		$user->method('getDisplayName')->willReturn($oldDisplayName);
677
-		$user->method('getSystemEMailAddress')->willReturn($oldEmailAddress);
678
-		$user->method('canChangeDisplayName')->willReturn(true);
679
-
680
-		if (strtolower($data[IAccountManager::PROPERTY_EMAIL]['value']) === strtolower($oldEmailAddress ?? '')) {
681
-			$user->expects($this->never())->method('setSystemEMailAddress');
682
-		} else {
683
-			$user->expects($this->once())->method('setSystemEMailAddress')
684
-				->with($data[IAccountManager::PROPERTY_EMAIL]['value']);
685
-		}
686
-
687
-		if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName ?? '') {
688
-			$user->expects($this->never())->method('setDisplayName');
689
-		} else {
690
-			$user->expects($this->once())->method('setDisplayName')
691
-				->with($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
692
-				->willReturn(true);
693
-		}
694
-
695
-		$properties = [];
696
-		foreach ($data as $propertyName => $propertyData) {
697
-			$properties[$propertyName] = $this->createMock(IAccountProperty::class);
698
-			$properties[$propertyName]->expects($this->any())
699
-				->method('getValue')
700
-				->willReturn($propertyData['value']);
701
-		}
702
-
703
-		$account = $this->createMock(IAccount::class);
704
-		$account->expects($this->any())
705
-			->method('getUser')
706
-			->willReturn($user);
707
-		$account->expects($this->any())
708
-			->method('getProperty')
709
-			->willReturnCallback(function (string $propertyName) use ($properties) {
710
-				return $properties[$propertyName];
711
-			});
712
-
713
-		$this->accountManager->expects($this->any())
714
-			->method('getAccount')
715
-			->willReturn($account);
716
-
717
-		$this->accountManager->expects($this->once())
718
-			->method('updateAccount')
719
-			->with($account);
720
-
721
-		$this->invokePrivate($controller, 'saveUserSettings', [$account]);
722
-	}
723
-
724
-	public static function dataTestSaveUserSettings(): array {
725
-		return [
726
-			[
727
-				[
728
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
729
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
730
-				],
731
-				'[email protected]',
732
-				'john doe'
733
-			],
734
-			[
735
-				[
736
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
737
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
738
-				],
739
-				'[email protected]',
740
-				'john New doe'
741
-			],
742
-			[
743
-				[
744
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
745
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
746
-				],
747
-				'[email protected]',
748
-				'john doe'
749
-			],
750
-			[
751
-				[
752
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
753
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
754
-				],
755
-				'[email protected]',
756
-				'john New doe'
757
-			],
758
-			[
759
-				[
760
-					IAccountManager::PROPERTY_EMAIL => ['value' => ''],
761
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
762
-				],
763
-				null,
764
-				'john New doe'
765
-			],
766
-			[
767
-				[
768
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
769
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
770
-				],
771
-				'[email protected]',
772
-				null
773
-			],
774
-			[
775
-				[
776
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
777
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
778
-				],
779
-				'[email protected]',
780
-				null
781
-			],
782
-
783
-		];
784
-	}
785
-
786
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestSaveUserSettingsException')]
787
-	public function testSaveUserSettingsException(
788
-		array $data,
789
-		string $oldEmailAddress,
790
-		string $oldDisplayName,
791
-		bool $setDisplayNameResult,
792
-		bool $canChangeEmail,
793
-	): void {
794
-		$this->expectException(ForbiddenException::class);
795
-
796
-		$controller = $this->getController();
797
-		$user = $this->createMock(IUser::class);
798
-
799
-		$user->method('getDisplayName')->willReturn($oldDisplayName);
800
-		$user->method('getEMailAddress')->willReturn($oldEmailAddress);
801
-
802
-		/** @var MockObject|IAccount $userAccount */
803
-		$userAccount = $this->createMock(IAccount::class);
804
-		$userAccount->expects($this->any())
805
-			->method('getUser')
806
-			->willReturn($user);
807
-		$propertyMocks = [];
808
-		foreach ($data as $propertyName => $propertyData) {
809
-			/** @var MockObject|IAccountProperty $property */
810
-			$propertyMocks[$propertyName] = $this->buildPropertyMock($propertyName, $propertyData['value'], '');
811
-		}
812
-		$userAccount->expects($this->any())
813
-			->method('getProperty')
814
-			->willReturnCallback(function (string $propertyName) use ($propertyMocks) {
815
-				return $propertyMocks[$propertyName];
816
-			});
817
-
818
-		if ($data[IAccountManager::PROPERTY_EMAIL]['value'] !== $oldEmailAddress) {
819
-			$user->method('canChangeDisplayName')
820
-				->willReturn($canChangeEmail);
821
-		}
822
-
823
-		if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] !== $oldDisplayName) {
824
-			$user->method('setDisplayName')
825
-				->with($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
826
-				->willReturn($setDisplayNameResult);
827
-		}
828
-
829
-		$this->invokePrivate($controller, 'saveUserSettings', [$userAccount]);
830
-	}
831
-
832
-
833
-	public static function dataTestSaveUserSettingsException(): array {
834
-		return [
835
-			[
836
-				[
837
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
838
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
839
-				],
840
-				'[email protected]',
841
-				'john New doe',
842
-				true,
843
-				false
844
-			],
845
-			[
846
-				[
847
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
848
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
849
-				],
850
-				'[email protected]',
851
-				'john New doe',
852
-				false,
853
-				true
854
-			],
855
-			[
856
-				[
857
-					IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
858
-					IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
859
-				],
860
-				'[email protected]',
861
-				'john New doe',
862
-				false,
863
-				false
864
-			],
865
-
866
-		];
867
-	}
868
-
869
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetVerificationCode')]
870
-	public function testGetVerificationCode(string $account, string $type, array $dataBefore, array $expectedData, bool $onlyVerificationCode): void {
871
-		$message = 'Use my Federated Cloud ID to share with me: [email protected]';
872
-		$signature = 'theSignature';
873
-
874
-		$code = $message . ' ' . $signature;
875
-		if ($type === IAccountManager::PROPERTY_TWITTER || $type === IAccountManager::PROPERTY_FEDIVERSE) {
876
-			$code = $message . ' ' . md5($signature);
877
-		}
878
-
879
-		$controller = $this->getController(false, ['signMessage', 'getCurrentTime']);
880
-
881
-		$user = $this->createMock(IUser::class);
882
-
883
-		$property = $this->buildPropertyMock($type, $dataBefore[$type]['value'], '', IAccountManager::NOT_VERIFIED);
884
-		$property->expects($this->atLeastOnce())
885
-			->method('setVerified')
886
-			->with(IAccountManager::VERIFICATION_IN_PROGRESS)
887
-			->willReturnSelf();
888
-		$property->expects($this->atLeastOnce())
889
-			->method('setVerificationData')
890
-			->with($signature)
891
-			->willReturnSelf();
892
-
893
-		$userAccount = $this->createMock(IAccount::class);
894
-		$userAccount->expects($this->any())
895
-			->method('getUser')
896
-			->willReturn($user);
897
-		$userAccount->expects($this->any())
898
-			->method('getProperty')
899
-			->willReturn($property);
900
-
901
-		$this->userSession->expects($this->once())->method('getUser')->willReturn($user);
902
-		$this->accountManager->expects($this->once())->method('getAccount')->with($user)->willReturn($userAccount);
903
-		$user->expects($this->any())->method('getCloudId')->willReturn('[email protected]');
904
-		$user->expects($this->any())->method('getUID')->willReturn('uid');
905
-		$controller->expects($this->once())->method('signMessage')->with($user, $message)->willReturn($signature);
906
-		$controller->expects($this->any())->method('getCurrentTime')->willReturn(1234567);
907
-
908
-		if ($onlyVerificationCode === false) {
909
-			$this->accountManager->expects($this->once())->method('updateAccount')->with($userAccount)->willReturnArgument(1);
910
-			$this->jobList->expects($this->once())->method('add')
911
-				->with('OCA\Settings\BackgroundJobs\VerifyUserData',
912
-					[
913
-						'verificationCode' => $code,
914
-						'data' => $dataBefore[$type]['value'],
915
-						'type' => $type,
916
-						'uid' => 'uid',
917
-						'try' => 0,
918
-						'lastRun' => 1234567
919
-					]);
920
-		}
921
-
922
-		$result = $controller->getVerificationCode($account, $onlyVerificationCode);
923
-
924
-		$data = $result->getData();
925
-		$this->assertSame(Http::STATUS_OK, $result->getStatus());
926
-		$this->assertSame($code, $data['code']);
927
-	}
928
-
929
-	public static function dataTestGetVerificationCode(): array {
930
-		$accountDataBefore = [
931
-			IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::NOT_VERIFIED],
932
-			IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
933
-		];
934
-
935
-		$accountDataAfterWebsite = [
936
-			IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
937
-			IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
938
-		];
939
-
940
-		$accountDataAfterTwitter = [
941
-			IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::NOT_VERIFIED],
942
-			IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
943
-		];
944
-
945
-		return [
946
-			['verify-twitter', IAccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, false],
947
-			['verify-website', IAccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, false],
948
-			['verify-twitter', IAccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, true],
949
-			['verify-website', IAccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, true],
950
-		];
951
-	}
952
-
953
-	/**
954
-	 * test get verification code in case no valid user was given
955
-	 */
956
-	public function testGetVerificationCodeInvalidUser(): void {
957
-		$controller = $this->getController();
958
-		$this->userSession->expects($this->once())->method('getUser')->willReturn(null);
959
-		$result = $controller->getVerificationCode('account', false);
960
-
961
-		$this->assertSame(Http::STATUS_BAD_REQUEST, $result->getStatus());
962
-	}
963
-
964
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataTestCanAdminChangeUserPasswords')]
965
-	public function testCanAdminChangeUserPasswords(
966
-		bool $encryptionEnabled,
967
-		bool $encryptionModuleLoaded,
968
-		bool $masterKeyEnabled,
969
-		bool $expected,
970
-	): void {
971
-		$controller = $this->getController();
972
-
973
-		$this->encryptionManager->expects($this->any())
974
-			->method('isEnabled')
975
-			->willReturn($encryptionEnabled);
976
-		$this->encryptionManager->expects($this->any())
977
-			->method('getEncryptionModule')
978
-			->willReturnCallback(function () use ($encryptionModuleLoaded) {
979
-				if ($encryptionModuleLoaded) {
980
-					return $this->encryptionModule;
981
-				} else {
982
-					throw new ModuleDoesNotExistsException();
983
-				}
984
-			});
985
-		$this->encryptionModule->expects($this->any())
986
-			->method('needDetailedAccessList')
987
-			->willReturn(!$masterKeyEnabled);
988
-
989
-		$result = $this->invokePrivate($controller, 'canAdminChangeUserPasswords', []);
990
-		$this->assertSame($expected, $result);
991
-	}
992
-
993
-	public static function dataTestCanAdminChangeUserPasswords(): array {
994
-		return [
995
-			// encryptionEnabled, encryptionModuleLoaded, masterKeyEnabled, expectedResult
996
-			[true, true, true, true],
997
-			[false, true, true, true],
998
-			[true, false, true, false],
999
-			[false, false, true, true],
1000
-			[true, true, false, false],
1001
-			[false, true, false, false],
1002
-			[true, false, false, false],
1003
-			[false, false, false, true],
1004
-		];
1005
-	}
1006
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataSetPreference')]
1007
-	public function testSetPreference(string $key, string $value, bool $isUserValue, bool $isAppValue, int $expectedStatus): void {
1008
-		$controller = $this->getController(false, []);
1009
-		$user = $this->createMock(IUser::class);
1010
-		$user->method('getUID')->willReturn('testUser');
1011
-		$this->userSession->method('getUser')->willReturn($user);
1012
-
1013
-		if ($isAppValue) {
1014
-			if ($value === 'true' || $value === 'false' || $value === 'yes' || $value === 'no') {
1015
-				$this->appConfig->expects($this->once())
1016
-					->method('setValueBool')
1017
-					->with('core', $key, $value === 'yes' || $value === 'true');
1018
-			} else {
1019
-				$this->appConfig->expects($this->once())
1020
-					->method('setValueString')
1021
-					->with('core', $key, $value);
1022
-			}
1023
-			$this->userConfig->expects($this->never())
1024
-				->method('setValueBool');
1025
-		} elseif ($isUserValue) {
1026
-			$this->userConfig->expects($this->once())
1027
-				->method('setValueBool')
1028
-				->with('testUser', 'settings', $key, $value === 'true');
1029
-			$this->appConfig->expects($this->never())
1030
-				->method('setValueString');
1031
-			$this->appConfig->expects($this->never())
1032
-				->method('setValueBool');
1033
-		} else {
1034
-			$this->appConfig->expects($this->never())->method('setValueString');
1035
-			$this->appConfig->expects($this->never())->method('setValueBool');
1036
-			$this->userConfig->expects($this->never())->method('setValueString');
1037
-			$this->userConfig->expects($this->never())->method('setValueBool');
1038
-		}
1039
-
1040
-		$response = $controller->setPreference($key, $value);
1041
-		$this->assertEquals($expectedStatus, $response->getStatus());
1042
-	}
1043
-
1044
-	public static function dataSetPreference(): array {
1045
-		return [
1046
-			['newUser.sendEmail', 'yes', false, true, Http::STATUS_OK],
1047
-			['newUser.sendEmail', 'no', false, true, Http::STATUS_OK],
1048
-			['group.sortBy', '1', false, true, Http::STATUS_OK],
1049
-			[ConfigLexicon::USER_LIST_SHOW_STORAGE_PATH, 'true', true, false, Http::STATUS_OK],
1050
-			[ConfigLexicon::USER_LIST_SHOW_USER_BACKEND, 'false', true, false, Http::STATUS_OK],
1051
-			[ConfigLexicon::USER_LIST_SHOW_FIRST_LOGIN, 'true', true, false, Http::STATUS_OK],
1052
-			[ConfigLexicon::USER_LIST_SHOW_LAST_LOGIN, 'true', true, false, Http::STATUS_OK],
1053
-			[ConfigLexicon::USER_LIST_SHOW_NEW_USER_FORM, 'true', true, false, Http::STATUS_OK],
1054
-			[ConfigLexicon::USER_LIST_SHOW_LANGUAGES, 'true', true, false, Http::STATUS_OK],
1055
-			['invalidKey', 'value', false, false, Http::STATUS_FORBIDDEN],
1056
-		];
1057
-	}
47
+    private IGroupManager&MockObject $groupManager;
48
+    private UserManager&MockObject $userManager;
49
+    private IUserSession&MockObject $userSession;
50
+    private IConfig&MockObject $config;
51
+    private IAppConfig&MockObject $appConfig;
52
+    private IUserConfig&MockObject $userConfig;
53
+    private IMailer&MockObject $mailer;
54
+    private IFactory&MockObject $l10nFactory;
55
+    private IAppManager&MockObject $appManager;
56
+    private IL10N&MockObject $l;
57
+    private AccountManager&MockObject $accountManager;
58
+    private IJobList&MockObject $jobList;
59
+    private \OC\Security\IdentityProof\Manager&MockObject $securityManager;
60
+    private IManager&MockObject $encryptionManager;
61
+    private KnownUserService&MockObject $knownUserService;
62
+    private IEncryptionModule&MockObject $encryptionModule;
63
+    private IEventDispatcher&MockObject $dispatcher;
64
+    private IInitialState&MockObject $initialState;
65
+
66
+    protected function setUp(): void {
67
+        parent::setUp();
68
+
69
+        $this->userManager = $this->createMock(UserManager::class);
70
+        $this->groupManager = $this->createMock(Manager::class);
71
+        $this->userSession = $this->createMock(IUserSession::class);
72
+        $this->config = $this->createMock(IConfig::class);
73
+        $this->appConfig = $this->createMock(IAppConfig::class);
74
+        $this->userConfig = $this->createMock(IUserConfig::class);
75
+        $this->l = $this->createMock(IL10N::class);
76
+        $this->mailer = $this->createMock(IMailer::class);
77
+        $this->l10nFactory = $this->createMock(IFactory::class);
78
+        $this->appManager = $this->createMock(IAppManager::class);
79
+        $this->accountManager = $this->createMock(AccountManager::class);
80
+        $this->securityManager = $this->createMock(\OC\Security\IdentityProof\Manager::class);
81
+        $this->jobList = $this->createMock(IJobList::class);
82
+        $this->encryptionManager = $this->createMock(IManager::class);
83
+        $this->knownUserService = $this->createMock(KnownUserService::class);
84
+        $this->dispatcher = $this->createMock(IEventDispatcher::class);
85
+        $this->initialState = $this->createMock(IInitialState::class);
86
+
87
+        $this->l->method('t')
88
+            ->willReturnCallback(function ($text, $parameters = []) {
89
+                return vsprintf($text, $parameters);
90
+            });
91
+
92
+        $this->encryptionModule = $this->createMock(IEncryptionModule::class);
93
+        $this->encryptionManager->expects($this->any())->method('getEncryptionModules')
94
+            ->willReturn(['encryptionModule' => ['callback' => function () {
95
+                return $this->encryptionModule;
96
+            }]]);
97
+    }
98
+
99
+    /**
100
+     * @param bool $isAdmin
101
+     * @return UsersController|MockObject
102
+     */
103
+    protected function getController(bool $isAdmin = false, array $mockedMethods = []) {
104
+        $this->groupManager->expects($this->any())
105
+            ->method('isAdmin')
106
+            ->willReturn($isAdmin);
107
+
108
+        if (empty($mockedMethods)) {
109
+            return new UsersController(
110
+                'settings',
111
+                $this->createMock(IRequest::class),
112
+                $this->userManager,
113
+                $this->groupManager,
114
+                $this->userSession,
115
+                $this->config,
116
+                $this->appConfig,
117
+                $this->userConfig,
118
+                $this->l,
119
+                $this->mailer,
120
+                $this->l10nFactory,
121
+                $this->appManager,
122
+                $this->accountManager,
123
+                $this->securityManager,
124
+                $this->jobList,
125
+                $this->encryptionManager,
126
+                $this->knownUserService,
127
+                $this->dispatcher,
128
+                $this->initialState,
129
+            );
130
+        } else {
131
+            return $this->getMockBuilder(UsersController::class)
132
+                ->setConstructorArgs(
133
+                    [
134
+                        'settings',
135
+                        $this->createMock(IRequest::class),
136
+                        $this->userManager,
137
+                        $this->groupManager,
138
+                        $this->userSession,
139
+                        $this->config,
140
+                        $this->appConfig,
141
+                        $this->userConfig,
142
+                        $this->l,
143
+                        $this->mailer,
144
+                        $this->l10nFactory,
145
+                        $this->appManager,
146
+                        $this->accountManager,
147
+                        $this->securityManager,
148
+                        $this->jobList,
149
+                        $this->encryptionManager,
150
+                        $this->knownUserService,
151
+                        $this->dispatcher,
152
+                        $this->initialState,
153
+                    ]
154
+                )
155
+                ->onlyMethods($mockedMethods)
156
+                ->getMock();
157
+        }
158
+    }
159
+
160
+    protected function buildPropertyMock(string $name, string $value, string $scope, string $verified = IAccountManager::VERIFIED): MockObject {
161
+        $property = $this->createMock(IAccountProperty::class);
162
+        $property->expects($this->any())
163
+            ->method('getName')
164
+            ->willReturn($name);
165
+        $property->expects($this->any())
166
+            ->method('getValue')
167
+            ->willReturn($value);
168
+        $property->expects($this->any())
169
+            ->method('getScope')
170
+            ->willReturn($scope);
171
+        $property->expects($this->any())
172
+            ->method('getVerified')
173
+            ->willReturn($verified);
174
+
175
+        return $property;
176
+    }
177
+
178
+    protected function getDefaultAccountMock(): MockObject {
179
+        $propertyMocks = [
180
+            IAccountManager::PROPERTY_DISPLAYNAME => $this->buildPropertyMock(
181
+                IAccountManager::PROPERTY_DISPLAYNAME,
182
+                'Default display name',
183
+                IAccountManager::SCOPE_FEDERATED,
184
+            ),
185
+            IAccountManager::PROPERTY_ADDRESS => $this->buildPropertyMock(
186
+                IAccountManager::PROPERTY_ADDRESS,
187
+                'Default address',
188
+                IAccountManager::SCOPE_LOCAL,
189
+            ),
190
+            IAccountManager::PROPERTY_WEBSITE => $this->buildPropertyMock(
191
+                IAccountManager::PROPERTY_WEBSITE,
192
+                'Default website',
193
+                IAccountManager::SCOPE_LOCAL,
194
+            ),
195
+            IAccountManager::PROPERTY_EMAIL => $this->buildPropertyMock(
196
+                IAccountManager::PROPERTY_EMAIL,
197
+                'Default email',
198
+                IAccountManager::SCOPE_FEDERATED,
199
+            ),
200
+            IAccountManager::PROPERTY_AVATAR => $this->buildPropertyMock(
201
+                IAccountManager::PROPERTY_AVATAR,
202
+                '',
203
+                IAccountManager::SCOPE_FEDERATED,
204
+            ),
205
+            IAccountManager::PROPERTY_PHONE => $this->buildPropertyMock(
206
+                IAccountManager::PROPERTY_PHONE,
207
+                'Default phone',
208
+                IAccountManager::SCOPE_LOCAL,
209
+            ),
210
+            IAccountManager::PROPERTY_TWITTER => $this->buildPropertyMock(
211
+                IAccountManager::PROPERTY_TWITTER,
212
+                'Default twitter',
213
+                IAccountManager::SCOPE_LOCAL,
214
+            ),
215
+            IAccountManager::PROPERTY_BLUESKY => $this->buildPropertyMock(
216
+                IAccountManager::PROPERTY_BLUESKY,
217
+                'Default bluesky',
218
+                IAccountManager::SCOPE_LOCAL,
219
+            ),
220
+            IAccountManager::PROPERTY_FEDIVERSE => $this->buildPropertyMock(
221
+                IAccountManager::PROPERTY_FEDIVERSE,
222
+                'Default fediverse',
223
+                IAccountManager::SCOPE_LOCAL,
224
+            ),
225
+            IAccountManager::PROPERTY_BIRTHDATE => $this->buildPropertyMock(
226
+                IAccountManager::PROPERTY_BIRTHDATE,
227
+                'Default birthdate',
228
+                IAccountManager::SCOPE_LOCAL,
229
+            ),
230
+            IAccountManager::PROPERTY_PRONOUNS => $this->buildPropertyMock(
231
+                IAccountManager::PROPERTY_PRONOUNS,
232
+                'Default pronouns',
233
+                IAccountManager::SCOPE_LOCAL,
234
+            ),
235
+        ];
236
+
237
+        $account = $this->createMock(IAccount::class);
238
+        $account->expects($this->any())
239
+            ->method('getProperty')
240
+            ->willReturnCallback(function (string $propertyName) use ($propertyMocks) {
241
+                if (isset($propertyMocks[$propertyName])) {
242
+                    return $propertyMocks[$propertyName];
243
+                }
244
+                throw new PropertyDoesNotExistException($propertyName);
245
+            });
246
+        $account->expects($this->any())
247
+            ->method('getProperties')
248
+            ->willReturn($propertyMocks);
249
+
250
+        return $account;
251
+    }
252
+
253
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSetUserSettings')]
254
+    public function testSetUserSettings(string $email, bool $validEmail, int $expectedStatus): void {
255
+        $controller = $this->getController(false, ['saveUserSettings']);
256
+        $user = $this->createMock(IUser::class);
257
+        $user->method('getUID')->willReturn('johndoe');
258
+
259
+        $this->userSession->method('getUser')->willReturn($user);
260
+
261
+        if (!empty($email) && $validEmail) {
262
+            $this->mailer->expects($this->once())->method('validateMailAddress')
263
+                ->willReturn($validEmail);
264
+        }
265
+
266
+        $saveData = (!empty($email) && $validEmail) || empty($email);
267
+
268
+        if ($saveData) {
269
+            $this->accountManager->expects($this->once())
270
+                ->method('getAccount')
271
+                ->with($user)
272
+                ->willReturn($this->getDefaultAccountMock());
273
+
274
+            $controller->expects($this->once())
275
+                ->method('saveUserSettings');
276
+        } else {
277
+            $controller->expects($this->never())->method('saveUserSettings');
278
+        }
279
+
280
+        $result = $controller->setUserSettings(
281
+            AccountManager::SCOPE_FEDERATED,
282
+            'displayName',
283
+            AccountManager::SCOPE_FEDERATED,
284
+            '47658468',
285
+            AccountManager::SCOPE_FEDERATED,
286
+            $email,
287
+            AccountManager::SCOPE_FEDERATED,
288
+            'nextcloud.com',
289
+            AccountManager::SCOPE_FEDERATED,
290
+            'street and city',
291
+            AccountManager::SCOPE_FEDERATED,
292
+            '@nextclouders',
293
+            AccountManager::SCOPE_FEDERATED,
294
+            '@nextclouders',
295
+            AccountManager::SCOPE_FEDERATED,
296
+            '2020-01-01',
297
+            AccountManager::SCOPE_FEDERATED,
298
+            'they/them',
299
+            AccountManager::SCOPE_FEDERATED,
300
+        );
301
+
302
+        $this->assertSame($expectedStatus, $result->getStatus());
303
+    }
304
+
305
+    public static function dataTestSetUserSettings(): array {
306
+        return [
307
+            ['', true, Http::STATUS_OK],
308
+            ['', false, Http::STATUS_OK],
309
+            ['example.com', false, Http::STATUS_UNPROCESSABLE_ENTITY],
310
+            ['[email protected]', true, Http::STATUS_OK],
311
+        ];
312
+    }
313
+
314
+    public function testSetUserSettingsWhenUserDisplayNameChangeNotAllowed(): void {
315
+        $controller = $this->getController(false, ['saveUserSettings']);
316
+
317
+        $avatarScope = IAccountManager::SCOPE_PUBLISHED;
318
+        $displayName = 'Display name';
319
+        $displayNameScope = IAccountManager::SCOPE_PUBLISHED;
320
+        $phone = '47658468';
321
+        $phoneScope = IAccountManager::SCOPE_PUBLISHED;
322
+        $email = '[email protected]';
323
+        $emailScope = IAccountManager::SCOPE_PUBLISHED;
324
+        $website = 'nextcloud.com';
325
+        $websiteScope = IAccountManager::SCOPE_PUBLISHED;
326
+        $address = 'street and city';
327
+        $addressScope = IAccountManager::SCOPE_PUBLISHED;
328
+        $twitter = '@nextclouders';
329
+        $twitterScope = IAccountManager::SCOPE_PUBLISHED;
330
+        $fediverse = '@[email protected]';
331
+        $fediverseScope = IAccountManager::SCOPE_PUBLISHED;
332
+        $birtdate = '2020-01-01';
333
+        $birthdateScope = IAccountManager::SCOPE_PUBLISHED;
334
+        $pronouns = 'she/her';
335
+        $pronounsScope = IAccountManager::SCOPE_PUBLISHED;
336
+
337
+        $user = $this->createMock(IUser::class);
338
+        $user->method('getUID')->willReturn('johndoe');
339
+
340
+        $this->userSession->method('getUser')->willReturn($user);
341
+
342
+        /** @var MockObject|IAccount $userAccount */
343
+        $userAccount = $this->getDefaultAccountMock();
344
+        $this->accountManager->expects($this->once())
345
+            ->method('getAccount')
346
+            ->with($user)
347
+            ->willReturn($userAccount);
348
+
349
+        /** @var MockObject|IAccountProperty $avatarProperty */
350
+        $avatarProperty = $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR);
351
+        $avatarProperty->expects($this->atLeastOnce())
352
+            ->method('setScope')
353
+            ->with($avatarScope)
354
+            ->willReturnSelf();
355
+
356
+        /** @var MockObject|IAccountProperty $avatarProperty */
357
+        $avatarProperty = $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS);
358
+        $avatarProperty->expects($this->atLeastOnce())
359
+            ->method('setScope')
360
+            ->with($addressScope)
361
+            ->willReturnSelf();
362
+        $avatarProperty->expects($this->atLeastOnce())
363
+            ->method('setValue')
364
+            ->with($address)
365
+            ->willReturnSelf();
366
+
367
+        /** @var MockObject|IAccountProperty $emailProperty */
368
+        $emailProperty = $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL);
369
+        $emailProperty->expects($this->never())
370
+            ->method('setValue');
371
+        $emailProperty->expects($this->never())
372
+            ->method('setScope');
373
+
374
+        /** @var MockObject|IAccountProperty $emailProperty */
375
+        $emailProperty = $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
376
+        $emailProperty->expects($this->never())
377
+            ->method('setValue');
378
+        $emailProperty->expects($this->never())
379
+            ->method('setScope');
380
+
381
+        $this->config->expects($this->once())
382
+            ->method('getSystemValueBool')
383
+            ->with('allow_user_to_change_display_name')
384
+            ->willReturn(false);
385
+
386
+        $this->appManager->expects($this->any())
387
+            ->method('isEnabledForUser')
388
+            ->with('federatedfilesharing')
389
+            ->willReturn(true);
390
+
391
+        $this->mailer->expects($this->once())->method('validateMailAddress')
392
+            ->willReturn(true);
393
+
394
+        $controller->expects($this->once())
395
+            ->method('saveUserSettings');
396
+
397
+        $controller->setUserSettings(
398
+            $avatarScope,
399
+            $displayName,
400
+            $displayNameScope,
401
+            $phone,
402
+            $phoneScope,
403
+            $email,
404
+            $emailScope,
405
+            $website,
406
+            $websiteScope,
407
+            $address,
408
+            $addressScope,
409
+            $twitter,
410
+            $twitterScope,
411
+            $fediverse,
412
+            $fediverseScope,
413
+            $birtdate,
414
+            $birthdateScope,
415
+            $pronouns,
416
+            $pronounsScope,
417
+        );
418
+    }
419
+
420
+    public function testSetUserSettingsWhenFederatedFilesharingNotEnabled(): void {
421
+        $controller = $this->getController(false, ['saveUserSettings']);
422
+        $user = $this->createMock(IUser::class);
423
+        $user->method('getUID')->willReturn('johndoe');
424
+
425
+        $this->userSession->method('getUser')->willReturn($user);
426
+
427
+        $defaultProperties = []; //$this->getDefaultAccountMock();
428
+
429
+        $userAccount = $this->getDefaultAccountMock();
430
+        $this->accountManager->expects($this->once())
431
+            ->method('getAccount')
432
+            ->with($user)
433
+            ->willReturn($userAccount);
434
+
435
+        $this->appManager->expects($this->any())
436
+            ->method('isEnabledForUser')
437
+            ->with('federatedfilesharing')
438
+            ->willReturn(false);
439
+
440
+        $avatarScope = IAccountManager::SCOPE_PUBLISHED;
441
+        $displayName = 'Display name';
442
+        $displayNameScope = IAccountManager::SCOPE_PUBLISHED;
443
+        $phone = '47658468';
444
+        $phoneScope = IAccountManager::SCOPE_PUBLISHED;
445
+        $email = '[email protected]';
446
+        $emailScope = IAccountManager::SCOPE_PUBLISHED;
447
+        $website = 'nextcloud.com';
448
+        $websiteScope = IAccountManager::SCOPE_PUBLISHED;
449
+        $address = 'street and city';
450
+        $addressScope = IAccountManager::SCOPE_PUBLISHED;
451
+        $twitter = '@nextclouders';
452
+        $twitterScope = IAccountManager::SCOPE_PUBLISHED;
453
+        $bluesky = 'nextclouders.net';
454
+        $blueskyScope = IAccountManager::SCOPE_PUBLISHED;
455
+        $fediverse = '@[email protected]';
456
+        $fediverseScope = IAccountManager::SCOPE_PUBLISHED;
457
+        $birthdate = '2020-01-01';
458
+        $birthdateScope = IAccountManager::SCOPE_PUBLISHED;
459
+        $pronouns = 'she/her';
460
+        $pronounsScope = IAccountManager::SCOPE_PUBLISHED;
461
+
462
+        // All settings are changed (in the past phone, website, address and
463
+        // twitter were not changed).
464
+        $expectedProperties = $defaultProperties;
465
+        $expectedProperties[IAccountManager::PROPERTY_AVATAR]['scope'] = $avatarScope;
466
+        $expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayName;
467
+        $expectedProperties[IAccountManager::PROPERTY_DISPLAYNAME]['scope'] = $displayNameScope;
468
+        $expectedProperties[IAccountManager::PROPERTY_EMAIL]['value'] = $email;
469
+        $expectedProperties[IAccountManager::PROPERTY_EMAIL]['scope'] = $emailScope;
470
+        $expectedProperties[IAccountManager::PROPERTY_PHONE]['value'] = $phone;
471
+        $expectedProperties[IAccountManager::PROPERTY_PHONE]['scope'] = $phoneScope;
472
+        $expectedProperties[IAccountManager::PROPERTY_WEBSITE]['value'] = $website;
473
+        $expectedProperties[IAccountManager::PROPERTY_WEBSITE]['scope'] = $websiteScope;
474
+        $expectedProperties[IAccountManager::PROPERTY_ADDRESS]['value'] = $address;
475
+        $expectedProperties[IAccountManager::PROPERTY_ADDRESS]['scope'] = $addressScope;
476
+        $expectedProperties[IAccountManager::PROPERTY_TWITTER]['value'] = $twitter;
477
+        $expectedProperties[IAccountManager::PROPERTY_TWITTER]['scope'] = $twitterScope;
478
+        $expectedProperties[IAccountManager::PROPERTY_BLUESKY]['value'] = $bluesky;
479
+        $expectedProperties[IAccountManager::PROPERTY_BLUESKY]['scope'] = $blueskyScope;
480
+        $expectedProperties[IAccountManager::PROPERTY_FEDIVERSE]['value'] = $fediverse;
481
+        $expectedProperties[IAccountManager::PROPERTY_FEDIVERSE]['scope'] = $fediverseScope;
482
+        $expectedProperties[IAccountManager::PROPERTY_BIRTHDATE]['value'] = $birthdate;
483
+        $expectedProperties[IAccountManager::PROPERTY_BIRTHDATE]['scope'] = $birthdateScope;
484
+        $expectedProperties[IAccountManager::PROPERTY_PRONOUNS]['value'] = $pronouns;
485
+        $expectedProperties[IAccountManager::PROPERTY_PRONOUNS]['scope'] = $pronounsScope;
486
+
487
+        $this->mailer->expects($this->once())->method('validateMailAddress')
488
+            ->willReturn(true);
489
+
490
+        $controller->expects($this->once())
491
+            ->method('saveUserSettings')
492
+            ->with($userAccount);
493
+
494
+        $controller->setUserSettings(
495
+            $avatarScope,
496
+            $displayName,
497
+            $displayNameScope,
498
+            $phone,
499
+            $phoneScope,
500
+            $email,
501
+            $emailScope,
502
+            $website,
503
+            $websiteScope,
504
+            $address,
505
+            $addressScope,
506
+            $twitter,
507
+            $twitterScope,
508
+            $bluesky,
509
+            $blueskyScope,
510
+            $fediverse,
511
+            $fediverseScope,
512
+            $birthdate,
513
+            $birthdateScope,
514
+            $pronouns,
515
+            $pronounsScope,
516
+        );
517
+    }
518
+
519
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSetUserSettingsSubset')]
520
+    public function testSetUserSettingsSubset(string $property, string $propertyValue): void {
521
+        $controller = $this->getController(false, ['saveUserSettings']);
522
+        $user = $this->createMock(IUser::class);
523
+        $user->method('getUID')->willReturn('johndoe');
524
+
525
+        $this->userSession->method('getUser')->willReturn($user);
526
+
527
+        /** @var IAccount&MockObject $userAccount */
528
+        $userAccount = $this->getDefaultAccountMock();
529
+
530
+        $this->accountManager->expects($this->once())
531
+            ->method('getAccount')
532
+            ->with($user)
533
+            ->willReturn($userAccount);
534
+
535
+        $avatarScope = ($property === 'avatarScope') ? $propertyValue : null;
536
+        $displayName = ($property === 'displayName') ? $propertyValue : null;
537
+        $displayNameScope = ($property === 'displayNameScope') ? $propertyValue : null;
538
+        $phone = ($property === 'phone') ? $propertyValue : null;
539
+        $phoneScope = ($property === 'phoneScope') ? $propertyValue : null;
540
+        $email = ($property === 'email') ? $propertyValue : null;
541
+        $emailScope = ($property === 'emailScope') ? $propertyValue : null;
542
+        $website = ($property === 'website') ? $propertyValue : null;
543
+        $websiteScope = ($property === 'websiteScope') ? $propertyValue : null;
544
+        $address = ($property === 'address') ? $propertyValue : null;
545
+        $addressScope = ($property === 'addressScope') ? $propertyValue : null;
546
+        $twitter = ($property === 'twitter') ? $propertyValue : null;
547
+        $twitterScope = ($property === 'twitterScope') ? $propertyValue : null;
548
+        $bluesky = ($property === 'bluesky') ? $propertyValue : null;
549
+        $blueskyScope = ($property === 'blueskyScope') ? $propertyValue : null;
550
+        $fediverse = ($property === 'fediverse') ? $propertyValue : null;
551
+        $fediverseScope = ($property === 'fediverseScope') ? $propertyValue : null;
552
+        $birthdate = ($property === 'birthdate') ? $propertyValue : null;
553
+        $birthdateScope = ($property === 'birthdateScope') ? $propertyValue : null;
554
+        $pronouns = ($property === 'pronouns') ? $propertyValue : null;
555
+        $pronounsScope = ($property === 'pronounsScope') ? $propertyValue : null;
556
+
557
+        /** @var IAccountProperty[]&MockObject[] $expectedProperties */
558
+        $expectedProperties = $userAccount->getProperties();
559
+        $isScope = strrpos($property, 'Scope') === strlen($property) - strlen('5');
560
+        switch ($property) {
561
+            case 'avatarScope':
562
+                $propertyId = IAccountManager::PROPERTY_AVATAR;
563
+                break;
564
+            case 'displayName':
565
+            case 'displayNameScope':
566
+                $propertyId = IAccountManager::PROPERTY_DISPLAYNAME;
567
+                break;
568
+            case 'phone':
569
+            case 'phoneScope':
570
+                $propertyId = IAccountManager::PROPERTY_PHONE;
571
+                break;
572
+            case 'email':
573
+            case 'emailScope':
574
+                $propertyId = IAccountManager::PROPERTY_EMAIL;
575
+                break;
576
+            case 'website':
577
+            case 'websiteScope':
578
+                $propertyId = IAccountManager::PROPERTY_WEBSITE;
579
+                break;
580
+            case 'address':
581
+            case 'addressScope':
582
+                $propertyId = IAccountManager::PROPERTY_ADDRESS;
583
+                break;
584
+            case 'twitter':
585
+            case 'twitterScope':
586
+                $propertyId = IAccountManager::PROPERTY_TWITTER;
587
+                break;
588
+            case 'bluesky':
589
+            case 'blueskyScope':
590
+                $propertyId = IAccountManager::PROPERTY_BLUESKY;
591
+                break;
592
+            case 'fediverse':
593
+            case 'fediverseScope':
594
+                $propertyId = IAccountManager::PROPERTY_FEDIVERSE;
595
+                break;
596
+            case 'birthdate':
597
+            case 'birthdateScope':
598
+                $propertyId = IAccountManager::PROPERTY_BIRTHDATE;
599
+                break;
600
+            case 'pronouns':
601
+            case 'pronounsScope':
602
+                $propertyId = IAccountManager::PROPERTY_PRONOUNS;
603
+                break;
604
+            default:
605
+                $propertyId = '404';
606
+        }
607
+        $expectedProperties[$propertyId]->expects($this->any())
608
+            ->method($isScope ? 'getScope' : 'getValue')
609
+            ->willReturn($propertyValue);
610
+
611
+        if (!empty($email)) {
612
+            $this->mailer->expects($this->once())->method('validateMailAddress')
613
+                ->willReturn(true);
614
+        }
615
+
616
+        $controller->expects($this->once())
617
+            ->method('saveUserSettings')
618
+            ->with($userAccount);
619
+
620
+        $controller->setUserSettings(
621
+            $avatarScope,
622
+            $displayName,
623
+            $displayNameScope,
624
+            $phone,
625
+            $phoneScope,
626
+            $email,
627
+            $emailScope,
628
+            $website,
629
+            $websiteScope,
630
+            $address,
631
+            $addressScope,
632
+            $twitter,
633
+            $twitterScope,
634
+            $bluesky,
635
+            $blueskyScope,
636
+            $fediverse,
637
+            $fediverseScope,
638
+            $birthdate,
639
+            $birthdateScope,
640
+            $pronouns,
641
+            $pronounsScope,
642
+        );
643
+    }
644
+
645
+    public static function dataTestSetUserSettingsSubset(): array {
646
+        return [
647
+            ['avatarScope', IAccountManager::SCOPE_PUBLISHED],
648
+            ['displayName', 'Display name'],
649
+            ['displayNameScope', IAccountManager::SCOPE_PUBLISHED],
650
+            ['phone', '47658468'],
651
+            ['phoneScope', IAccountManager::SCOPE_PUBLISHED],
652
+            ['email', '[email protected]'],
653
+            ['emailScope', IAccountManager::SCOPE_PUBLISHED],
654
+            ['website', 'nextcloud.com'],
655
+            ['websiteScope', IAccountManager::SCOPE_PUBLISHED],
656
+            ['address', 'street and city'],
657
+            ['addressScope', IAccountManager::SCOPE_PUBLISHED],
658
+            ['twitter', '@nextclouders'],
659
+            ['twitterScope', IAccountManager::SCOPE_PUBLISHED],
660
+            ['bluesky', 'nextclouders.net'],
661
+            ['blueskyScope', IAccountManager::SCOPE_PUBLISHED],
662
+            ['fediverse', '@[email protected]'],
663
+            ['fediverseScope', IAccountManager::SCOPE_PUBLISHED],
664
+            ['birthdate', '2020-01-01'],
665
+            ['birthdateScope', IAccountManager::SCOPE_PUBLISHED],
666
+            ['pronouns', 'he/him'],
667
+            ['pronounsScope', IAccountManager::SCOPE_PUBLISHED],
668
+        ];
669
+    }
670
+
671
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSaveUserSettings')]
672
+    public function testSaveUserSettings(array $data, ?string $oldEmailAddress, ?string $oldDisplayName): void {
673
+        $controller = $this->getController();
674
+        $user = $this->createMock(IUser::class);
675
+
676
+        $user->method('getDisplayName')->willReturn($oldDisplayName);
677
+        $user->method('getSystemEMailAddress')->willReturn($oldEmailAddress);
678
+        $user->method('canChangeDisplayName')->willReturn(true);
679
+
680
+        if (strtolower($data[IAccountManager::PROPERTY_EMAIL]['value']) === strtolower($oldEmailAddress ?? '')) {
681
+            $user->expects($this->never())->method('setSystemEMailAddress');
682
+        } else {
683
+            $user->expects($this->once())->method('setSystemEMailAddress')
684
+                ->with($data[IAccountManager::PROPERTY_EMAIL]['value']);
685
+        }
686
+
687
+        if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName ?? '') {
688
+            $user->expects($this->never())->method('setDisplayName');
689
+        } else {
690
+            $user->expects($this->once())->method('setDisplayName')
691
+                ->with($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
692
+                ->willReturn(true);
693
+        }
694
+
695
+        $properties = [];
696
+        foreach ($data as $propertyName => $propertyData) {
697
+            $properties[$propertyName] = $this->createMock(IAccountProperty::class);
698
+            $properties[$propertyName]->expects($this->any())
699
+                ->method('getValue')
700
+                ->willReturn($propertyData['value']);
701
+        }
702
+
703
+        $account = $this->createMock(IAccount::class);
704
+        $account->expects($this->any())
705
+            ->method('getUser')
706
+            ->willReturn($user);
707
+        $account->expects($this->any())
708
+            ->method('getProperty')
709
+            ->willReturnCallback(function (string $propertyName) use ($properties) {
710
+                return $properties[$propertyName];
711
+            });
712
+
713
+        $this->accountManager->expects($this->any())
714
+            ->method('getAccount')
715
+            ->willReturn($account);
716
+
717
+        $this->accountManager->expects($this->once())
718
+            ->method('updateAccount')
719
+            ->with($account);
720
+
721
+        $this->invokePrivate($controller, 'saveUserSettings', [$account]);
722
+    }
723
+
724
+    public static function dataTestSaveUserSettings(): array {
725
+        return [
726
+            [
727
+                [
728
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
729
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
730
+                ],
731
+                '[email protected]',
732
+                'john doe'
733
+            ],
734
+            [
735
+                [
736
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
737
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
738
+                ],
739
+                '[email protected]',
740
+                'john New doe'
741
+            ],
742
+            [
743
+                [
744
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
745
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
746
+                ],
747
+                '[email protected]',
748
+                'john doe'
749
+            ],
750
+            [
751
+                [
752
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
753
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
754
+                ],
755
+                '[email protected]',
756
+                'john New doe'
757
+            ],
758
+            [
759
+                [
760
+                    IAccountManager::PROPERTY_EMAIL => ['value' => ''],
761
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
762
+                ],
763
+                null,
764
+                'john New doe'
765
+            ],
766
+            [
767
+                [
768
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
769
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
770
+                ],
771
+                '[email protected]',
772
+                null
773
+            ],
774
+            [
775
+                [
776
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
777
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
778
+                ],
779
+                '[email protected]',
780
+                null
781
+            ],
782
+
783
+        ];
784
+    }
785
+
786
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSaveUserSettingsException')]
787
+    public function testSaveUserSettingsException(
788
+        array $data,
789
+        string $oldEmailAddress,
790
+        string $oldDisplayName,
791
+        bool $setDisplayNameResult,
792
+        bool $canChangeEmail,
793
+    ): void {
794
+        $this->expectException(ForbiddenException::class);
795
+
796
+        $controller = $this->getController();
797
+        $user = $this->createMock(IUser::class);
798
+
799
+        $user->method('getDisplayName')->willReturn($oldDisplayName);
800
+        $user->method('getEMailAddress')->willReturn($oldEmailAddress);
801
+
802
+        /** @var MockObject|IAccount $userAccount */
803
+        $userAccount = $this->createMock(IAccount::class);
804
+        $userAccount->expects($this->any())
805
+            ->method('getUser')
806
+            ->willReturn($user);
807
+        $propertyMocks = [];
808
+        foreach ($data as $propertyName => $propertyData) {
809
+            /** @var MockObject|IAccountProperty $property */
810
+            $propertyMocks[$propertyName] = $this->buildPropertyMock($propertyName, $propertyData['value'], '');
811
+        }
812
+        $userAccount->expects($this->any())
813
+            ->method('getProperty')
814
+            ->willReturnCallback(function (string $propertyName) use ($propertyMocks) {
815
+                return $propertyMocks[$propertyName];
816
+            });
817
+
818
+        if ($data[IAccountManager::PROPERTY_EMAIL]['value'] !== $oldEmailAddress) {
819
+            $user->method('canChangeDisplayName')
820
+                ->willReturn($canChangeEmail);
821
+        }
822
+
823
+        if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] !== $oldDisplayName) {
824
+            $user->method('setDisplayName')
825
+                ->with($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
826
+                ->willReturn($setDisplayNameResult);
827
+        }
828
+
829
+        $this->invokePrivate($controller, 'saveUserSettings', [$userAccount]);
830
+    }
831
+
832
+
833
+    public static function dataTestSaveUserSettingsException(): array {
834
+        return [
835
+            [
836
+                [
837
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
838
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
839
+                ],
840
+                '[email protected]',
841
+                'john New doe',
842
+                true,
843
+                false
844
+            ],
845
+            [
846
+                [
847
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
848
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
849
+                ],
850
+                '[email protected]',
851
+                'john New doe',
852
+                false,
853
+                true
854
+            ],
855
+            [
856
+                [
857
+                    IAccountManager::PROPERTY_EMAIL => ['value' => '[email protected]'],
858
+                    IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
859
+                ],
860
+                '[email protected]',
861
+                'john New doe',
862
+                false,
863
+                false
864
+            ],
865
+
866
+        ];
867
+    }
868
+
869
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetVerificationCode')]
870
+    public function testGetVerificationCode(string $account, string $type, array $dataBefore, array $expectedData, bool $onlyVerificationCode): void {
871
+        $message = 'Use my Federated Cloud ID to share with me: [email protected]';
872
+        $signature = 'theSignature';
873
+
874
+        $code = $message . ' ' . $signature;
875
+        if ($type === IAccountManager::PROPERTY_TWITTER || $type === IAccountManager::PROPERTY_FEDIVERSE) {
876
+            $code = $message . ' ' . md5($signature);
877
+        }
878
+
879
+        $controller = $this->getController(false, ['signMessage', 'getCurrentTime']);
880
+
881
+        $user = $this->createMock(IUser::class);
882
+
883
+        $property = $this->buildPropertyMock($type, $dataBefore[$type]['value'], '', IAccountManager::NOT_VERIFIED);
884
+        $property->expects($this->atLeastOnce())
885
+            ->method('setVerified')
886
+            ->with(IAccountManager::VERIFICATION_IN_PROGRESS)
887
+            ->willReturnSelf();
888
+        $property->expects($this->atLeastOnce())
889
+            ->method('setVerificationData')
890
+            ->with($signature)
891
+            ->willReturnSelf();
892
+
893
+        $userAccount = $this->createMock(IAccount::class);
894
+        $userAccount->expects($this->any())
895
+            ->method('getUser')
896
+            ->willReturn($user);
897
+        $userAccount->expects($this->any())
898
+            ->method('getProperty')
899
+            ->willReturn($property);
900
+
901
+        $this->userSession->expects($this->once())->method('getUser')->willReturn($user);
902
+        $this->accountManager->expects($this->once())->method('getAccount')->with($user)->willReturn($userAccount);
903
+        $user->expects($this->any())->method('getCloudId')->willReturn('[email protected]');
904
+        $user->expects($this->any())->method('getUID')->willReturn('uid');
905
+        $controller->expects($this->once())->method('signMessage')->with($user, $message)->willReturn($signature);
906
+        $controller->expects($this->any())->method('getCurrentTime')->willReturn(1234567);
907
+
908
+        if ($onlyVerificationCode === false) {
909
+            $this->accountManager->expects($this->once())->method('updateAccount')->with($userAccount)->willReturnArgument(1);
910
+            $this->jobList->expects($this->once())->method('add')
911
+                ->with('OCA\Settings\BackgroundJobs\VerifyUserData',
912
+                    [
913
+                        'verificationCode' => $code,
914
+                        'data' => $dataBefore[$type]['value'],
915
+                        'type' => $type,
916
+                        'uid' => 'uid',
917
+                        'try' => 0,
918
+                        'lastRun' => 1234567
919
+                    ]);
920
+        }
921
+
922
+        $result = $controller->getVerificationCode($account, $onlyVerificationCode);
923
+
924
+        $data = $result->getData();
925
+        $this->assertSame(Http::STATUS_OK, $result->getStatus());
926
+        $this->assertSame($code, $data['code']);
927
+    }
928
+
929
+    public static function dataTestGetVerificationCode(): array {
930
+        $accountDataBefore = [
931
+            IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::NOT_VERIFIED],
932
+            IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
933
+        ];
934
+
935
+        $accountDataAfterWebsite = [
936
+            IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
937
+            IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
938
+        ];
939
+
940
+        $accountDataAfterTwitter = [
941
+            IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => IAccountManager::NOT_VERIFIED],
942
+            IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => IAccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
943
+        ];
944
+
945
+        return [
946
+            ['verify-twitter', IAccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, false],
947
+            ['verify-website', IAccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, false],
948
+            ['verify-twitter', IAccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, true],
949
+            ['verify-website', IAccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, true],
950
+        ];
951
+    }
952
+
953
+    /**
954
+     * test get verification code in case no valid user was given
955
+     */
956
+    public function testGetVerificationCodeInvalidUser(): void {
957
+        $controller = $this->getController();
958
+        $this->userSession->expects($this->once())->method('getUser')->willReturn(null);
959
+        $result = $controller->getVerificationCode('account', false);
960
+
961
+        $this->assertSame(Http::STATUS_BAD_REQUEST, $result->getStatus());
962
+    }
963
+
964
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataTestCanAdminChangeUserPasswords')]
965
+    public function testCanAdminChangeUserPasswords(
966
+        bool $encryptionEnabled,
967
+        bool $encryptionModuleLoaded,
968
+        bool $masterKeyEnabled,
969
+        bool $expected,
970
+    ): void {
971
+        $controller = $this->getController();
972
+
973
+        $this->encryptionManager->expects($this->any())
974
+            ->method('isEnabled')
975
+            ->willReturn($encryptionEnabled);
976
+        $this->encryptionManager->expects($this->any())
977
+            ->method('getEncryptionModule')
978
+            ->willReturnCallback(function () use ($encryptionModuleLoaded) {
979
+                if ($encryptionModuleLoaded) {
980
+                    return $this->encryptionModule;
981
+                } else {
982
+                    throw new ModuleDoesNotExistsException();
983
+                }
984
+            });
985
+        $this->encryptionModule->expects($this->any())
986
+            ->method('needDetailedAccessList')
987
+            ->willReturn(!$masterKeyEnabled);
988
+
989
+        $result = $this->invokePrivate($controller, 'canAdminChangeUserPasswords', []);
990
+        $this->assertSame($expected, $result);
991
+    }
992
+
993
+    public static function dataTestCanAdminChangeUserPasswords(): array {
994
+        return [
995
+            // encryptionEnabled, encryptionModuleLoaded, masterKeyEnabled, expectedResult
996
+            [true, true, true, true],
997
+            [false, true, true, true],
998
+            [true, false, true, false],
999
+            [false, false, true, true],
1000
+            [true, true, false, false],
1001
+            [false, true, false, false],
1002
+            [true, false, false, false],
1003
+            [false, false, false, true],
1004
+        ];
1005
+    }
1006
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataSetPreference')]
1007
+    public function testSetPreference(string $key, string $value, bool $isUserValue, bool $isAppValue, int $expectedStatus): void {
1008
+        $controller = $this->getController(false, []);
1009
+        $user = $this->createMock(IUser::class);
1010
+        $user->method('getUID')->willReturn('testUser');
1011
+        $this->userSession->method('getUser')->willReturn($user);
1012
+
1013
+        if ($isAppValue) {
1014
+            if ($value === 'true' || $value === 'false' || $value === 'yes' || $value === 'no') {
1015
+                $this->appConfig->expects($this->once())
1016
+                    ->method('setValueBool')
1017
+                    ->with('core', $key, $value === 'yes' || $value === 'true');
1018
+            } else {
1019
+                $this->appConfig->expects($this->once())
1020
+                    ->method('setValueString')
1021
+                    ->with('core', $key, $value);
1022
+            }
1023
+            $this->userConfig->expects($this->never())
1024
+                ->method('setValueBool');
1025
+        } elseif ($isUserValue) {
1026
+            $this->userConfig->expects($this->once())
1027
+                ->method('setValueBool')
1028
+                ->with('testUser', 'settings', $key, $value === 'true');
1029
+            $this->appConfig->expects($this->never())
1030
+                ->method('setValueString');
1031
+            $this->appConfig->expects($this->never())
1032
+                ->method('setValueBool');
1033
+        } else {
1034
+            $this->appConfig->expects($this->never())->method('setValueString');
1035
+            $this->appConfig->expects($this->never())->method('setValueBool');
1036
+            $this->userConfig->expects($this->never())->method('setValueString');
1037
+            $this->userConfig->expects($this->never())->method('setValueBool');
1038
+        }
1039
+
1040
+        $response = $controller->setPreference($key, $value);
1041
+        $this->assertEquals($expectedStatus, $response->getStatus());
1042
+    }
1043
+
1044
+    public static function dataSetPreference(): array {
1045
+        return [
1046
+            ['newUser.sendEmail', 'yes', false, true, Http::STATUS_OK],
1047
+            ['newUser.sendEmail', 'no', false, true, Http::STATUS_OK],
1048
+            ['group.sortBy', '1', false, true, Http::STATUS_OK],
1049
+            [ConfigLexicon::USER_LIST_SHOW_STORAGE_PATH, 'true', true, false, Http::STATUS_OK],
1050
+            [ConfigLexicon::USER_LIST_SHOW_USER_BACKEND, 'false', true, false, Http::STATUS_OK],
1051
+            [ConfigLexicon::USER_LIST_SHOW_FIRST_LOGIN, 'true', true, false, Http::STATUS_OK],
1052
+            [ConfigLexicon::USER_LIST_SHOW_LAST_LOGIN, 'true', true, false, Http::STATUS_OK],
1053
+            [ConfigLexicon::USER_LIST_SHOW_NEW_USER_FORM, 'true', true, false, Http::STATUS_OK],
1054
+            [ConfigLexicon::USER_LIST_SHOW_LANGUAGES, 'true', true, false, Http::STATUS_OK],
1055
+            ['invalidKey', 'value', false, false, Http::STATUS_FORBIDDEN],
1056
+        ];
1057
+    }
1058 1058
 }
Please login to merge, or discard this patch.