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