Passed
Push — master ( b67ac4...89cf1c )
by Joas
15:14 queued 12s
created

UsersController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 34
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 17
c 1
b 0
f 0
nc 1
nop 16
dl 0
loc 34
rs 9.7

How to fix   Many Parameters   

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
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author Bjoern Schiessle <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Daniel Calviño Sánchez <[email protected]>
12
 * @author Daniel Kesselberg <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author John Molakvoæ (skjnldsv) <[email protected]>
15
 * @author Julius Härtl <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author michag86 <[email protected]>
18
 * @author Mikael Hammarin <[email protected]>
19
 * @author Morris Jobke <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Sujith Haridasan <[email protected]>
23
 * @author Thomas Citharel <[email protected]>
24
 * @author Thomas Müller <[email protected]>
25
 * @author Tom Needham <[email protected]>
26
 *
27
 * @license AGPL-3.0
28
 *
29
 * This code is free software: you can redistribute it and/or modify
30
 * it under the terms of the GNU Affero General Public License, version 3,
31
 * as published by the Free Software Foundation.
32
 *
33
 * This program is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
 * GNU Affero General Public License for more details.
37
 *
38
 * You should have received a copy of the GNU Affero General Public License, version 3,
39
 * along with this program. If not, see <http://www.gnu.org/licenses/>
40
 *
41
 */
42
43
namespace OCA\Provisioning_API\Controller;
44
45
use libphonenumber\NumberParseException;
46
use libphonenumber\PhoneNumber;
47
use libphonenumber\PhoneNumberFormat;
48
use libphonenumber\PhoneNumberUtil;
49
use OC\Accounts\AccountManager;
50
use OC\Authentication\Token\RemoteWipe;
51
use OC\HintException;
52
use OC\KnownUser\KnownUserService;
53
use OCA\Settings\Mailer\NewUserMailHelper;
54
use OCP\Accounts\IAccountManager;
55
use OCP\App\IAppManager;
56
use OCP\AppFramework\Http;
57
use OCP\AppFramework\Http\DataResponse;
58
use OCP\AppFramework\OCS\OCSException;
59
use OCP\AppFramework\OCS\OCSForbiddenException;
60
use OCP\AppFramework\OCSController;
61
use OCP\IConfig;
62
use OCP\IGroup;
63
use OCP\IGroupManager;
64
use OCP\IRequest;
65
use OCP\IURLGenerator;
66
use OCP\IUser;
67
use OCP\IUserManager;
68
use OCP\IUserSession;
69
use OCP\L10N\IFactory;
70
use OCP\Security\ISecureRandom;
71
use OCP\Security\Events\GenerateSecurePasswordEvent;
72
use OCP\EventDispatcher\IEventDispatcher;
73
use Psr\Log\LoggerInterface;
74
75
class UsersController extends AUserData {
76
77
	/** @var IAppManager */
78
	private $appManager;
79
	/** @var IURLGenerator */
80
	protected $urlGenerator;
81
	/** @var LoggerInterface */
82
	private $logger;
83
	/** @var IFactory */
84
	protected $l10nFactory;
85
	/** @var NewUserMailHelper */
86
	private $newUserMailHelper;
87
	/** @var ISecureRandom */
88
	private $secureRandom;
89
	/** @var RemoteWipe */
90
	private $remoteWipe;
91
	/** @var KnownUserService */
92
	private $knownUserService;
93
	/** @var IEventDispatcher */
94
	private $eventDispatcher;
95
96
	public function __construct(string $appName,
97
								IRequest $request,
98
								IUserManager $userManager,
99
								IConfig $config,
100
								IAppManager $appManager,
101
								IGroupManager $groupManager,
102
								IUserSession $userSession,
103
								AccountManager $accountManager,
104
								IURLGenerator $urlGenerator,
105
								LoggerInterface $logger,
106
								IFactory $l10nFactory,
107
								NewUserMailHelper $newUserMailHelper,
108
								ISecureRandom $secureRandom,
109
								RemoteWipe $remoteWipe,
110
								KnownUserService $knownUserService,
111
								IEventDispatcher $eventDispatcher) {
112
		parent::__construct($appName,
113
							$request,
114
							$userManager,
115
							$config,
116
							$groupManager,
117
							$userSession,
118
							$accountManager,
119
							$l10nFactory);
120
121
		$this->appManager = $appManager;
122
		$this->urlGenerator = $urlGenerator;
123
		$this->logger = $logger;
124
		$this->l10nFactory = $l10nFactory;
125
		$this->newUserMailHelper = $newUserMailHelper;
126
		$this->secureRandom = $secureRandom;
127
		$this->remoteWipe = $remoteWipe;
128
		$this->knownUserService = $knownUserService;
129
		$this->eventDispatcher = $eventDispatcher;
130
	}
131
132
	/**
133
	 * @NoAdminRequired
134
	 *
135
	 * returns a list of users
136
	 *
137
	 * @param string $search
138
	 * @param int $limit
139
	 * @param int $offset
140
	 * @return DataResponse
141
	 */
142
	public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
143
		$user = $this->userSession->getUser();
144
		$users = [];
145
146
		// Admin? Or SubAdmin?
147
		$uid = $user->getUID();
148
		$subAdminManager = $this->groupManager->getSubAdmin();
0 ignored issues
show
Bug introduced by
The method getSubAdmin() does not exist on OCP\IGroupManager. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\IGroupManager. ( Ignorable by Annotation )

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

148
		/** @scrutinizer ignore-call */ 
149
  $subAdminManager = $this->groupManager->getSubAdmin();
Loading history...
149
		if ($this->groupManager->isAdmin($uid)) {
150
			$users = $this->userManager->search($search, $limit, $offset);
151
		} elseif ($subAdminManager->isSubAdmin($user)) {
152
			$subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
153
			foreach ($subAdminOfGroups as $key => $group) {
154
				$subAdminOfGroups[$key] = $group->getGID();
155
			}
156
157
			$users = [];
158
			foreach ($subAdminOfGroups as $group) {
159
				$users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
160
			}
161
		}
162
163
		$users = array_keys($users);
164
165
		return new DataResponse([
166
			'users' => $users
167
		]);
168
	}
169
170
	/**
171
	 * @NoAdminRequired
172
	 *
173
	 * returns a list of users and their data
174
	 */
175
	public function getUsersDetails(string $search = '', int $limit = null, int $offset = 0): DataResponse {
176
		$currentUser = $this->userSession->getUser();
177
		$users = [];
178
179
		// Admin? Or SubAdmin?
180
		$uid = $currentUser->getUID();
181
		$subAdminManager = $this->groupManager->getSubAdmin();
182
		if ($this->groupManager->isAdmin($uid)) {
183
			$users = $this->userManager->search($search, $limit, $offset);
184
			$users = array_keys($users);
185
		} elseif ($subAdminManager->isSubAdmin($currentUser)) {
186
			$subAdminOfGroups = $subAdminManager->getSubAdminsGroups($currentUser);
187
			foreach ($subAdminOfGroups as $key => $group) {
188
				$subAdminOfGroups[$key] = $group->getGID();
189
			}
190
191
			$users = [];
192
			foreach ($subAdminOfGroups as $group) {
193
				$users[] = array_keys($this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
194
			}
195
			$users = array_merge(...$users);
196
		}
197
198
		$usersDetails = [];
199
		foreach ($users as $userId) {
200
			$userId = (string) $userId;
201
			$userData = $this->getUserData($userId);
202
			// Do not insert empty entry
203
			if (!empty($userData)) {
204
				$usersDetails[$userId] = $userData;
205
			} else {
206
				// Logged user does not have permissions to see this user
207
				// only showing its id
208
				$usersDetails[$userId] = ['id' => $userId];
209
			}
210
		}
211
212
		return new DataResponse([
213
			'users' => $usersDetails
214
		]);
215
	}
216
217
218
	/**
219
	 * @NoAdminRequired
220
	 * @NoSubAdminRequired
221
	 *
222
	 * @param string $location
223
	 * @param array $search
224
	 * @return DataResponse
225
	 */
226
	public function searchByPhoneNumbers(string $location, array $search): DataResponse {
227
		$phoneUtil = PhoneNumberUtil::getInstance();
228
229
		if ($phoneUtil->getCountryCodeForRegion($location) === 0) {
230
			// Not a valid region code
231
			return new DataResponse([], Http::STATUS_BAD_REQUEST);
232
		}
233
234
		/** @var IUser $user */
235
		$user = $this->userSession->getUser();
236
		$knownTo = $user->getUID();
237
		$defaultPhoneRegion = $this->config->getSystemValueString('default_phone_region');
238
239
		$normalizedNumberToKey = [];
240
		foreach ($search as $key => $phoneNumbers) {
241
			foreach ($phoneNumbers as $phone) {
242
				try {
243
					$phoneNumber = $phoneUtil->parse($phone, $location);
244
					if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
245
						$normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
246
						$normalizedNumberToKey[$normalizedNumber] = (string) $key;
247
					}
248
				} catch (NumberParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
249
				}
250
251
				if ($defaultPhoneRegion !== '' && $defaultPhoneRegion !== $location && strpos($phone, '0') === 0) {
252
					// If the number has a leading zero (no country code),
253
					// we also check the default phone region of the instance,
254
					// when it's different to the user's given region.
255
					try {
256
						$phoneNumber = $phoneUtil->parse($phone, $defaultPhoneRegion);
257
						if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
258
							$normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
259
							$normalizedNumberToKey[$normalizedNumber] = (string) $key;
260
						}
261
					} catch (NumberParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
262
					}
263
				}
264
			}
265
		}
266
267
		$phoneNumbers = array_keys($normalizedNumberToKey);
268
269
		if (empty($phoneNumbers)) {
270
			return new DataResponse();
271
		}
272
273
		// Cleanup all previous entries and only allow new matches
274
		$this->knownUserService->deleteKnownTo($knownTo);
275
276
		$userMatches = $this->accountManager->searchUsers(IAccountManager::PROPERTY_PHONE, $phoneNumbers);
277
278
		if (empty($userMatches)) {
279
			return new DataResponse();
280
		}
281
282
		$cloudUrl = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
283
		if (strpos($cloudUrl, 'http://') === 0) {
284
			$cloudUrl = substr($cloudUrl, strlen('http://'));
285
		} elseif (strpos($cloudUrl, 'https://') === 0) {
286
			$cloudUrl = substr($cloudUrl, strlen('https://'));
287
		}
288
289
		$matches = [];
290
		foreach ($userMatches as $phone => $userId) {
291
			// Not using the ICloudIdManager as that would run a search for each contact to find the display name in the address book
292
			$matches[$normalizedNumberToKey[$phone]] = $userId . '@' . $cloudUrl;
293
			$this->knownUserService->storeIsKnownToUser($knownTo, $userId);
294
		}
295
296
		return new DataResponse($matches);
297
	}
298
299
	/**
300
	 * @throws OCSException
301
	 */
302
	private function createNewUserId(): string {
303
		$attempts = 0;
304
		do {
305
			$uidCandidate = $this->secureRandom->generate(10, ISecureRandom::CHAR_HUMAN_READABLE);
306
			if (!$this->userManager->userExists($uidCandidate)) {
307
				return $uidCandidate;
308
			}
309
			$attempts++;
310
		} while ($attempts < 10);
311
		throw new OCSException('Could not create non-existing user id', 111);
312
	}
313
314
	/**
315
	 * @PasswordConfirmationRequired
316
	 * @NoAdminRequired
317
	 *
318
	 * @param string $userid
319
	 * @param string $password
320
	 * @param string $displayName
321
	 * @param string $email
322
	 * @param array $groups
323
	 * @param array $subadmin
324
	 * @param string $quota
325
	 * @param string $language
326
	 * @return DataResponse
327
	 * @throws OCSException
328
	 */
329
	public function addUser(string $userid,
330
							string $password = '',
331
							string $displayName = '',
332
							string $email = '',
333
							array $groups = [],
334
							array $subadmin = [],
335
							string $quota = '',
336
							string $language = ''): DataResponse {
337
		$user = $this->userSession->getUser();
338
		$isAdmin = $this->groupManager->isAdmin($user->getUID());
339
		$subAdminManager = $this->groupManager->getSubAdmin();
340
341
		if (empty($userid) && $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes') {
342
			$userid = $this->createNewUserId();
343
		}
344
345
		if ($this->userManager->userExists($userid)) {
346
			$this->logger->error('Failed addUser attempt: User already exists.', ['app' => 'ocs_api']);
347
			throw new OCSException('User already exists', 102);
348
		}
349
350
		if ($groups !== []) {
351
			foreach ($groups as $group) {
352
				if (!$this->groupManager->groupExists($group)) {
353
					throw new OCSException('group '.$group.' does not exist', 104);
354
				}
355
				if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) {
356
					throw new OCSException('insufficient privileges for group '. $group, 105);
357
				}
358
			}
359
		} else {
360
			if (!$isAdmin) {
361
				throw new OCSException('no group specified (required for subadmins)', 106);
362
			}
363
		}
364
365
		$subadminGroups = [];
366
		if ($subadmin !== []) {
367
			foreach ($subadmin as $groupid) {
368
				$group = $this->groupManager->get($groupid);
369
				// Check if group exists
370
				if ($group === null) {
371
					throw new OCSException('Subadmin group does not exist',  102);
372
				}
373
				// Check if trying to make subadmin of admin group
374
				if ($group->getGID() === 'admin') {
375
					throw new OCSException('Cannot create subadmins for admin group', 103);
376
				}
377
				// Check if has permission to promote subadmins
378
				if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin) {
379
					throw new OCSForbiddenException('No permissions to promote subadmins');
380
				}
381
				$subadminGroups[] = $group;
382
			}
383
		}
384
385
		$generatePasswordResetToken = false;
386
		if ($password === '') {
387
			if ($email === '') {
388
				throw new OCSException('To send a password link to the user an email address is required.', 108);
389
			}
390
391
			$passwordEvent = new GenerateSecurePasswordEvent();
392
			$this->eventDispatcher->dispatchTyped($passwordEvent);
393
394
			$password = $passwordEvent->getPassword();
395
			if ($password === null) {
396
				// Fallback: ensure to pass password_policy in any case
397
				$password = $this->secureRandom->generate(10)
398
					. $this->secureRandom->generate(1, ISecureRandom::CHAR_UPPER)
399
					. $this->secureRandom->generate(1, ISecureRandom::CHAR_LOWER)
400
					. $this->secureRandom->generate(1, ISecureRandom::CHAR_DIGITS)
401
					. $this->secureRandom->generate(1, ISecureRandom::CHAR_SYMBOLS);
402
			}
403
			$generatePasswordResetToken = true;
404
		}
405
406
		if ($email === '' && $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes') {
407
			throw new OCSException('Required email address was not provided', 110);
408
		}
409
410
		try {
411
			$newUser = $this->userManager->createUser($userid, $password);
412
			$this->logger->info('Successful addUser call with userid: ' . $userid, ['app' => 'ocs_api']);
413
414
			foreach ($groups as $group) {
415
				$this->groupManager->get($group)->addUser($newUser);
416
				$this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']);
417
			}
418
			foreach ($subadminGroups as $group) {
419
				$subAdminManager->createSubAdmin($newUser, $group);
420
			}
421
422
			if ($displayName !== '') {
423
				$this->editUser($userid, 'display', $displayName);
424
			}
425
426
			if ($quota !== '') {
427
				$this->editUser($userid, 'quota', $quota);
428
			}
429
430
			if ($language !== '') {
431
				$this->editUser($userid, 'language', $language);
432
			}
433
434
			// Send new user mail only if a mail is set
435
			if ($email !== '') {
436
				$newUser->setEMailAddress($email);
437
				if ($this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes') {
438
					try {
439
						$emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken);
440
						$this->newUserMailHelper->sendMail($newUser, $emailTemplate);
441
					} catch (\Exception $e) {
442
						// Mail could be failing hard or just be plain not configured
443
						// Logging error as it is the hardest of the two
444
						$this->logger->error("Unable to send the invitation mail to $email",
445
							[
446
								'app' => 'ocs_api',
447
								'exception' => $e,
448
							]
449
						);
450
					}
451
				}
452
			}
453
454
			return new DataResponse(['id' => $userid]);
455
		} catch (HintException $e) {
456
			$this->logger->warning('Failed addUser attempt with hint exception.',
457
				[
458
					'app' => 'ocs_api',
459
					'exception' => $e,
460
				]
461
			);
462
			throw new OCSException($e->getHint(), 107);
463
		} catch (OCSException $e) {
464
			$this->logger->warning('Failed addUser attempt with ocs exeption.',
465
				[
466
					'app' => 'ocs_api',
467
					'exception' => $e,
468
				]
469
			);
470
			throw $e;
471
		} catch (\InvalidArgumentException $e) {
472
			$this->logger->error('Failed addUser attempt with invalid argument exeption.',
473
				[
474
					'app' => 'ocs_api',
475
					'exception' => $e,
476
				]
477
			);
478
			throw new OCSException($e->getMessage(), 101);
479
		} catch (\Exception $e) {
480
			$this->logger->error('Failed addUser attempt with exception.',
481
				[
482
					'app' => 'ocs_api',
483
					'exception' => $e
484
				]
485
			);
486
			throw new OCSException('Bad request', 101);
487
		}
488
	}
489
490
	/**
491
	 * @NoAdminRequired
492
	 * @NoSubAdminRequired
493
	 *
494
	 * gets user info
495
	 *
496
	 * @param string $userId
497
	 * @return DataResponse
498
	 * @throws OCSException
499
	 */
500
	public function getUser(string $userId): DataResponse {
501
		$includeScopes = false;
502
		$currentUser = $this->userSession->getUser();
503
		if ($currentUser && $currentUser->getUID() === $userId) {
504
			$includeScopes = true;
505
		}
506
507
		$data = $this->getUserData($userId, $includeScopes);
508
		// getUserData returns empty array if not enough permissions
509
		if (empty($data)) {
510
			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
511
		}
512
		return new DataResponse($data);
513
	}
514
515
	/**
516
	 * @NoAdminRequired
517
	 * @NoSubAdminRequired
518
	 *
519
	 * gets user info from the currently logged in user
520
	 *
521
	 * @return DataResponse
522
	 * @throws OCSException
523
	 */
524
	public function getCurrentUser(): DataResponse {
525
		$user = $this->userSession->getUser();
526
		if ($user) {
527
			$data = $this->getUserData($user->getUID(), true);
528
			// rename "displayname" to "display-name" only for this call to keep
529
			// the API stable.
530
			$data['display-name'] = $data['displayname'];
531
			unset($data['displayname']);
532
			return new DataResponse($data);
533
		}
534
535
		throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
536
	}
537
538
	/**
539
	 * @NoAdminRequired
540
	 * @NoSubAdminRequired
541
	 */
542
	public function getEditableFields(): DataResponse {
543
		$permittedFields = [];
544
545
		// Editing self (display, email)
546
		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
547
			$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
548
			$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
549
		}
550
551
		$permittedFields[] = IAccountManager::PROPERTY_PHONE;
552
		$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
553
		$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
554
		$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
555
556
		return new DataResponse($permittedFields);
557
	}
558
559
	/**
560
	 * @NoAdminRequired
561
	 * @NoSubAdminRequired
562
	 * @PasswordConfirmationRequired
563
	 *
564
	 * edit users
565
	 *
566
	 * @param string $userId
567
	 * @param string $key
568
	 * @param string $value
569
	 * @return DataResponse
570
	 * @throws OCSException
571
	 */
572
	public function editUser(string $userId, string $key, string $value): DataResponse {
573
		$currentLoggedInUser = $this->userSession->getUser();
574
575
		$targetUser = $this->userManager->get($userId);
576
		if ($targetUser === null) {
577
			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
578
		}
579
580
		$permittedFields = [];
581
		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
582
			// Editing self (display, email)
583
			if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
584
				$permittedFields[] = 'display';
585
				$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
586
				$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
587
			}
588
589
			$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX;
590
			$permittedFields[] = IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX;
591
592
			$permittedFields[] = 'password';
593
			if ($this->config->getSystemValue('force_language', false) === false ||
594
				$this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
595
				$permittedFields[] = 'language';
596
			}
597
598
			if ($this->config->getSystemValue('force_locale', false) === false ||
599
				$this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
600
				$permittedFields[] = 'locale';
601
			}
602
603
			$permittedFields[] = IAccountManager::PROPERTY_PHONE;
604
			$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
605
			$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
606
			$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
607
			$permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
608
			$permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
609
			$permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
610
			$permittedFields[] = IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX;
611
612
			$permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
613
614
			// If admin they can edit their own quota
615
			if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
616
				$permittedFields[] = 'quota';
617
			}
618
		} else {
619
			// Check if admin / subadmin
620
			$subAdminManager = $this->groupManager->getSubAdmin();
621
			if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())
622
			|| $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
623
				// They have permissions over the user
624
				$permittedFields[] = 'display';
625
				$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
626
				$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
627
				$permittedFields[] = 'password';
628
				$permittedFields[] = 'language';
629
				$permittedFields[] = 'locale';
630
				$permittedFields[] = IAccountManager::PROPERTY_PHONE;
631
				$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
632
				$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
633
				$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
634
				$permittedFields[] = 'quota';
635
			} else {
636
				// No rights
637
				throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
638
			}
639
		}
640
		// Check if permitted to edit this field
641
		if (!in_array($key, $permittedFields)) {
642
			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
643
		}
644
		// Process the edit
645
		switch ($key) {
646
			case 'display':
647
			case IAccountManager::PROPERTY_DISPLAYNAME:
648
				$targetUser->setDisplayName($value);
649
				break;
650
			case 'quota':
651
				$quota = $value;
652
				if ($quota !== 'none' && $quota !== 'default') {
653
					if (is_numeric($quota)) {
654
						$quota = (float) $quota;
655
					} else {
656
						$quota = \OCP\Util::computerFileSize($quota);
657
					}
658
					if ($quota === false) {
0 ignored issues
show
introduced by
The condition $quota === false is always false.
Loading history...
659
						throw new OCSException('Invalid quota value '.$value, 103);
660
					}
661
					if ($quota === -1) {
0 ignored issues
show
introduced by
The condition $quota === -1 is always false.
Loading history...
662
						$quota = 'none';
663
					} else {
664
						$quota = \OCP\Util::humanFileSize($quota);
0 ignored issues
show
Bug introduced by
$quota of type double is incompatible with the type integer expected by parameter $bytes of OCP\Util::humanFileSize(). ( Ignorable by Annotation )

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

664
						$quota = \OCP\Util::humanFileSize(/** @scrutinizer ignore-type */ $quota);
Loading history...
665
					}
666
				}
667
				$targetUser->setQuota($quota);
668
				break;
669
			case 'password':
670
				try {
671
					if (!$targetUser->canChangePassword()) {
672
						throw new OCSException('Setting the password is not supported by the users backend', 103);
673
					}
674
					$targetUser->setPassword($value);
675
				} catch (HintException $e) { // password policy error
676
					throw new OCSException($e->getMessage(), 103);
677
				}
678
				break;
679
			case 'language':
680
				$languagesCodes = $this->l10nFactory->findAvailableLanguages();
681
				if (!in_array($value, $languagesCodes, true) && $value !== 'en') {
682
					throw new OCSException('Invalid language', 102);
683
				}
684
				$this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value);
685
				break;
686
			case 'locale':
687
				if (!$this->l10nFactory->localeExists($value)) {
688
					throw new OCSException('Invalid locale', 102);
689
				}
690
				$this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value);
691
				break;
692
			case IAccountManager::PROPERTY_EMAIL:
693
				if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
694
					$targetUser->setEMailAddress($value);
695
				} else {
696
					throw new OCSException('', 102);
697
				}
698
				break;
699
			case IAccountManager::PROPERTY_PHONE:
700
			case IAccountManager::PROPERTY_ADDRESS:
701
			case IAccountManager::PROPERTY_WEBSITE:
702
			case IAccountManager::PROPERTY_TWITTER:
703
				$userAccount = $this->accountManager->getUser($targetUser);
704
				if ($userAccount[$key]['value'] !== $value) {
705
					$userAccount[$key]['value'] = $value;
706
					try {
707
						$this->accountManager->updateUser($targetUser, $userAccount, true);
708
709
						if ($key === IAccountManager::PROPERTY_PHONE) {
710
							$this->knownUserService->deleteByContactUserId($targetUser->getUID());
711
						}
712
					} catch (\InvalidArgumentException $e) {
713
						throw new OCSException('Invalid ' . $e->getMessage(), 102);
714
					}
715
				}
716
				break;
717
			case IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX:
718
			case IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX:
719
			case IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX:
720
			case IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX:
721
			case IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX:
722
			case IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX:
723
			case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX:
724
				$propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX));
725
				$userAccount = $this->accountManager->getUser($targetUser);
726
				if ($userAccount[$propertyName]['scope'] !== $value) {
727
					$userAccount[$propertyName]['scope'] = $value;
728
					try {
729
						$this->accountManager->updateUser($targetUser, $userAccount, true);
730
					} catch (\InvalidArgumentException $e) {
731
						throw new OCSException('Invalid ' . $e->getMessage(), 102);
732
					}
733
				}
734
				break;
735
			default:
736
				throw new OCSException('', 103);
737
		}
738
		return new DataResponse();
739
	}
740
741
	/**
742
	 * @PasswordConfirmationRequired
743
	 * @NoAdminRequired
744
	 *
745
	 * @param string $userId
746
	 *
747
	 * @return DataResponse
748
	 *
749
	 * @throws OCSException
750
	 */
751
	public function wipeUserDevices(string $userId): DataResponse {
752
		/** @var IUser $currentLoggedInUser */
753
		$currentLoggedInUser = $this->userSession->getUser();
754
755
		$targetUser = $this->userManager->get($userId);
756
757
		if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
758
			throw new OCSException('', 101);
759
		}
760
761
		// If not permitted
762
		$subAdminManager = $this->groupManager->getSubAdmin();
763
		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
764
			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
765
		}
766
767
		$this->remoteWipe->markAllTokensForWipe($targetUser);
768
769
		return new DataResponse();
770
	}
771
772
	/**
773
	 * @PasswordConfirmationRequired
774
	 * @NoAdminRequired
775
	 *
776
	 * @param string $userId
777
	 * @return DataResponse
778
	 * @throws OCSException
779
	 */
780
	public function deleteUser(string $userId): DataResponse {
781
		$currentLoggedInUser = $this->userSession->getUser();
782
783
		$targetUser = $this->userManager->get($userId);
784
785
		if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
786
			throw new OCSException('', 101);
787
		}
788
789
		// If not permitted
790
		$subAdminManager = $this->groupManager->getSubAdmin();
791
		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
792
			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
793
		}
794
795
		// Go ahead with the delete
796
		if ($targetUser->delete()) {
797
			return new DataResponse();
798
		} else {
799
			throw new OCSException('', 101);
800
		}
801
	}
802
803
	/**
804
	 * @PasswordConfirmationRequired
805
	 * @NoAdminRequired
806
	 *
807
	 * @param string $userId
808
	 * @return DataResponse
809
	 * @throws OCSException
810
	 * @throws OCSForbiddenException
811
	 */
812
	public function disableUser(string $userId): DataResponse {
813
		return $this->setEnabled($userId, false);
814
	}
815
816
	/**
817
	 * @PasswordConfirmationRequired
818
	 * @NoAdminRequired
819
	 *
820
	 * @param string $userId
821
	 * @return DataResponse
822
	 * @throws OCSException
823
	 * @throws OCSForbiddenException
824
	 */
825
	public function enableUser(string $userId): DataResponse {
826
		return $this->setEnabled($userId, true);
827
	}
828
829
	/**
830
	 * @param string $userId
831
	 * @param bool $value
832
	 * @return DataResponse
833
	 * @throws OCSException
834
	 */
835
	private function setEnabled(string $userId, bool $value): DataResponse {
836
		$currentLoggedInUser = $this->userSession->getUser();
837
838
		$targetUser = $this->userManager->get($userId);
839
		if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
840
			throw new OCSException('', 101);
841
		}
842
843
		// If not permitted
844
		$subAdminManager = $this->groupManager->getSubAdmin();
845
		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
846
			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
847
		}
848
849
		// enable/disable the user now
850
		$targetUser->setEnabled($value);
851
		return new DataResponse();
852
	}
853
854
	/**
855
	 * @NoAdminRequired
856
	 * @NoSubAdminRequired
857
	 *
858
	 * @param string $userId
859
	 * @return DataResponse
860
	 * @throws OCSException
861
	 */
862
	public function getUsersGroups(string $userId): DataResponse {
863
		$loggedInUser = $this->userSession->getUser();
864
865
		$targetUser = $this->userManager->get($userId);
866
		if ($targetUser === null) {
867
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
868
		}
869
870
		if ($targetUser->getUID() === $loggedInUser->getUID() || $this->groupManager->isAdmin($loggedInUser->getUID())) {
871
			// Self lookup or admin lookup
872
			return new DataResponse([
873
				'groups' => $this->groupManager->getUserGroupIds($targetUser)
874
			]);
875
		} else {
876
			$subAdminManager = $this->groupManager->getSubAdmin();
877
878
			// Looking up someone else
879
			if ($subAdminManager->isUserAccessible($loggedInUser, $targetUser)) {
880
				// Return the group that the method caller is subadmin of for the user in question
881
				/** @var IGroup[] $getSubAdminsGroups */
882
				$getSubAdminsGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
883
				foreach ($getSubAdminsGroups as $key => $group) {
884
					$getSubAdminsGroups[$key] = $group->getGID();
885
				}
886
				$groups = array_intersect(
887
					$getSubAdminsGroups,
888
					$this->groupManager->getUserGroupIds($targetUser)
889
				);
890
				return new DataResponse(['groups' => $groups]);
891
			} else {
892
				// Not permitted
893
				throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
894
			}
895
		}
896
	}
897
898
	/**
899
	 * @PasswordConfirmationRequired
900
	 * @NoAdminRequired
901
	 *
902
	 * @param string $userId
903
	 * @param string $groupid
904
	 * @return DataResponse
905
	 * @throws OCSException
906
	 */
907
	public function addToGroup(string $userId, string $groupid = ''): DataResponse {
908
		if ($groupid === '') {
909
			throw new OCSException('', 101);
910
		}
911
912
		$group = $this->groupManager->get($groupid);
913
		$targetUser = $this->userManager->get($userId);
914
		if ($group === null) {
915
			throw new OCSException('', 102);
916
		}
917
		if ($targetUser === null) {
918
			throw new OCSException('', 103);
919
		}
920
921
		// If they're not an admin, check they are a subadmin of the group in question
922
		$loggedInUser = $this->userSession->getUser();
923
		$subAdminManager = $this->groupManager->getSubAdmin();
924
		if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
925
			throw new OCSException('', 104);
926
		}
927
928
		// Add user to group
929
		$group->addUser($targetUser);
930
		return new DataResponse();
931
	}
932
933
	/**
934
	 * @PasswordConfirmationRequired
935
	 * @NoAdminRequired
936
	 *
937
	 * @param string $userId
938
	 * @param string $groupid
939
	 * @return DataResponse
940
	 * @throws OCSException
941
	 */
942
	public function removeFromGroup(string $userId, string $groupid): DataResponse {
943
		$loggedInUser = $this->userSession->getUser();
944
945
		if ($groupid === null || trim($groupid) === '') {
946
			throw new OCSException('', 101);
947
		}
948
949
		$group = $this->groupManager->get($groupid);
950
		if ($group === null) {
951
			throw new OCSException('', 102);
952
		}
953
954
		$targetUser = $this->userManager->get($userId);
955
		if ($targetUser === null) {
956
			throw new OCSException('', 103);
957
		}
958
959
		// If they're not an admin, check they are a subadmin of the group in question
960
		$subAdminManager = $this->groupManager->getSubAdmin();
961
		if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
962
			throw new OCSException('', 104);
963
		}
964
965
		// Check they aren't removing themselves from 'admin' or their 'subadmin; group
966
		if ($targetUser->getUID() === $loggedInUser->getUID()) {
967
			if ($this->groupManager->isAdmin($loggedInUser->getUID())) {
968
				if ($group->getGID() === 'admin') {
969
					throw new OCSException('Cannot remove yourself from the admin group', 105);
970
				}
971
			} else {
972
				// Not an admin, so the user must be a subadmin of this group, but that is not allowed.
973
				throw new OCSException('Cannot remove yourself from this group as you are a SubAdmin', 105);
974
			}
975
		} elseif (!$this->groupManager->isAdmin($loggedInUser->getUID())) {
976
			/** @var IGroup[] $subAdminGroups */
977
			$subAdminGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
978
			$subAdminGroups = array_map(function (IGroup $subAdminGroup) {
979
				return $subAdminGroup->getGID();
980
			}, $subAdminGroups);
981
			$userGroups = $this->groupManager->getUserGroupIds($targetUser);
982
			$userSubAdminGroups = array_intersect($subAdminGroups, $userGroups);
983
984
			if (count($userSubAdminGroups) <= 1) {
985
				// Subadmin must not be able to remove a user from all their subadmin groups.
986
				throw new OCSException('Not viable to remove user from the last group you are SubAdmin of', 105);
987
			}
988
		}
989
990
		// Remove user from group
991
		$group->removeUser($targetUser);
992
		return new DataResponse();
993
	}
994
995
	/**
996
	 * Creates a subadmin
997
	 *
998
	 * @PasswordConfirmationRequired
999
	 *
1000
	 * @param string $userId
1001
	 * @param string $groupid
1002
	 * @return DataResponse
1003
	 * @throws OCSException
1004
	 */
1005
	public function addSubAdmin(string $userId, string $groupid): DataResponse {
1006
		$group = $this->groupManager->get($groupid);
1007
		$user = $this->userManager->get($userId);
1008
1009
		// Check if the user exists
1010
		if ($user === null) {
1011
			throw new OCSException('User does not exist', 101);
1012
		}
1013
		// Check if group exists
1014
		if ($group === null) {
1015
			throw new OCSException('Group does not exist',  102);
1016
		}
1017
		// Check if trying to make subadmin of admin group
1018
		if ($group->getGID() === 'admin') {
1019
			throw new OCSException('Cannot create subadmins for admin group', 103);
1020
		}
1021
1022
		$subAdminManager = $this->groupManager->getSubAdmin();
1023
1024
		// We cannot be subadmin twice
1025
		if ($subAdminManager->isSubAdminOfGroup($user, $group)) {
1026
			return new DataResponse();
1027
		}
1028
		// Go
1029
		$subAdminManager->createSubAdmin($user, $group);
1030
		return new DataResponse();
1031
	}
1032
1033
	/**
1034
	 * Removes a subadmin from a group
1035
	 *
1036
	 * @PasswordConfirmationRequired
1037
	 *
1038
	 * @param string $userId
1039
	 * @param string $groupid
1040
	 * @return DataResponse
1041
	 * @throws OCSException
1042
	 */
1043
	public function removeSubAdmin(string $userId, string $groupid): DataResponse {
1044
		$group = $this->groupManager->get($groupid);
1045
		$user = $this->userManager->get($userId);
1046
		$subAdminManager = $this->groupManager->getSubAdmin();
1047
1048
		// Check if the user exists
1049
		if ($user === null) {
1050
			throw new OCSException('User does not exist', 101);
1051
		}
1052
		// Check if the group exists
1053
		if ($group === null) {
1054
			throw new OCSException('Group does not exist', 101);
1055
		}
1056
		// Check if they are a subadmin of this said group
1057
		if (!$subAdminManager->isSubAdminOfGroup($user, $group)) {
1058
			throw new OCSException('User is not a subadmin of this group', 102);
1059
		}
1060
1061
		// Go
1062
		$subAdminManager->deleteSubAdmin($user, $group);
1063
		return new DataResponse();
1064
	}
1065
1066
	/**
1067
	 * Get the groups a user is a subadmin of
1068
	 *
1069
	 * @param string $userId
1070
	 * @return DataResponse
1071
	 * @throws OCSException
1072
	 */
1073
	public function getUserSubAdminGroups(string $userId): DataResponse {
1074
		$groups = $this->getUserSubAdminGroupsData($userId);
1075
		return new DataResponse($groups);
1076
	}
1077
1078
	/**
1079
	 * @NoAdminRequired
1080
	 * @PasswordConfirmationRequired
1081
	 *
1082
	 * resend welcome message
1083
	 *
1084
	 * @param string $userId
1085
	 * @return DataResponse
1086
	 * @throws OCSException
1087
	 */
1088
	public function resendWelcomeMessage(string $userId): DataResponse {
1089
		$currentLoggedInUser = $this->userSession->getUser();
1090
1091
		$targetUser = $this->userManager->get($userId);
1092
		if ($targetUser === null) {
1093
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1094
		}
1095
1096
		// Check if admin / subadmin
1097
		$subAdminManager = $this->groupManager->getSubAdmin();
1098
		if (!$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
1099
			&& !$this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
1100
			// No rights
1101
			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
1102
		}
1103
1104
		$email = $targetUser->getEMailAddress();
1105
		if ($email === '' || $email === null) {
1106
			throw new OCSException('Email address not available', 101);
1107
		}
1108
1109
		try {
1110
			$emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false);
1111
			$this->newUserMailHelper->sendMail($targetUser, $emailTemplate);
1112
		} catch (\Exception $e) {
1113
			$this->logger->error("Can't send new user mail to $email",
1114
				[
1115
					'app' => 'settings',
1116
					'exception' => $e,
1117
				]
1118
			);
1119
			throw new OCSException('Sending email failed', 102);
1120
		}
1121
1122
		return new DataResponse();
1123
	}
1124
}
1125