Completed
Push — master ( b40bae...a2c518 )
by Morris
16:08
created

UsersController::usersList()   F

Complexity

Conditions 13
Paths 360

Size

Total Lines 109
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 69
nc 360
nop 0
dl 0
loc 109
rs 3.7737
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\ForbiddenException;
45
use OC\Security\IdentityProof\Manager;
46
use OCP\App\IAppManager;
47
use OCP\AppFramework\Controller;
48
use OCP\AppFramework\Http\DataResponse;
49
use OCP\AppFramework\Http\TemplateResponse;
50
use OCP\BackgroundJob\IJobList;
51
use OCP\Encryption\IManager;
52
use OCP\IConfig;
53
use OCP\IGroupManager;
54
use OCP\IL10N;
55
use OCP\IRequest;
56
use OCP\IURLGenerator;
57
use OCP\IUser;
58
use OCP\IUserManager;
59
use OCP\IUserSession;
60
use OCP\L10N\IFactory;
61
use OCP\Mail\IMailer;
62
use OC\Settings\BackgroundJobs\VerifyUserData;
63
64
/**
65
 * @package OC\Settings\Controller
66
 */
67
class UsersController extends Controller {
68
	/** @var IUserManager */
69
	private $userManager;
70
	/** @var IGroupManager */
71
	private $groupManager;
72
	/** @var IUserSession */
73
	private $userSession;
74
	/** @var IConfig */
75
	private $config;
76
	/** @var bool */
77
	private $isAdmin;
78
	/** @var IL10N */
79
	private $l10n;
80
	/** @var IMailer */
81
	private $mailer;
82
	/** @var IFactory */
83
	private $l10nFactory;
84
	/** @var IAppManager */
85
	private $appManager;
86
	/** @var AccountManager */
87
	private $accountManager;
88
	/** @var Manager */
89
	private $keyManager;
90
	/** @var IJobList */
91
	private $jobList;
92
	/** @var IManager */
93
	private $encryptionManager;
94
95
96 View Code Duplication
	public function __construct(string $appName,
97
								IRequest $request,
98
								IUserManager $userManager,
99
								IGroupManager $groupManager,
100
								IUserSession $userSession,
101
								IConfig $config,
102
								bool $isAdmin,
103
								IL10N $l10n,
104
								IMailer $mailer,
105
								IFactory $l10nFactory,
106
								IAppManager $appManager,
107
								AccountManager $accountManager,
108
								Manager $keyManager,
109
								IJobList $jobList,
110
								IManager $encryptionManager) {
111
		parent::__construct($appName, $request);
112
		$this->userManager = $userManager;
113
		$this->groupManager = $groupManager;
114
		$this->userSession = $userSession;
115
		$this->config = $config;
116
		$this->isAdmin = $isAdmin;
117
		$this->l10n = $l10n;
118
		$this->mailer = $mailer;
119
		$this->l10nFactory = $l10nFactory;
120
		$this->appManager = $appManager;
121
		$this->accountManager = $accountManager;
122
		$this->keyManager = $keyManager;
123
		$this->jobList = $jobList;
124
		$this->encryptionManager = $encryptionManager;
125
	}
126
127
128
	/**
129
	 * @NoCSRFRequired
130
	 * @NoAdminRequired
131
	 * 
132
	 * Display users list template
133
	 * 
134
	 * @return TemplateResponse
135
	 */
136
	public function usersListByGroup() {
137
		return $this->usersList();
138
	}
139
140
	/**
141
	 * @NoCSRFRequired
142
	 * @NoAdminRequired
143
	 * 
144
	 * Display users list template
145
	 * 
146
	 * @return TemplateResponse
147
	 */
148
	public function usersList() {
149
		$user = $this->userSession->getUser();
150
		$uid = $user->getUID();
151
152
		\OC::$server->getNavigationManager()->setActiveEntry('core_users');
153
				
154
		/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
155
		$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
156
		if ($this->config->getSystemValue('sort_groups_by_name', false)) {
157
			$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
158
		} else {
159
			$isLDAPUsed = false;
160
			if ($this->appManager->isEnabledForUser('user_ldap')) {
161
				$isLDAPUsed =
162
					$this->groupManager->isBackendUsed('\OCA\User_LDAP\Group_LDAP')
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
		/* ENCRYPTION CONFIG */
172
		$isEncryptionEnabled = $this->encryptionManager->isEnabled();
173
		$useMasterKey = $this->config->getAppValue('encryption', 'useMasterKey', true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
174
		// If masterKey enabled, then you can change password. This is to avoid data loss!
175
		$canChangePassword = ($isEncryptionEnabled && $useMasterKey) || $useMasterKey;
176
		
177
		
178
		/* GROUPS */		
179
		$groupsInfo = new \OC\Group\MetaData(
180
			$uid,
181
			$this->isAdmin,
182
			$this->groupManager,
183
			$this->userSession
184
		);
185
		
186
		$groupsInfo->setSorting($sortGroupsBy);
187
		list($adminGroup, $groups) = $groupsInfo->get();
188
		
189
		if ($this->isAdmin) {
190
			$subAdmins = $this->groupManager->getSubAdmin()->getAllSubAdmins();
191
			// New class returns IUser[] so convert back
192
			$result = [];
193
			foreach ($subAdmins as $subAdmin) {
194
				$result[] = [
195
					'gid' => $subAdmin['group']->getGID(),
196
					'uid' => $subAdmin['user']->getUID(),
197
				];
198
			}
199
			$subAdmins = $result;
200
		} else {
201
			/* Retrieve group IDs from $groups array, so we can pass that information into OC_Group::displayNamesInGroups() */
202
			$gids = array();
203
			foreach($groups as $group) {
204
				if (isset($group['id'])) {
205
					$gids[] = $group['id'];
206
				}
207
			}
208
			$subAdmins = false;
209
		}
210
		
211
		$disabledUsers = $isLDAPUsed ? 0 : $this->userManager->countDisabledUsers();
0 ignored issues
show
Bug introduced by
The variable $isLDAPUsed does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
212
		$disabledUsersGroup = [
213
			'id' => 'disabled',
214
			'name' => 'Disabled users',
215
			'usercount' => $disabledUsers
216
		];
217
		$allGroups = array_merge_recursive($adminGroup, $groups);
0 ignored issues
show
Unused Code introduced by
$allGroups is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
218
		
219
		/* QUOTAS PRESETS */
220
		$quotaPreset = $this->config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
221
		$quotaPreset = explode(',', $quotaPreset);
222
		foreach ($quotaPreset as &$preset) {
223
			$preset = trim($preset);
224
		}
225
		$quotaPreset = array_diff($quotaPreset, array('default', 'none'));
226
		$defaultQuota = $this->config->getAppValue('files', 'default_quota', 'none');
227
		
228
		\OC::$server->getEventDispatcher()->dispatch('OC\Settings\Users::loadAdditionalScripts');
229
		
230
		/* TOTAL USERS COUNT */
231
		$userCount = array_reduce($this->userManager->countUsers(), function($v, $w) {
232
			return $v + (int)$w;
233
		}, 0);
234
		
235
		/* LANGUAGES */
236
		$languages = $this->l10nFactory->getLanguages();
237
		
238
		/* FINAL DATA */
239
		$serverData = array();
240
		// groups
241
		$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
242
		$serverData['subadmingroups'] = $groups;
243
		// Various data
244
		$serverData['isAdmin'] = $this->isAdmin;
245
		$serverData['subadmins'] = $subAdmins;
246
		$serverData['sortGroups'] = $sortGroupsBy;
247
		$serverData['quotaPreset'] = $quotaPreset;
248
		$serverData['userCount'] = $userCount-$disabledUsers;
249
		$serverData['languages'] = $languages;
250
		$serverData['defaultLanguage'] = $this->config->getSystemValue('default_language', 'en');
251
		// Settings
252
		$serverData['defaultQuota'] = $defaultQuota;
253
		$serverData['canChangePassword'] = $canChangePassword;
254
255
		return new TemplateResponse('settings', 'settings', ['serverData' => $serverData]);
256
	}
257
258
	/**
259
	 * @NoAdminRequired
260
	 * @NoSubadminRequired
261
	 * @PasswordConfirmationRequired
262
	 *
263
	 * @param string $avatarScope
264
	 * @param string $displayname
265
	 * @param string $displaynameScope
266
	 * @param string $phone
267
	 * @param string $phoneScope
268
	 * @param string $email
269
	 * @param string $emailScope
270
	 * @param string $website
271
	 * @param string $websiteScope
272
	 * @param string $address
273
	 * @param string $addressScope
274
	 * @param string $twitter
275
	 * @param string $twitterScope
276
	 * @return DataResponse
277
	 */
278
	public function setUserSettings($avatarScope,
279
									$displayname,
280
									$displaynameScope,
281
									$phone,
282
									$phoneScope,
283
									$email,
284
									$emailScope,
285
									$website,
286
									$websiteScope,
287
									$address,
288
									$addressScope,
289
									$twitter,
290
									$twitterScope
291
	) {
292
		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
293
			return new DataResponse(
294
				[
295
					'status' => 'error',
296
					'data' => [
297
						'message' => $this->l10n->t('Invalid mail address')
298
					]
299
				],
300
				Http::STATUS_UNPROCESSABLE_ENTITY
301
			);
302
		}
303
		$user = $this->userSession->getUser();
304
		$data = $this->accountManager->getUser($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userSession->getUser() on line 303 can be null; however, OC\Accounts\AccountManager::getUser() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
305
		$data[AccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
306
		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
307
			$data[AccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
308
			$data[AccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
309
		}
310
		if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
311
			$federatedFileSharing = new \OCA\FederatedFileSharing\AppInfo\Application();
312
			$shareProvider = $federatedFileSharing->getFederatedShareProvider();
313
			if ($shareProvider->isLookupServerUploadEnabled()) {
314
				$data[AccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
315
				$data[AccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
316
				$data[AccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
317
				$data[AccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
318
			}
319
		}
320
		try {
321
			$this->saveUserSettings($user, $data);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userSession->getUser() on line 303 can be null; however, OC\Settings\Controller\U...ler::saveUserSettings() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

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