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