Passed
Push — master ( 17cdcf...aa80aa )
by John
09:42 queued 11s
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 OCA\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 OCA\Settings\BackgroundJobs\VerifyUserData;
64
65
class UsersController extends Controller {
66
	/** @var IUserManager */
67
	private $userManager;
68
	/** @var IGroupManager */
69
	private $groupManager;
70
	/** @var IUserSession */
71
	private $userSession;
72
	/** @var IConfig */
73
	private $config;
74
	/** @var bool */
75
	private $isAdmin;
76
	/** @var IL10N */
77
	private $l10n;
78
	/** @var IMailer */
79
	private $mailer;
80
	/** @var IFactory */
81
	private $l10nFactory;
82
	/** @var IAppManager */
83
	private $appManager;
84
	/** @var AccountManager */
85
	private $accountManager;
86
	/** @var Manager */
87
	private $keyManager;
88
	/** @var IJobList */
89
	private $jobList;
90
	/** @var IManager */
91
	private $encryptionManager;
92
93
94
	public function __construct(string $appName,
95
								IRequest $request,
96
								IUserManager $userManager,
97
								IGroupManager $groupManager,
98
								IUserSession $userSession,
99
								IConfig $config,
100
								bool $isAdmin,
101
								IL10N $l10n,
102
								IMailer $mailer,
103
								IFactory $l10nFactory,
104
								IAppManager $appManager,
105
								AccountManager $accountManager,
106
								Manager $keyManager,
107
								IJobList $jobList,
108
								IManager $encryptionManager) {
109
		parent::__construct($appName, $request);
110
		$this->userManager = $userManager;
111
		$this->groupManager = $groupManager;
112
		$this->userSession = $userSession;
113
		$this->config = $config;
114
		$this->isAdmin = $isAdmin;
115
		$this->l10n = $l10n;
116
		$this->mailer = $mailer;
117
		$this->l10nFactory = $l10nFactory;
118
		$this->appManager = $appManager;
119
		$this->accountManager = $accountManager;
120
		$this->keyManager = $keyManager;
121
		$this->jobList = $jobList;
122
		$this->encryptionManager = $encryptionManager;
123
	}
124
125
126
	/**
127
	 * @NoCSRFRequired
128
	 * @NoAdminRequired
129
	 *
130
	 * Display users list template
131
	 *
132
	 * @return TemplateResponse
133
	 */
134
	public function usersListByGroup() {
135
		return $this->usersList();
136
	}
137
138
	/**
139
	 * @NoCSRFRequired
140
	 * @NoAdminRequired
141
	 *
142
	 * Display users list template
143
	 *
144
	 * @return TemplateResponse
145
	 */
146
	public function usersList() {
147
		$user = $this->userSession->getUser();
148
		$uid = $user->getUID();
149
150
		\OC::$server->getNavigationManager()->setActiveEntry('core_users');
151
152
		/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
153
		$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
154
		$isLDAPUsed = false;
155
		if ($this->config->getSystemValue('sort_groups_by_name', false)) {
156
			$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
157
		} else {
158
			if ($this->appManager->isEnabledForUser('user_ldap')) {
159
				$isLDAPUsed =
160
					$this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_Proxy');
161
				if ($isLDAPUsed) {
162
					// LDAP user count can be slow, so we sort by group name here
163
					$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
164
				}
165
			}
166
		}
167
168
		$canChangePassword = $this->canAdminChangeUserPasswords();
169
170
		/* GROUPS */
171
		$groupsInfo = new \OC\Group\MetaData(
172
			$uid,
173
			$this->isAdmin,
174
			$this->groupManager,
175
			$this->userSession
176
		);
177
178
		$groupsInfo->setSorting($sortGroupsBy);
179
		list($adminGroup, $groups) = $groupsInfo->get();
180
181
		if(!$isLDAPUsed && $this->appManager->isEnabledForUser('user_ldap')) {
182
			$isLDAPUsed = (bool)array_reduce($this->userManager->getBackends(), function ($ldapFound, $backend) {
183
				return $ldapFound || $backend instanceof User_Proxy;
184
			});
185
		}
186
187
		if ($this->isAdmin) {
188
			$disabledUsers = $isLDAPUsed ? -1 : $this->userManager->countDisabledUsers();
189
			$userCount = $isLDAPUsed ? 0 : array_reduce($this->userManager->countUsers(), function($v, $w) {
190
				return $v + (int)$w;
191
			}, 0);
192
		} else {
193
			// User is subadmin !
194
			// Map group list to names to retrieve the countDisabledUsersOfGroups
195
			$userGroups = $this->groupManager->getUserGroups($user);
196
			$groupsNames = [];
197
			$userCount = 0;
198
199
			foreach($groups as $key => $group) {
200
				// $userCount += (int)$group['usercount'];
201
				array_push($groupsNames, $group['name']);
202
				// we prevent subadmins from looking up themselves
203
				// so we lower the count of the groups he belongs to
204
				if (array_key_exists($group['id'], $userGroups)) {
205
					$groups[$key]['usercount']--;
206
					$userCount = -1; // we also lower from one the total count
207
				}
208
			};
209
			$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

209
			$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...
210
			$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

210
			$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...
211
		}
212
		$disabledUsersGroup = [
213
			'id' => 'disabled',
214
			'name' => 'Disabled users',
215
			'usercount' => $disabledUsers
216
		];
217
218
		/* QUOTAS PRESETS */
219
		$quotaPreset = $this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
220
		$quotaPreset = explode(',', $quotaPreset);
221
		foreach ($quotaPreset as &$preset) {
222
			$preset = trim($preset);
223
		}
224
		$quotaPreset = array_diff($quotaPreset, array('default', 'none'));
225
		$defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
226
227
		\OC::$server->getEventDispatcher()->dispatch('OC\Settings\Users::loadAdditionalScripts');
0 ignored issues
show
Bug introduced by
'OC\Settings\Users::loadAdditionalScripts' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

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

227
		\OC::$server->getEventDispatcher()->dispatch(/** @scrutinizer ignore-type */ 'OC\Settings\Users::loadAdditionalScripts');
Loading history...
228
229
		/* LANGUAGES */
230
		$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

230
		/** @scrutinizer ignore-call */ 
231
  $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...
231
232
		/* FINAL DATA */
233
		$serverData = array();
234
		// groups
235
		$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
236
		// Various data
237
		$serverData['isAdmin'] = $this->isAdmin;
238
		$serverData['sortGroups'] = $sortGroupsBy;
239
		$serverData['quotaPreset'] = $quotaPreset;
240
		$serverData['userCount'] = $userCount - $disabledUsers;
241
		$serverData['languages'] = $languages;
242
		$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
243
		// Settings
244
		$serverData['defaultQuota'] = $defaultQuota;
245
		$serverData['canChangePassword'] = $canChangePassword;
246
		$serverData['newUserGenerateUserID'] = $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes';
247
		$serverData['newUserRequireEmail'] = $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes';
248
249
		return new TemplateResponse('settings', 'settings-vue', ['serverData' => $serverData]);
250
	}
251
252
	/**
253
	 * check if the admin can change the users password
254
	 *
255
	 * The admin can change the passwords if:
256
	 *
257
	 *   - no encryption module is loaded and encryption is disabled
258
	 *   - encryption module is loaded but it doesn't require per user keys
259
	 *
260
	 * The admin can not change the passwords if:
261
	 *
262
	 *   - an encryption module is loaded and it uses per-user keys
263
	 *   - encryption is enabled but no encryption modules are loaded
264
	 *
265
	 * @return bool
266
	 */
267
	protected function canAdminChangeUserPasswords() {
268
		$isEncryptionEnabled = $this->encryptionManager->isEnabled();
269
		try {
270
			$noUserSpecificEncryptionKeys =!$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
271
			$isEncryptionModuleLoaded = true;
272
		} catch (ModuleDoesNotExistsException $e) {
273
			$noUserSpecificEncryptionKeys = true;
274
			$isEncryptionModuleLoaded = false;
275
		}
276
277
		$canChangePassword = ($isEncryptionEnabled && $isEncryptionModuleLoaded  && $noUserSpecificEncryptionKeys)
278
			|| (!$isEncryptionEnabled && !$isEncryptionModuleLoaded)
279
			|| (!$isEncryptionEnabled && $isEncryptionModuleLoaded && $noUserSpecificEncryptionKeys);
280
281
		return $canChangePassword;
282
	}
283
284
	/**
285
	 * @NoAdminRequired
286
	 * @NoSubadminRequired
287
	 * @PasswordConfirmationRequired
288
	 *
289
	 * @param string $avatarScope
290
	 * @param string $displayname
291
	 * @param string $displaynameScope
292
	 * @param string $phone
293
	 * @param string $phoneScope
294
	 * @param string $email
295
	 * @param string $emailScope
296
	 * @param string $website
297
	 * @param string $websiteScope
298
	 * @param string $address
299
	 * @param string $addressScope
300
	 * @param string $twitter
301
	 * @param string $twitterScope
302
	 * @return DataResponse
303
	 */
304
	public function setUserSettings($avatarScope,
305
									$displayname,
306
									$displaynameScope,
307
									$phone,
308
									$phoneScope,
309
									$email,
310
									$emailScope,
311
									$website,
312
									$websiteScope,
313
									$address,
314
									$addressScope,
315
									$twitter,
316
									$twitterScope
317
	) {
318
		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
319
			return new DataResponse(
320
				[
321
					'status' => 'error',
322
					'data' => [
323
						'message' => $this->l10n->t('Invalid mail address')
324
					]
325
				],
326
				Http::STATUS_UNPROCESSABLE_ENTITY
327
			);
328
		}
329
		$user = $this->userSession->getUser();
330
		$data = $this->accountManager->getUser($user);
331
		$data[AccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
332
		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
333
			$data[AccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
334
			$data[AccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
335
		}
336
		if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
337
			$federatedFileSharing = new \OCA\FederatedFileSharing\AppInfo\Application();
338
			$shareProvider = $federatedFileSharing->getFederatedShareProvider();
339
			if ($shareProvider->isLookupServerUploadEnabled()) {
340
				$data[AccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
341
				$data[AccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
342
				$data[AccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
343
				$data[AccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
344
			}
345
		}
346
		try {
347
			$this->saveUserSettings($user, $data);
348
			return new DataResponse(
349
				[
350
					'status' => 'success',
351
					'data' => [
352
						'userId' => $user->getUID(),
353
						'avatarScope' => $data[AccountManager::PROPERTY_AVATAR]['scope'],
354
						'displayname' => $data[AccountManager::PROPERTY_DISPLAYNAME]['value'],
355
						'displaynameScope' => $data[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
356
						'email' => $data[AccountManager::PROPERTY_EMAIL]['value'],
357
						'emailScope' => $data[AccountManager::PROPERTY_EMAIL]['scope'],
358
						'website' => $data[AccountManager::PROPERTY_WEBSITE]['value'],
359
						'websiteScope' => $data[AccountManager::PROPERTY_WEBSITE]['scope'],
360
						'address' => $data[AccountManager::PROPERTY_ADDRESS]['value'],
361
						'addressScope' => $data[AccountManager::PROPERTY_ADDRESS]['scope'],
362
						'message' => $this->l10n->t('Settings saved')
363
					]
364
				],
365
				Http::STATUS_OK
366
			);
367
		} catch (ForbiddenException $e) {
368
			return new DataResponse([
369
				'status' => 'error',
370
				'data' => [
371
					'message' => $e->getMessage()
372
				],
373
			]);
374
		}
375
	}
376
	/**
377
	 * update account manager with new user data
378
	 *
379
	 * @param IUser $user
380
	 * @param array $data
381
	 * @throws ForbiddenException
382
	 */
383
	protected function saveUserSettings(IUser $user, array $data) {
384
		// keep the user back-end up-to-date with the latest display name and email
385
		// address
386
		$oldDisplayName = $user->getDisplayName();
387
		$oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
0 ignored issues
show
introduced by
The condition is_null($oldDisplayName) is always false.
Loading history...
388
		if (isset($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
389
			&& $oldDisplayName !== $data[AccountManager::PROPERTY_DISPLAYNAME]['value']
390
		) {
391
			$result = $user->setDisplayName($data[AccountManager::PROPERTY_DISPLAYNAME]['value']);
392
			if ($result === false) {
393
				throw new ForbiddenException($this->l10n->t('Unable to change full name'));
394
			}
395
		}
396
		$oldEmailAddress = $user->getEMailAddress();
397
		$oldEmailAddress = is_null($oldEmailAddress) ? '' : $oldEmailAddress;
398
		if (isset($data[AccountManager::PROPERTY_EMAIL]['value'])
399
			&& $oldEmailAddress !== $data[AccountManager::PROPERTY_EMAIL]['value']
400
		) {
401
			// this is the only permission a backend provides and is also used
402
			// for the permission of setting a email address
403
			if (!$user->canChangeDisplayName()) {
404
				throw new ForbiddenException($this->l10n->t('Unable to change email address'));
405
			}
406
			$user->setEMailAddress($data[AccountManager::PROPERTY_EMAIL]['value']);
407
		}
408
		$this->accountManager->updateUser($user, $data);
409
	}
410
411
	/**
412
	 * Set the mail address of a user
413
	 *
414
	 * @NoAdminRequired
415
	 * @NoSubadminRequired
416
	 * @PasswordConfirmationRequired
417
	 *
418
	 * @param string $account
419
	 * @param bool $onlyVerificationCode only return verification code without updating the data
420
	 * @return DataResponse
421
	 */
422
	public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
423
424
		$user = $this->userSession->getUser();
425
426
		if ($user === null) {
427
			return new DataResponse([], Http::STATUS_BAD_REQUEST);
428
		}
429
430
		$accountData = $this->accountManager->getUser($user);
431
		$cloudId = $user->getCloudId();
432
		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
433
		$signature = $this->signMessage($user, $message);
434
435
		$code = $message . ' ' . $signature;
436
		$codeMd5 = $message . ' ' . md5($signature);
437
438
		switch ($account) {
439
			case 'verify-twitter':
440
				$accountData[AccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
441
				$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):');
442
				$code = $codeMd5;
443
				$type = AccountManager::PROPERTY_TWITTER;
444
				$data = $accountData[AccountManager::PROPERTY_TWITTER]['value'];
445
				$accountData[AccountManager::PROPERTY_TWITTER]['signature'] = $signature;
446
				break;
447
			case 'verify-website':
448
				$accountData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
449
				$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):');
450
				$type = AccountManager::PROPERTY_WEBSITE;
451
				$data = $accountData[AccountManager::PROPERTY_WEBSITE]['value'];
452
				$accountData[AccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
453
				break;
454
			default:
455
				return new DataResponse([], Http::STATUS_BAD_REQUEST);
456
		}
457
458
		if ($onlyVerificationCode === false) {
459
			$this->accountManager->updateUser($user, $accountData);
460
461
			$this->jobList->add(VerifyUserData::class,
462
				[
463
					'verificationCode' => $code,
464
					'data' => $data,
465
					'type' => $type,
466
					'uid' => $user->getUID(),
467
					'try' => 0,
468
					'lastRun' => $this->getCurrentTime()
469
				]
470
			);
471
		}
472
473
		return new DataResponse(['msg' => $msg, 'code' => $code]);
474
	}
475
476
	/**
477
	 * get current timestamp
478
	 *
479
	 * @return int
480
	 */
481
	protected function getCurrentTime(): int {
482
		return time();
483
	}
484
485
	/**
486
	 * sign message with users private key
487
	 *
488
	 * @param IUser $user
489
	 * @param string $message
490
	 *
491
	 * @return string base64 encoded signature
492
	 */
493
	protected function signMessage(IUser $user, string $message): string {
494
		$privateKey = $this->keyManager->getKey($user)->getPrivate();
495
		openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
496
		return base64_encode($signature);
497
	}
498
}
499