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

UsersController::searchByPhoneNumbers()   F

Complexity

Conditions 18
Paths 353

Size

Total Lines 71
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 18
eloc 39
c 3
b 0
f 0
nc 353
nop 2
dl 0
loc 71
rs 2.1208

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
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