Passed
Push — master ( 0247f2...f657f0 )
by Morris
13:19 queued 11s
created

UsersController::saveUserSettings()   B

Complexity

Conditions 9
Paths 26

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 15
nc 26
nop 2
dl 0
loc 26
rs 8.0555
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Daniel Kesselberg <[email protected]>
9
 * @author GretaD <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author John Molakvoæ (skjnldsv) <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program. If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
// FIXME: disabled for now to be able to inject IGroupManager and also use
32
// getSubAdmin()
33
//declare(strict_types=1);
34
35
namespace OCA\Settings\Controller;
36
37
use OC\Accounts\AccountManager;
38
use OC\AppFramework\Http;
39
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
40
use OC\ForbiddenException;
41
use OC\Group\Manager as GroupManager;
42
use OC\L10N\Factory;
43
use OC\Security\IdentityProof\Manager;
44
use OC\User\Manager as UserManager;
45
use OCA\FederatedFileSharing\FederatedShareProvider;
46
use OCA\Settings\BackgroundJobs\VerifyUserData;
47
use OCA\Settings\Events\BeforeTemplateRenderedEvent;
48
use OCA\User_LDAP\User_Proxy;
49
use OCP\App\IAppManager;
50
use OCP\AppFramework\Controller;
51
use OCP\AppFramework\Http\DataResponse;
52
use OCP\AppFramework\Http\JSONResponse;
53
use OCP\AppFramework\Http\TemplateResponse;
54
use OCP\BackgroundJob\IJobList;
55
use OCP\Encryption\IManager;
56
use OCP\EventDispatcher\IEventDispatcher;
57
use OCP\IConfig;
58
use OCP\IGroupManager;
59
use OCP\IL10N;
60
use OCP\IRequest;
61
use OCP\IUser;
62
use OCP\IUserManager;
63
use OCP\IUserSession;
64
use OCP\L10N\IFactory;
65
use OCP\Mail\IMailer;
66
use function in_array;
67
68
class UsersController extends Controller {
69
	/** @var UserManager */
70
	private $userManager;
71
	/** @var GroupManager */
72
	private $groupManager;
73
	/** @var IUserSession */
74
	private $userSession;
75
	/** @var IConfig */
76
	private $config;
77
	/** @var bool */
78
	private $isAdmin;
79
	/** @var IL10N */
80
	private $l10n;
81
	/** @var IMailer */
82
	private $mailer;
83
	/** @var Factory */
84
	private $l10nFactory;
85
	/** @var IAppManager */
86
	private $appManager;
87
	/** @var AccountManager */
88
	private $accountManager;
89
	/** @var Manager */
90
	private $keyManager;
91
	/** @var IJobList */
92
	private $jobList;
93
	/** @var IManager */
94
	private $encryptionManager;
95
	/** @var IEventDispatcher */
96
	private $dispatcher;
97
98
99
	public function __construct(
100
		string $appName,
101
		IRequest $request,
102
		IUserManager $userManager,
103
		IGroupManager $groupManager,
104
		IUserSession $userSession,
105
		IConfig $config,
106
		bool $isAdmin,
107
		IL10N $l10n,
108
		IMailer $mailer,
109
		IFactory $l10nFactory,
110
		IAppManager $appManager,
111
		AccountManager $accountManager,
112
		Manager $keyManager,
113
		IJobList $jobList,
114
		IManager $encryptionManager,
115
		IEventDispatcher $dispatcher
116
	) {
117
		parent::__construct($appName, $request);
118
		$this->userManager = $userManager;
0 ignored issues
show
Documentation Bug introduced by
$userManager is of type OCP\IUserManager, but the property $userManager was declared to be of type OC\User\Manager. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
119
		$this->groupManager = $groupManager;
0 ignored issues
show
Documentation Bug introduced by
$groupManager is of type OCP\IGroupManager, but the property $groupManager was declared to be of type OC\Group\Manager. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
120
		$this->userSession = $userSession;
121
		$this->config = $config;
122
		$this->isAdmin = $isAdmin;
123
		$this->l10n = $l10n;
124
		$this->mailer = $mailer;
125
		$this->l10nFactory = $l10nFactory;
0 ignored issues
show
Documentation Bug introduced by
$l10nFactory is of type OCP\L10N\IFactory, but the property $l10nFactory was declared to be of type OC\L10N\Factory. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
126
		$this->appManager = $appManager;
127
		$this->accountManager = $accountManager;
128
		$this->keyManager = $keyManager;
129
		$this->jobList = $jobList;
130
		$this->encryptionManager = $encryptionManager;
131
		$this->dispatcher = $dispatcher;
132
	}
133
134
135
	/**
136
	 * @NoCSRFRequired
137
	 * @NoAdminRequired
138
	 *
139
	 * Display users list template
140
	 *
141
	 * @return TemplateResponse
142
	 */
143
	public function usersListByGroup() {
144
		return $this->usersList();
145
	}
146
147
	/**
148
	 * @NoCSRFRequired
149
	 * @NoAdminRequired
150
	 *
151
	 * Display users list template
152
	 *
153
	 * @return TemplateResponse
154
	 */
155
	public function usersList() {
156
		$user = $this->userSession->getUser();
157
		$uid = $user->getUID();
158
159
		\OC::$server->getNavigationManager()->setActiveEntry('core_users');
160
161
		/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
162
		$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
163
		$isLDAPUsed = false;
164
		if ($this->config->getSystemValue('sort_groups_by_name', false)) {
165
			$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
166
		} else {
167
			if ($this->appManager->isEnabledForUser('user_ldap')) {
168
				$isLDAPUsed =
169
					$this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
170
				if ($isLDAPUsed) {
171
					// LDAP user count can be slow, so we sort by group name here
172
					$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
173
				}
174
			}
175
		}
176
177
		$canChangePassword = $this->canAdminChangeUserPasswords();
178
179
		/* GROUPS */
180
		$groupsInfo = new \OC\Group\MetaData(
181
			$uid,
182
			$this->isAdmin,
183
			$this->groupManager,
184
			$this->userSession
185
		);
186
187
		$groupsInfo->setSorting($sortGroupsBy);
188
		list($adminGroup, $groups) = $groupsInfo->get();
189
190
		if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
191
			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
192
				return $ldapFound || $backend instanceof User_Proxy;
193
			});
194
		}
195
196
		$disabledUsers = -1;
197
		$userCount = 0;
198
199
		if (!$isLDAPUsed) {
200
			if ($this->isAdmin) {
201
				$disabledUsers = $this->userManager->countDisabledUsers();
202
				$userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
203
					return $v + (int)$w;
204
				}, 0);
205
			} else {
206
				// User is subadmin !
207
				// Map group list to names to retrieve the countDisabledUsersOfGroups
208
				$userGroups = $this->groupManager->getUserGroups($user);
209
				$groupsNames = [];
210
211
				foreach ($groups as $key => $group) {
212
					// $userCount += (int)$group['usercount'];
213
					array_push($groupsNames, $group['name']);
214
					// we prevent subadmins from looking up themselves
215
					// so we lower the count of the groups he belongs to
216
					if (array_key_exists($group['id'], $userGroups)) {
217
						$groups[$key]['usercount']--;
218
						$userCount -= 1; // we also lower from one the total count
219
					}
220
				}
221
				$userCount += $this->userManager->countUsersOfGroups($groupsInfo->getGroups());
222
				$disabledUsers = $this->userManager->countDisabledUsersOfGroups($groupsNames);
223
			}
224
225
			$userCount -= $disabledUsers;
226
		}
227
228
		$disabledUsersGroup = [
229
			'id' => 'disabled',
230
			'name' => 'Disabled users',
231
			'usercount' => $disabledUsers
232
		];
233
234
		/* QUOTAS PRESETS */
235
		$quotaPreset = $this->parseQuotaPreset($this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
236
		$defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
237
238
		$event = new BeforeTemplateRenderedEvent();
239
		$this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
240
		$this->dispatcher->dispatchTyped($event);
241
242
		/* LANGUAGES */
243
		$languages = $this->l10nFactory->getLanguages();
244
245
		/* FINAL DATA */
246
		$serverData = [];
247
		// groups
248
		$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
249
		// Various data
250
		$serverData['isAdmin'] = $this->isAdmin;
251
		$serverData['sortGroups'] = $sortGroupsBy;
252
		$serverData['quotaPreset'] = $quotaPreset;
253
		$serverData['userCount'] = $userCount;
254
		$serverData['languages'] = $languages;
255
		$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
256
		$serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
257
		// Settings
258
		$serverData['defaultQuota'] = $defaultQuota;
259
		$serverData['canChangePassword'] = $canChangePassword;
260
		$serverData['newUserGenerateUserID'] = $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes';
261
		$serverData['newUserRequireEmail'] = $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes';
262
		$serverData['newUserSendEmail'] = $this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes';
263
264
		return new TemplateResponse('settings', 'settings-vue', ['serverData' => $serverData]);
265
	}
266
267
	/**
268
	 * @param string $key
269
	 * @param string $value
270
	 *
271
	 * @return JSONResponse
272
	 */
273
	public function setPreference(string $key, string $value): JSONResponse {
274
		$allowed = ['newUser.sendEmail'];
275
		if (!in_array($key, $allowed, true)) {
276
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
277
		}
278
279
		$this->config->setAppValue('core', $key, $value);
280
281
		return new JSONResponse([]);
282
	}
283
284
	/**
285
	 * Parse the app value for quota_present
286
	 *
287
	 * @param string $quotaPreset
288
	 * @return array
289
	 */
290
	protected function parseQuotaPreset(string $quotaPreset): array {
291
		// 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
292
		$presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
293
		// Drop default and none, Make array indexes numerically
294
		return array_values(array_diff($presets, ['default', 'none']));
295
	}
296
297
	/**
298
	 * check if the admin can change the users password
299
	 *
300
	 * The admin can change the passwords if:
301
	 *
302
	 *   - no encryption module is loaded and encryption is disabled
303
	 *   - encryption module is loaded but it doesn't require per user keys
304
	 *
305
	 * The admin can not change the passwords if:
306
	 *
307
	 *   - an encryption module is loaded and it uses per-user keys
308
	 *   - encryption is enabled but no encryption modules are loaded
309
	 *
310
	 * @return bool
311
	 */
312
	protected function canAdminChangeUserPasswords() {
313
		$isEncryptionEnabled = $this->encryptionManager->isEnabled();
314
		try {
315
			$noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
316
			$isEncryptionModuleLoaded = true;
317
		} catch (ModuleDoesNotExistsException $e) {
318
			$noUserSpecificEncryptionKeys = true;
319
			$isEncryptionModuleLoaded = false;
320
		}
321
		$canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $canChangePassword = ($i...! $isEncryptionEnabled), Probably Intended Meaning: $canChangePassword = $is...! $isEncryptionEnabled)
Loading history...
322
			|| (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
323
324
		return $canChangePassword;
325
	}
326
327
	/**
328
	 * @NoAdminRequired
329
	 * @NoSubAdminRequired
330
	 * @PasswordConfirmationRequired
331
	 *
332
	 * @param string $avatarScope
333
	 * @param string $displayname
334
	 * @param string $displaynameScope
335
	 * @param string $phone
336
	 * @param string $phoneScope
337
	 * @param string $email
338
	 * @param string $emailScope
339
	 * @param string $website
340
	 * @param string $websiteScope
341
	 * @param string $address
342
	 * @param string $addressScope
343
	 * @param string $twitter
344
	 * @param string $twitterScope
345
	 * @return DataResponse
346
	 */
347
	public function setUserSettings($avatarScope,
348
									$displayname,
349
									$displaynameScope,
350
									$phone,
351
									$phoneScope,
352
									$email,
353
									$emailScope,
354
									$website,
355
									$websiteScope,
356
									$address,
357
									$addressScope,
358
									$twitter,
359
									$twitterScope
360
	) {
361
		$email = strtolower($email);
362
		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
363
			return new DataResponse(
364
				[
365
					'status' => 'error',
366
					'data' => [
367
						'message' => $this->l10n->t('Invalid mail address')
368
					]
369
				],
370
				Http::STATUS_UNPROCESSABLE_ENTITY
371
			);
372
		}
373
		$user = $this->userSession->getUser();
374
		$data = $this->accountManager->getUser($user);
375
		$data[AccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
376
		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
377
			$data[AccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
378
			$data[AccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
379
		}
380
		if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
381
			$shareProvider = \OC::$server->query(FederatedShareProvider::class);
382
			if ($shareProvider->isLookupServerUploadEnabled()) {
0 ignored issues
show
Bug introduced by
The method isLookupServerUploadEnabled() does not exist on stdClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

382
			if ($shareProvider->/** @scrutinizer ignore-call */ isLookupServerUploadEnabled()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
383
				$data[AccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
384
				$data[AccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
385
				$data[AccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
386
				$data[AccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
387
			}
388
		}
389
		try {
390
			$this->saveUserSettings($user, $data);
391
			return new DataResponse(
392
				[
393
					'status' => 'success',
394
					'data' => [
395
						'userId' => $user->getUID(),
396
						'avatarScope' => $data[AccountManager::PROPERTY_AVATAR]['scope'],
397
						'displayname' => $data[AccountManager::PROPERTY_DISPLAYNAME]['value'],
398
						'displaynameScope' => $data[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
399
						'email' => $data[AccountManager::PROPERTY_EMAIL]['value'],
400
						'emailScope' => $data[AccountManager::PROPERTY_EMAIL]['scope'],
401
						'website' => $data[AccountManager::PROPERTY_WEBSITE]['value'],
402
						'websiteScope' => $data[AccountManager::PROPERTY_WEBSITE]['scope'],
403
						'address' => $data[AccountManager::PROPERTY_ADDRESS]['value'],
404
						'addressScope' => $data[AccountManager::PROPERTY_ADDRESS]['scope'],
405
						'message' => $this->l10n->t('Settings saved')
406
					]
407
				],
408
				Http::STATUS_OK
409
			);
410
		} catch (ForbiddenException $e) {
411
			return new DataResponse([
412
				'status' => 'error',
413
				'data' => [
414
					'message' => $e->getMessage()
415
				],
416
			]);
417
		}
418
	}
419
	/**
420
	 * update account manager with new user data
421
	 *
422
	 * @param IUser $user
423
	 * @param array $data
424
	 * @throws ForbiddenException
425
	 */
426
	protected function saveUserSettings(IUser $user, array $data) {
427
		// keep the user back-end up-to-date with the latest display name and email
428
		// address
429
		$oldDisplayName = $user->getDisplayName();
430
		$oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
0 ignored issues
show
introduced by
The condition is_null($oldDisplayName) is always false.
Loading history...
431
		if (isset($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
432
			&& $oldDisplayName !== $data[AccountManager::PROPERTY_DISPLAYNAME]['value']
433
		) {
434
			$result = $user->setDisplayName($data[AccountManager::PROPERTY_DISPLAYNAME]['value']);
435
			if ($result === false) {
436
				throw new ForbiddenException($this->l10n->t('Unable to change full name'));
437
			}
438
		}
439
		$oldEmailAddress = $user->getEMailAddress();
440
		$oldEmailAddress = is_null($oldEmailAddress) ? '' : strtolower($oldEmailAddress);
441
		if (isset($data[AccountManager::PROPERTY_EMAIL]['value'])
442
			&& $oldEmailAddress !== $data[AccountManager::PROPERTY_EMAIL]['value']
443
		) {
444
			// this is the only permission a backend provides and is also used
445
			// for the permission of setting a email address
446
			if (!$user->canChangeDisplayName()) {
447
				throw new ForbiddenException($this->l10n->t('Unable to change email address'));
448
			}
449
			$user->setEMailAddress($data[AccountManager::PROPERTY_EMAIL]['value']);
450
		}
451
		$this->accountManager->updateUser($user, $data);
452
	}
453
454
	/**
455
	 * Set the mail address of a user
456
	 *
457
	 * @NoAdminRequired
458
	 * @NoSubAdminRequired
459
	 * @PasswordConfirmationRequired
460
	 *
461
	 * @param string $account
462
	 * @param bool $onlyVerificationCode only return verification code without updating the data
463
	 * @return DataResponse
464
	 */
465
	public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
466
		$user = $this->userSession->getUser();
467
468
		if ($user === null) {
469
			return new DataResponse([], Http::STATUS_BAD_REQUEST);
470
		}
471
472
		$accountData = $this->accountManager->getUser($user);
473
		$cloudId = $user->getCloudId();
474
		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
475
		$signature = $this->signMessage($user, $message);
476
477
		$code = $message . ' ' . $signature;
478
		$codeMd5 = $message . ' ' . md5($signature);
479
480
		switch ($account) {
481
			case 'verify-twitter':
482
				$accountData[AccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
483
				$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):');
484
				$code = $codeMd5;
485
				$type = AccountManager::PROPERTY_TWITTER;
486
				$data = $accountData[AccountManager::PROPERTY_TWITTER]['value'];
487
				$accountData[AccountManager::PROPERTY_TWITTER]['signature'] = $signature;
488
				break;
489
			case 'verify-website':
490
				$accountData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
491
				$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):');
492
				$type = AccountManager::PROPERTY_WEBSITE;
493
				$data = $accountData[AccountManager::PROPERTY_WEBSITE]['value'];
494
				$accountData[AccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
495
				break;
496
			default:
497
				return new DataResponse([], Http::STATUS_BAD_REQUEST);
498
		}
499
500
		if ($onlyVerificationCode === false) {
501
			$this->accountManager->updateUser($user, $accountData);
502
503
			$this->jobList->add(VerifyUserData::class,
504
				[
505
					'verificationCode' => $code,
506
					'data' => $data,
507
					'type' => $type,
508
					'uid' => $user->getUID(),
509
					'try' => 0,
510
					'lastRun' => $this->getCurrentTime()
511
				]
512
			);
513
		}
514
515
		return new DataResponse(['msg' => $msg, 'code' => $code]);
516
	}
517
518
	/**
519
	 * get current timestamp
520
	 *
521
	 * @return int
522
	 */
523
	protected function getCurrentTime(): int {
524
		return time();
525
	}
526
527
	/**
528
	 * sign message with users private key
529
	 *
530
	 * @param IUser $user
531
	 * @param string $message
532
	 *
533
	 * @return string base64 encoded signature
534
	 */
535
	protected function signMessage(IUser $user, string $message): string {
536
		$privateKey = $this->keyManager->getKey($user)->getPrivate();
537
		openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
538
		return base64_encode($signature);
539
	}
540
}
541