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

UsersController::getEditableFieldsForUser()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 24
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 38
rs 8.0555
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author Bjoern Schiessle <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Daniel Calviño Sánchez <[email protected]>
12
 * @author Daniel Kesselberg <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author John Molakvoæ (skjnldsv) <[email protected]>
15
 * @author Julius Härtl <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author michag86 <[email protected]>
18
 * @author Mikael Hammarin <[email protected]>
19
 * @author Morris Jobke <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Sujith Haridasan <[email protected]>
23
 * @author Thomas Citharel <[email protected]>
24
 * @author Thomas Müller <[email protected]>
25
 * @author Tom Needham <[email protected]>
26
 *
27
 * @license AGPL-3.0
28
 *
29
 * This code is free software: you can redistribute it and/or modify
30
 * it under the terms of the GNU Affero General Public License, version 3,
31
 * as published by the Free Software Foundation.
32
 *
33
 * This program is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
 * GNU Affero General Public License for more details.
37
 *
38
 * You should have received a copy of the GNU Affero General Public License, version 3,
39
 * along with this program. If not, see <http://www.gnu.org/licenses/>
40
 *
41
 */
42
43
namespace OCA\Provisioning_API\Controller;
44
45
use libphonenumber\NumberParseException;
46
use libphonenumber\PhoneNumber;
47
use libphonenumber\PhoneNumberFormat;
48
use libphonenumber\PhoneNumberUtil;
49
use OC\Authentication\Token\RemoteWipe;
50
use OC\HintException;
51
use OC\KnownUser\KnownUserService;
52
use OC\User\Backend;
53
use OCA\Settings\Mailer\NewUserMailHelper;
54
use OCP\Accounts\IAccountManager;
55
use OCP\App\IAppManager;
56
use OCP\AppFramework\Http;
57
use OCP\AppFramework\Http\DataResponse;
58
use OCP\AppFramework\OCS\OCSException;
59
use OCP\AppFramework\OCS\OCSForbiddenException;
60
use OCP\AppFramework\OCSController;
61
use OCP\IConfig;
62
use OCP\IGroup;
63
use OCP\IGroupManager;
64
use OCP\IRequest;
65
use OCP\IURLGenerator;
66
use OCP\IUser;
67
use OCP\IUserManager;
68
use OCP\IUserSession;
69
use OCP\L10N\IFactory;
70
use OCP\Security\ISecureRandom;
71
use OCP\Security\Events\GenerateSecurePasswordEvent;
72
use OCP\EventDispatcher\IEventDispatcher;
73
use OCP\User\Backend\ISetDisplayNameBackend;
74
use Psr\Log\LoggerInterface;
75
76
class UsersController extends AUserData {
77
78
	/** @var IAppManager */
79
	private $appManager;
80
	/** @var IURLGenerator */
81
	protected $urlGenerator;
82
	/** @var LoggerInterface */
83
	private $logger;
84
	/** @var IFactory */
85
	protected $l10nFactory;
86
	/** @var NewUserMailHelper */
87
	private $newUserMailHelper;
88
	/** @var ISecureRandom */
89
	private $secureRandom;
90
	/** @var RemoteWipe */
91
	private $remoteWipe;
92
	/** @var KnownUserService */
93
	private $knownUserService;
94
	/** @var IEventDispatcher */
95
	private $eventDispatcher;
96
97
	public function __construct(string $appName,
98
								IRequest $request,
99
								IUserManager $userManager,
100
								IConfig $config,
101
								IAppManager $appManager,
102
								IGroupManager $groupManager,
103
								IUserSession $userSession,
104
								IAccountManager $accountManager,
105
								IURLGenerator $urlGenerator,
106
								LoggerInterface $logger,
107
								IFactory $l10nFactory,
108
								NewUserMailHelper $newUserMailHelper,
109
								ISecureRandom $secureRandom,
110
								RemoteWipe $remoteWipe,
111
								KnownUserService $knownUserService,
112
								IEventDispatcher $eventDispatcher) {
113
		parent::__construct($appName,
114
							$request,
115
							$userManager,
116
							$config,
117
							$groupManager,
118
							$userSession,
119
							$accountManager,
120
							$l10nFactory);
121
122
		$this->appManager = $appManager;
123
		$this->urlGenerator = $urlGenerator;
124
		$this->logger = $logger;
125
		$this->l10nFactory = $l10nFactory;
126
		$this->newUserMailHelper = $newUserMailHelper;
127
		$this->secureRandom = $secureRandom;
128
		$this->remoteWipe = $remoteWipe;
129
		$this->knownUserService = $knownUserService;
130
		$this->eventDispatcher = $eventDispatcher;
131
	}
132
133
	/**
134
	 * @NoAdminRequired
135
	 *
136
	 * returns a list of users
137
	 *
138
	 * @param string $search
139
	 * @param int $limit
140
	 * @param int $offset
141
	 * @return DataResponse
142
	 */
143
	public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
144
		$user = $this->userSession->getUser();
145
		$users = [];
146
147
		// Admin? Or SubAdmin?
148
		$uid = $user->getUID();
149
		$subAdminManager = $this->groupManager->getSubAdmin();
0 ignored issues
show
Bug introduced by
The method getSubAdmin() does not exist on OCP\IGroupManager. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\IGroupManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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