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

UsersController::setUserSettings()   B

Complexity

Conditions 7
Paths 19

Size

Total Lines 68
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 42
nc 19
nop 13
dl 0
loc 68
rs 8.3146
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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