Passed
Push — master ( b88864...2db1e7 )
by Blizzz
14:30 queued 14s
created
lib/private/User/Manager.php 1 patch
Indentation   +692 added lines, -692 removed lines patch added patch discarded remove patch
@@ -74,696 +74,696 @@
 block discarded – undo
74 74
  * @package OC\User
75 75
  */
76 76
 class Manager extends PublicEmitter implements IUserManager {
77
-	/**
78
-	 * @var \OCP\UserInterface[] $backends
79
-	 */
80
-	private $backends = [];
81
-
82
-	/**
83
-	 * @var \OC\User\User[] $cachedUsers
84
-	 */
85
-	private $cachedUsers = [];
86
-
87
-	/** @var IConfig */
88
-	private $config;
89
-
90
-	/** @var EventDispatcherInterface */
91
-	private $dispatcher;
92
-
93
-	/** @var ICache */
94
-	private $cache;
95
-
96
-	/** @var IEventDispatcher */
97
-	private $eventDispatcher;
98
-
99
-	private DisplayNameCache $displayNameCache;
100
-
101
-	public function __construct(IConfig $config,
102
-								EventDispatcherInterface $oldDispatcher,
103
-								ICacheFactory $cacheFactory,
104
-								IEventDispatcher $eventDispatcher) {
105
-		$this->config = $config;
106
-		$this->dispatcher = $oldDispatcher;
107
-		$this->cache = $cacheFactory->createDistributed('user_backend_map');
108
-		$cachedUsers = &$this->cachedUsers;
109
-		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
110
-			/** @var \OC\User\User $user */
111
-			unset($cachedUsers[$user->getUID()]);
112
-		});
113
-		$this->eventDispatcher = $eventDispatcher;
114
-		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
115
-	}
116
-
117
-	/**
118
-	 * Get the active backends
119
-	 * @return \OCP\UserInterface[]
120
-	 */
121
-	public function getBackends() {
122
-		return $this->backends;
123
-	}
124
-
125
-	/**
126
-	 * register a user backend
127
-	 *
128
-	 * @param \OCP\UserInterface $backend
129
-	 */
130
-	public function registerBackend($backend) {
131
-		$this->backends[] = $backend;
132
-	}
133
-
134
-	/**
135
-	 * remove a user backend
136
-	 *
137
-	 * @param \OCP\UserInterface $backend
138
-	 */
139
-	public function removeBackend($backend) {
140
-		$this->cachedUsers = [];
141
-		if (($i = array_search($backend, $this->backends)) !== false) {
142
-			unset($this->backends[$i]);
143
-		}
144
-	}
145
-
146
-	/**
147
-	 * remove all user backends
148
-	 */
149
-	public function clearBackends() {
150
-		$this->cachedUsers = [];
151
-		$this->backends = [];
152
-	}
153
-
154
-	/**
155
-	 * get a user by user id
156
-	 *
157
-	 * @param string $uid
158
-	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
159
-	 */
160
-	public function get($uid) {
161
-		if (is_null($uid) || $uid === '' || $uid === false) {
162
-			return null;
163
-		}
164
-		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
165
-			return $this->cachedUsers[$uid];
166
-		}
167
-
168
-		$cachedBackend = $this->cache->get(sha1($uid));
169
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
170
-			// Cache has the info of the user backend already, so ask that one directly
171
-			$backend = $this->backends[$cachedBackend];
172
-			if ($backend->userExists($uid)) {
173
-				return $this->getUserObject($uid, $backend);
174
-			}
175
-		}
176
-
177
-		foreach ($this->backends as $i => $backend) {
178
-			if ($i === $cachedBackend) {
179
-				// Tried that one already
180
-				continue;
181
-			}
182
-
183
-			if ($backend->userExists($uid)) {
184
-				// Hash $uid to ensure that only valid characters are used for the cache key
185
-				$this->cache->set(sha1($uid), $i, 300);
186
-				return $this->getUserObject($uid, $backend);
187
-			}
188
-		}
189
-		return null;
190
-	}
191
-
192
-	public function getDisplayName(string $uid): ?string {
193
-		return $this->displayNameCache->getDisplayName($uid);
194
-	}
195
-
196
-	/**
197
-	 * get or construct the user object
198
-	 *
199
-	 * @param string $uid
200
-	 * @param \OCP\UserInterface $backend
201
-	 * @param bool $cacheUser If false the newly created user object will not be cached
202
-	 * @return \OC\User\User
203
-	 */
204
-	protected function getUserObject($uid, $backend, $cacheUser = true) {
205
-		if ($backend instanceof IGetRealUIDBackend) {
206
-			$uid = $backend->getRealUID($uid);
207
-		}
208
-
209
-		if (isset($this->cachedUsers[$uid])) {
210
-			return $this->cachedUsers[$uid];
211
-		}
212
-
213
-		$user = new User($uid, $backend, $this->dispatcher, $this, $this->config);
214
-		if ($cacheUser) {
215
-			$this->cachedUsers[$uid] = $user;
216
-		}
217
-		return $user;
218
-	}
219
-
220
-	/**
221
-	 * check if a user exists
222
-	 *
223
-	 * @param string $uid
224
-	 * @return bool
225
-	 */
226
-	public function userExists($uid) {
227
-		$user = $this->get($uid);
228
-		return ($user !== null);
229
-	}
230
-
231
-	/**
232
-	 * Check if the password is valid for the user
233
-	 *
234
-	 * @param string $loginName
235
-	 * @param string $password
236
-	 * @return IUser|false the User object on success, false otherwise
237
-	 */
238
-	public function checkPassword($loginName, $password) {
239
-		$result = $this->checkPasswordNoLogging($loginName, $password);
240
-
241
-		if ($result === false) {
242
-			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
243
-		}
244
-
245
-		return $result;
246
-	}
247
-
248
-	/**
249
-	 * Check if the password is valid for the user
250
-	 *
251
-	 * @internal
252
-	 * @param string $loginName
253
-	 * @param string $password
254
-	 * @return IUser|false the User object on success, false otherwise
255
-	 */
256
-	public function checkPasswordNoLogging($loginName, $password) {
257
-		$loginName = str_replace("\0", '', $loginName);
258
-		$password = str_replace("\0", '', $password);
259
-
260
-		$cachedBackend = $this->cache->get($loginName);
261
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
262
-			$backends = [$this->backends[$cachedBackend]];
263
-		} else {
264
-			$backends = $this->backends;
265
-		}
266
-		foreach ($backends as $backend) {
267
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
268
-				/** @var ICheckPasswordBackend $backend */
269
-				$uid = $backend->checkPassword($loginName, $password);
270
-				if ($uid !== false) {
271
-					return $this->getUserObject($uid, $backend);
272
-				}
273
-			}
274
-		}
275
-
276
-		// since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
277
-		// we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
278
-		// to contain urlencoded patterns by "accident".
279
-		$password = urldecode($password);
280
-
281
-		foreach ($backends as $backend) {
282
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
283
-				/** @var ICheckPasswordBackend|UserInterface $backend */
284
-				$uid = $backend->checkPassword($loginName, $password);
285
-				if ($uid !== false) {
286
-					return $this->getUserObject($uid, $backend);
287
-				}
288
-			}
289
-		}
290
-
291
-		return false;
292
-	}
293
-
294
-	/**
295
-	 * search by user id
296
-	 *
297
-	 * @param string $pattern
298
-	 * @param int $limit
299
-	 * @param int $offset
300
-	 * @return \OC\User\User[]
301
-	 */
302
-	public function search($pattern, $limit = null, $offset = null) {
303
-		$users = [];
304
-		foreach ($this->backends as $backend) {
305
-			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
306
-			if (is_array($backendUsers)) {
307
-				foreach ($backendUsers as $uid) {
308
-					$users[$uid] = $this->getUserObject($uid, $backend);
309
-				}
310
-			}
311
-		}
312
-
313
-		uasort($users, function ($a, $b) {
314
-			/**
315
-			 * @var \OC\User\User $a
316
-			 * @var \OC\User\User $b
317
-			 */
318
-			return strcasecmp($a->getUID(), $b->getUID());
319
-		});
320
-		return $users;
321
-	}
322
-
323
-	/**
324
-	 * search by displayName
325
-	 *
326
-	 * @param string $pattern
327
-	 * @param int $limit
328
-	 * @param int $offset
329
-	 * @return \OC\User\User[]
330
-	 */
331
-	public function searchDisplayName($pattern, $limit = null, $offset = null) {
332
-		$users = [];
333
-		foreach ($this->backends as $backend) {
334
-			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
335
-			if (is_array($backendUsers)) {
336
-				foreach ($backendUsers as $uid => $displayName) {
337
-					$users[] = $this->getUserObject($uid, $backend);
338
-				}
339
-			}
340
-		}
341
-
342
-		usort($users, function ($a, $b) {
343
-			/**
344
-			 * @var \OC\User\User $a
345
-			 * @var \OC\User\User $b
346
-			 */
347
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
348
-		});
349
-		return $users;
350
-	}
351
-
352
-	/**
353
-	 * Search known users (from phonebook sync) by displayName
354
-	 *
355
-	 * @param string $searcher
356
-	 * @param string $pattern
357
-	 * @param int|null $limit
358
-	 * @param int|null $offset
359
-	 * @return IUser[]
360
-	 */
361
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
362
-		$users = [];
363
-		foreach ($this->backends as $backend) {
364
-			if ($backend instanceof ISearchKnownUsersBackend) {
365
-				$backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
366
-			} else {
367
-				// Better than nothing, but filtering after pagination can remove lots of results.
368
-				$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
369
-			}
370
-			if (is_array($backendUsers)) {
371
-				foreach ($backendUsers as $uid => $displayName) {
372
-					$users[] = $this->getUserObject($uid, $backend);
373
-				}
374
-			}
375
-		}
376
-
377
-		usort($users, function ($a, $b) {
378
-			/**
379
-			 * @var IUser $a
380
-			 * @var IUser $b
381
-			 */
382
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
383
-		});
384
-		return $users;
385
-	}
386
-
387
-	/**
388
-	 * @param string $uid
389
-	 * @param string $password
390
-	 * @return false|IUser the created user or false
391
-	 * @throws \InvalidArgumentException
392
-	 * @throws HintException
393
-	 */
394
-	public function createUser($uid, $password) {
395
-		// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
396
-		/** @var IAssertion $assertion */
397
-		$assertion = \OC::$server->get(IAssertion::class);
398
-		$assertion->createUserIsLegit();
399
-
400
-		$localBackends = [];
401
-		foreach ($this->backends as $backend) {
402
-			if ($backend instanceof Database) {
403
-				// First check if there is another user backend
404
-				$localBackends[] = $backend;
405
-				continue;
406
-			}
407
-
408
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
409
-				return $this->createUserFromBackend($uid, $password, $backend);
410
-			}
411
-		}
412
-
413
-		foreach ($localBackends as $backend) {
414
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
415
-				return $this->createUserFromBackend($uid, $password, $backend);
416
-			}
417
-		}
418
-
419
-		return false;
420
-	}
421
-
422
-	/**
423
-	 * @param string $uid
424
-	 * @param string $password
425
-	 * @param UserInterface $backend
426
-	 * @return IUser|false
427
-	 * @throws \InvalidArgumentException
428
-	 */
429
-	public function createUserFromBackend($uid, $password, UserInterface $backend) {
430
-		$l = \OC::$server->getL10N('lib');
431
-
432
-		$this->validateUserId($uid, true);
433
-
434
-		// No empty password
435
-		if (trim($password) === '') {
436
-			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
437
-		}
438
-
439
-		// Check if user already exists
440
-		if ($this->userExists($uid)) {
441
-			throw new \InvalidArgumentException($l->t('The username is already being used'));
442
-		}
443
-
444
-		/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
445
-		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
446
-		$this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
447
-		$state = $backend->createUser($uid, $password);
448
-		if ($state === false) {
449
-			throw new \InvalidArgumentException($l->t('Could not create user'));
450
-		}
451
-		$user = $this->getUserObject($uid, $backend);
452
-		if ($user instanceof IUser) {
453
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
454
-			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
455
-			$this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
456
-			return $user;
457
-		}
458
-		return false;
459
-	}
460
-
461
-	/**
462
-	 * returns how many users per backend exist (if supported by backend)
463
-	 *
464
-	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
465
-	 *                entry in the preferences table will be affected
466
-	 * @return array<string, int> an array of backend class as key and count number as value
467
-	 */
468
-	public function countUsers() {
469
-		$userCountStatistics = [];
470
-		foreach ($this->backends as $backend) {
471
-			if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
472
-				/** @var ICountUsersBackend|IUserBackend $backend */
473
-				$backendUsers = $backend->countUsers();
474
-				if ($backendUsers !== false) {
475
-					if ($backend instanceof IUserBackend) {
476
-						$name = $backend->getBackendName();
477
-					} else {
478
-						$name = get_class($backend);
479
-					}
480
-					if (isset($userCountStatistics[$name])) {
481
-						$userCountStatistics[$name] += $backendUsers;
482
-					} else {
483
-						$userCountStatistics[$name] = $backendUsers;
484
-					}
485
-				}
486
-			}
487
-		}
488
-		return $userCountStatistics;
489
-	}
490
-
491
-	/**
492
-	 * returns how many users per backend exist in the requested groups (if supported by backend)
493
-	 *
494
-	 * @param IGroup[] $groups an array of gid to search in
495
-	 * @return array|int an array of backend class as key and count number as value
496
-	 *                if $hasLoggedIn is true only an int is returned
497
-	 */
498
-	public function countUsersOfGroups(array $groups) {
499
-		$users = [];
500
-		foreach ($groups as $group) {
501
-			$usersIds = array_map(function ($user) {
502
-				return $user->getUID();
503
-			}, $group->getUsers());
504
-			$users = array_merge($users, $usersIds);
505
-		}
506
-		return count(array_unique($users));
507
-	}
508
-
509
-	/**
510
-	 * The callback is executed for each user on each backend.
511
-	 * If the callback returns false no further users will be retrieved.
512
-	 *
513
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
514
-	 * @param string $search
515
-	 * @param boolean $onlySeen when true only users that have a lastLogin entry
516
-	 *                in the preferences table will be affected
517
-	 * @since 9.0.0
518
-	 */
519
-	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
520
-		if ($onlySeen) {
521
-			$this->callForSeenUsers($callback);
522
-		} else {
523
-			foreach ($this->getBackends() as $backend) {
524
-				$limit = 500;
525
-				$offset = 0;
526
-				do {
527
-					$users = $backend->getUsers($search, $limit, $offset);
528
-					foreach ($users as $uid) {
529
-						if (!$backend->userExists($uid)) {
530
-							continue;
531
-						}
532
-						$user = $this->getUserObject($uid, $backend, false);
533
-						$return = $callback($user);
534
-						if ($return === false) {
535
-							break;
536
-						}
537
-					}
538
-					$offset += $limit;
539
-				} while (count($users) >= $limit);
540
-			}
541
-		}
542
-	}
543
-
544
-	/**
545
-	 * returns how many users are disabled
546
-	 *
547
-	 * @return int
548
-	 * @since 12.0.0
549
-	 */
550
-	public function countDisabledUsers(): int {
551
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
552
-		$queryBuilder->select($queryBuilder->func()->count('*'))
553
-			->from('preferences')
554
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
555
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
556
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
557
-
558
-
559
-		$result = $queryBuilder->execute();
560
-		$count = $result->fetchOne();
561
-		$result->closeCursor();
562
-
563
-		if ($count !== false) {
564
-			$count = (int)$count;
565
-		} else {
566
-			$count = 0;
567
-		}
568
-
569
-		return $count;
570
-	}
571
-
572
-	/**
573
-	 * returns how many users are disabled in the requested groups
574
-	 *
575
-	 * @param array $groups groupids to search
576
-	 * @return int
577
-	 * @since 14.0.0
578
-	 */
579
-	public function countDisabledUsersOfGroups(array $groups): int {
580
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
581
-		$queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
582
-			->from('preferences', 'p')
583
-			->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
584
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
585
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
586
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
587
-			->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
588
-
589
-		$result = $queryBuilder->execute();
590
-		$count = $result->fetchOne();
591
-		$result->closeCursor();
592
-
593
-		if ($count !== false) {
594
-			$count = (int)$count;
595
-		} else {
596
-			$count = 0;
597
-		}
598
-
599
-		return $count;
600
-	}
601
-
602
-	/**
603
-	 * returns how many users have logged in once
604
-	 *
605
-	 * @return int
606
-	 * @since 11.0.0
607
-	 */
608
-	public function countSeenUsers() {
609
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
610
-		$queryBuilder->select($queryBuilder->func()->count('*'))
611
-			->from('preferences')
612
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
613
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
614
-
615
-		$query = $queryBuilder->execute();
616
-
617
-		$result = (int)$query->fetchOne();
618
-		$query->closeCursor();
619
-
620
-		return $result;
621
-	}
622
-
623
-	/**
624
-	 * @param \Closure $callback
625
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
626
-	 * @since 11.0.0
627
-	 */
628
-	public function callForSeenUsers(\Closure $callback) {
629
-		$limit = 1000;
630
-		$offset = 0;
631
-		do {
632
-			$userIds = $this->getSeenUserIds($limit, $offset);
633
-			$offset += $limit;
634
-			foreach ($userIds as $userId) {
635
-				foreach ($this->backends as $backend) {
636
-					if ($backend->userExists($userId)) {
637
-						$user = $this->getUserObject($userId, $backend, false);
638
-						$return = $callback($user);
639
-						if ($return === false) {
640
-							return;
641
-						}
642
-						break;
643
-					}
644
-				}
645
-			}
646
-		} while (count($userIds) >= $limit);
647
-	}
648
-
649
-	/**
650
-	 * Getting all userIds that have a listLogin value requires checking the
651
-	 * value in php because on oracle you cannot use a clob in a where clause,
652
-	 * preventing us from doing a not null or length(value) > 0 check.
653
-	 *
654
-	 * @param int $limit
655
-	 * @param int $offset
656
-	 * @return string[] with user ids
657
-	 */
658
-	private function getSeenUserIds($limit = null, $offset = null) {
659
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
660
-		$queryBuilder->select(['userid'])
661
-			->from('preferences')
662
-			->where($queryBuilder->expr()->eq(
663
-				'appid', $queryBuilder->createNamedParameter('login'))
664
-			)
665
-			->andWhere($queryBuilder->expr()->eq(
666
-				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
667
-			)
668
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
669
-			);
670
-
671
-		if ($limit !== null) {
672
-			$queryBuilder->setMaxResults($limit);
673
-		}
674
-		if ($offset !== null) {
675
-			$queryBuilder->setFirstResult($offset);
676
-		}
677
-		$query = $queryBuilder->execute();
678
-		$result = [];
679
-
680
-		while ($row = $query->fetch()) {
681
-			$result[] = $row['userid'];
682
-		}
683
-
684
-		$query->closeCursor();
685
-
686
-		return $result;
687
-	}
688
-
689
-	/**
690
-	 * @param string $email
691
-	 * @return IUser[]
692
-	 * @since 9.1.0
693
-	 */
694
-	public function getByEmail($email) {
695
-		// looking for 'email' only (and not primary_mail) is intentional
696
-		$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
697
-
698
-		$users = array_map(function ($uid) {
699
-			return $this->get($uid);
700
-		}, $userIds);
701
-
702
-		return array_values(array_filter($users, function ($u) {
703
-			return ($u instanceof IUser);
704
-		}));
705
-	}
706
-
707
-	/**
708
-	 * @param string $uid
709
-	 * @param bool $checkDataDirectory
710
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
711
-	 * @since 26.0.0
712
-	 */
713
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
714
-		$l = Server::get(IFactory::class)->get('lib');
715
-
716
-		// Check the name for bad characters
717
-		// Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
718
-		if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
719
-			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
720
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
721
-		}
722
-
723
-		// No empty username
724
-		if (trim($uid) === '') {
725
-			throw new \InvalidArgumentException($l->t('A valid username must be provided'));
726
-		}
727
-
728
-		// No whitespace at the beginning or at the end
729
-		if (trim($uid) !== $uid) {
730
-			throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
731
-		}
732
-
733
-		// Username only consists of 1 or 2 dots (directory traversal)
734
-		if ($uid === '.' || $uid === '..') {
735
-			throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
736
-		}
737
-
738
-		if (!$this->verifyUid($uid, $checkDataDirectory)) {
739
-			throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
740
-		}
741
-	}
742
-
743
-	private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
744
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
745
-
746
-		if (\in_array($uid, [
747
-			'.htaccess',
748
-			'files_external',
749
-			'__groupfolders',
750
-			'.ocdata',
751
-			'owncloud.log',
752
-			'nextcloud.log',
753
-			$appdata], true)) {
754
-			return false;
755
-		}
756
-
757
-		if (!$checkDataDirectory) {
758
-			return true;
759
-		}
760
-
761
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
762
-
763
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
764
-	}
765
-
766
-	public function getDisplayNameCache(): DisplayNameCache {
767
-		return $this->displayNameCache;
768
-	}
77
+    /**
78
+     * @var \OCP\UserInterface[] $backends
79
+     */
80
+    private $backends = [];
81
+
82
+    /**
83
+     * @var \OC\User\User[] $cachedUsers
84
+     */
85
+    private $cachedUsers = [];
86
+
87
+    /** @var IConfig */
88
+    private $config;
89
+
90
+    /** @var EventDispatcherInterface */
91
+    private $dispatcher;
92
+
93
+    /** @var ICache */
94
+    private $cache;
95
+
96
+    /** @var IEventDispatcher */
97
+    private $eventDispatcher;
98
+
99
+    private DisplayNameCache $displayNameCache;
100
+
101
+    public function __construct(IConfig $config,
102
+                                EventDispatcherInterface $oldDispatcher,
103
+                                ICacheFactory $cacheFactory,
104
+                                IEventDispatcher $eventDispatcher) {
105
+        $this->config = $config;
106
+        $this->dispatcher = $oldDispatcher;
107
+        $this->cache = $cacheFactory->createDistributed('user_backend_map');
108
+        $cachedUsers = &$this->cachedUsers;
109
+        $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
110
+            /** @var \OC\User\User $user */
111
+            unset($cachedUsers[$user->getUID()]);
112
+        });
113
+        $this->eventDispatcher = $eventDispatcher;
114
+        $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
115
+    }
116
+
117
+    /**
118
+     * Get the active backends
119
+     * @return \OCP\UserInterface[]
120
+     */
121
+    public function getBackends() {
122
+        return $this->backends;
123
+    }
124
+
125
+    /**
126
+     * register a user backend
127
+     *
128
+     * @param \OCP\UserInterface $backend
129
+     */
130
+    public function registerBackend($backend) {
131
+        $this->backends[] = $backend;
132
+    }
133
+
134
+    /**
135
+     * remove a user backend
136
+     *
137
+     * @param \OCP\UserInterface $backend
138
+     */
139
+    public function removeBackend($backend) {
140
+        $this->cachedUsers = [];
141
+        if (($i = array_search($backend, $this->backends)) !== false) {
142
+            unset($this->backends[$i]);
143
+        }
144
+    }
145
+
146
+    /**
147
+     * remove all user backends
148
+     */
149
+    public function clearBackends() {
150
+        $this->cachedUsers = [];
151
+        $this->backends = [];
152
+    }
153
+
154
+    /**
155
+     * get a user by user id
156
+     *
157
+     * @param string $uid
158
+     * @return \OC\User\User|null Either the user or null if the specified user does not exist
159
+     */
160
+    public function get($uid) {
161
+        if (is_null($uid) || $uid === '' || $uid === false) {
162
+            return null;
163
+        }
164
+        if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
165
+            return $this->cachedUsers[$uid];
166
+        }
167
+
168
+        $cachedBackend = $this->cache->get(sha1($uid));
169
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
170
+            // Cache has the info of the user backend already, so ask that one directly
171
+            $backend = $this->backends[$cachedBackend];
172
+            if ($backend->userExists($uid)) {
173
+                return $this->getUserObject($uid, $backend);
174
+            }
175
+        }
176
+
177
+        foreach ($this->backends as $i => $backend) {
178
+            if ($i === $cachedBackend) {
179
+                // Tried that one already
180
+                continue;
181
+            }
182
+
183
+            if ($backend->userExists($uid)) {
184
+                // Hash $uid to ensure that only valid characters are used for the cache key
185
+                $this->cache->set(sha1($uid), $i, 300);
186
+                return $this->getUserObject($uid, $backend);
187
+            }
188
+        }
189
+        return null;
190
+    }
191
+
192
+    public function getDisplayName(string $uid): ?string {
193
+        return $this->displayNameCache->getDisplayName($uid);
194
+    }
195
+
196
+    /**
197
+     * get or construct the user object
198
+     *
199
+     * @param string $uid
200
+     * @param \OCP\UserInterface $backend
201
+     * @param bool $cacheUser If false the newly created user object will not be cached
202
+     * @return \OC\User\User
203
+     */
204
+    protected function getUserObject($uid, $backend, $cacheUser = true) {
205
+        if ($backend instanceof IGetRealUIDBackend) {
206
+            $uid = $backend->getRealUID($uid);
207
+        }
208
+
209
+        if (isset($this->cachedUsers[$uid])) {
210
+            return $this->cachedUsers[$uid];
211
+        }
212
+
213
+        $user = new User($uid, $backend, $this->dispatcher, $this, $this->config);
214
+        if ($cacheUser) {
215
+            $this->cachedUsers[$uid] = $user;
216
+        }
217
+        return $user;
218
+    }
219
+
220
+    /**
221
+     * check if a user exists
222
+     *
223
+     * @param string $uid
224
+     * @return bool
225
+     */
226
+    public function userExists($uid) {
227
+        $user = $this->get($uid);
228
+        return ($user !== null);
229
+    }
230
+
231
+    /**
232
+     * Check if the password is valid for the user
233
+     *
234
+     * @param string $loginName
235
+     * @param string $password
236
+     * @return IUser|false the User object on success, false otherwise
237
+     */
238
+    public function checkPassword($loginName, $password) {
239
+        $result = $this->checkPasswordNoLogging($loginName, $password);
240
+
241
+        if ($result === false) {
242
+            \OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
243
+        }
244
+
245
+        return $result;
246
+    }
247
+
248
+    /**
249
+     * Check if the password is valid for the user
250
+     *
251
+     * @internal
252
+     * @param string $loginName
253
+     * @param string $password
254
+     * @return IUser|false the User object on success, false otherwise
255
+     */
256
+    public function checkPasswordNoLogging($loginName, $password) {
257
+        $loginName = str_replace("\0", '', $loginName);
258
+        $password = str_replace("\0", '', $password);
259
+
260
+        $cachedBackend = $this->cache->get($loginName);
261
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
262
+            $backends = [$this->backends[$cachedBackend]];
263
+        } else {
264
+            $backends = $this->backends;
265
+        }
266
+        foreach ($backends as $backend) {
267
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
268
+                /** @var ICheckPasswordBackend $backend */
269
+                $uid = $backend->checkPassword($loginName, $password);
270
+                if ($uid !== false) {
271
+                    return $this->getUserObject($uid, $backend);
272
+                }
273
+            }
274
+        }
275
+
276
+        // since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
277
+        // we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
278
+        // to contain urlencoded patterns by "accident".
279
+        $password = urldecode($password);
280
+
281
+        foreach ($backends as $backend) {
282
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
283
+                /** @var ICheckPasswordBackend|UserInterface $backend */
284
+                $uid = $backend->checkPassword($loginName, $password);
285
+                if ($uid !== false) {
286
+                    return $this->getUserObject($uid, $backend);
287
+                }
288
+            }
289
+        }
290
+
291
+        return false;
292
+    }
293
+
294
+    /**
295
+     * search by user id
296
+     *
297
+     * @param string $pattern
298
+     * @param int $limit
299
+     * @param int $offset
300
+     * @return \OC\User\User[]
301
+     */
302
+    public function search($pattern, $limit = null, $offset = null) {
303
+        $users = [];
304
+        foreach ($this->backends as $backend) {
305
+            $backendUsers = $backend->getUsers($pattern, $limit, $offset);
306
+            if (is_array($backendUsers)) {
307
+                foreach ($backendUsers as $uid) {
308
+                    $users[$uid] = $this->getUserObject($uid, $backend);
309
+                }
310
+            }
311
+        }
312
+
313
+        uasort($users, function ($a, $b) {
314
+            /**
315
+             * @var \OC\User\User $a
316
+             * @var \OC\User\User $b
317
+             */
318
+            return strcasecmp($a->getUID(), $b->getUID());
319
+        });
320
+        return $users;
321
+    }
322
+
323
+    /**
324
+     * search by displayName
325
+     *
326
+     * @param string $pattern
327
+     * @param int $limit
328
+     * @param int $offset
329
+     * @return \OC\User\User[]
330
+     */
331
+    public function searchDisplayName($pattern, $limit = null, $offset = null) {
332
+        $users = [];
333
+        foreach ($this->backends as $backend) {
334
+            $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
335
+            if (is_array($backendUsers)) {
336
+                foreach ($backendUsers as $uid => $displayName) {
337
+                    $users[] = $this->getUserObject($uid, $backend);
338
+                }
339
+            }
340
+        }
341
+
342
+        usort($users, function ($a, $b) {
343
+            /**
344
+             * @var \OC\User\User $a
345
+             * @var \OC\User\User $b
346
+             */
347
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
348
+        });
349
+        return $users;
350
+    }
351
+
352
+    /**
353
+     * Search known users (from phonebook sync) by displayName
354
+     *
355
+     * @param string $searcher
356
+     * @param string $pattern
357
+     * @param int|null $limit
358
+     * @param int|null $offset
359
+     * @return IUser[]
360
+     */
361
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
362
+        $users = [];
363
+        foreach ($this->backends as $backend) {
364
+            if ($backend instanceof ISearchKnownUsersBackend) {
365
+                $backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
366
+            } else {
367
+                // Better than nothing, but filtering after pagination can remove lots of results.
368
+                $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
369
+            }
370
+            if (is_array($backendUsers)) {
371
+                foreach ($backendUsers as $uid => $displayName) {
372
+                    $users[] = $this->getUserObject($uid, $backend);
373
+                }
374
+            }
375
+        }
376
+
377
+        usort($users, function ($a, $b) {
378
+            /**
379
+             * @var IUser $a
380
+             * @var IUser $b
381
+             */
382
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
383
+        });
384
+        return $users;
385
+    }
386
+
387
+    /**
388
+     * @param string $uid
389
+     * @param string $password
390
+     * @return false|IUser the created user or false
391
+     * @throws \InvalidArgumentException
392
+     * @throws HintException
393
+     */
394
+    public function createUser($uid, $password) {
395
+        // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
396
+        /** @var IAssertion $assertion */
397
+        $assertion = \OC::$server->get(IAssertion::class);
398
+        $assertion->createUserIsLegit();
399
+
400
+        $localBackends = [];
401
+        foreach ($this->backends as $backend) {
402
+            if ($backend instanceof Database) {
403
+                // First check if there is another user backend
404
+                $localBackends[] = $backend;
405
+                continue;
406
+            }
407
+
408
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
409
+                return $this->createUserFromBackend($uid, $password, $backend);
410
+            }
411
+        }
412
+
413
+        foreach ($localBackends as $backend) {
414
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
415
+                return $this->createUserFromBackend($uid, $password, $backend);
416
+            }
417
+        }
418
+
419
+        return false;
420
+    }
421
+
422
+    /**
423
+     * @param string $uid
424
+     * @param string $password
425
+     * @param UserInterface $backend
426
+     * @return IUser|false
427
+     * @throws \InvalidArgumentException
428
+     */
429
+    public function createUserFromBackend($uid, $password, UserInterface $backend) {
430
+        $l = \OC::$server->getL10N('lib');
431
+
432
+        $this->validateUserId($uid, true);
433
+
434
+        // No empty password
435
+        if (trim($password) === '') {
436
+            throw new \InvalidArgumentException($l->t('A valid password must be provided'));
437
+        }
438
+
439
+        // Check if user already exists
440
+        if ($this->userExists($uid)) {
441
+            throw new \InvalidArgumentException($l->t('The username is already being used'));
442
+        }
443
+
444
+        /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
445
+        $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
446
+        $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
447
+        $state = $backend->createUser($uid, $password);
448
+        if ($state === false) {
449
+            throw new \InvalidArgumentException($l->t('Could not create user'));
450
+        }
451
+        $user = $this->getUserObject($uid, $backend);
452
+        if ($user instanceof IUser) {
453
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
454
+            $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
455
+            $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
456
+            return $user;
457
+        }
458
+        return false;
459
+    }
460
+
461
+    /**
462
+     * returns how many users per backend exist (if supported by backend)
463
+     *
464
+     * @param boolean $hasLoggedIn when true only users that have a lastLogin
465
+     *                entry in the preferences table will be affected
466
+     * @return array<string, int> an array of backend class as key and count number as value
467
+     */
468
+    public function countUsers() {
469
+        $userCountStatistics = [];
470
+        foreach ($this->backends as $backend) {
471
+            if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
472
+                /** @var ICountUsersBackend|IUserBackend $backend */
473
+                $backendUsers = $backend->countUsers();
474
+                if ($backendUsers !== false) {
475
+                    if ($backend instanceof IUserBackend) {
476
+                        $name = $backend->getBackendName();
477
+                    } else {
478
+                        $name = get_class($backend);
479
+                    }
480
+                    if (isset($userCountStatistics[$name])) {
481
+                        $userCountStatistics[$name] += $backendUsers;
482
+                    } else {
483
+                        $userCountStatistics[$name] = $backendUsers;
484
+                    }
485
+                }
486
+            }
487
+        }
488
+        return $userCountStatistics;
489
+    }
490
+
491
+    /**
492
+     * returns how many users per backend exist in the requested groups (if supported by backend)
493
+     *
494
+     * @param IGroup[] $groups an array of gid to search in
495
+     * @return array|int an array of backend class as key and count number as value
496
+     *                if $hasLoggedIn is true only an int is returned
497
+     */
498
+    public function countUsersOfGroups(array $groups) {
499
+        $users = [];
500
+        foreach ($groups as $group) {
501
+            $usersIds = array_map(function ($user) {
502
+                return $user->getUID();
503
+            }, $group->getUsers());
504
+            $users = array_merge($users, $usersIds);
505
+        }
506
+        return count(array_unique($users));
507
+    }
508
+
509
+    /**
510
+     * The callback is executed for each user on each backend.
511
+     * If the callback returns false no further users will be retrieved.
512
+     *
513
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
514
+     * @param string $search
515
+     * @param boolean $onlySeen when true only users that have a lastLogin entry
516
+     *                in the preferences table will be affected
517
+     * @since 9.0.0
518
+     */
519
+    public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
520
+        if ($onlySeen) {
521
+            $this->callForSeenUsers($callback);
522
+        } else {
523
+            foreach ($this->getBackends() as $backend) {
524
+                $limit = 500;
525
+                $offset = 0;
526
+                do {
527
+                    $users = $backend->getUsers($search, $limit, $offset);
528
+                    foreach ($users as $uid) {
529
+                        if (!$backend->userExists($uid)) {
530
+                            continue;
531
+                        }
532
+                        $user = $this->getUserObject($uid, $backend, false);
533
+                        $return = $callback($user);
534
+                        if ($return === false) {
535
+                            break;
536
+                        }
537
+                    }
538
+                    $offset += $limit;
539
+                } while (count($users) >= $limit);
540
+            }
541
+        }
542
+    }
543
+
544
+    /**
545
+     * returns how many users are disabled
546
+     *
547
+     * @return int
548
+     * @since 12.0.0
549
+     */
550
+    public function countDisabledUsers(): int {
551
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
552
+        $queryBuilder->select($queryBuilder->func()->count('*'))
553
+            ->from('preferences')
554
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
555
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
556
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
557
+
558
+
559
+        $result = $queryBuilder->execute();
560
+        $count = $result->fetchOne();
561
+        $result->closeCursor();
562
+
563
+        if ($count !== false) {
564
+            $count = (int)$count;
565
+        } else {
566
+            $count = 0;
567
+        }
568
+
569
+        return $count;
570
+    }
571
+
572
+    /**
573
+     * returns how many users are disabled in the requested groups
574
+     *
575
+     * @param array $groups groupids to search
576
+     * @return int
577
+     * @since 14.0.0
578
+     */
579
+    public function countDisabledUsersOfGroups(array $groups): int {
580
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
581
+        $queryBuilder->select($queryBuilder->createFunction('COUNT(DISTINCT ' . $queryBuilder->getColumnName('uid') . ')'))
582
+            ->from('preferences', 'p')
583
+            ->innerJoin('p', 'group_user', 'g', $queryBuilder->expr()->eq('p.userid', 'g.uid'))
584
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
585
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
586
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR))
587
+            ->andWhere($queryBuilder->expr()->in('gid', $queryBuilder->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)));
588
+
589
+        $result = $queryBuilder->execute();
590
+        $count = $result->fetchOne();
591
+        $result->closeCursor();
592
+
593
+        if ($count !== false) {
594
+            $count = (int)$count;
595
+        } else {
596
+            $count = 0;
597
+        }
598
+
599
+        return $count;
600
+    }
601
+
602
+    /**
603
+     * returns how many users have logged in once
604
+     *
605
+     * @return int
606
+     * @since 11.0.0
607
+     */
608
+    public function countSeenUsers() {
609
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
610
+        $queryBuilder->select($queryBuilder->func()->count('*'))
611
+            ->from('preferences')
612
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
613
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
614
+
615
+        $query = $queryBuilder->execute();
616
+
617
+        $result = (int)$query->fetchOne();
618
+        $query->closeCursor();
619
+
620
+        return $result;
621
+    }
622
+
623
+    /**
624
+     * @param \Closure $callback
625
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
626
+     * @since 11.0.0
627
+     */
628
+    public function callForSeenUsers(\Closure $callback) {
629
+        $limit = 1000;
630
+        $offset = 0;
631
+        do {
632
+            $userIds = $this->getSeenUserIds($limit, $offset);
633
+            $offset += $limit;
634
+            foreach ($userIds as $userId) {
635
+                foreach ($this->backends as $backend) {
636
+                    if ($backend->userExists($userId)) {
637
+                        $user = $this->getUserObject($userId, $backend, false);
638
+                        $return = $callback($user);
639
+                        if ($return === false) {
640
+                            return;
641
+                        }
642
+                        break;
643
+                    }
644
+                }
645
+            }
646
+        } while (count($userIds) >= $limit);
647
+    }
648
+
649
+    /**
650
+     * Getting all userIds that have a listLogin value requires checking the
651
+     * value in php because on oracle you cannot use a clob in a where clause,
652
+     * preventing us from doing a not null or length(value) > 0 check.
653
+     *
654
+     * @param int $limit
655
+     * @param int $offset
656
+     * @return string[] with user ids
657
+     */
658
+    private function getSeenUserIds($limit = null, $offset = null) {
659
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
660
+        $queryBuilder->select(['userid'])
661
+            ->from('preferences')
662
+            ->where($queryBuilder->expr()->eq(
663
+                'appid', $queryBuilder->createNamedParameter('login'))
664
+            )
665
+            ->andWhere($queryBuilder->expr()->eq(
666
+                'configkey', $queryBuilder->createNamedParameter('lastLogin'))
667
+            )
668
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
669
+            );
670
+
671
+        if ($limit !== null) {
672
+            $queryBuilder->setMaxResults($limit);
673
+        }
674
+        if ($offset !== null) {
675
+            $queryBuilder->setFirstResult($offset);
676
+        }
677
+        $query = $queryBuilder->execute();
678
+        $result = [];
679
+
680
+        while ($row = $query->fetch()) {
681
+            $result[] = $row['userid'];
682
+        }
683
+
684
+        $query->closeCursor();
685
+
686
+        return $result;
687
+    }
688
+
689
+    /**
690
+     * @param string $email
691
+     * @return IUser[]
692
+     * @since 9.1.0
693
+     */
694
+    public function getByEmail($email) {
695
+        // looking for 'email' only (and not primary_mail) is intentional
696
+        $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
697
+
698
+        $users = array_map(function ($uid) {
699
+            return $this->get($uid);
700
+        }, $userIds);
701
+
702
+        return array_values(array_filter($users, function ($u) {
703
+            return ($u instanceof IUser);
704
+        }));
705
+    }
706
+
707
+    /**
708
+     * @param string $uid
709
+     * @param bool $checkDataDirectory
710
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
711
+     * @since 26.0.0
712
+     */
713
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
714
+        $l = Server::get(IFactory::class)->get('lib');
715
+
716
+        // Check the name for bad characters
717
+        // Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
718
+        if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
719
+            throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
720
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
721
+        }
722
+
723
+        // No empty username
724
+        if (trim($uid) === '') {
725
+            throw new \InvalidArgumentException($l->t('A valid username must be provided'));
726
+        }
727
+
728
+        // No whitespace at the beginning or at the end
729
+        if (trim($uid) !== $uid) {
730
+            throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
731
+        }
732
+
733
+        // Username only consists of 1 or 2 dots (directory traversal)
734
+        if ($uid === '.' || $uid === '..') {
735
+            throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
736
+        }
737
+
738
+        if (!$this->verifyUid($uid, $checkDataDirectory)) {
739
+            throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
740
+        }
741
+    }
742
+
743
+    private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
744
+        $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
745
+
746
+        if (\in_array($uid, [
747
+            '.htaccess',
748
+            'files_external',
749
+            '__groupfolders',
750
+            '.ocdata',
751
+            'owncloud.log',
752
+            'nextcloud.log',
753
+            $appdata], true)) {
754
+            return false;
755
+        }
756
+
757
+        if (!$checkDataDirectory) {
758
+            return true;
759
+        }
760
+
761
+        $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
762
+
763
+        return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
764
+    }
765
+
766
+    public function getDisplayNameCache(): DisplayNameCache {
767
+        return $this->displayNameCache;
768
+    }
769 769
 }
Please login to merge, or discard this patch.