Passed
Push — master ( 17cdcf...aa80aa )
by John
09:42 queued 11s
created

UsersController::getVerificationCode()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 52
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 37
nc 6
nop 2
dl 0
loc 52
rs 9.0168
c 0
b 0
f 0

How to fix   Long Method   

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 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