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