Completed
Push — master ( 8df331...e22910 )
by
unknown
21:14 queued 14s
created
apps/settings/lib/Controller/UsersController.php 1 patch
Indentation   +518 added lines, -518 removed lines patch added patch discarded remove patch
@@ -56,522 +56,522 @@
 block discarded – undo
56 56
 
57 57
 #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
58 58
 class UsersController extends Controller {
59
-	/** Limit for counting users for subadmins, to avoid spending too much time */
60
-	private const COUNT_LIMIT_FOR_SUBADMINS = 999;
61
-
62
-	public function __construct(
63
-		string $appName,
64
-		IRequest $request,
65
-		private UserManager $userManager,
66
-		private IGroupManager $groupManager,
67
-		private IUserSession $userSession,
68
-		private IConfig $config,
69
-		private IL10N $l10n,
70
-		private IMailer $mailer,
71
-		private IFactory $l10nFactory,
72
-		private IAppManager $appManager,
73
-		private IAccountManager $accountManager,
74
-		private Manager $keyManager,
75
-		private IJobList $jobList,
76
-		private IManager $encryptionManager,
77
-		private KnownUserService $knownUserService,
78
-		private IEventDispatcher $dispatcher,
79
-		private IInitialState $initialState,
80
-	) {
81
-		parent::__construct($appName, $request);
82
-	}
83
-
84
-
85
-	/**
86
-	 * Display users list template
87
-	 *
88
-	 * @return TemplateResponse
89
-	 */
90
-	#[NoAdminRequired]
91
-	#[NoCSRFRequired]
92
-	public function usersListByGroup(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
93
-		return $this->usersList($navigationManager, $subAdmin);
94
-	}
95
-
96
-	/**
97
-	 * Display users list template
98
-	 *
99
-	 * @return TemplateResponse
100
-	 */
101
-	#[NoAdminRequired]
102
-	#[NoCSRFRequired]
103
-	public function usersList(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
104
-		$user = $this->userSession->getUser();
105
-		$uid = $user->getUID();
106
-		$isAdmin = $this->groupManager->isAdmin($uid);
107
-		$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($uid);
108
-
109
-		$navigationManager->setActiveEntry('core_users');
110
-
111
-		/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
112
-		$sortGroupsBy = MetaData::SORT_USERCOUNT;
113
-		$isLDAPUsed = false;
114
-		if ($this->config->getSystemValueBool('sort_groups_by_name', false)) {
115
-			$sortGroupsBy = MetaData::SORT_GROUPNAME;
116
-		} else {
117
-			if ($this->appManager->isEnabledForUser('user_ldap')) {
118
-				$isLDAPUsed =
119
-					$this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
120
-				if ($isLDAPUsed) {
121
-					// LDAP user count can be slow, so we sort by group name here
122
-					$sortGroupsBy = MetaData::SORT_GROUPNAME;
123
-				}
124
-			}
125
-		}
126
-
127
-		$canChangePassword = $this->canAdminChangeUserPasswords();
128
-
129
-		/* GROUPS */
130
-		$groupsInfo = new MetaData(
131
-			$uid,
132
-			$isAdmin,
133
-			$isDelegatedAdmin,
134
-			$this->groupManager,
135
-			$this->userSession
136
-		);
137
-
138
-		$adminGroup = $this->groupManager->get('admin');
139
-		$adminGroupData = [
140
-			'id' => $adminGroup->getGID(),
141
-			'name' => $adminGroup->getDisplayName(),
142
-			'usercount' => $sortGroupsBy === MetaData::SORT_USERCOUNT ? $adminGroup->count() : 0,
143
-			'disabled' => $adminGroup->countDisabled(),
144
-			'canAdd' => $adminGroup->canAddUser(),
145
-			'canRemove' => $adminGroup->canRemoveUser(),
146
-		];
147
-
148
-		if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
149
-			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
150
-				return $ldapFound || $backend instanceof User_Proxy;
151
-			});
152
-		}
153
-
154
-		$disabledUsers = -1;
155
-		$userCount = 0;
156
-
157
-		if (!$isLDAPUsed) {
158
-			if ($isAdmin || $isDelegatedAdmin) {
159
-				$disabledUsers = $this->userManager->countDisabledUsers();
160
-				$userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
161
-					return $v + (int)$w;
162
-				}, 0);
163
-			} else {
164
-				// User is subadmin !
165
-				[$userCount,$disabledUsers] = $this->userManager->countUsersAndDisabledUsersOfGroups($groupsInfo->getGroups(), self::COUNT_LIMIT_FOR_SUBADMINS);
166
-			}
167
-
168
-			if ($disabledUsers > 0) {
169
-				$userCount -= $disabledUsers;
170
-			}
171
-		}
172
-
173
-		$recentUsersGroup = [
174
-			'id' => '__nc_internal_recent',
175
-			'name' => $this->l10n->t('Recently active'),
176
-			'usercount' => $this->userManager->countSeenUsers(),
177
-		];
178
-
179
-		$disabledUsersGroup = [
180
-			'id' => 'disabled',
181
-			'name' => $this->l10n->t('Disabled accounts'),
182
-			'usercount' => $disabledUsers
183
-		];
184
-
185
-		if (!$isAdmin && !$isDelegatedAdmin) {
186
-			$subAdminGroups = array_map(
187
-				fn (IGroup $group) => ['id' => $group->getGID(), 'name' => $group->getDisplayName()],
188
-				$subAdmin->getSubAdminsGroups($user),
189
-			);
190
-			$subAdminGroups = array_values($subAdminGroups);
191
-		}
192
-
193
-		/* QUOTAS PRESETS */
194
-		$quotaPreset = $this->parseQuotaPreset($this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
195
-		$allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
196
-		if (!$allowUnlimitedQuota && count($quotaPreset) > 0) {
197
-			$defaultQuota = $this->config->getAppValue('files', 'default_quota', $quotaPreset[0]);
198
-		} else {
199
-			$defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
200
-		}
201
-
202
-		$event = new BeforeTemplateRenderedEvent();
203
-		$this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
204
-		$this->dispatcher->dispatchTyped($event);
205
-
206
-		/* LANGUAGES */
207
-		$languages = $this->l10nFactory->getLanguages();
208
-
209
-		/** Using LDAP or admins (system config) can enfore sorting by group name, in this case the frontend setting is overwritten */
210
-		$forceSortGroupByName = $sortGroupsBy === MetaData::SORT_GROUPNAME;
211
-
212
-		/* FINAL DATA */
213
-		$serverData = [];
214
-		// groups
215
-		$serverData['systemGroups'] = [$adminGroupData, $recentUsersGroup, $disabledUsersGroup];
216
-		$serverData['subAdminGroups'] = $subAdminGroups ?? [];
217
-		// Various data
218
-		$serverData['isAdmin'] = $isAdmin;
219
-		$serverData['isDelegatedAdmin'] = $isDelegatedAdmin;
220
-		$serverData['sortGroups'] = $forceSortGroupByName
221
-			? MetaData::SORT_GROUPNAME
222
-			: (int)$this->config->getAppValue('core', 'group.sortBy', (string)MetaData::SORT_USERCOUNT);
223
-		$serverData['forceSortGroupByName'] = $forceSortGroupByName;
224
-		$serverData['quotaPreset'] = $quotaPreset;
225
-		$serverData['allowUnlimitedQuota'] = $allowUnlimitedQuota;
226
-		$serverData['userCount'] = $userCount;
227
-		$serverData['languages'] = $languages;
228
-		$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
229
-		$serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
230
-		// Settings
231
-		$serverData['defaultQuota'] = $defaultQuota;
232
-		$serverData['canChangePassword'] = $canChangePassword;
233
-		$serverData['newUserGenerateUserID'] = $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes';
234
-		$serverData['newUserRequireEmail'] = $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes';
235
-		$serverData['newUserSendEmail'] = $this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes';
236
-
237
-		$this->initialState->provideInitialState('usersSettings', $serverData);
238
-
239
-		Util::addStyle('settings', 'settings');
240
-		Util::addScript('settings', 'vue-settings-apps-users-management');
241
-
242
-		return new TemplateResponse('settings', 'settings/empty', ['pageTitle' => $this->l10n->t('Settings')]);
243
-	}
244
-
245
-	/**
246
-	 * @param string $key
247
-	 * @param string $value
248
-	 *
249
-	 * @return JSONResponse
250
-	 */
251
-	#[AuthorizedAdminSetting(settings:Users::class)]
252
-	public function setPreference(string $key, string $value): JSONResponse {
253
-		$allowed = ['newUser.sendEmail', 'group.sortBy'];
254
-		if (!in_array($key, $allowed, true)) {
255
-			return new JSONResponse([], Http::STATUS_FORBIDDEN);
256
-		}
257
-
258
-		$this->config->setAppValue('core', $key, $value);
259
-
260
-		return new JSONResponse([]);
261
-	}
262
-
263
-	/**
264
-	 * Parse the app value for quota_present
265
-	 *
266
-	 * @param string $quotaPreset
267
-	 * @return array
268
-	 */
269
-	protected function parseQuotaPreset(string $quotaPreset): array {
270
-		// 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
271
-		$presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
272
-		// Drop default and none, Make array indexes numerically
273
-		return array_values(array_diff($presets, ['default', 'none']));
274
-	}
275
-
276
-	/**
277
-	 * check if the admin can change the users password
278
-	 *
279
-	 * The admin can change the passwords if:
280
-	 *
281
-	 *   - no encryption module is loaded and encryption is disabled
282
-	 *   - encryption module is loaded but it doesn't require per user keys
283
-	 *
284
-	 * The admin can not change the passwords if:
285
-	 *
286
-	 *   - an encryption module is loaded and it uses per-user keys
287
-	 *   - encryption is enabled but no encryption modules are loaded
288
-	 *
289
-	 * @return bool
290
-	 */
291
-	protected function canAdminChangeUserPasswords(): bool {
292
-		$isEncryptionEnabled = $this->encryptionManager->isEnabled();
293
-		try {
294
-			$noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
295
-			$isEncryptionModuleLoaded = true;
296
-		} catch (ModuleDoesNotExistsException $e) {
297
-			$noUserSpecificEncryptionKeys = true;
298
-			$isEncryptionModuleLoaded = false;
299
-		}
300
-		$canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
301
-			|| (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
302
-
303
-		return $canChangePassword;
304
-	}
305
-
306
-	/**
307
-	 * @NoSubAdminRequired
308
-	 *
309
-	 * @param string|null $avatarScope
310
-	 * @param string|null $displayname
311
-	 * @param string|null $displaynameScope
312
-	 * @param string|null $phone
313
-	 * @param string|null $phoneScope
314
-	 * @param string|null $email
315
-	 * @param string|null $emailScope
316
-	 * @param string|null $website
317
-	 * @param string|null $websiteScope
318
-	 * @param string|null $address
319
-	 * @param string|null $addressScope
320
-	 * @param string|null $twitter
321
-	 * @param string|null $twitterScope
322
-	 * @param string|null $fediverse
323
-	 * @param string|null $fediverseScope
324
-	 * @param string|null $birthdate
325
-	 * @param string|null $birthdateScope
326
-	 *
327
-	 * @return DataResponse
328
-	 */
329
-	#[NoAdminRequired]
330
-	#[PasswordConfirmationRequired]
331
-	#[UserRateLimit(limit: 5, period: 60)]
332
-	public function setUserSettings(?string $avatarScope = null,
333
-		?string $displayname = null,
334
-		?string $displaynameScope = null,
335
-		?string $phone = null,
336
-		?string $phoneScope = null,
337
-		?string $email = null,
338
-		?string $emailScope = null,
339
-		?string $website = null,
340
-		?string $websiteScope = null,
341
-		?string $address = null,
342
-		?string $addressScope = null,
343
-		?string $twitter = null,
344
-		?string $twitterScope = null,
345
-		?string $fediverse = null,
346
-		?string $fediverseScope = null,
347
-		?string $birthdate = null,
348
-		?string $birthdateScope = null,
349
-		?string $pronouns = null,
350
-		?string $pronounsScope = null,
351
-	) {
352
-		$user = $this->userSession->getUser();
353
-		if (!$user instanceof IUser) {
354
-			return new DataResponse(
355
-				[
356
-					'status' => 'error',
357
-					'data' => [
358
-						'message' => $this->l10n->t('Invalid account')
359
-					]
360
-				],
361
-				Http::STATUS_UNAUTHORIZED
362
-			);
363
-		}
364
-
365
-		$email = !is_null($email) ? strtolower($email) : $email;
366
-		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
367
-			return new DataResponse(
368
-				[
369
-					'status' => 'error',
370
-					'data' => [
371
-						'message' => $this->l10n->t('Invalid mail address')
372
-					]
373
-				],
374
-				Http::STATUS_UNPROCESSABLE_ENTITY
375
-			);
376
-		}
377
-
378
-		$userAccount = $this->accountManager->getAccount($user);
379
-		$oldPhoneValue = $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
380
-
381
-		$updatable = [
382
-			IAccountManager::PROPERTY_AVATAR => ['value' => null, 'scope' => $avatarScope],
383
-			IAccountManager::PROPERTY_DISPLAYNAME => ['value' => $displayname, 'scope' => $displaynameScope],
384
-			IAccountManager::PROPERTY_EMAIL => ['value' => $email, 'scope' => $emailScope],
385
-			IAccountManager::PROPERTY_WEBSITE => ['value' => $website, 'scope' => $websiteScope],
386
-			IAccountManager::PROPERTY_ADDRESS => ['value' => $address, 'scope' => $addressScope],
387
-			IAccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
388
-			IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
389
-			IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
390
-			IAccountManager::PROPERTY_BIRTHDATE => ['value' => $birthdate, 'scope' => $birthdateScope],
391
-			IAccountManager::PROPERTY_PRONOUNS => ['value' => $pronouns, 'scope' => $pronounsScope],
392
-		];
393
-		$allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
394
-		foreach ($updatable as $property => $data) {
395
-			if ($allowUserToChangeDisplayName === false
396
-				&& in_array($property, [IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::PROPERTY_EMAIL], true)) {
397
-				continue;
398
-			}
399
-			$property = $userAccount->getProperty($property);
400
-			if ($data['value'] !== null) {
401
-				$property->setValue($data['value']);
402
-			}
403
-			if ($data['scope'] !== null) {
404
-				$property->setScope($data['scope']);
405
-			}
406
-		}
407
-
408
-		try {
409
-			$this->saveUserSettings($userAccount);
410
-			if ($oldPhoneValue !== $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue()) {
411
-				$this->knownUserService->deleteByContactUserId($user->getUID());
412
-			}
413
-			return new DataResponse(
414
-				[
415
-					'status' => 'success',
416
-					'data' => [
417
-						'userId' => $user->getUID(),
418
-						'avatarScope' => $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(),
419
-						'displayname' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(),
420
-						'displaynameScope' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(),
421
-						'phone' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(),
422
-						'phoneScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getScope(),
423
-						'email' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
424
-						'emailScope' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
425
-						'website' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(),
426
-						'websiteScope' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getScope(),
427
-						'address' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getValue(),
428
-						'addressScope' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getScope(),
429
-						'twitter' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(),
430
-						'twitterScope' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
431
-						'fediverse' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue(),
432
-						'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
433
-						'birthdate' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getValue(),
434
-						'birthdateScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getScope(),
435
-						'pronouns' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getValue(),
436
-						'pronounsScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getScope(),
437
-						'message' => $this->l10n->t('Settings saved'),
438
-					],
439
-				],
440
-				Http::STATUS_OK
441
-			);
442
-		} catch (ForbiddenException|InvalidArgumentException|PropertyDoesNotExistException $e) {
443
-			return new DataResponse([
444
-				'status' => 'error',
445
-				'data' => [
446
-					'message' => $e->getMessage()
447
-				],
448
-			]);
449
-		}
450
-	}
451
-	/**
452
-	 * update account manager with new user data
453
-	 *
454
-	 * @throws ForbiddenException
455
-	 * @throws InvalidArgumentException
456
-	 */
457
-	protected function saveUserSettings(IAccount $userAccount): void {
458
-		// keep the user back-end up-to-date with the latest display name and email
459
-		// address
460
-		$oldDisplayName = $userAccount->getUser()->getDisplayName();
461
-		if ($oldDisplayName !== $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue()) {
462
-			$result = $userAccount->getUser()->setDisplayName($userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue());
463
-			if ($result === false) {
464
-				throw new ForbiddenException($this->l10n->t('Unable to change full name'));
465
-			}
466
-		}
467
-
468
-		$oldEmailAddress = $userAccount->getUser()->getSystemEMailAddress();
469
-		$oldEmailAddress = strtolower((string)$oldEmailAddress);
470
-		if ($oldEmailAddress !== strtolower($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue())) {
471
-			// this is the only permission a backend provides and is also used
472
-			// for the permission of setting a email address
473
-			if (!$userAccount->getUser()->canChangeDisplayName()) {
474
-				throw new ForbiddenException($this->l10n->t('Unable to change email address'));
475
-			}
476
-			$userAccount->getUser()->setSystemEMailAddress($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue());
477
-		}
478
-
479
-		try {
480
-			$this->accountManager->updateAccount($userAccount);
481
-		} catch (InvalidArgumentException $e) {
482
-			if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
483
-				throw new InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
484
-			}
485
-			if ($e->getMessage() === IAccountManager::PROPERTY_WEBSITE) {
486
-				throw new InvalidArgumentException($this->l10n->t('Unable to set invalid website'));
487
-			}
488
-			throw new InvalidArgumentException($this->l10n->t('Some account data was invalid'));
489
-		}
490
-	}
491
-
492
-	/**
493
-	 * Set the mail address of a user
494
-	 *
495
-	 * @NoSubAdminRequired
496
-	 *
497
-	 * @param string $account
498
-	 * @param bool $onlyVerificationCode only return verification code without updating the data
499
-	 * @return DataResponse
500
-	 */
501
-	#[NoAdminRequired]
502
-	#[PasswordConfirmationRequired]
503
-	public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
504
-		$user = $this->userSession->getUser();
505
-
506
-		if ($user === null) {
507
-			return new DataResponse([], Http::STATUS_BAD_REQUEST);
508
-		}
509
-
510
-		$userAccount = $this->accountManager->getAccount($user);
511
-		$cloudId = $user->getCloudId();
512
-		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
513
-		$signature = $this->signMessage($user, $message);
514
-
515
-		$code = $message . ' ' . $signature;
516
-		$codeMd5 = $message . ' ' . md5($signature);
517
-
518
-		switch ($account) {
519
-			case 'verify-twitter':
520
-				$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):');
521
-				$code = $codeMd5;
522
-				$type = IAccountManager::PROPERTY_TWITTER;
523
-				break;
524
-			case 'verify-website':
525
-				$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):');
526
-				$type = IAccountManager::PROPERTY_WEBSITE;
527
-				break;
528
-			default:
529
-				return new DataResponse([], Http::STATUS_BAD_REQUEST);
530
-		}
531
-
532
-		$userProperty = $userAccount->getProperty($type);
533
-		$userProperty
534
-			->setVerified(IAccountManager::VERIFICATION_IN_PROGRESS)
535
-			->setVerificationData($signature);
536
-
537
-		if ($onlyVerificationCode === false) {
538
-			$this->accountManager->updateAccount($userAccount);
539
-
540
-			$this->jobList->add(VerifyUserData::class,
541
-				[
542
-					'verificationCode' => $code,
543
-					'data' => $userProperty->getValue(),
544
-					'type' => $type,
545
-					'uid' => $user->getUID(),
546
-					'try' => 0,
547
-					'lastRun' => $this->getCurrentTime()
548
-				]
549
-			);
550
-		}
551
-
552
-		return new DataResponse(['msg' => $msg, 'code' => $code]);
553
-	}
554
-
555
-	/**
556
-	 * get current timestamp
557
-	 *
558
-	 * @return int
559
-	 */
560
-	protected function getCurrentTime(): int {
561
-		return time();
562
-	}
563
-
564
-	/**
565
-	 * sign message with users private key
566
-	 *
567
-	 * @param IUser $user
568
-	 * @param string $message
569
-	 *
570
-	 * @return string base64 encoded signature
571
-	 */
572
-	protected function signMessage(IUser $user, string $message): string {
573
-		$privateKey = $this->keyManager->getKey($user)->getPrivate();
574
-		openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
575
-		return base64_encode($signature);
576
-	}
59
+    /** Limit for counting users for subadmins, to avoid spending too much time */
60
+    private const COUNT_LIMIT_FOR_SUBADMINS = 999;
61
+
62
+    public function __construct(
63
+        string $appName,
64
+        IRequest $request,
65
+        private UserManager $userManager,
66
+        private IGroupManager $groupManager,
67
+        private IUserSession $userSession,
68
+        private IConfig $config,
69
+        private IL10N $l10n,
70
+        private IMailer $mailer,
71
+        private IFactory $l10nFactory,
72
+        private IAppManager $appManager,
73
+        private IAccountManager $accountManager,
74
+        private Manager $keyManager,
75
+        private IJobList $jobList,
76
+        private IManager $encryptionManager,
77
+        private KnownUserService $knownUserService,
78
+        private IEventDispatcher $dispatcher,
79
+        private IInitialState $initialState,
80
+    ) {
81
+        parent::__construct($appName, $request);
82
+    }
83
+
84
+
85
+    /**
86
+     * Display users list template
87
+     *
88
+     * @return TemplateResponse
89
+     */
90
+    #[NoAdminRequired]
91
+    #[NoCSRFRequired]
92
+    public function usersListByGroup(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
93
+        return $this->usersList($navigationManager, $subAdmin);
94
+    }
95
+
96
+    /**
97
+     * Display users list template
98
+     *
99
+     * @return TemplateResponse
100
+     */
101
+    #[NoAdminRequired]
102
+    #[NoCSRFRequired]
103
+    public function usersList(INavigationManager $navigationManager, ISubAdmin $subAdmin): TemplateResponse {
104
+        $user = $this->userSession->getUser();
105
+        $uid = $user->getUID();
106
+        $isAdmin = $this->groupManager->isAdmin($uid);
107
+        $isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($uid);
108
+
109
+        $navigationManager->setActiveEntry('core_users');
110
+
111
+        /* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
112
+        $sortGroupsBy = MetaData::SORT_USERCOUNT;
113
+        $isLDAPUsed = false;
114
+        if ($this->config->getSystemValueBool('sort_groups_by_name', false)) {
115
+            $sortGroupsBy = MetaData::SORT_GROUPNAME;
116
+        } else {
117
+            if ($this->appManager->isEnabledForUser('user_ldap')) {
118
+                $isLDAPUsed =
119
+                    $this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
120
+                if ($isLDAPUsed) {
121
+                    // LDAP user count can be slow, so we sort by group name here
122
+                    $sortGroupsBy = MetaData::SORT_GROUPNAME;
123
+                }
124
+            }
125
+        }
126
+
127
+        $canChangePassword = $this->canAdminChangeUserPasswords();
128
+
129
+        /* GROUPS */
130
+        $groupsInfo = new MetaData(
131
+            $uid,
132
+            $isAdmin,
133
+            $isDelegatedAdmin,
134
+            $this->groupManager,
135
+            $this->userSession
136
+        );
137
+
138
+        $adminGroup = $this->groupManager->get('admin');
139
+        $adminGroupData = [
140
+            'id' => $adminGroup->getGID(),
141
+            'name' => $adminGroup->getDisplayName(),
142
+            'usercount' => $sortGroupsBy === MetaData::SORT_USERCOUNT ? $adminGroup->count() : 0,
143
+            'disabled' => $adminGroup->countDisabled(),
144
+            'canAdd' => $adminGroup->canAddUser(),
145
+            'canRemove' => $adminGroup->canRemoveUser(),
146
+        ];
147
+
148
+        if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
149
+            $isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
150
+                return $ldapFound || $backend instanceof User_Proxy;
151
+            });
152
+        }
153
+
154
+        $disabledUsers = -1;
155
+        $userCount = 0;
156
+
157
+        if (!$isLDAPUsed) {
158
+            if ($isAdmin || $isDelegatedAdmin) {
159
+                $disabledUsers = $this->userManager->countDisabledUsers();
160
+                $userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
161
+                    return $v + (int)$w;
162
+                }, 0);
163
+            } else {
164
+                // User is subadmin !
165
+                [$userCount,$disabledUsers] = $this->userManager->countUsersAndDisabledUsersOfGroups($groupsInfo->getGroups(), self::COUNT_LIMIT_FOR_SUBADMINS);
166
+            }
167
+
168
+            if ($disabledUsers > 0) {
169
+                $userCount -= $disabledUsers;
170
+            }
171
+        }
172
+
173
+        $recentUsersGroup = [
174
+            'id' => '__nc_internal_recent',
175
+            'name' => $this->l10n->t('Recently active'),
176
+            'usercount' => $this->userManager->countSeenUsers(),
177
+        ];
178
+
179
+        $disabledUsersGroup = [
180
+            'id' => 'disabled',
181
+            'name' => $this->l10n->t('Disabled accounts'),
182
+            'usercount' => $disabledUsers
183
+        ];
184
+
185
+        if (!$isAdmin && !$isDelegatedAdmin) {
186
+            $subAdminGroups = array_map(
187
+                fn (IGroup $group) => ['id' => $group->getGID(), 'name' => $group->getDisplayName()],
188
+                $subAdmin->getSubAdminsGroups($user),
189
+            );
190
+            $subAdminGroups = array_values($subAdminGroups);
191
+        }
192
+
193
+        /* QUOTAS PRESETS */
194
+        $quotaPreset = $this->parseQuotaPreset($this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
195
+        $allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
196
+        if (!$allowUnlimitedQuota && count($quotaPreset) > 0) {
197
+            $defaultQuota = $this->config->getAppValue('files', 'default_quota', $quotaPreset[0]);
198
+        } else {
199
+            $defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
200
+        }
201
+
202
+        $event = new BeforeTemplateRenderedEvent();
203
+        $this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
204
+        $this->dispatcher->dispatchTyped($event);
205
+
206
+        /* LANGUAGES */
207
+        $languages = $this->l10nFactory->getLanguages();
208
+
209
+        /** Using LDAP or admins (system config) can enfore sorting by group name, in this case the frontend setting is overwritten */
210
+        $forceSortGroupByName = $sortGroupsBy === MetaData::SORT_GROUPNAME;
211
+
212
+        /* FINAL DATA */
213
+        $serverData = [];
214
+        // groups
215
+        $serverData['systemGroups'] = [$adminGroupData, $recentUsersGroup, $disabledUsersGroup];
216
+        $serverData['subAdminGroups'] = $subAdminGroups ?? [];
217
+        // Various data
218
+        $serverData['isAdmin'] = $isAdmin;
219
+        $serverData['isDelegatedAdmin'] = $isDelegatedAdmin;
220
+        $serverData['sortGroups'] = $forceSortGroupByName
221
+            ? MetaData::SORT_GROUPNAME
222
+            : (int)$this->config->getAppValue('core', 'group.sortBy', (string)MetaData::SORT_USERCOUNT);
223
+        $serverData['forceSortGroupByName'] = $forceSortGroupByName;
224
+        $serverData['quotaPreset'] = $quotaPreset;
225
+        $serverData['allowUnlimitedQuota'] = $allowUnlimitedQuota;
226
+        $serverData['userCount'] = $userCount;
227
+        $serverData['languages'] = $languages;
228
+        $serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
229
+        $serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
230
+        // Settings
231
+        $serverData['defaultQuota'] = $defaultQuota;
232
+        $serverData['canChangePassword'] = $canChangePassword;
233
+        $serverData['newUserGenerateUserID'] = $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes';
234
+        $serverData['newUserRequireEmail'] = $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes';
235
+        $serverData['newUserSendEmail'] = $this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes';
236
+
237
+        $this->initialState->provideInitialState('usersSettings', $serverData);
238
+
239
+        Util::addStyle('settings', 'settings');
240
+        Util::addScript('settings', 'vue-settings-apps-users-management');
241
+
242
+        return new TemplateResponse('settings', 'settings/empty', ['pageTitle' => $this->l10n->t('Settings')]);
243
+    }
244
+
245
+    /**
246
+     * @param string $key
247
+     * @param string $value
248
+     *
249
+     * @return JSONResponse
250
+     */
251
+    #[AuthorizedAdminSetting(settings:Users::class)]
252
+    public function setPreference(string $key, string $value): JSONResponse {
253
+        $allowed = ['newUser.sendEmail', 'group.sortBy'];
254
+        if (!in_array($key, $allowed, true)) {
255
+            return new JSONResponse([], Http::STATUS_FORBIDDEN);
256
+        }
257
+
258
+        $this->config->setAppValue('core', $key, $value);
259
+
260
+        return new JSONResponse([]);
261
+    }
262
+
263
+    /**
264
+     * Parse the app value for quota_present
265
+     *
266
+     * @param string $quotaPreset
267
+     * @return array
268
+     */
269
+    protected function parseQuotaPreset(string $quotaPreset): array {
270
+        // 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
271
+        $presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
272
+        // Drop default and none, Make array indexes numerically
273
+        return array_values(array_diff($presets, ['default', 'none']));
274
+    }
275
+
276
+    /**
277
+     * check if the admin can change the users password
278
+     *
279
+     * The admin can change the passwords if:
280
+     *
281
+     *   - no encryption module is loaded and encryption is disabled
282
+     *   - encryption module is loaded but it doesn't require per user keys
283
+     *
284
+     * The admin can not change the passwords if:
285
+     *
286
+     *   - an encryption module is loaded and it uses per-user keys
287
+     *   - encryption is enabled but no encryption modules are loaded
288
+     *
289
+     * @return bool
290
+     */
291
+    protected function canAdminChangeUserPasswords(): bool {
292
+        $isEncryptionEnabled = $this->encryptionManager->isEnabled();
293
+        try {
294
+            $noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
295
+            $isEncryptionModuleLoaded = true;
296
+        } catch (ModuleDoesNotExistsException $e) {
297
+            $noUserSpecificEncryptionKeys = true;
298
+            $isEncryptionModuleLoaded = false;
299
+        }
300
+        $canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
301
+            || (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
302
+
303
+        return $canChangePassword;
304
+    }
305
+
306
+    /**
307
+     * @NoSubAdminRequired
308
+     *
309
+     * @param string|null $avatarScope
310
+     * @param string|null $displayname
311
+     * @param string|null $displaynameScope
312
+     * @param string|null $phone
313
+     * @param string|null $phoneScope
314
+     * @param string|null $email
315
+     * @param string|null $emailScope
316
+     * @param string|null $website
317
+     * @param string|null $websiteScope
318
+     * @param string|null $address
319
+     * @param string|null $addressScope
320
+     * @param string|null $twitter
321
+     * @param string|null $twitterScope
322
+     * @param string|null $fediverse
323
+     * @param string|null $fediverseScope
324
+     * @param string|null $birthdate
325
+     * @param string|null $birthdateScope
326
+     *
327
+     * @return DataResponse
328
+     */
329
+    #[NoAdminRequired]
330
+    #[PasswordConfirmationRequired]
331
+    #[UserRateLimit(limit: 5, period: 60)]
332
+    public function setUserSettings(?string $avatarScope = null,
333
+        ?string $displayname = null,
334
+        ?string $displaynameScope = null,
335
+        ?string $phone = null,
336
+        ?string $phoneScope = null,
337
+        ?string $email = null,
338
+        ?string $emailScope = null,
339
+        ?string $website = null,
340
+        ?string $websiteScope = null,
341
+        ?string $address = null,
342
+        ?string $addressScope = null,
343
+        ?string $twitter = null,
344
+        ?string $twitterScope = null,
345
+        ?string $fediverse = null,
346
+        ?string $fediverseScope = null,
347
+        ?string $birthdate = null,
348
+        ?string $birthdateScope = null,
349
+        ?string $pronouns = null,
350
+        ?string $pronounsScope = null,
351
+    ) {
352
+        $user = $this->userSession->getUser();
353
+        if (!$user instanceof IUser) {
354
+            return new DataResponse(
355
+                [
356
+                    'status' => 'error',
357
+                    'data' => [
358
+                        'message' => $this->l10n->t('Invalid account')
359
+                    ]
360
+                ],
361
+                Http::STATUS_UNAUTHORIZED
362
+            );
363
+        }
364
+
365
+        $email = !is_null($email) ? strtolower($email) : $email;
366
+        if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
367
+            return new DataResponse(
368
+                [
369
+                    'status' => 'error',
370
+                    'data' => [
371
+                        'message' => $this->l10n->t('Invalid mail address')
372
+                    ]
373
+                ],
374
+                Http::STATUS_UNPROCESSABLE_ENTITY
375
+            );
376
+        }
377
+
378
+        $userAccount = $this->accountManager->getAccount($user);
379
+        $oldPhoneValue = $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
380
+
381
+        $updatable = [
382
+            IAccountManager::PROPERTY_AVATAR => ['value' => null, 'scope' => $avatarScope],
383
+            IAccountManager::PROPERTY_DISPLAYNAME => ['value' => $displayname, 'scope' => $displaynameScope],
384
+            IAccountManager::PROPERTY_EMAIL => ['value' => $email, 'scope' => $emailScope],
385
+            IAccountManager::PROPERTY_WEBSITE => ['value' => $website, 'scope' => $websiteScope],
386
+            IAccountManager::PROPERTY_ADDRESS => ['value' => $address, 'scope' => $addressScope],
387
+            IAccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
388
+            IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
389
+            IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
390
+            IAccountManager::PROPERTY_BIRTHDATE => ['value' => $birthdate, 'scope' => $birthdateScope],
391
+            IAccountManager::PROPERTY_PRONOUNS => ['value' => $pronouns, 'scope' => $pronounsScope],
392
+        ];
393
+        $allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
394
+        foreach ($updatable as $property => $data) {
395
+            if ($allowUserToChangeDisplayName === false
396
+                && in_array($property, [IAccountManager::PROPERTY_DISPLAYNAME, IAccountManager::PROPERTY_EMAIL], true)) {
397
+                continue;
398
+            }
399
+            $property = $userAccount->getProperty($property);
400
+            if ($data['value'] !== null) {
401
+                $property->setValue($data['value']);
402
+            }
403
+            if ($data['scope'] !== null) {
404
+                $property->setScope($data['scope']);
405
+            }
406
+        }
407
+
408
+        try {
409
+            $this->saveUserSettings($userAccount);
410
+            if ($oldPhoneValue !== $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue()) {
411
+                $this->knownUserService->deleteByContactUserId($user->getUID());
412
+            }
413
+            return new DataResponse(
414
+                [
415
+                    'status' => 'success',
416
+                    'data' => [
417
+                        'userId' => $user->getUID(),
418
+                        'avatarScope' => $userAccount->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(),
419
+                        'displayname' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(),
420
+                        'displaynameScope' => $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(),
421
+                        'phone' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(),
422
+                        'phoneScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getScope(),
423
+                        'email' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
424
+                        'emailScope' => $userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
425
+                        'website' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(),
426
+                        'websiteScope' => $userAccount->getProperty(IAccountManager::PROPERTY_WEBSITE)->getScope(),
427
+                        'address' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getValue(),
428
+                        'addressScope' => $userAccount->getProperty(IAccountManager::PROPERTY_ADDRESS)->getScope(),
429
+                        'twitter' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(),
430
+                        'twitterScope' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
431
+                        'fediverse' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue(),
432
+                        'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
433
+                        'birthdate' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getValue(),
434
+                        'birthdateScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getScope(),
435
+                        'pronouns' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getValue(),
436
+                        'pronounsScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getScope(),
437
+                        'message' => $this->l10n->t('Settings saved'),
438
+                    ],
439
+                ],
440
+                Http::STATUS_OK
441
+            );
442
+        } catch (ForbiddenException|InvalidArgumentException|PropertyDoesNotExistException $e) {
443
+            return new DataResponse([
444
+                'status' => 'error',
445
+                'data' => [
446
+                    'message' => $e->getMessage()
447
+                ],
448
+            ]);
449
+        }
450
+    }
451
+    /**
452
+     * update account manager with new user data
453
+     *
454
+     * @throws ForbiddenException
455
+     * @throws InvalidArgumentException
456
+     */
457
+    protected function saveUserSettings(IAccount $userAccount): void {
458
+        // keep the user back-end up-to-date with the latest display name and email
459
+        // address
460
+        $oldDisplayName = $userAccount->getUser()->getDisplayName();
461
+        if ($oldDisplayName !== $userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue()) {
462
+            $result = $userAccount->getUser()->setDisplayName($userAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue());
463
+            if ($result === false) {
464
+                throw new ForbiddenException($this->l10n->t('Unable to change full name'));
465
+            }
466
+        }
467
+
468
+        $oldEmailAddress = $userAccount->getUser()->getSystemEMailAddress();
469
+        $oldEmailAddress = strtolower((string)$oldEmailAddress);
470
+        if ($oldEmailAddress !== strtolower($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue())) {
471
+            // this is the only permission a backend provides and is also used
472
+            // for the permission of setting a email address
473
+            if (!$userAccount->getUser()->canChangeDisplayName()) {
474
+                throw new ForbiddenException($this->l10n->t('Unable to change email address'));
475
+            }
476
+            $userAccount->getUser()->setSystemEMailAddress($userAccount->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue());
477
+        }
478
+
479
+        try {
480
+            $this->accountManager->updateAccount($userAccount);
481
+        } catch (InvalidArgumentException $e) {
482
+            if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
483
+                throw new InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
484
+            }
485
+            if ($e->getMessage() === IAccountManager::PROPERTY_WEBSITE) {
486
+                throw new InvalidArgumentException($this->l10n->t('Unable to set invalid website'));
487
+            }
488
+            throw new InvalidArgumentException($this->l10n->t('Some account data was invalid'));
489
+        }
490
+    }
491
+
492
+    /**
493
+     * Set the mail address of a user
494
+     *
495
+     * @NoSubAdminRequired
496
+     *
497
+     * @param string $account
498
+     * @param bool $onlyVerificationCode only return verification code without updating the data
499
+     * @return DataResponse
500
+     */
501
+    #[NoAdminRequired]
502
+    #[PasswordConfirmationRequired]
503
+    public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
504
+        $user = $this->userSession->getUser();
505
+
506
+        if ($user === null) {
507
+            return new DataResponse([], Http::STATUS_BAD_REQUEST);
508
+        }
509
+
510
+        $userAccount = $this->accountManager->getAccount($user);
511
+        $cloudId = $user->getCloudId();
512
+        $message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
513
+        $signature = $this->signMessage($user, $message);
514
+
515
+        $code = $message . ' ' . $signature;
516
+        $codeMd5 = $message . ' ' . md5($signature);
517
+
518
+        switch ($account) {
519
+            case 'verify-twitter':
520
+                $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):');
521
+                $code = $codeMd5;
522
+                $type = IAccountManager::PROPERTY_TWITTER;
523
+                break;
524
+            case 'verify-website':
525
+                $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):');
526
+                $type = IAccountManager::PROPERTY_WEBSITE;
527
+                break;
528
+            default:
529
+                return new DataResponse([], Http::STATUS_BAD_REQUEST);
530
+        }
531
+
532
+        $userProperty = $userAccount->getProperty($type);
533
+        $userProperty
534
+            ->setVerified(IAccountManager::VERIFICATION_IN_PROGRESS)
535
+            ->setVerificationData($signature);
536
+
537
+        if ($onlyVerificationCode === false) {
538
+            $this->accountManager->updateAccount($userAccount);
539
+
540
+            $this->jobList->add(VerifyUserData::class,
541
+                [
542
+                    'verificationCode' => $code,
543
+                    'data' => $userProperty->getValue(),
544
+                    'type' => $type,
545
+                    'uid' => $user->getUID(),
546
+                    'try' => 0,
547
+                    'lastRun' => $this->getCurrentTime()
548
+                ]
549
+            );
550
+        }
551
+
552
+        return new DataResponse(['msg' => $msg, 'code' => $code]);
553
+    }
554
+
555
+    /**
556
+     * get current timestamp
557
+     *
558
+     * @return int
559
+     */
560
+    protected function getCurrentTime(): int {
561
+        return time();
562
+    }
563
+
564
+    /**
565
+     * sign message with users private key
566
+     *
567
+     * @param IUser $user
568
+     * @param string $message
569
+     *
570
+     * @return string base64 encoded signature
571
+     */
572
+    protected function signMessage(IUser $user, string $message): string {
573
+        $privateKey = $this->keyManager->getKey($user)->getPrivate();
574
+        openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
575
+        return base64_encode($signature);
576
+    }
577 577
 }
Please login to merge, or discard this patch.