UsersController::searchByPhoneNumbers()   F
last analyzed

Complexity

Conditions 16
Paths 353

Size

Total Lines 71
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 39
nc 353
nop 2
dl 0
loc 71
rs 2.8208
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
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æ <[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
 * @author Vincent Petry <[email protected]>
27
 * @author Kate Döen <[email protected]>
28
 *
29
 * @license AGPL-3.0
30
 *
31
 * This code is free software: you can redistribute it and/or modify
32
 * it under the terms of the GNU Affero General Public License, version 3,
33
 * as published by the Free Software Foundation.
34
 *
35
 * This program is distributed in the hope that it will be useful,
36
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38
 * GNU Affero General Public License for more details.
39
 *
40
 * You should have received a copy of the GNU Affero General Public License, version 3,
41
 * along with this program. If not, see <http://www.gnu.org/licenses/>
42
 *
43
 */
44
45
namespace OCA\Provisioning_API\Controller;
46
47
use InvalidArgumentException;
48
use libphonenumber\NumberParseException;
49
use libphonenumber\PhoneNumberFormat;
50
use libphonenumber\PhoneNumberUtil;
51
use OC\Authentication\Token\RemoteWipe;
52
use OC\KnownUser\KnownUserService;
53
use OC\User\Backend;
54
use OCA\Settings\Mailer\NewUserMailHelper;
55
use OCP\Accounts\IAccountManager;
56
use OCP\Accounts\IAccountProperty;
57
use OCP\Accounts\PropertyDoesNotExistException;
58
use OCP\AppFramework\Http;
59
use OCP\AppFramework\Http\DataResponse;
60
use OCP\AppFramework\OCS\OCSException;
61
use OCP\AppFramework\OCS\OCSForbiddenException;
62
use OCP\AppFramework\OCSController;
63
use OCP\EventDispatcher\IEventDispatcher;
64
use OCP\HintException;
65
use OCP\IConfig;
66
use OCP\IGroup;
67
use OCP\IGroupManager;
68
use OCP\IRequest;
69
use OCP\IURLGenerator;
70
use OCP\IUser;
71
use OCP\IUserManager;
72
use OCP\IUserSession;
73
use OCP\L10N\IFactory;
74
use OCP\Security\Events\GenerateSecurePasswordEvent;
75
use OCP\Security\ISecureRandom;
76
use OCP\User\Backend\ISetDisplayNameBackend;
77
use Psr\Log\LoggerInterface;
78
79
class UsersController extends AUserData {
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(
98
		string $appName,
99
		IRequest $request,
100
		IUserManager $userManager,
101
		IConfig $config,
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
	) {
114
		parent::__construct(
115
			$appName,
116
			$request,
117
			$userManager,
118
			$config,
119
			$groupManager,
120
			$userSession,
121
			$accountManager,
122
			$l10nFactory
123
		);
124
125
		$this->urlGenerator = $urlGenerator;
126
		$this->logger = $logger;
127
		$this->l10nFactory = $l10nFactory;
128
		$this->newUserMailHelper = $newUserMailHelper;
129
		$this->secureRandom = $secureRandom;
130
		$this->remoteWipe = $remoteWipe;
131
		$this->knownUserService = $knownUserService;
132
		$this->eventDispatcher = $eventDispatcher;
133
	}
134
135
	/**
136
	 * @NoAdminRequired
137
	 *
138
	 * returns a list of users
139
	 *
140
	 * @param string $search
141
	 * @param int $limit
142
	 * @param int $offset
143
	 * @return DataResponse
144
	 */
145
	public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
146
		$user = $this->userSession->getUser();
147
		$users = [];
148
149
		// Admin? Or SubAdmin?
150
		$uid = $user->getUID();
151
		$subAdminManager = $this->groupManager->getSubAdmin();
152
		if ($this->groupManager->isAdmin($uid)) {
153
			$users = $this->userManager->search($search, $limit, $offset);
154
		} elseif ($subAdminManager->isSubAdmin($user)) {
155
			$subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
156
			foreach ($subAdminOfGroups as $key => $group) {
157
				$subAdminOfGroups[$key] = $group->getGID();
158
			}
159
160
			$users = [];
161
			foreach ($subAdminOfGroups as $group) {
162
				$users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
163
			}
164
		}
165
166
		$users = array_keys($users);
167
168
		return new DataResponse([
169
			'users' => $users
170
		]);
171
	}
172
173
	/**
174
	 * @NoAdminRequired
175
	 *
176
	 * returns a list of users and their data
177
	 */
178
	public function getUsersDetails(string $search = '', int $limit = null, int $offset = 0): DataResponse {
179
		$currentUser = $this->userSession->getUser();
180
		$users = [];
181
182
		// Admin? Or SubAdmin?
183
		$uid = $currentUser->getUID();
184
		$subAdminManager = $this->groupManager->getSubAdmin();
185
		if ($this->groupManager->isAdmin($uid)) {
186
			$users = $this->userManager->search($search, $limit, $offset);
187
			$users = array_keys($users);
188
		} elseif ($subAdminManager->isSubAdmin($currentUser)) {
189
			$subAdminOfGroups = $subAdminManager->getSubAdminsGroups($currentUser);
190
			foreach ($subAdminOfGroups as $key => $group) {
191
				$subAdminOfGroups[$key] = $group->getGID();
192
			}
193
194
			$users = [];
195
			foreach ($subAdminOfGroups as $group) {
196
				$users[] = array_keys($this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
197
			}
198
			$users = array_merge(...$users);
199
		}
200
201
		$usersDetails = [];
202
		foreach ($users as $userId) {
203
			$userId = (string) $userId;
204
			$userData = $this->getUserData($userId);
205
			// Do not insert empty entry
206
			if (!empty($userData)) {
207
				$usersDetails[$userId] = $userData;
208
			} else {
209
				// Logged user does not have permissions to see this user
210
				// only showing its id
211
				$usersDetails[$userId] = ['id' => $userId];
212
			}
213
		}
214
215
		return new DataResponse([
216
			'users' => $usersDetails
217
		]);
218
	}
219
220
221
	/**
222
	 * @NoAdminRequired
223
	 * @NoSubAdminRequired
224
	 *
225
	 * @param string $location
226
	 * @param array $search
227
	 * @return DataResponse
228
	 */
229
	public function searchByPhoneNumbers(string $location, array $search): DataResponse {
230
		$phoneUtil = PhoneNumberUtil::getInstance();
231
232
		if ($phoneUtil->getCountryCodeForRegion($location) === 0) {
233
			// Not a valid region code
234
			return new DataResponse([], Http::STATUS_BAD_REQUEST);
235
		}
236
237
		/** @var IUser $user */
238
		$user = $this->userSession->getUser();
239
		$knownTo = $user->getUID();
240
		$defaultPhoneRegion = $this->config->getSystemValueString('default_phone_region');
241
242
		$normalizedNumberToKey = [];
243
		foreach ($search as $key => $phoneNumbers) {
244
			foreach ($phoneNumbers as $phone) {
245
				try {
246
					$phoneNumber = $phoneUtil->parse($phone, $location);
247
					if ($phoneUtil->isValidNumber($phoneNumber)) {
248
						$normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
249
						$normalizedNumberToKey[$normalizedNumber] = (string) $key;
250
					}
251
				} catch (NumberParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
252
				}
253
254
				if ($defaultPhoneRegion !== '' && $defaultPhoneRegion !== $location && strpos($phone, '0') === 0) {
255
					// If the number has a leading zero (no country code),
256
					// we also check the default phone region of the instance,
257
					// when it's different to the user's given region.
258
					try {
259
						$phoneNumber = $phoneUtil->parse($phone, $defaultPhoneRegion);
260
						if ($phoneUtil->isValidNumber($phoneNumber)) {
261
							$normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
262
							$normalizedNumberToKey[$normalizedNumber] = (string) $key;
263
						}
264
					} catch (NumberParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
265
					}
266
				}
267
			}
268
		}
269
270
		$phoneNumbers = array_keys($normalizedNumberToKey);
271
272
		if (empty($phoneNumbers)) {
273
			return new DataResponse();
274
		}
275
276
		// Cleanup all previous entries and only allow new matches
277
		$this->knownUserService->deleteKnownTo($knownTo);
278
279
		$userMatches = $this->accountManager->searchUsers(IAccountManager::PROPERTY_PHONE, $phoneNumbers);
280
281
		if (empty($userMatches)) {
282
			return new DataResponse();
283
		}
284
285
		$cloudUrl = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
286
		if (strpos($cloudUrl, 'http://') === 0) {
287
			$cloudUrl = substr($cloudUrl, strlen('http://'));
288
		} elseif (strpos($cloudUrl, 'https://') === 0) {
289
			$cloudUrl = substr($cloudUrl, strlen('https://'));
290
		}
291
292
		$matches = [];
293
		foreach ($userMatches as $phone => $userId) {
294
			// Not using the ICloudIdManager as that would run a search for each contact to find the display name in the address book
295
			$matches[$normalizedNumberToKey[$phone]] = $userId . '@' . $cloudUrl;
296
			$this->knownUserService->storeIsKnownToUser($knownTo, $userId);
297
		}
298
299
		return new DataResponse($matches);
300
	}
301
302
	/**
303
	 * @throws OCSException
304
	 */
305
	private function createNewUserId(): string {
306
		$attempts = 0;
307
		do {
308
			$uidCandidate = $this->secureRandom->generate(10, ISecureRandom::CHAR_HUMAN_READABLE);
309
			if (!$this->userManager->userExists($uidCandidate)) {
310
				return $uidCandidate;
311
			}
312
			$attempts++;
313
		} while ($attempts < 10);
314
		throw new OCSException('Could not create non-existing user id', 111);
315
	}
316
317
	/**
318
	 * @PasswordConfirmationRequired
319
	 * @NoAdminRequired
320
	 *
321
	 * @param string $userid
322
	 * @param string $password
323
	 * @param string $displayName
324
	 * @param string $email
325
	 * @param array $groups
326
	 * @param array $subadmin
327
	 * @param string $quota
328
	 * @param string $language
329
	 * @return DataResponse
330
	 * @throws OCSException
331
	 */
332
	public function addUser(
333
		string $userid,
334
		string $password = '',
335
		string $displayName = '',
336
		string $email = '',
337
		array $groups = [],
338
		array $subadmin = [],
339
		string $quota = '',
340
		string $language = '',
341
		?string $manager = null,
342
	): DataResponse {
343
		$user = $this->userSession->getUser();
344
		$isAdmin = $this->groupManager->isAdmin($user->getUID());
345
		$subAdminManager = $this->groupManager->getSubAdmin();
346
347
		if (empty($userid) && $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes') {
348
			$userid = $this->createNewUserId();
349
		}
350
351
		if ($this->userManager->userExists($userid)) {
352
			$this->logger->error('Failed addUser attempt: User already exists.', ['app' => 'ocs_api']);
353
			throw new OCSException($this->l10nFactory->get('provisioning_api')->t('User already exists'), 102);
354
		}
355
356
		if ($groups !== []) {
357
			foreach ($groups as $group) {
358
				if (!$this->groupManager->groupExists($group)) {
359
					throw new OCSException('group ' . $group . ' does not exist', 104);
360
				}
361
				if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) {
362
					throw new OCSException('insufficient privileges for group ' . $group, 105);
363
				}
364
			}
365
		} else {
366
			if (!$isAdmin) {
367
				throw new OCSException('no group specified (required for subadmins)', 106);
368
			}
369
		}
370
371
		$subadminGroups = [];
372
		if ($subadmin !== []) {
373
			foreach ($subadmin as $groupid) {
374
				$group = $this->groupManager->get($groupid);
375
				// Check if group exists
376
				if ($group === null) {
377
					throw new OCSException('Subadmin group does not exist', 102);
378
				}
379
				// Check if trying to make subadmin of admin group
380
				if ($group->getGID() === 'admin') {
381
					throw new OCSException('Cannot create subadmins for admin group', 103);
382
				}
383
				// Check if has permission to promote subadmins
384
				if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin) {
385
					throw new OCSForbiddenException('No permissions to promote subadmins');
386
				}
387
				$subadminGroups[] = $group;
388
			}
389
		}
390
391
		$generatePasswordResetToken = false;
392
		if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
393
			throw new OCSException('Invalid password value', 101);
394
		}
395
		if ($password === '') {
396
			if ($email === '') {
397
				throw new OCSException('To send a password link to the user an email address is required.', 108);
398
			}
399
400
			$passwordEvent = new GenerateSecurePasswordEvent();
401
			$this->eventDispatcher->dispatchTyped($passwordEvent);
402
403
			$password = $passwordEvent->getPassword();
404
			if ($password === null) {
405
				// Fallback: ensure to pass password_policy in any case
406
				$password = $this->secureRandom->generate(10)
407
					. $this->secureRandom->generate(1, ISecureRandom::CHAR_UPPER)
408
					. $this->secureRandom->generate(1, ISecureRandom::CHAR_LOWER)
409
					. $this->secureRandom->generate(1, ISecureRandom::CHAR_DIGITS)
410
					. $this->secureRandom->generate(1, ISecureRandom::CHAR_SYMBOLS);
411
			}
412
			$generatePasswordResetToken = true;
413
		}
414
415
		if ($email === '' && $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes') {
416
			throw new OCSException('Required email address was not provided', 110);
417
		}
418
419
		try {
420
			$newUser = $this->userManager->createUser($userid, $password);
421
			$this->logger->info('Successful addUser call with userid: ' . $userid, ['app' => 'ocs_api']);
422
423
			foreach ($groups as $group) {
424
				$this->groupManager->get($group)->addUser($newUser);
425
				$this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']);
426
			}
427
			foreach ($subadminGroups as $group) {
428
				$subAdminManager->createSubAdmin($newUser, $group);
429
			}
430
431
			if ($displayName !== '') {
432
				try {
433
					$this->editUser($userid, self::USER_FIELD_DISPLAYNAME, $displayName);
434
				} catch (OCSException $e) {
435
					if ($newUser instanceof IUser) {
436
						$newUser->delete();
437
					}
438
					throw $e;
439
				}
440
			}
441
442
			if ($quota !== '') {
443
				$this->editUser($userid, self::USER_FIELD_QUOTA, $quota);
444
			}
445
446
			if ($language !== '') {
447
				$this->editUser($userid, self::USER_FIELD_LANGUAGE, $language);
448
			}
449
450
			/**
451
			 * null -> nothing sent
452
			 * '' -> unset manager
453
			 * else -> set manager
454
			 */
455
			if ($manager !== null) {
456
				$this->editUser($userid, self::USER_FIELD_MANAGER, $manager);
457
			}
458
459
			// Send new user mail only if a mail is set
460
			if ($email !== '') {
461
				$newUser->setEMailAddress($email);
462
				if ($this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes') {
463
					try {
464
						$emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken);
465
						$this->newUserMailHelper->sendMail($newUser, $emailTemplate);
466
					} catch (\Exception $e) {
467
						// Mail could be failing hard or just be plain not configured
468
						// Logging error as it is the hardest of the two
469
						$this->logger->error(
470
							"Unable to send the invitation mail to $email",
471
							[
472
								'app' => 'ocs_api',
473
								'exception' => $e,
474
							]
475
						);
476
					}
477
				}
478
			}
479
480
			return new DataResponse(['id' => $userid]);
481
		} catch (HintException $e) {
482
			$this->logger->warning(
483
				'Failed addUser attempt with hint exception.',
484
				[
485
					'app' => 'ocs_api',
486
					'exception' => $e,
487
				]
488
			);
489
			throw new OCSException($e->getHint(), 107);
490
		} catch (OCSException $e) {
491
			$this->logger->warning(
492
				'Failed addUser attempt with ocs exception.',
493
				[
494
					'app' => 'ocs_api',
495
					'exception' => $e,
496
				]
497
			);
498
			throw $e;
499
		} catch (InvalidArgumentException $e) {
500
			$this->logger->error(
501
				'Failed addUser attempt with invalid argument exception.',
502
				[
503
					'app' => 'ocs_api',
504
					'exception' => $e,
505
				]
506
			);
507
			throw new OCSException($e->getMessage(), 101);
508
		} catch (\Exception $e) {
509
			$this->logger->error(
510
				'Failed addUser attempt with exception.',
511
				[
512
					'app' => 'ocs_api',
513
					'exception' => $e
514
				]
515
			);
516
			throw new OCSException('Bad request', 101);
517
		}
518
	}
519
520
	/**
521
	 * @NoAdminRequired
522
	 * @NoSubAdminRequired
523
	 *
524
	 * gets user info
525
	 *
526
	 * @param string $userId
527
	 * @return DataResponse
528
	 * @throws OCSException
529
	 */
530
	public function getUser(string $userId): DataResponse {
531
		$includeScopes = false;
532
		$currentUser = $this->userSession->getUser();
533
		if ($currentUser && $currentUser->getUID() === $userId) {
534
			$includeScopes = true;
535
		}
536
537
		$data = $this->getUserData($userId, $includeScopes);
538
		// getUserData returns empty array if not enough permissions
539
		if (empty($data)) {
540
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
541
		}
542
		return new DataResponse($data);
543
	}
544
545
	/**
546
	 * @NoAdminRequired
547
	 * @NoSubAdminRequired
548
	 *
549
	 * gets user info from the currently logged in user
550
	 *
551
	 * @return DataResponse
552
	 * @throws OCSException
553
	 */
554
	public function getCurrentUser(): DataResponse {
555
		$user = $this->userSession->getUser();
556
		if ($user) {
557
			$data = $this->getUserData($user->getUID(), true);
558
			return new DataResponse($data);
559
		}
560
561
		throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
562
	}
563
564
	/**
565
	 * @NoAdminRequired
566
	 * @NoSubAdminRequired
567
	 *
568
	 * @return DataResponse
569
	 * @throws OCSException
570
	 */
571
	public function getEditableFields(): DataResponse {
572
		$currentLoggedInUser = $this->userSession->getUser();
573
		if (!$currentLoggedInUser instanceof IUser) {
574
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
575
		}
576
577
		return $this->getEditableFieldsForUser($currentLoggedInUser->getUID());
578
	}
579
580
	/**
581
	 * @NoAdminRequired
582
	 * @NoSubAdminRequired
583
	 *
584
	 * @param string $userId
585
	 * @return DataResponse
586
	 * @throws OCSException
587
	 */
588
	public function getEditableFieldsForUser(string $userId): DataResponse {
589
		$currentLoggedInUser = $this->userSession->getUser();
590
		if (!$currentLoggedInUser instanceof IUser) {
591
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
592
		}
593
594
		$permittedFields = [];
595
596
		if ($userId !== $currentLoggedInUser->getUID()) {
597
			$targetUser = $this->userManager->get($userId);
598
			if (!$targetUser instanceof IUser) {
599
				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
600
			}
601
602
			$subAdminManager = $this->groupManager->getSubAdmin();
603
			if (
604
				!$this->groupManager->isAdmin($currentLoggedInUser->getUID())
605
				&& !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
606
			) {
607
				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
608
			}
609
		} else {
610
			$targetUser = $currentLoggedInUser;
611
		}
612
613
		// Editing self (display, email)
614
		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
615
			if (
616
				$targetUser->getBackend() instanceof ISetDisplayNameBackend
617
				|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
618
			) {
619
				$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
620
			}
621
			$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
622
		}
623
624
		$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
625
		$permittedFields[] = IAccountManager::PROPERTY_PHONE;
626
		$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
627
		$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
628
		$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
629
		$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
630
		$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
631
		$permittedFields[] = IAccountManager::PROPERTY_ROLE;
632
		$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
633
		$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
634
		$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
635
636
		return new DataResponse($permittedFields);
637
	}
638
639
	/**
640
	 * @NoAdminRequired
641
	 * @NoSubAdminRequired
642
	 * @PasswordConfirmationRequired
643
	 * @UserRateThrottle(limit=5, period=60)
644
	 *
645
	 * @throws OCSException
646
	 */
647
	public function editUserMultiValue(
648
		string $userId,
649
		string $collectionName,
650
		string $key,
651
		string $value
652
	): DataResponse {
653
		$currentLoggedInUser = $this->userSession->getUser();
654
		if ($currentLoggedInUser === null) {
655
			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
656
		}
657
658
		$targetUser = $this->userManager->get($userId);
659
		if ($targetUser === null) {
660
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
661
		}
662
663
		$subAdminManager = $this->groupManager->getSubAdmin();
664
		$isAdminOrSubadmin = $this->groupManager->isAdmin($currentLoggedInUser->getUID())
665
			|| $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser);
666
667
		$permittedFields = [];
668
		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
669
			// Editing self (display, email)
670
			$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
671
			$permittedFields[] = IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX;
672
		} else {
673
			// Check if admin / subadmin
674
			if ($isAdminOrSubadmin) {
675
				// They have permissions over the user
676
				$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
677
			} else {
678
				// No rights
679
				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
680
			}
681
		}
682
683
		// Check if permitted to edit this field
684
		if (!in_array($collectionName, $permittedFields)) {
685
			throw new OCSException('', 103);
686
		}
687
688
		switch ($collectionName) {
689
			case IAccountManager::COLLECTION_EMAIL:
690
				$userAccount = $this->accountManager->getAccount($targetUser);
691
				$mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
692
				$mailCollection->removePropertyByValue($key);
693
				if ($value !== '') {
694
					$mailCollection->addPropertyWithDefaults($value);
695
					$property = $mailCollection->getPropertyByValue($key);
696
					if ($isAdminOrSubadmin && $property) {
697
						// admin set mails are auto-verified
698
						$property->setLocallyVerified(IAccountManager::VERIFIED);
699
					}
700
				}
701
				$this->accountManager->updateAccount($userAccount);
702
				break;
703
704
			case IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX:
705
				$userAccount = $this->accountManager->getAccount($targetUser);
706
				$mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
707
				$targetProperty = null;
708
				foreach ($mailCollection->getProperties() as $property) {
709
					if ($property->getValue() === $key) {
710
						$targetProperty = $property;
711
						break;
712
					}
713
				}
714
				if ($targetProperty instanceof IAccountProperty) {
715
					try {
716
						$targetProperty->setScope($value);
717
						$this->accountManager->updateAccount($userAccount);
718
					} catch (InvalidArgumentException $e) {
719
						throw new OCSException('', 102);
720
					}
721
				} else {
722
					throw new OCSException('', 102);
723
				}
724
				break;
725
726
			default:
727
				throw new OCSException('', 103);
728
		}
729
		return new DataResponse();
730
	}
731
732
	/**
733
	 * @NoAdminRequired
734
	 * @NoSubAdminRequired
735
	 * @PasswordConfirmationRequired
736
	 * @UserRateThrottle(limit=50, period=600)
737
	 *
738
	 * edit users
739
	 *
740
	 * @param string $userId
741
	 * @param string $key
742
	 * @param string $value
743
	 * @return DataResponse
744
	 * @throws OCSException
745
	 */
746
	public function editUser(string $userId, string $key, string $value): DataResponse {
747
		$currentLoggedInUser = $this->userSession->getUser();
748
749
		$targetUser = $this->userManager->get($userId);
750
		if ($targetUser === null) {
751
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
752
		}
753
754
		$permittedFields = [];
755
		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
756
			// Editing self (display, email)
757
			if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
758
				if (
759
					$targetUser->getBackend() instanceof ISetDisplayNameBackend
760
					|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
761
				) {
762
					$permittedFields[] = self::USER_FIELD_DISPLAYNAME;
763
					$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
764
				}
765
				$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
766
			}
767
768
			$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX;
769
			$permittedFields[] = IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX;
770
771
			$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
772
773
			$permittedFields[] = self::USER_FIELD_PASSWORD;
774
			$permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
775
			if (
776
				$this->config->getSystemValue('force_language', false) === false ||
777
				$this->groupManager->isAdmin($currentLoggedInUser->getUID())
778
			) {
779
				$permittedFields[] = self::USER_FIELD_LANGUAGE;
780
			}
781
782
			if (
783
				$this->config->getSystemValue('force_locale', false) === false ||
784
				$this->groupManager->isAdmin($currentLoggedInUser->getUID())
785
			) {
786
				$permittedFields[] = self::USER_FIELD_LOCALE;
787
			}
788
789
			$permittedFields[] = IAccountManager::PROPERTY_PHONE;
790
			$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
791
			$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
792
			$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
793
			$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
794
			$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
795
			$permittedFields[] = IAccountManager::PROPERTY_ROLE;
796
			$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
797
			$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
798
			$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
799
			$permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
800
			$permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
801
			$permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
802
			$permittedFields[] = IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX;
803
			$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX;
804
			$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX;
805
			$permittedFields[] = IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX;
806
			$permittedFields[] = IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX;
807
			$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX;
808
			$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX;
809
810
			$permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
811
812
			// If admin they can edit their own quota and manager
813
			if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
814
				$permittedFields[] = self::USER_FIELD_QUOTA;
815
				$permittedFields[] = self::USER_FIELD_MANAGER;
816
817
			}
818
		} else {
819
			// Check if admin / subadmin
820
			$subAdminManager = $this->groupManager->getSubAdmin();
821
			if (
822
				$this->groupManager->isAdmin($currentLoggedInUser->getUID())
823
				|| $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
824
			) {
825
				// They have permissions over the user
826
				if (
827
					$targetUser->getBackend() instanceof ISetDisplayNameBackend
828
					|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
829
				) {
830
					$permittedFields[] = self::USER_FIELD_DISPLAYNAME;
831
					$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
832
				}
833
				$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
834
				$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
835
				$permittedFields[] = self::USER_FIELD_PASSWORD;
836
				$permittedFields[] = self::USER_FIELD_LANGUAGE;
837
				$permittedFields[] = self::USER_FIELD_LOCALE;
838
				$permittedFields[] = IAccountManager::PROPERTY_PHONE;
839
				$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
840
				$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
841
				$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
842
				$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
843
				$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
844
				$permittedFields[] = IAccountManager::PROPERTY_ROLE;
845
				$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
846
				$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
847
				$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
848
				$permittedFields[] = self::USER_FIELD_QUOTA;
849
				$permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
850
				$permittedFields[] = self::USER_FIELD_MANAGER;
851
			} else {
852
				// No rights
853
				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
854
			}
855
		}
856
		// Check if permitted to edit this field
857
		if (!in_array($key, $permittedFields)) {
858
			throw new OCSException('', 103);
859
		}
860
		// Process the edit
861
		switch ($key) {
862
			case self::USER_FIELD_DISPLAYNAME:
863
			case IAccountManager::PROPERTY_DISPLAYNAME:
864
				try {
865
					$targetUser->setDisplayName($value);
866
				} catch (InvalidArgumentException $e) {
867
					throw new OCSException($e->getMessage(), 101);
868
				}
869
				break;
870
			case self::USER_FIELD_QUOTA:
871
				$quota = $value;
872
				if ($quota !== 'none' && $quota !== 'default') {
873
					if (is_numeric($quota)) {
874
						$quota = (float) $quota;
875
					} else {
876
						$quota = \OCP\Util::computerFileSize($quota);
877
					}
878
					if ($quota === false) {
879
						throw new OCSException('Invalid quota value ' . $value, 102);
880
					}
881
					if ($quota === -1) {
882
						$quota = 'none';
883
					} else {
884
						$maxQuota = (int) $this->config->getAppValue('files', 'max_quota', '-1');
885
						if ($maxQuota !== -1 && $quota > $maxQuota) {
886
							throw new OCSException('Invalid quota value. ' . $value . ' is exceeding the maximum quota', 102);
887
						}
888
						$quota = \OCP\Util::humanFileSize($quota);
889
					}
890
				}
891
				// no else block because quota can be set to 'none' in previous if
892
				if ($quota === 'none') {
893
					$allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
894
					if (!$allowUnlimitedQuota) {
895
						throw new OCSException('Unlimited quota is forbidden on this instance', 102);
896
					}
897
				}
898
				$targetUser->setQuota($quota);
899
				break;
900
			case self::USER_FIELD_MANAGER:
901
				$targetUser->setManagerUids([$value]);
902
				break;
903
			case self::USER_FIELD_PASSWORD:
904
				try {
905
					if (strlen($value) > IUserManager::MAX_PASSWORD_LENGTH) {
906
						throw new OCSException('Invalid password value', 102);
907
					}
908
					if (!$targetUser->canChangePassword()) {
909
						throw new OCSException('Setting the password is not supported by the users backend', 103);
910
					}
911
					$targetUser->setPassword($value);
912
				} catch (HintException $e) { // password policy error
913
					throw new OCSException($e->getMessage(), 103);
914
				}
915
				break;
916
			case self::USER_FIELD_LANGUAGE:
917
				$languagesCodes = $this->l10nFactory->findAvailableLanguages();
918
				if (!in_array($value, $languagesCodes, true) && $value !== 'en') {
919
					throw new OCSException('Invalid language', 102);
920
				}
921
				$this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value);
922
				break;
923
			case self::USER_FIELD_LOCALE:
924
				if (!$this->l10nFactory->localeExists($value)) {
925
					throw new OCSException('Invalid locale', 102);
926
				}
927
				$this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value);
928
				break;
929
			case self::USER_FIELD_NOTIFICATION_EMAIL:
930
				$success = false;
931
				if ($value === '' || filter_var($value, FILTER_VALIDATE_EMAIL)) {
932
					try {
933
						$targetUser->setPrimaryEMailAddress($value);
934
						$success = true;
935
					} catch (InvalidArgumentException $e) {
936
						$this->logger->info(
937
							'Cannot set primary email, because provided address is not verified',
938
							[
939
								'app' => 'provisioning_api',
940
								'exception' => $e,
941
							]
942
						);
943
					}
944
				}
945
				if (!$success) {
946
					throw new OCSException('', 102);
947
				}
948
				break;
949
			case IAccountManager::PROPERTY_EMAIL:
950
				if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
951
					$targetUser->setEMailAddress($value);
952
				} else {
953
					throw new OCSException('', 102);
954
				}
955
				break;
956
			case IAccountManager::COLLECTION_EMAIL:
957
				if (filter_var($value, FILTER_VALIDATE_EMAIL) && $value !== $targetUser->getSystemEMailAddress()) {
958
					$userAccount = $this->accountManager->getAccount($targetUser);
959
					$mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
960
961
					if ($mailCollection->getPropertyByValue($value)) {
962
						throw new OCSException('', 102);
963
					}
964
965
					$mailCollection->addPropertyWithDefaults($value);
966
					$this->accountManager->updateAccount($userAccount);
967
				} else {
968
					throw new OCSException('', 102);
969
				}
970
				break;
971
			case IAccountManager::PROPERTY_PHONE:
972
			case IAccountManager::PROPERTY_ADDRESS:
973
			case IAccountManager::PROPERTY_WEBSITE:
974
			case IAccountManager::PROPERTY_TWITTER:
975
			case IAccountManager::PROPERTY_FEDIVERSE:
976
			case IAccountManager::PROPERTY_ORGANISATION:
977
			case IAccountManager::PROPERTY_ROLE:
978
			case IAccountManager::PROPERTY_HEADLINE:
979
			case IAccountManager::PROPERTY_BIOGRAPHY:
980
				$userAccount = $this->accountManager->getAccount($targetUser);
981
				try {
982
					$userProperty = $userAccount->getProperty($key);
983
					if ($userProperty->getValue() !== $value) {
984
						try {
985
							$userProperty->setValue($value);
986
							if ($userProperty->getName() === IAccountManager::PROPERTY_PHONE) {
987
								$this->knownUserService->deleteByContactUserId($targetUser->getUID());
988
							}
989
						} catch (InvalidArgumentException $e) {
990
							throw new OCSException('Invalid ' . $e->getMessage(), 102);
991
						}
992
					}
993
				} catch (PropertyDoesNotExistException $e) {
994
					$userAccount->setProperty($key, $value, IAccountManager::SCOPE_PRIVATE, IAccountManager::NOT_VERIFIED);
995
				}
996
				try {
997
					$this->accountManager->updateAccount($userAccount);
998
				} catch (InvalidArgumentException $e) {
999
					throw new OCSException('Invalid ' . $e->getMessage(), 102);
1000
				}
1001
				break;
1002
			case IAccountManager::PROPERTY_PROFILE_ENABLED:
1003
				$userAccount = $this->accountManager->getAccount($targetUser);
1004
				try {
1005
					$userProperty = $userAccount->getProperty($key);
1006
					if ($userProperty->getValue() !== $value) {
1007
						$userProperty->setValue($value);
1008
					}
1009
				} catch (PropertyDoesNotExistException $e) {
1010
					$userAccount->setProperty($key, $value, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
1011
				}
1012
				$this->accountManager->updateAccount($userAccount);
1013
				break;
1014
			case IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX:
1015
			case IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX:
1016
			case IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX:
1017
			case IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX:
1018
			case IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX:
1019
			case IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX:
1020
			case IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX:
1021
			case IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX:
1022
			case IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX:
1023
			case IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX:
1024
			case IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX:
1025
			case IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX:
1026
			case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX:
1027
				$propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX));
1028
				$userAccount = $this->accountManager->getAccount($targetUser);
1029
				$userProperty = $userAccount->getProperty($propertyName);
1030
				if ($userProperty->getScope() !== $value) {
1031
					try {
1032
						$userProperty->setScope($value);
1033
						$this->accountManager->updateAccount($userAccount);
1034
					} catch (InvalidArgumentException $e) {
1035
						throw new OCSException('Invalid ' . $e->getMessage(), 102);
1036
					}
1037
				}
1038
				break;
1039
			default:
1040
				throw new OCSException('', 103);
1041
		}
1042
		return new DataResponse();
1043
	}
1044
1045
	/**
1046
	 * @PasswordConfirmationRequired
1047
	 * @NoAdminRequired
1048
	 *
1049
	 * @param string $userId
1050
	 *
1051
	 * @return DataResponse
1052
	 *
1053
	 * @throws OCSException
1054
	 */
1055
	public function wipeUserDevices(string $userId): DataResponse {
1056
		/** @var IUser $currentLoggedInUser */
1057
		$currentLoggedInUser = $this->userSession->getUser();
1058
1059
		$targetUser = $this->userManager->get($userId);
1060
1061
		if ($targetUser === null) {
1062
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1063
		}
1064
1065
		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
1066
			throw new OCSException('', 101);
1067
		}
1068
1069
		// If not permitted
1070
		$subAdminManager = $this->groupManager->getSubAdmin();
1071
		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1072
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1073
		}
1074
1075
		$this->remoteWipe->markAllTokensForWipe($targetUser);
1076
1077
		return new DataResponse();
1078
	}
1079
1080
	/**
1081
	 * @PasswordConfirmationRequired
1082
	 * @NoAdminRequired
1083
	 *
1084
	 * @param string $userId
1085
	 * @return DataResponse
1086
	 * @throws OCSException
1087
	 */
1088
	public function deleteUser(string $userId): DataResponse {
1089
		$currentLoggedInUser = $this->userSession->getUser();
1090
1091
		$targetUser = $this->userManager->get($userId);
1092
1093
		if ($targetUser === null) {
1094
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1095
		}
1096
1097
		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
1098
			throw new OCSException('', 101);
1099
		}
1100
1101
		// If not permitted
1102
		$subAdminManager = $this->groupManager->getSubAdmin();
1103
		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1104
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1105
		}
1106
1107
		// Go ahead with the delete
1108
		if ($targetUser->delete()) {
1109
			return new DataResponse();
1110
		} else {
1111
			throw new OCSException('', 101);
1112
		}
1113
	}
1114
1115
	/**
1116
	 * @PasswordConfirmationRequired
1117
	 * @NoAdminRequired
1118
	 *
1119
	 * @param string $userId
1120
	 * @return DataResponse
1121
	 * @throws OCSException
1122
	 * @throws OCSForbiddenException
1123
	 */
1124
	public function disableUser(string $userId): DataResponse {
1125
		return $this->setEnabled($userId, false);
1126
	}
1127
1128
	/**
1129
	 * @PasswordConfirmationRequired
1130
	 * @NoAdminRequired
1131
	 *
1132
	 * @param string $userId
1133
	 * @return DataResponse
1134
	 * @throws OCSException
1135
	 * @throws OCSForbiddenException
1136
	 */
1137
	public function enableUser(string $userId): DataResponse {
1138
		return $this->setEnabled($userId, true);
1139
	}
1140
1141
	/**
1142
	 * @param string $userId
1143
	 * @param bool $value
1144
	 * @return DataResponse
1145
	 * @throws OCSException
1146
	 */
1147
	private function setEnabled(string $userId, bool $value): DataResponse {
1148
		$currentLoggedInUser = $this->userSession->getUser();
1149
1150
		$targetUser = $this->userManager->get($userId);
1151
		if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
1152
			throw new OCSException('', 101);
1153
		}
1154
1155
		// If not permitted
1156
		$subAdminManager = $this->groupManager->getSubAdmin();
1157
		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1158
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1159
		}
1160
1161
		// enable/disable the user now
1162
		$targetUser->setEnabled($value);
1163
		return new DataResponse();
1164
	}
1165
1166
	/**
1167
	 * @NoAdminRequired
1168
	 * @NoSubAdminRequired
1169
	 *
1170
	 * @param string $userId
1171
	 * @return DataResponse
1172
	 * @throws OCSException
1173
	 */
1174
	public function getUsersGroups(string $userId): DataResponse {
1175
		$loggedInUser = $this->userSession->getUser();
1176
1177
		$targetUser = $this->userManager->get($userId);
1178
		if ($targetUser === null) {
1179
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1180
		}
1181
1182
		if ($targetUser->getUID() === $loggedInUser->getUID() || $this->groupManager->isAdmin($loggedInUser->getUID())) {
1183
			// Self lookup or admin lookup
1184
			return new DataResponse([
1185
				'groups' => $this->groupManager->getUserGroupIds($targetUser)
1186
			]);
1187
		} else {
1188
			$subAdminManager = $this->groupManager->getSubAdmin();
1189
1190
			// Looking up someone else
1191
			if ($subAdminManager->isUserAccessible($loggedInUser, $targetUser)) {
1192
				// Return the group that the method caller is subadmin of for the user in question
1193
				/** @var IGroup[] $getSubAdminsGroups */
1194
				$getSubAdminsGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
1195
				foreach ($getSubAdminsGroups as $key => $group) {
1196
					$getSubAdminsGroups[$key] = $group->getGID();
1197
				}
1198
				$groups = array_intersect(
1199
					$getSubAdminsGroups,
1200
					$this->groupManager->getUserGroupIds($targetUser)
1201
				);
1202
				return new DataResponse(['groups' => $groups]);
1203
			} else {
1204
				// Not permitted
1205
				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1206
			}
1207
		}
1208
	}
1209
1210
	/**
1211
	 * @PasswordConfirmationRequired
1212
	 * @NoAdminRequired
1213
	 *
1214
	 * @param string $userId
1215
	 * @param string $groupid
1216
	 * @return DataResponse
1217
	 * @throws OCSException
1218
	 */
1219
	public function addToGroup(string $userId, string $groupid = ''): DataResponse {
1220
		if ($groupid === '') {
1221
			throw new OCSException('', 101);
1222
		}
1223
1224
		$group = $this->groupManager->get($groupid);
1225
		$targetUser = $this->userManager->get($userId);
1226
		if ($group === null) {
1227
			throw new OCSException('', 102);
1228
		}
1229
		if ($targetUser === null) {
1230
			throw new OCSException('', 103);
1231
		}
1232
1233
		// If they're not an admin, check they are a subadmin of the group in question
1234
		$loggedInUser = $this->userSession->getUser();
1235
		$subAdminManager = $this->groupManager->getSubAdmin();
1236
		if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
1237
			throw new OCSException('', 104);
1238
		}
1239
1240
		// Add user to group
1241
		$group->addUser($targetUser);
1242
		return new DataResponse();
1243
	}
1244
1245
	/**
1246
	 * @PasswordConfirmationRequired
1247
	 * @NoAdminRequired
1248
	 *
1249
	 * @param string $userId
1250
	 * @param string $groupid
1251
	 * @return DataResponse
1252
	 * @throws OCSException
1253
	 */
1254
	public function removeFromGroup(string $userId, string $groupid): DataResponse {
1255
		$loggedInUser = $this->userSession->getUser();
1256
1257
		if ($groupid === null || trim($groupid) === '') {
1258
			throw new OCSException('', 101);
1259
		}
1260
1261
		$group = $this->groupManager->get($groupid);
1262
		if ($group === null) {
1263
			throw new OCSException('', 102);
1264
		}
1265
1266
		$targetUser = $this->userManager->get($userId);
1267
		if ($targetUser === null) {
1268
			throw new OCSException('', 103);
1269
		}
1270
1271
		// If they're not an admin, check they are a subadmin of the group in question
1272
		$subAdminManager = $this->groupManager->getSubAdmin();
1273
		if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
1274
			throw new OCSException('', 104);
1275
		}
1276
1277
		// Check they aren't removing themselves from 'admin' or their 'subadmin; group
1278
		if ($targetUser->getUID() === $loggedInUser->getUID()) {
1279
			if ($this->groupManager->isAdmin($loggedInUser->getUID())) {
1280
				if ($group->getGID() === 'admin') {
1281
					throw new OCSException('Cannot remove yourself from the admin group', 105);
1282
				}
1283
			} else {
1284
				// Not an admin, so the user must be a subadmin of this group, but that is not allowed.
1285
				throw new OCSException('Cannot remove yourself from this group as you are a SubAdmin', 105);
1286
			}
1287
		} elseif (!$this->groupManager->isAdmin($loggedInUser->getUID())) {
1288
			/** @var IGroup[] $subAdminGroups */
1289
			$subAdminGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
1290
			$subAdminGroups = array_map(function (IGroup $subAdminGroup) {
1291
				return $subAdminGroup->getGID();
1292
			}, $subAdminGroups);
1293
			$userGroups = $this->groupManager->getUserGroupIds($targetUser);
1294
			$userSubAdminGroups = array_intersect($subAdminGroups, $userGroups);
1295
1296
			if (count($userSubAdminGroups) <= 1) {
1297
				// Subadmin must not be able to remove a user from all their subadmin groups.
1298
				throw new OCSException('Not viable to remove user from the last group you are SubAdmin of', 105);
1299
			}
1300
		}
1301
1302
		// Remove user from group
1303
		$group->removeUser($targetUser);
1304
		return new DataResponse();
1305
	}
1306
1307
	/**
1308
	 * Creates a subadmin
1309
	 *
1310
	 * @PasswordConfirmationRequired
1311
	 *
1312
	 * @param string $userId
1313
	 * @param string $groupid
1314
	 * @return DataResponse
1315
	 * @throws OCSException
1316
	 */
1317
	public function addSubAdmin(string $userId, string $groupid): DataResponse {
1318
		$group = $this->groupManager->get($groupid);
1319
		$user = $this->userManager->get($userId);
1320
1321
		// Check if the user exists
1322
		if ($user === null) {
1323
			throw new OCSException('User does not exist', 101);
1324
		}
1325
		// Check if group exists
1326
		if ($group === null) {
1327
			throw new OCSException('Group does not exist', 102);
1328
		}
1329
		// Check if trying to make subadmin of admin group
1330
		if ($group->getGID() === 'admin') {
1331
			throw new OCSException('Cannot create subadmins for admin group', 103);
1332
		}
1333
1334
		$subAdminManager = $this->groupManager->getSubAdmin();
1335
1336
		// We cannot be subadmin twice
1337
		if ($subAdminManager->isSubAdminOfGroup($user, $group)) {
1338
			return new DataResponse();
1339
		}
1340
		// Go
1341
		$subAdminManager->createSubAdmin($user, $group);
1342
		return new DataResponse();
1343
	}
1344
1345
	/**
1346
	 * Removes a subadmin from a group
1347
	 *
1348
	 * @PasswordConfirmationRequired
1349
	 *
1350
	 * @param string $userId
1351
	 * @param string $groupid
1352
	 * @return DataResponse
1353
	 * @throws OCSException
1354
	 */
1355
	public function removeSubAdmin(string $userId, string $groupid): DataResponse {
1356
		$group = $this->groupManager->get($groupid);
1357
		$user = $this->userManager->get($userId);
1358
		$subAdminManager = $this->groupManager->getSubAdmin();
1359
1360
		// Check if the user exists
1361
		if ($user === null) {
1362
			throw new OCSException('User does not exist', 101);
1363
		}
1364
		// Check if the group exists
1365
		if ($group === null) {
1366
			throw new OCSException('Group does not exist', 101);
1367
		}
1368
		// Check if they are a subadmin of this said group
1369
		if (!$subAdminManager->isSubAdminOfGroup($user, $group)) {
1370
			throw new OCSException('User is not a subadmin of this group', 102);
1371
		}
1372
1373
		// Go
1374
		$subAdminManager->deleteSubAdmin($user, $group);
1375
		return new DataResponse();
1376
	}
1377
1378
	/**
1379
	 * Get the groups a user is a subadmin of
1380
	 *
1381
	 * @param string $userId
1382
	 * @return DataResponse
1383
	 * @throws OCSException
1384
	 */
1385
	public function getUserSubAdminGroups(string $userId): DataResponse {
1386
		$groups = $this->getUserSubAdminGroupsData($userId);
1387
		return new DataResponse($groups);
1388
	}
1389
1390
	/**
1391
	 * @NoAdminRequired
1392
	 * @PasswordConfirmationRequired
1393
	 *
1394
	 * resend welcome message
1395
	 *
1396
	 * @param string $userId
1397
	 * @return DataResponse
1398
	 * @throws OCSException
1399
	 */
1400
	public function resendWelcomeMessage(string $userId): DataResponse {
1401
		$currentLoggedInUser = $this->userSession->getUser();
1402
1403
		$targetUser = $this->userManager->get($userId);
1404
		if ($targetUser === null) {
1405
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1406
		}
1407
1408
		// Check if admin / subadmin
1409
		$subAdminManager = $this->groupManager->getSubAdmin();
1410
		if (
1411
			!$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
1412
			&& !$this->groupManager->isAdmin($currentLoggedInUser->getUID())
1413
		) {
1414
			// No rights
1415
			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1416
		}
1417
1418
		$email = $targetUser->getEMailAddress();
1419
		if ($email === '' || $email === null) {
1420
			throw new OCSException('Email address not available', 101);
1421
		}
1422
1423
		try {
1424
			$emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false);
1425
			$this->newUserMailHelper->sendMail($targetUser, $emailTemplate);
1426
		} catch (\Exception $e) {
1427
			$this->logger->error(
1428
				"Can't send new user mail to $email",
1429
				[
1430
					'app' => 'settings',
1431
					'exception' => $e,
1432
				]
1433
			);
1434
			throw new OCSException('Sending email failed', 102);
1435
		}
1436
1437
		return new DataResponse();
1438
	}
1439
}
1440