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