Passed
Push — master ( e1a300...aa651f )
by Joas
15:36 queued 12s
created

UsersController::saveUserSettings()   C

Complexity

Conditions 12
Paths 74

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 22
c 0
b 0
f 0
nc 74
nop 2
dl 0
loc 37
rs 6.9666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author Bjoern Schiessle <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Daniel Kesselberg <[email protected]>
12
 * @author GretaD <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author John Molakvoæ (skjnldsv) <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 *
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program. If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
35
// FIXME: disabled for now to be able to inject IGroupManager and also use
36
// getSubAdmin()
37
38
namespace OCA\Settings\Controller;
39
40
use OC\Accounts\AccountManager;
41
use OC\AppFramework\Http;
42
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
43
use OC\ForbiddenException;
44
use OC\Group\Manager as GroupManager;
45
use OC\KnownUser\KnownUserService;
46
use OC\L10N\Factory;
47
use OC\Security\IdentityProof\Manager;
48
use OC\User\Manager as UserManager;
49
use OCA\Settings\BackgroundJobs\VerifyUserData;
50
use OCA\Settings\Events\BeforeTemplateRenderedEvent;
51
use OCA\User_LDAP\User_Proxy;
52
use OCP\Accounts\IAccountManager;
53
use OCP\App\IAppManager;
54
use OCP\AppFramework\Controller;
55
use OCP\AppFramework\Http\DataResponse;
56
use OCP\AppFramework\Http\JSONResponse;
57
use OCP\AppFramework\Http\TemplateResponse;
58
use OCP\BackgroundJob\IJobList;
59
use OCP\Encryption\IManager;
60
use OCP\EventDispatcher\IEventDispatcher;
61
use OCP\IConfig;
62
use OCP\IGroupManager;
63
use OCP\IL10N;
64
use OCP\IRequest;
65
use OCP\IUser;
66
use OCP\IUserManager;
67
use OCP\IUserSession;
68
use OCP\L10N\IFactory;
69
use OCP\Mail\IMailer;
70
use function in_array;
71
72
class UsersController extends Controller {
73
	/** @var UserManager */
74
	private $userManager;
75
	/** @var GroupManager */
76
	private $groupManager;
77
	/** @var IUserSession */
78
	private $userSession;
79
	/** @var IConfig */
80
	private $config;
81
	/** @var bool */
82
	private $isAdmin;
83
	/** @var IL10N */
84
	private $l10n;
85
	/** @var IMailer */
86
	private $mailer;
87
	/** @var Factory */
88
	private $l10nFactory;
89
	/** @var IAppManager */
90
	private $appManager;
91
	/** @var AccountManager */
92
	private $accountManager;
93
	/** @var Manager */
94
	private $keyManager;
95
	/** @var IJobList */
96
	private $jobList;
97
	/** @var IManager */
98
	private $encryptionManager;
99
	/** @var KnownUserService */
100
	private $knownUserService;
101
	/** @var IEventDispatcher */
102
	private $dispatcher;
103
104
105
	public function __construct(
106
		string $appName,
107
		IRequest $request,
108
		IUserManager $userManager,
109
		IGroupManager $groupManager,
110
		IUserSession $userSession,
111
		IConfig $config,
112
		bool $isAdmin,
113
		IL10N $l10n,
114
		IMailer $mailer,
115
		IFactory $l10nFactory,
116
		IAppManager $appManager,
117
		AccountManager $accountManager,
118
		Manager $keyManager,
119
		IJobList $jobList,
120
		IManager $encryptionManager,
121
		KnownUserService $knownUserService,
122
		IEventDispatcher $dispatcher
123
	) {
124
		parent::__construct($appName, $request);
125
		$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...
126
		$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...
127
		$this->userSession = $userSession;
128
		$this->config = $config;
129
		$this->isAdmin = $isAdmin;
130
		$this->l10n = $l10n;
131
		$this->mailer = $mailer;
132
		$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...
133
		$this->appManager = $appManager;
134
		$this->accountManager = $accountManager;
135
		$this->keyManager = $keyManager;
136
		$this->jobList = $jobList;
137
		$this->encryptionManager = $encryptionManager;
138
		$this->knownUserService = $knownUserService;
139
		$this->dispatcher = $dispatcher;
140
	}
141
142
143
	/**
144
	 * @NoCSRFRequired
145
	 * @NoAdminRequired
146
	 *
147
	 * Display users list template
148
	 *
149
	 * @return TemplateResponse
150
	 */
151
	public function usersListByGroup(): TemplateResponse {
152
		return $this->usersList();
153
	}
154
155
	/**
156
	 * @NoCSRFRequired
157
	 * @NoAdminRequired
158
	 *
159
	 * Display users list template
160
	 *
161
	 * @return TemplateResponse
162
	 */
163
	public function usersList(): TemplateResponse {
164
		$user = $this->userSession->getUser();
165
		$uid = $user->getUID();
166
167
		\OC::$server->getNavigationManager()->setActiveEntry('core_users');
168
169
		/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
170
		$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
171
		$isLDAPUsed = false;
172
		if ($this->config->getSystemValue('sort_groups_by_name', false)) {
173
			$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
174
		} else {
175
			if ($this->appManager->isEnabledForUser('user_ldap')) {
176
				$isLDAPUsed =
177
					$this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
178
				if ($isLDAPUsed) {
179
					// LDAP user count can be slow, so we sort by group name here
180
					$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
181
				}
182
			}
183
		}
184
185
		$canChangePassword = $this->canAdminChangeUserPasswords();
186
187
		/* GROUPS */
188
		$groupsInfo = new \OC\Group\MetaData(
189
			$uid,
190
			$this->isAdmin,
191
			$this->groupManager,
192
			$this->userSession
193
		);
194
195
		$groupsInfo->setSorting($sortGroupsBy);
196
		[$adminGroup, $groups] = $groupsInfo->get();
197
198
		if (!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
199
			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
200
				return $ldapFound || $backend instanceof User_Proxy;
201
			});
202
		}
203
204
		$disabledUsers = -1;
205
		$userCount = 0;
206
207
		if (!$isLDAPUsed) {
208
			if ($this->isAdmin) {
209
				$disabledUsers = $this->userManager->countDisabledUsers();
210
				$userCount = array_reduce($this->userManager->countUsers(), function ($v, $w) {
211
					return $v + (int)$w;
212
				}, 0);
213
			} else {
214
				// User is subadmin !
215
				// Map group list to names to retrieve the countDisabledUsersOfGroups
216
				$userGroups = $this->groupManager->getUserGroups($user);
217
				$groupsNames = [];
218
219
				foreach ($groups as $key => $group) {
220
					// $userCount += (int)$group['usercount'];
221
					array_push($groupsNames, $group['name']);
222
					// we prevent subadmins from looking up themselves
223
					// so we lower the count of the groups he belongs to
224
					if (array_key_exists($group['id'], $userGroups)) {
225
						$groups[$key]['usercount']--;
226
						$userCount -= 1; // we also lower from one the total count
227
					}
228
				}
229
				$userCount += $this->userManager->countUsersOfGroups($groupsInfo->getGroups());
230
				$disabledUsers = $this->userManager->countDisabledUsersOfGroups($groupsNames);
231
			}
232
233
			$userCount -= $disabledUsers;
234
		}
235
236
		$disabledUsersGroup = [
237
			'id' => 'disabled',
238
			'name' => 'Disabled users',
239
			'usercount' => $disabledUsers
240
		];
241
242
		/* QUOTAS PRESETS */
243
		$quotaPreset = $this->parseQuotaPreset($this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'));
244
		$defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
245
246
		$event = new BeforeTemplateRenderedEvent();
247
		$this->dispatcher->dispatch('OC\Settings\Users::loadAdditionalScripts', $event);
248
		$this->dispatcher->dispatchTyped($event);
249
250
		/* LANGUAGES */
251
		$languages = $this->l10nFactory->getLanguages();
252
253
		/* FINAL DATA */
254
		$serverData = [];
255
		// groups
256
		$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
257
		// Various data
258
		$serverData['isAdmin'] = $this->isAdmin;
259
		$serverData['sortGroups'] = $sortGroupsBy;
260
		$serverData['quotaPreset'] = $quotaPreset;
261
		$serverData['userCount'] = $userCount;
262
		$serverData['languages'] = $languages;
263
		$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
264
		$serverData['forceLanguage'] = $this->config->getSystemValue('force_language', false);
265
		// Settings
266
		$serverData['defaultQuota'] = $defaultQuota;
267
		$serverData['canChangePassword'] = $canChangePassword;
268
		$serverData['newUserGenerateUserID'] = $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes';
269
		$serverData['newUserRequireEmail'] = $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes';
270
		$serverData['newUserSendEmail'] = $this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes';
271
272
		return new TemplateResponse('settings', 'settings-vue', ['serverData' => $serverData]);
273
	}
274
275
	/**
276
	 * @param string $key
277
	 * @param string $value
278
	 *
279
	 * @return JSONResponse
280
	 */
281
	public function setPreference(string $key, string $value): JSONResponse {
282
		$allowed = ['newUser.sendEmail'];
283
		if (!in_array($key, $allowed, true)) {
284
			return new JSONResponse([], Http::STATUS_FORBIDDEN);
285
		}
286
287
		$this->config->setAppValue('core', $key, $value);
288
289
		return new JSONResponse([]);
290
	}
291
292
	/**
293
	 * Parse the app value for quota_present
294
	 *
295
	 * @param string $quotaPreset
296
	 * @return array
297
	 */
298
	protected function parseQuotaPreset(string $quotaPreset): array {
299
		// 1 GB, 5 GB, 10 GB => [1 GB, 5 GB, 10 GB]
300
		$presets = array_filter(array_map('trim', explode(',', $quotaPreset)));
301
		// Drop default and none, Make array indexes numerically
302
		return array_values(array_diff($presets, ['default', 'none']));
303
	}
304
305
	/**
306
	 * check if the admin can change the users password
307
	 *
308
	 * The admin can change the passwords if:
309
	 *
310
	 *   - no encryption module is loaded and encryption is disabled
311
	 *   - encryption module is loaded but it doesn't require per user keys
312
	 *
313
	 * The admin can not change the passwords if:
314
	 *
315
	 *   - an encryption module is loaded and it uses per-user keys
316
	 *   - encryption is enabled but no encryption modules are loaded
317
	 *
318
	 * @return bool
319
	 */
320
	protected function canAdminChangeUserPasswords(): bool {
321
		$isEncryptionEnabled = $this->encryptionManager->isEnabled();
322
		try {
323
			$noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
324
			$isEncryptionModuleLoaded = true;
325
		} catch (ModuleDoesNotExistsException $e) {
326
			$noUserSpecificEncryptionKeys = true;
327
			$isEncryptionModuleLoaded = false;
328
		}
329
		$canChangePassword = ($isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys)
330
			|| (!$isEncryptionModuleLoaded && !$isEncryptionEnabled);
331
332
		return $canChangePassword;
333
	}
334
335
	/**
336
	 * @NoAdminRequired
337
	 * @NoSubAdminRequired
338
	 * @PasswordConfirmationRequired
339
	 *
340
	 * @param string|null $avatarScope
341
	 * @param string|null $displayname
342
	 * @param string|null $displaynameScope
343
	 * @param string|null $phone
344
	 * @param string|null $phoneScope
345
	 * @param string|null $email
346
	 * @param string|null $emailScope
347
	 * @param string|null $website
348
	 * @param string|null $websiteScope
349
	 * @param string|null $address
350
	 * @param string|null $addressScope
351
	 * @param string|null $twitter
352
	 * @param string|null $twitterScope
353
	 *
354
	 * @return DataResponse
355
	 */
356
	public function setUserSettings(?string $avatarScope = null,
357
									?string $displayname = null,
358
									?string $displaynameScope = null,
359
									?string $phone = null,
360
									?string $phoneScope = null,
361
									?string $email = null,
362
									?string $emailScope = null,
363
									?string $website = null,
364
									?string $websiteScope = null,
365
									?string $address = null,
366
									?string $addressScope = null,
367
									?string $twitter = null,
368
									?string $twitterScope = null
369
	) {
370
		$user = $this->userSession->getUser();
371
		if (!$user instanceof IUser) {
372
			return new DataResponse(
373
				[
374
					'status' => 'error',
375
					'data' => [
376
						'message' => $this->l10n->t('Invalid user')
377
					]
378
				],
379
				Http::STATUS_UNAUTHORIZED
380
			);
381
		}
382
383
		$email = !is_null($email) ? strtolower($email) : $email;
384
		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
385
			return new DataResponse(
386
				[
387
					'status' => 'error',
388
					'data' => [
389
						'message' => $this->l10n->t('Invalid mail address')
390
					]
391
				],
392
				Http::STATUS_UNPROCESSABLE_ENTITY
393
			);
394
		}
395
396
		$data = $this->accountManager->getUser($user);
397
		$beforeData = $data;
398
		if (!is_null($avatarScope)) {
399
			$data[IAccountManager::PROPERTY_AVATAR]['scope'] = $avatarScope;
400
		}
401
		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
402
			if (!is_null($displayname)) {
403
				$data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayname;
404
			}
405
			if (!is_null($displaynameScope)) {
406
				$data[IAccountManager::PROPERTY_DISPLAYNAME]['scope'] = $displaynameScope;
407
			}
408
			if (!is_null($email)) {
409
				$data[IAccountManager::PROPERTY_EMAIL]['value'] = $email;
410
			}
411
			if (!is_null($emailScope)) {
412
				$data[IAccountManager::PROPERTY_EMAIL]['scope'] = $emailScope;
413
			}
414
		}
415
		if (!is_null($website)) {
416
			$data[IAccountManager::PROPERTY_WEBSITE]['value'] = $website;
417
		}
418
		if (!is_null($websiteScope)) {
419
			$data[IAccountManager::PROPERTY_WEBSITE]['scope'] = $websiteScope;
420
		}
421
		if (!is_null($address)) {
422
			$data[IAccountManager::PROPERTY_ADDRESS]['value'] = $address;
423
		}
424
		if (!is_null($addressScope)) {
425
			$data[IAccountManager::PROPERTY_ADDRESS]['scope'] = $addressScope;
426
		}
427
		if (!is_null($phone)) {
428
			$data[IAccountManager::PROPERTY_PHONE]['value'] = $phone;
429
		}
430
		if (!is_null($phoneScope)) {
431
			$data[IAccountManager::PROPERTY_PHONE]['scope'] = $phoneScope;
432
		}
433
		if (!is_null($twitter)) {
434
			$data[IAccountManager::PROPERTY_TWITTER]['value'] = $twitter;
435
		}
436
		if (!is_null($twitterScope)) {
437
			$data[IAccountManager::PROPERTY_TWITTER]['scope'] = $twitterScope;
438
		}
439
440
		try {
441
			$data = $this->saveUserSettings($user, $data);
442
			if ($beforeData[IAccountManager::PROPERTY_PHONE]['value'] !== $data[IAccountManager::PROPERTY_PHONE]['value']) {
443
				$this->knownUserService->deleteByContactUserId($user->getUID());
444
			}
445
			return new DataResponse(
446
				[
447
					'status' => 'success',
448
					'data' => [
449
						'userId' => $user->getUID(),
450
						'avatarScope' => $data[IAccountManager::PROPERTY_AVATAR]['scope'],
451
						'displayname' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['value'],
452
						'displaynameScope' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['scope'],
453
						'phone' => $data[IAccountManager::PROPERTY_PHONE]['value'],
454
						'phoneScope' => $data[IAccountManager::PROPERTY_PHONE]['scope'],
455
						'email' => $data[IAccountManager::PROPERTY_EMAIL]['value'],
456
						'emailScope' => $data[IAccountManager::PROPERTY_EMAIL]['scope'],
457
						'website' => $data[IAccountManager::PROPERTY_WEBSITE]['value'],
458
						'websiteScope' => $data[IAccountManager::PROPERTY_WEBSITE]['scope'],
459
						'address' => $data[IAccountManager::PROPERTY_ADDRESS]['value'],
460
						'addressScope' => $data[IAccountManager::PROPERTY_ADDRESS]['scope'],
461
						'twitter' => $data[IAccountManager::PROPERTY_TWITTER]['value'],
462
						'twitterScope' => $data[IAccountManager::PROPERTY_TWITTER]['scope'],
463
						'message' => $this->l10n->t('Settings saved')
464
					]
465
				],
466
				Http::STATUS_OK
467
			);
468
		} catch (ForbiddenException $e) {
469
			return new DataResponse([
470
				'status' => 'error',
471
				'data' => [
472
					'message' => $e->getMessage()
473
				],
474
			]);
475
		} catch (\InvalidArgumentException $e) {
476
			return new DataResponse([
477
				'status' => 'error',
478
				'data' => [
479
					'message' => $e->getMessage()
480
				],
481
			]);
482
		}
483
	}
484
	/**
485
	 * update account manager with new user data
486
	 *
487
	 * @param IUser $user
488
	 * @param array $data
489
	 * @return array
490
	 * @throws ForbiddenException
491
	 * @throws \InvalidArgumentException
492
	 */
493
	protected function saveUserSettings(IUser $user, array $data): array {
494
		// keep the user back-end up-to-date with the latest display name and email
495
		// address
496
		$oldDisplayName = $user->getDisplayName();
497
		$oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
0 ignored issues
show
introduced by
The condition is_null($oldDisplayName) is always false.
Loading history...
498
		if (isset($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
499
			&& $oldDisplayName !== $data[IAccountManager::PROPERTY_DISPLAYNAME]['value']
500
		) {
501
			$result = $user->setDisplayName($data[IAccountManager::PROPERTY_DISPLAYNAME]['value']);
502
			if ($result === false) {
503
				throw new ForbiddenException($this->l10n->t('Unable to change full name'));
504
			}
505
		}
506
507
		$oldEmailAddress = $user->getEMailAddress();
508
		$oldEmailAddress = is_null($oldEmailAddress) ? '' : strtolower($oldEmailAddress);
509
		if (isset($data[IAccountManager::PROPERTY_EMAIL]['value'])
510
			&& $oldEmailAddress !== $data[IAccountManager::PROPERTY_EMAIL]['value']
511
		) {
512
			// this is the only permission a backend provides and is also used
513
			// for the permission of setting a email address
514
			if (!$user->canChangeDisplayName()) {
515
				throw new ForbiddenException($this->l10n->t('Unable to change email address'));
516
			}
517
			$user->setEMailAddress($data[IAccountManager::PROPERTY_EMAIL]['value']);
518
		}
519
520
		try {
521
			return $this->accountManager->updateUser($user, $data, true);
522
		} catch (\InvalidArgumentException $e) {
523
			if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
524
				throw new \InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
525
			}
526
			if ($e->getMessage() === IAccountManager::PROPERTY_WEBSITE) {
527
				throw new \InvalidArgumentException($this->l10n->t('Unable to set invalid website'));
528
			}
529
			throw new \InvalidArgumentException($this->l10n->t('Some account data was invalid'));
530
		}
531
	}
532
533
	/**
534
	 * Set the mail address of a user
535
	 *
536
	 * @NoAdminRequired
537
	 * @NoSubAdminRequired
538
	 * @PasswordConfirmationRequired
539
	 *
540
	 * @param string $account
541
	 * @param bool $onlyVerificationCode only return verification code without updating the data
542
	 * @return DataResponse
543
	 */
544
	public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
545
		$user = $this->userSession->getUser();
546
547
		if ($user === null) {
548
			return new DataResponse([], Http::STATUS_BAD_REQUEST);
549
		}
550
551
		$accountData = $this->accountManager->getUser($user);
552
		$cloudId = $user->getCloudId();
553
		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
554
		$signature = $this->signMessage($user, $message);
555
556
		$code = $message . ' ' . $signature;
557
		$codeMd5 = $message . ' ' . md5($signature);
558
559
		switch ($account) {
560
			case 'verify-twitter':
561
				$accountData[IAccountManager::PROPERTY_TWITTER]['verified'] = IAccountManager::VERIFICATION_IN_PROGRESS;
562
				$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):');
563
				$code = $codeMd5;
564
				$type = IAccountManager::PROPERTY_TWITTER;
565
				$accountData[IAccountManager::PROPERTY_TWITTER]['signature'] = $signature;
566
				break;
567
			case 'verify-website':
568
				$accountData[IAccountManager::PROPERTY_WEBSITE]['verified'] = IAccountManager::VERIFICATION_IN_PROGRESS;
569
				$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):');
570
				$type = IAccountManager::PROPERTY_WEBSITE;
571
				$accountData[IAccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
572
				break;
573
			default:
574
				return new DataResponse([], Http::STATUS_BAD_REQUEST);
575
		}
576
577
		if ($onlyVerificationCode === false) {
578
			$accountData = $this->accountManager->updateUser($user, $accountData);
579
			$data = $accountData[$type]['value'];
580
581
			$this->jobList->add(VerifyUserData::class,
582
				[
583
					'verificationCode' => $code,
584
					'data' => $data,
585
					'type' => $type,
586
					'uid' => $user->getUID(),
587
					'try' => 0,
588
					'lastRun' => $this->getCurrentTime()
589
				]
590
			);
591
		}
592
593
		return new DataResponse(['msg' => $msg, 'code' => $code]);
594
	}
595
596
	/**
597
	 * get current timestamp
598
	 *
599
	 * @return int
600
	 */
601
	protected function getCurrentTime(): int {
602
		return time();
603
	}
604
605
	/**
606
	 * sign message with users private key
607
	 *
608
	 * @param IUser $user
609
	 * @param string $message
610
	 *
611
	 * @return string base64 encoded signature
612
	 */
613
	protected function signMessage(IUser $user, string $message): string {
614
		$privateKey = $this->keyManager->getKey($user)->getPrivate();
615
		openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
616
		return base64_encode($signature);
617
	}
618
}
619