Passed
Push — master ( d93b00...b971f6 )
by Morris
13:15 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\Accounts\AccountManager;
50
use OC\Authentication\Token\RemoteWipe;
51
use OC\HintException;
52
use OC\KnownUser\KnownUserService;
53
use OC\User\Backend;
54
use OCA\Settings\Mailer\NewUserMailHelper;
55
use OCP\Accounts\IAccountManager;
56
use OCP\App\IAppManager;
57
use OCP\AppFramework\Http;
58
use OCP\AppFramework\Http\DataResponse;
59
use OCP\AppFramework\OCS\OCSException;
60
use OCP\AppFramework\OCS\OCSForbiddenException;
61
use OCP\AppFramework\OCSController;
62
use OCP\IConfig;
63
use OCP\IGroup;
64
use OCP\IGroupManager;
65
use OCP\IRequest;
66
use OCP\IURLGenerator;
67
use OCP\IUser;
68
use OCP\IUserManager;
69
use OCP\IUserSession;
70
use OCP\L10N\IFactory;
71
use OCP\Security\ISecureRandom;
72
use OCP\Security\Events\GenerateSecurePasswordEvent;
73
use OCP\EventDispatcher\IEventDispatcher;
74
use OCP\User\Backend\ISetDisplayNameBackend;
75
use Psr\Log\LoggerInterface;
76
77
class UsersController extends AUserData {
78
79
	/** @var IAppManager */
80
	private $appManager;
81
	/** @var IURLGenerator */
82
	protected $urlGenerator;
83
	/** @var LoggerInterface */
84
	private $logger;
85
	/** @var IFactory */
86
	protected $l10nFactory;
87
	/** @var NewUserMailHelper */
88
	private $newUserMailHelper;
89
	/** @var ISecureRandom */
90
	private $secureRandom;
91
	/** @var RemoteWipe */
92
	private $remoteWipe;
93
	/** @var KnownUserService */
94
	private $knownUserService;
95
	/** @var IEventDispatcher */
96
	private $eventDispatcher;
97
98
	public function __construct(string $appName,
99
								IRequest $request,
100
								IUserManager $userManager,
101
								IConfig $config,
102
								IAppManager $appManager,
103
								IGroupManager $groupManager,
104
								IUserSession $userSession,
105
								AccountManager $accountManager,
106
								IURLGenerator $urlGenerator,
107
								LoggerInterface $logger,
108
								IFactory $l10nFactory,
109
								NewUserMailHelper $newUserMailHelper,
110
								ISecureRandom $secureRandom,
111
								RemoteWipe $remoteWipe,
112
								KnownUserService $knownUserService,
113
								IEventDispatcher $eventDispatcher) {
114
		parent::__construct($appName,
115
							$request,
116
							$userManager,
117
							$config,
118
							$groupManager,
119
							$userSession,
120
							$accountManager,
121
							$l10nFactory);
122
123
		$this->appManager = $appManager;
124
		$this->urlGenerator = $urlGenerator;
125
		$this->logger = $logger;
126
		$this->l10nFactory = $l10nFactory;
127
		$this->newUserMailHelper = $newUserMailHelper;
128
		$this->secureRandom = $secureRandom;
129
		$this->remoteWipe = $remoteWipe;
130
		$this->knownUserService = $knownUserService;
131
		$this->eventDispatcher = $eventDispatcher;
132
	}
133
134
	/**
135
	 * @NoAdminRequired
136
	 *
137
	 * returns a list of users
138
	 *
139
	 * @param string $search
140
	 * @param int $limit
141
	 * @param int $offset
142
	 * @return DataResponse
143
	 */
144
	public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
145
		$user = $this->userSession->getUser();
146
		$users = [];
147
148
		// Admin? Or SubAdmin?
149
		$uid = $user->getUID();
150
		$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

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

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