Passed
Push — master ( d93b00...b971f6 )
by Morris
13:15 queued 11s
created

UsersController::getEditableFields()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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