Passed
Push — master ( fe3d8f...57fc37 )
by Morris
11:33
created

UsersController::usersList()   C

Complexity

Conditions 15
Paths 96

Size

Total Lines 102
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 62
nc 96
nop 0
dl 0
loc 102
rs 5.9166
c 0
b 0
f 0

How to fix   Long Method    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
// FIXME: disabled for now to be able to inject IGroupManager and also use
3
// getSubAdmin()
4
//declare(strict_types=1);
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author Bjoern Schiessle <[email protected]>
10
 * @author Björn Schießle <[email protected]>
11
 * @author Christoph Wurst <[email protected]>
12
 * @author Clark Tomlinson <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author Thomas Müller <[email protected]>
19
 * @author Thomas Pulzer <[email protected]>
20
 * @author Tobia De Koninck <[email protected]>
21
 * @author Tobias Kaminsky <[email protected]>
22
 * @author Vincent Petry <[email protected]>
23
 *
24
 * @license AGPL-3.0
25
 *
26
 * This code is free software: you can redistribute it and/or modify
27
 * it under the terms of the GNU Affero General Public License, version 3,
28
 * as published by the Free Software Foundation.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License, version 3,
36
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
37
 *
38
 */
39
40
namespace OC\Settings\Controller;
41
42
use OC\Accounts\AccountManager;
43
use OC\AppFramework\Http;
44
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
45
use OC\ForbiddenException;
46
use OC\Security\IdentityProof\Manager;
47
use OCA\User_LDAP\User_Proxy;
48
use OCP\App\IAppManager;
49
use OCP\AppFramework\Controller;
50
use OCP\AppFramework\Http\DataResponse;
51
use OCP\AppFramework\Http\TemplateResponse;
52
use OCP\BackgroundJob\IJobList;
53
use OCP\Encryption\IManager;
54
use OCP\IConfig;
55
use OCP\IGroupManager;
56
use OCP\IL10N;
57
use OCP\IRequest;
58
use OCP\IUser;
59
use OCP\IUserManager;
60
use OCP\IUserSession;
61
use OCP\L10N\IFactory;
62
use OCP\Mail\IMailer;
63
use OC\Settings\BackgroundJobs\VerifyUserData;
64
65
/**
66
 * @package OC\Settings\Controller
67
 */
68
class UsersController extends Controller {
69
	/** @var IUserManager */
70
	private $userManager;
71
	/** @var IGroupManager */
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 IFactory */
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
96
97
	public function __construct(string $appName,
98
								IRequest $request,
99
								IUserManager $userManager,
100
								IGroupManager $groupManager,
101
								IUserSession $userSession,
102
								IConfig $config,
103
								bool $isAdmin,
104
								IL10N $l10n,
105
								IMailer $mailer,
106
								IFactory $l10nFactory,
107
								IAppManager $appManager,
108
								AccountManager $accountManager,
109
								Manager $keyManager,
110
								IJobList $jobList,
111
								IManager $encryptionManager) {
112
		parent::__construct($appName, $request);
113
		$this->userManager = $userManager;
114
		$this->groupManager = $groupManager;
115
		$this->userSession = $userSession;
116
		$this->config = $config;
117
		$this->isAdmin = $isAdmin;
118
		$this->l10n = $l10n;
119
		$this->mailer = $mailer;
120
		$this->l10nFactory = $l10nFactory;
121
		$this->appManager = $appManager;
122
		$this->accountManager = $accountManager;
123
		$this->keyManager = $keyManager;
124
		$this->jobList = $jobList;
125
		$this->encryptionManager = $encryptionManager;
126
	}
127
128
129
	/**
130
	 * @NoCSRFRequired
131
	 * @NoAdminRequired
132
	 *
133
	 * Display users list template
134
	 *
135
	 * @return TemplateResponse
136
	 */
137
	public function usersListByGroup() {
138
		return $this->usersList();
139
	}
140
141
	/**
142
	 * @NoCSRFRequired
143
	 * @NoAdminRequired
144
	 *
145
	 * Display users list template
146
	 *
147
	 * @return TemplateResponse
148
	 */
149
	public function usersList() {
150
		$user = $this->userSession->getUser();
151
		$uid = $user->getUID();
152
153
		\OC::$server->getNavigationManager()->setActiveEntry('core_users');
154
155
		/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
156
		$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
157
		$isLDAPUsed = false;
158
		if ($this->config->getSystemValue('sort_groups_by_name', false)) {
159
			$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
160
		} else {
161
			if ($this->appManager->isEnabledForUser('user_ldap')) {
162
				$isLDAPUsed =
163
					$this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
164
				if ($isLDAPUsed) {
165
					// LDAP user count can be slow, so we sort by group name here
166
					$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
167
				}
168
			}
169
		}
170
171
		$canChangePassword = $this->canAdminChangeUserPasswords();
172
173
		/* GROUPS */
174
		$groupsInfo = new \OC\Group\MetaData(
175
			$uid,
176
			$this->isAdmin,
177
			$this->groupManager,
178
			$this->userSession
179
		);
180
181
		$groupsInfo->setSorting($sortGroupsBy);
182
		list($adminGroup, $groups) = $groupsInfo->get();
183
184
		if(!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
185
			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
186
				return $ldapFound || $backend instanceof User_Proxy;
187
			});
188
		}
189
190
		if ($this->isAdmin) {
191
			$disabledUsers = $isLDAPUsed ? -1 : $this->userManager->countDisabledUsers();
192
			$userCount = $isLDAPUsed ? 0 : array_reduce($this->userManager->countUsers(), function($v, $w) {
193
				return $v + (int)$w;
194
			}, 0);
195
		} else {
196
			// User is subadmin !
197
			// Map group list to names to retrieve the countDisabledUsersOfGroups
198
			$userGroups = $this->groupManager->getUserGroups($user);
199
			$groupsNames = [];
200
			$userCount = 0;
201
202
			foreach($groups as $key => $group) {
203
				// $userCount += (int)$group['usercount'];
204
				array_push($groupsNames, $group['name']);
205
				// we prevent subadmins from looking up themselves
206
				// so we lower the count of the groups he belongs to
207
				if (array_key_exists($group['id'], $userGroups)) {
208
					$groups[$key]['usercount']--;
209
					$userCount = -1; // we also lower from one the total count
210
				}
211
			};
212
			$userCount += $isLDAPUsed ? 0 : $this->userManager->countUsersOfGroups($groupsInfo->getGroups());
0 ignored issues
show
Bug introduced by
The method countUsersOfGroups() does not exist on OCP\IUserManager. Did you maybe mean countUsers()? ( Ignorable by Annotation )

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

212
			$userCount += $isLDAPUsed ? 0 : $this->userManager->/** @scrutinizer ignore-call */ countUsersOfGroups($groupsInfo->getGroups());

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...
213
			$disabledUsers = $isLDAPUsed ? -1 : $this->userManager->countDisabledUsersOfGroups($groupsNames);
0 ignored issues
show
Bug introduced by
The method countDisabledUsersOfGroups() does not exist on OCP\IUserManager. Did you maybe mean countDisabledUsers()? ( Ignorable by Annotation )

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

213
			$disabledUsers = $isLDAPUsed ? -1 : $this->userManager->/** @scrutinizer ignore-call */ countDisabledUsersOfGroups($groupsNames);

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...
214
		}
215
		$disabledUsersGroup = [
216
			'id' => 'disabled',
217
			'name' => 'Disabled users',
218
			'usercount' => $disabledUsers
219
		];
220
221
		/* QUOTAS PRESETS */
222
		$quotaPreset = $this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
223
		$quotaPreset = explode(',', $quotaPreset);
224
		foreach ($quotaPreset as &$preset) {
225
			$preset = trim($preset);
226
		}
227
		$quotaPreset = array_diff($quotaPreset, array('default', 'none'));
228
		$defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
229
230
		\OC::$server->getEventDispatcher()->dispatch('OC\Settings\Users::loadAdditionalScripts');
231
232
		/* LANGUAGES */
233
		$languages = $this->l10nFactory->getLanguages();
0 ignored issues
show
Bug introduced by
The method getLanguages() does not exist on OCP\L10N\IFactory. Did you maybe mean getLanguageIterator()? ( Ignorable by Annotation )

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

233
		/** @scrutinizer ignore-call */ 
234
  $languages = $this->l10nFactory->getLanguages();

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...
234
235
		/* FINAL DATA */
236
		$serverData = array();
237
		// groups
238
		$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
239
		// Various data
240
		$serverData['isAdmin'] = $this->isAdmin;
241
		$serverData['sortGroups'] = $sortGroupsBy;
242
		$serverData['quotaPreset'] = $quotaPreset;
243
		$serverData['userCount'] = $userCount - $disabledUsers;
244
		$serverData['languages'] = $languages;
245
		$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
246
		// Settings
247
		$serverData['defaultQuota'] = $defaultQuota;
248
		$serverData['canChangePassword'] = $canChangePassword;
249
250
		return new TemplateResponse('settings', 'settings-vue', ['serverData' => $serverData]);
251
	}
252
253
	/**
254
	 * check if the admin can change the users password
255
	 *
256
	 * The admin can change the passwords if:
257
	 *
258
	 *   - no encryption module is loaded and encryption is disabled
259
	 *   - encryption module is loaded but it doesn't require per user keys
260
	 *
261
	 * The admin can not change the passwords if:
262
	 *
263
	 *   - an encryption module is loaded and it uses per-user keys
264
	 *   - encryption is enabled but no encryption modules are loaded
265
	 *
266
	 * @return bool
267
	 */
268
	protected function canAdminChangeUserPasswords() {
269
		$isEncryptionEnabled = $this->encryptionManager->isEnabled();
270
		try {
271
			$noUserSpecificEncryptionKeys =!$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
272
			$isEncryptionModuleLoaded = true;
273
		} catch (ModuleDoesNotExistsException $e) {
274
			$noUserSpecificEncryptionKeys = true;
275
			$isEncryptionModuleLoaded = false;
276
		}
277
278
		$canChangePassword = ($isEncryptionEnabled && $isEncryptionModuleLoaded  && $noUserSpecificEncryptionKeys)
279
			|| (!$isEncryptionEnabled && !$isEncryptionModuleLoaded)
280
			|| (!$isEncryptionEnabled && $isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys);
281
282
		return $canChangePassword;
283
	}
284
285
	/**
286
	 * @NoAdminRequired
287
	 * @NoSubadminRequired
288
	 * @PasswordConfirmationRequired
289
	 *
290
	 * @param string $avatarScope
291
	 * @param string $displayname
292
	 * @param string $displaynameScope
293
	 * @param string $phone
294
	 * @param string $phoneScope
295
	 * @param string $email
296
	 * @param string $emailScope
297
	 * @param string $website
298
	 * @param string $websiteScope
299
	 * @param string $address
300
	 * @param string $addressScope
301
	 * @param string $twitter
302
	 * @param string $twitterScope
303
	 * @return DataResponse
304
	 */
305
	public function setUserSettings($avatarScope,
306
									$displayname,
307
									$displaynameScope,
308
									$phone,
309
									$phoneScope,
310
									$email,
311
									$emailScope,
312
									$website,
313
									$websiteScope,
314
									$address,
315
									$addressScope,
316
									$twitter,
317
									$twitterScope
318
	) {
319
		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
320
			return new DataResponse(
321
				[
322
					'status' => 'error',
323
					'data' => [
324
						'message' => $this->l10n->t('Invalid mail address')
325
					]
326
				],
327
				Http::STATUS_UNPROCESSABLE_ENTITY
328
			);
329
		}
330
		$user = $this->userSession->getUser();
331
		$data = $this->accountManager->getUser($user);
0 ignored issues
show
Bug introduced by
It seems like $user can also be of type null; however, parameter $user of OC\Accounts\AccountManager::getUser() does only seem to accept OCP\IUser, maybe add an additional type check? ( Ignorable by Annotation )

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

331
		$data = $this->accountManager->getUser(/** @scrutinizer ignore-type */ $user);
Loading history...
332
		$data[AccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
333
		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
334
			$data[AccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
335
			$data[AccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
336
		}
337
		if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
338
			$federatedFileSharing = new \OCA\FederatedFileSharing\AppInfo\Application();
339
			$shareProvider = $federatedFileSharing->getFederatedShareProvider();
340
			if ($shareProvider->isLookupServerUploadEnabled()) {
341
				$data[AccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
342
				$data[AccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
343
				$data[AccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
344
				$data[AccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
345
			}
346
		}
347
		try {
348
			$this->saveUserSettings($user, $data);
0 ignored issues
show
Bug introduced by
It seems like $user can also be of type null; however, parameter $user of OC\Settings\Controller\U...ler::saveUserSettings() does only seem to accept OCP\IUser, maybe add an additional type check? ( Ignorable by Annotation )

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

348
			$this->saveUserSettings(/** @scrutinizer ignore-type */ $user, $data);
Loading history...
349
			return new DataResponse(
350
				[
351
					'status' => 'success',
352
					'data' => [
353
						'userId' => $user->getUID(),
354
						'avatarScope' => $data[AccountManager::PROPERTY_AVATAR]['scope'],
355
						'displayname' => $data[AccountManager::PROPERTY_DISPLAYNAME]['value'],
356
						'displaynameScope' => $data[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
357
						'email' => $data[AccountManager::PROPERTY_EMAIL]['value'],
358
						'emailScope' => $data[AccountManager::PROPERTY_EMAIL]['scope'],
359
						'website' => $data[AccountManager::PROPERTY_WEBSITE]['value'],
360
						'websiteScope' => $data[AccountManager::PROPERTY_WEBSITE]['scope'],
361
						'address' => $data[AccountManager::PROPERTY_ADDRESS]['value'],
362
						'addressScope' => $data[AccountManager::PROPERTY_ADDRESS]['scope'],
363
						'message' => $this->l10n->t('Settings saved')
364
					]
365
				],
366
				Http::STATUS_OK
367
			);
368
		} catch (ForbiddenException $e) {
369
			return new DataResponse([
370
				'status' => 'error',
371
				'data' => [
372
					'message' => $e->getMessage()
373
				],
374
			]);
375
		}
376
	}
377
	/**
378
	 * update account manager with new user data
379
	 *
380
	 * @param IUser $user
381
	 * @param array $data
382
	 * @throws ForbiddenException
383
	 */
384
	protected function saveUserSettings(IUser $user, array $data) {
385
		// keep the user back-end up-to-date with the latest display name and email
386
		// address
387
		$oldDisplayName = $user->getDisplayName();
388
		$oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
0 ignored issues
show
introduced by
The condition is_null($oldDisplayName) is always false.
Loading history...
389
		if (isset($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
390
			&& $oldDisplayName !== $data[AccountManager::PROPERTY_DISPLAYNAME]['value']
391
		) {
392
			$result = $user->setDisplayName($data[AccountManager::PROPERTY_DISPLAYNAME]['value']);
393
			if ($result === false) {
394
				throw new ForbiddenException($this->l10n->t('Unable to change full name'));
395
			}
396
		}
397
		$oldEmailAddress = $user->getEMailAddress();
398
		$oldEmailAddress = is_null($oldEmailAddress) ? '' : $oldEmailAddress;
399
		if (isset($data[AccountManager::PROPERTY_EMAIL]['value'])
400
			&& $oldEmailAddress !== $data[AccountManager::PROPERTY_EMAIL]['value']
401
		) {
402
			// this is the only permission a backend provides and is also used
403
			// for the permission of setting a email address
404
			if (!$user->canChangeDisplayName()) {
405
				throw new ForbiddenException($this->l10n->t('Unable to change email address'));
406
			}
407
			$user->setEMailAddress($data[AccountManager::PROPERTY_EMAIL]['value']);
408
		}
409
		$this->accountManager->updateUser($user, $data);
410
	}
411
412
	/**
413
	 * Set the mail address of a user
414
	 *
415
	 * @NoAdminRequired
416
	 * @NoSubadminRequired
417
	 * @PasswordConfirmationRequired
418
	 *
419
	 * @param string $account
420
	 * @param bool $onlyVerificationCode only return verification code without updating the data
421
	 * @return DataResponse
422
	 */
423
	public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
424
425
		$user = $this->userSession->getUser();
426
427
		if ($user === null) {
428
			return new DataResponse([], Http::STATUS_BAD_REQUEST);
429
		}
430
431
		$accountData = $this->accountManager->getUser($user);
432
		$cloudId = $user->getCloudId();
433
		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
434
		$signature = $this->signMessage($user, $message);
435
436
		$code = $message . ' ' . $signature;
437
		$codeMd5 = $message . ' ' . md5($signature);
438
439
		switch ($account) {
440
			case 'verify-twitter':
441
				$accountData[AccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
442
				$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):');
443
				$code = $codeMd5;
444
				$type = AccountManager::PROPERTY_TWITTER;
445
				$data = $accountData[AccountManager::PROPERTY_TWITTER]['value'];
446
				$accountData[AccountManager::PROPERTY_TWITTER]['signature'] = $signature;
447
				break;
448
			case 'verify-website':
449
				$accountData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
450
				$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):');
451
				$type = AccountManager::PROPERTY_WEBSITE;
452
				$data = $accountData[AccountManager::PROPERTY_WEBSITE]['value'];
453
				$accountData[AccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
454
				break;
455
			default:
456
				return new DataResponse([], Http::STATUS_BAD_REQUEST);
457
		}
458
459
		if ($onlyVerificationCode === false) {
460
			$this->accountManager->updateUser($user, $accountData);
461
462
			$this->jobList->add(VerifyUserData::class,
463
				[
464
					'verificationCode' => $code,
465
					'data' => $data,
466
					'type' => $type,
467
					'uid' => $user->getUID(),
468
					'try' => 0,
469
					'lastRun' => $this->getCurrentTime()
470
				]
471
			);
472
		}
473
474
		return new DataResponse(['msg' => $msg, 'code' => $code]);
475
	}
476
477
	/**
478
	 * get current timestamp
479
	 *
480
	 * @return int
481
	 */
482
	protected function getCurrentTime(): int {
483
		return time();
484
	}
485
486
	/**
487
	 * sign message with users private key
488
	 *
489
	 * @param IUser $user
490
	 * @param string $message
491
	 *
492
	 * @return string base64 encoded signature
493
	 */
494
	protected function signMessage(IUser $user, string $message): string {
495
		$privateKey = $this->keyManager->getKey($user)->getPrivate();
496
		openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
497
		return base64_encode($signature);
498
	}
499
}
500