Passed
Push — master ( e31290...b1610e )
by Joas
13:57 queued 11s
created

UsersController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 34
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 17
c 0
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\Authentication\Token\RemoteWipe;
50
use OC\HintException;
51
use OC\KnownUser\KnownUserService;
52
use OC\User\Backend;
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 OCP\User\Backend\ISetDisplayNameBackend;
74
use Psr\Log\LoggerInterface;
75
76
class UsersController extends AUserData {
77
78
	/** @var IAppManager */
79
	private $appManager;
80
	/** @var IURLGenerator */
81
	protected $urlGenerator;
82
	/** @var LoggerInterface */
83
	private $logger;
84
	/** @var IFactory */
85
	protected $l10nFactory;
86
	/** @var NewUserMailHelper */
87
	private $newUserMailHelper;
88
	/** @var ISecureRandom */
89
	private $secureRandom;
90
	/** @var RemoteWipe */
91
	private $remoteWipe;
92
	/** @var KnownUserService */
93
	private $knownUserService;
94
	/** @var IEventDispatcher */
95
	private $eventDispatcher;
96
97
	public function __construct(string $appName,
98
								IRequest $request,
99
								IUserManager $userManager,
100
								IConfig $config,
101
								IAppManager $appManager,
102
								IGroupManager $groupManager,
103
								IUserSession $userSession,
104
								IAccountManager $accountManager,
105
								IURLGenerator $urlGenerator,
106
								LoggerInterface $logger,
107
								IFactory $l10nFactory,
108
								NewUserMailHelper $newUserMailHelper,
109
								ISecureRandom $secureRandom,
110
								RemoteWipe $remoteWipe,
111
								KnownUserService $knownUserService,
112
								IEventDispatcher $eventDispatcher) {
113
		parent::__construct($appName,
114
							$request,
115
							$userManager,
116
							$config,
117
							$groupManager,
118
							$userSession,
119
							$accountManager,
120
							$l10nFactory);
121
122
		$this->appManager = $appManager;
123
		$this->urlGenerator = $urlGenerator;
124
		$this->logger = $logger;
125
		$this->l10nFactory = $l10nFactory;
126
		$this->newUserMailHelper = $newUserMailHelper;
127
		$this->secureRandom = $secureRandom;
128
		$this->remoteWipe = $remoteWipe;
129
		$this->knownUserService = $knownUserService;
130
		$this->eventDispatcher = $eventDispatcher;
131
	}
132
133
	/**
134
	 * @NoAdminRequired
135
	 *
136
	 * returns a list of users
137
	 *
138
	 * @param string $search
139
	 * @param int $limit
140
	 * @param int $offset
141
	 * @return DataResponse
142
	 */
143
	public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
144
		$user = $this->userSession->getUser();
145
		$users = [];
146
147
		// Admin? Or SubAdmin?
148
		$uid = $user->getUID();
149
		$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

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

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