Completed
Push — master ( a4cf71...6eb8e8 )
by Robin
58:26 queued 30:03
created
lib/public/IUserManager.php 1 patch
Indentation   +217 added lines, -217 removed lines patch added patch discarded remove patch
@@ -24,246 +24,246 @@
 block discarded – undo
24 24
  * @since 8.0.0
25 25
  */
26 26
 interface IUserManager {
27
-	/**
28
-	 * @since 26.0.0
29
-	 */
30
-	public const MAX_PASSWORD_LENGTH = 469;
27
+    /**
28
+     * @since 26.0.0
29
+     */
30
+    public const MAX_PASSWORD_LENGTH = 469;
31 31
 
32
-	/**
33
-	 * register a user backend
34
-	 *
35
-	 * @since 8.0.0
36
-	 * @return void
37
-	 */
38
-	public function registerBackend(UserInterface $backend);
32
+    /**
33
+     * register a user backend
34
+     *
35
+     * @since 8.0.0
36
+     * @return void
37
+     */
38
+    public function registerBackend(UserInterface $backend);
39 39
 
40
-	/**
41
-	 * Get the active backends
42
-	 * @return UserInterface[]
43
-	 * @since 8.0.0
44
-	 */
45
-	public function getBackends();
40
+    /**
41
+     * Get the active backends
42
+     * @return UserInterface[]
43
+     * @since 8.0.0
44
+     */
45
+    public function getBackends();
46 46
 
47
-	/**
48
-	 * remove a user backend
49
-	 *
50
-	 * @since 8.0.0
51
-	 * @return void
52
-	 */
53
-	public function removeBackend(UserInterface $backend);
47
+    /**
48
+     * remove a user backend
49
+     *
50
+     * @since 8.0.0
51
+     * @return void
52
+     */
53
+    public function removeBackend(UserInterface $backend);
54 54
 
55
-	/**
56
-	 * remove all user backends
57
-	 * @since 8.0.0
58
-	 * @return void
59
-	 */
60
-	public function clearBackends();
55
+    /**
56
+     * remove all user backends
57
+     * @since 8.0.0
58
+     * @return void
59
+     */
60
+    public function clearBackends();
61 61
 
62
-	/**
63
-	 * get a user by user id
64
-	 *
65
-	 * If you're already 100% sure that the user exists,
66
-	 * consider IUserManager::getExistingUser which has less overhead.
67
-	 *
68
-	 * @param string $uid
69
-	 * @return \OCP\IUser|null Either the user or null if the specified user does not exist
70
-	 * @since 8.0.0
71
-	 */
72
-	public function get($uid);
62
+    /**
63
+     * get a user by user id
64
+     *
65
+     * If you're already 100% sure that the user exists,
66
+     * consider IUserManager::getExistingUser which has less overhead.
67
+     *
68
+     * @param string $uid
69
+     * @return \OCP\IUser|null Either the user or null if the specified user does not exist
70
+     * @since 8.0.0
71
+     */
72
+    public function get($uid);
73 73
 
74
-	/**
75
-	 * Get the display name of a user
76
-	 *
77
-	 * @param string $uid
78
-	 * @return string|null
79
-	 * @since 25.0.0
80
-	 */
81
-	public function getDisplayName(string $uid): ?string;
74
+    /**
75
+     * Get the display name of a user
76
+     *
77
+     * @param string $uid
78
+     * @return string|null
79
+     * @since 25.0.0
80
+     */
81
+    public function getDisplayName(string $uid): ?string;
82 82
 
83
-	/**
84
-	 * check if a user exists
85
-	 *
86
-	 * @param string $uid
87
-	 * @return bool
88
-	 * @since 8.0.0
89
-	 */
90
-	public function userExists($uid);
83
+    /**
84
+     * check if a user exists
85
+     *
86
+     * @param string $uid
87
+     * @return bool
88
+     * @since 8.0.0
89
+     */
90
+    public function userExists($uid);
91 91
 
92
-	/**
93
-	 * Check if the password is valid for the user
94
-	 *
95
-	 * @param string $loginName
96
-	 * @param string $password
97
-	 * @return IUser|false the User object on success, false otherwise
98
-	 * @since 8.0.0
99
-	 */
100
-	public function checkPassword($loginName, $password);
92
+    /**
93
+     * Check if the password is valid for the user
94
+     *
95
+     * @param string $loginName
96
+     * @param string $password
97
+     * @return IUser|false the User object on success, false otherwise
98
+     * @since 8.0.0
99
+     */
100
+    public function checkPassword($loginName, $password);
101 101
 
102
-	/**
103
-	 * search by user id
104
-	 *
105
-	 * @param string $pattern
106
-	 * @param int $limit
107
-	 * @param int $offset
108
-	 * @return \OCP\IUser[]
109
-	 * @since 8.0.0
110
-	 * @deprecated 27.0.0, use searchDisplayName instead
111
-	 */
112
-	public function search($pattern, $limit = null, $offset = null);
102
+    /**
103
+     * search by user id
104
+     *
105
+     * @param string $pattern
106
+     * @param int $limit
107
+     * @param int $offset
108
+     * @return \OCP\IUser[]
109
+     * @since 8.0.0
110
+     * @deprecated 27.0.0, use searchDisplayName instead
111
+     */
112
+    public function search($pattern, $limit = null, $offset = null);
113 113
 
114
-	/**
115
-	 * search by displayName
116
-	 *
117
-	 * @param string $pattern
118
-	 * @param int $limit
119
-	 * @param int $offset
120
-	 * @return list<IUser>
121
-	 * @since 8.0.0
122
-	 */
123
-	public function searchDisplayName($pattern, $limit = null, $offset = null);
114
+    /**
115
+     * search by displayName
116
+     *
117
+     * @param string $pattern
118
+     * @param int $limit
119
+     * @param int $offset
120
+     * @return list<IUser>
121
+     * @since 8.0.0
122
+     */
123
+    public function searchDisplayName($pattern, $limit = null, $offset = null);
124 124
 
125
-	/**
126
-	 * @return IUser[]
127
-	 * @since 28.0.0
128
-	 * @since 30.0.0 $search parameter added
129
-	 */
130
-	public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
125
+    /**
126
+     * @return IUser[]
127
+     * @since 28.0.0
128
+     * @since 30.0.0 $search parameter added
129
+     */
130
+    public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
131 131
 
132
-	/**
133
-	 * Search known users (from phonebook sync) by displayName
134
-	 *
135
-	 * @param string $searcher
136
-	 * @param string $pattern
137
-	 * @param int|null $limit
138
-	 * @param int|null $offset
139
-	 * @return IUser[]
140
-	 * @since 21.0.1
141
-	 */
142
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
132
+    /**
133
+     * Search known users (from phonebook sync) by displayName
134
+     *
135
+     * @param string $searcher
136
+     * @param string $pattern
137
+     * @param int|null $limit
138
+     * @param int|null $offset
139
+     * @return IUser[]
140
+     * @since 21.0.1
141
+     */
142
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
143 143
 
144
-	/**
145
-	 * @param string $uid
146
-	 * @param string $password
147
-	 * @throws \InvalidArgumentException
148
-	 * @return false|\OCP\IUser the created user or false
149
-	 * @since 8.0.0
150
-	 */
151
-	public function createUser($uid, $password);
144
+    /**
145
+     * @param string $uid
146
+     * @param string $password
147
+     * @throws \InvalidArgumentException
148
+     * @return false|\OCP\IUser the created user or false
149
+     * @since 8.0.0
150
+     */
151
+    public function createUser($uid, $password);
152 152
 
153
-	/**
154
-	 * @param string $uid
155
-	 * @param string $password
156
-	 * @param UserInterface $backend
157
-	 * @return IUser|null
158
-	 * @throws \InvalidArgumentException
159
-	 * @since 12.0.0
160
-	 */
161
-	public function createUserFromBackend($uid, $password, UserInterface $backend);
153
+    /**
154
+     * @param string $uid
155
+     * @param string $password
156
+     * @param UserInterface $backend
157
+     * @return IUser|null
158
+     * @throws \InvalidArgumentException
159
+     * @since 12.0.0
160
+     */
161
+    public function createUserFromBackend($uid, $password, UserInterface $backend);
162 162
 
163
-	/**
164
-	 * Get how many users per backend exist (if supported by backend)
165
-	 *
166
-	 * @return array<string, int> an array of backend class name as key and count number as value
167
-	 * @since 8.0.0
168
-	 * @since 33.0.0 $onlyMappedUsers parameter
169
-	 */
170
-	public function countUsers(bool $onlyMappedUsers = false);
163
+    /**
164
+     * Get how many users per backend exist (if supported by backend)
165
+     *
166
+     * @return array<string, int> an array of backend class name as key and count number as value
167
+     * @since 8.0.0
168
+     * @since 33.0.0 $onlyMappedUsers parameter
169
+     */
170
+    public function countUsers(bool $onlyMappedUsers = false);
171 171
 
172
-	/**
173
-	 * Get how many users exists in total, whithin limit
174
-	 *
175
-	 * @param int $limit Limit the count to avoid resource waste. 0 to disable
176
-	 * @param bool $onlyMappedUsers Count mapped users instead of all users for compatible backends
177
-	 *
178
-	 * @since 31.0.0
179
-	 */
180
-	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false;
172
+    /**
173
+     * Get how many users exists in total, whithin limit
174
+     *
175
+     * @param int $limit Limit the count to avoid resource waste. 0 to disable
176
+     * @param bool $onlyMappedUsers Count mapped users instead of all users for compatible backends
177
+     *
178
+     * @since 31.0.0
179
+     */
180
+    public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false;
181 181
 
182
-	/**
183
-	 * @param \Closure $callback
184
-	 * @psalm-param \Closure(\OCP\IUser):void $callback
185
-	 * @param string $search
186
-	 * @since 9.0.0
187
-	 */
188
-	public function callForAllUsers(\Closure $callback, $search = '');
182
+    /**
183
+     * @param \Closure $callback
184
+     * @psalm-param \Closure(\OCP\IUser):void $callback
185
+     * @param string $search
186
+     * @since 9.0.0
187
+     */
188
+    public function callForAllUsers(\Closure $callback, $search = '');
189 189
 
190
-	/**
191
-	 * returns how many users have logged in once
192
-	 *
193
-	 * @return int
194
-	 * @since 11.0.0
195
-	 */
196
-	public function countDisabledUsers();
190
+    /**
191
+     * returns how many users have logged in once
192
+     *
193
+     * @return int
194
+     * @since 11.0.0
195
+     */
196
+    public function countDisabledUsers();
197 197
 
198
-	/**
199
-	 * returns how many users have logged in once
200
-	 *
201
-	 * @return int
202
-	 * @since 11.0.0
203
-	 */
204
-	public function countSeenUsers();
198
+    /**
199
+     * returns how many users have logged in once
200
+     *
201
+     * @return int
202
+     * @since 11.0.0
203
+     */
204
+    public function countSeenUsers();
205 205
 
206
-	/**
207
-	 * @param \Closure $callback
208
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
209
-	 * @since 11.0.0
210
-	 */
211
-	public function callForSeenUsers(\Closure $callback);
206
+    /**
207
+     * @param \Closure $callback
208
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
209
+     * @since 11.0.0
210
+     */
211
+    public function callForSeenUsers(\Closure $callback);
212 212
 
213
-	/**
214
-	 * returns all users having the provided email set as system email address
215
-	 *
216
-	 * @param string $email
217
-	 * @return IUser[]
218
-	 * @since 9.1.0
219
-	 */
220
-	public function getByEmail($email);
213
+    /**
214
+     * returns all users having the provided email set as system email address
215
+     *
216
+     * @param string $email
217
+     * @return IUser[]
218
+     * @since 9.1.0
219
+     */
220
+    public function getByEmail($email);
221 221
 
222
-	/**
223
-	 * @param string $uid The user ID to validate
224
-	 * @param bool $checkDataDirectory Whether it should be checked if files for the ID exist inside the data directory
225
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the ID is not valid
226
-	 * @since 26.0.0
227
-	 */
228
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void;
222
+    /**
223
+     * @param string $uid The user ID to validate
224
+     * @param bool $checkDataDirectory Whether it should be checked if files for the ID exist inside the data directory
225
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the ID is not valid
226
+     * @since 26.0.0
227
+     */
228
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void;
229 229
 
230
-	/**
231
-	 * Gets the list of users sorted by lastLogin, from most recent to least recent
232
-	 *
233
-	 * @param int|null $limit how many records to fetch
234
-	 * @param int $offset from which offset to fetch
235
-	 * @param string $search search users based on search params
236
-	 * @return list<string> list of user IDs
237
-	 * @since 30.0.0
238
-	 */
239
-	public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
230
+    /**
231
+     * Gets the list of users sorted by lastLogin, from most recent to least recent
232
+     *
233
+     * @param int|null $limit how many records to fetch
234
+     * @param int $offset from which offset to fetch
235
+     * @param string $search search users based on search params
236
+     * @return list<string> list of user IDs
237
+     * @since 30.0.0
238
+     */
239
+    public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
240 240
 
241
-	/**
242
-	 * Gets the list of users.
243
-	 * An iterator is returned allowing the caller to stop the iteration at any time.
244
-	 * The offset argument allows the caller to continue the iteration at a specific offset.
245
-	 *
246
-	 * @since 33.0.0 users are yielded with the user id as key
247
-	 *
248
-	 * @param int $offset from which offset to fetch
249
-	 * @param int|null $limit maximum number of records to fetch
250
-	 * @return \Iterator<string, IUser> list of IUser object
251
-	 * @since 32.0.0
252
-	 */
253
-	public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator;
241
+    /**
242
+     * Gets the list of users.
243
+     * An iterator is returned allowing the caller to stop the iteration at any time.
244
+     * The offset argument allows the caller to continue the iteration at a specific offset.
245
+     *
246
+     * @since 33.0.0 users are yielded with the user id as key
247
+     *
248
+     * @param int $offset from which offset to fetch
249
+     * @param int|null $limit maximum number of records to fetch
250
+     * @return \Iterator<string, IUser> list of IUser object
251
+     * @since 32.0.0
252
+     */
253
+    public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator;
254 254
 
255
-	/**
256
-	 * Get a user by user id without validating that the user exists.
257
-	 *
258
-	 * This should only be used if you're certain that the provided user id exists in the system.
259
-	 * Using this to get a user object for a non-existing user will lead to unexpected behavior down the line.
260
-	 *
261
-	 * If you're not 100% sure that the user exists, use IUserManager::get instead.
262
-	 *
263
-	 * @param string $userId
264
-	 * @param ?string $displayName If the display name is known in advance you can provide it so it doesn't have to be fetched again
265
-	 * @return IUser
266
-	 * @since 33.0.0
267
-	 */
268
-	public function getExistingUser(string $userId, ?string $displayName = null): IUser;
255
+    /**
256
+     * Get a user by user id without validating that the user exists.
257
+     *
258
+     * This should only be used if you're certain that the provided user id exists in the system.
259
+     * Using this to get a user object for a non-existing user will lead to unexpected behavior down the line.
260
+     *
261
+     * If you're not 100% sure that the user exists, use IUserManager::get instead.
262
+     *
263
+     * @param string $userId
264
+     * @param ?string $displayName If the display name is known in advance you can provide it so it doesn't have to be fetched again
265
+     * @return IUser
266
+     * @since 33.0.0
267
+     */
268
+    public function getExistingUser(string $userId, ?string $displayName = null): IUser;
269 269
 }
Please login to merge, or discard this patch.
lib/private/User/Manager.php 1 patch
Indentation   +783 added lines, -783 removed lines patch added patch discarded remove patch
@@ -54,787 +54,787 @@
 block discarded – undo
54 54
  * @package OC\User
55 55
  */
56 56
 class Manager extends PublicEmitter implements IUserManager {
57
-	/**
58
-	 * @var UserInterface[] $backends
59
-	 */
60
-	private array $backends = [];
61
-
62
-	/**
63
-	 * @var array<string,\OC\User\User> $cachedUsers
64
-	 */
65
-	private array $cachedUsers = [];
66
-
67
-	private ICache $cache;
68
-
69
-	private DisplayNameCache $displayNameCache;
70
-
71
-	// This constructor can't autoload any class requiring a DB connection.
72
-	public function __construct(
73
-		private IConfig $config,
74
-		ICacheFactory $cacheFactory,
75
-		private IEventDispatcher $eventDispatcher,
76
-		private LoggerInterface $logger,
77
-	) {
78
-		$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
79
-		$this->listen('\OC\User', 'postDelete', function (IUser $user): void {
80
-			unset($this->cachedUsers[$user->getUID()]);
81
-		});
82
-		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
83
-	}
84
-
85
-	/**
86
-	 * Get the active backends
87
-	 * @return UserInterface[]
88
-	 */
89
-	public function getBackends(): array {
90
-		return $this->backends;
91
-	}
92
-
93
-	public function registerBackend(UserInterface $backend): void {
94
-		$this->backends[] = $backend;
95
-	}
96
-
97
-	public function removeBackend(UserInterface $backend): void {
98
-		$this->cachedUsers = [];
99
-		if (($i = array_search($backend, $this->backends)) !== false) {
100
-			unset($this->backends[$i]);
101
-		}
102
-	}
103
-
104
-	public function clearBackends(): void {
105
-		$this->cachedUsers = [];
106
-		$this->backends = [];
107
-	}
108
-
109
-	/**
110
-	 * get a user by user id
111
-	 *
112
-	 * @param string $uid
113
-	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
114
-	 */
115
-	public function get($uid) {
116
-		if (is_null($uid) || $uid === '' || $uid === false) {
117
-			return null;
118
-		}
119
-		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
120
-			return $this->cachedUsers[$uid];
121
-		}
122
-
123
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
124
-			return null;
125
-		}
126
-
127
-		$cachedBackend = $this->cache->get(sha1($uid));
128
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
129
-			// Cache has the info of the user backend already, so ask that one directly
130
-			$backend = $this->backends[$cachedBackend];
131
-			if ($backend->userExists($uid)) {
132
-				return $this->getUserObject($uid, $backend);
133
-			}
134
-		}
135
-
136
-		foreach ($this->backends as $i => $backend) {
137
-			if ($i === $cachedBackend) {
138
-				// Tried that one already
139
-				continue;
140
-			}
141
-
142
-			if ($backend->userExists($uid)) {
143
-				// Hash $uid to ensure that only valid characters are used for the cache key
144
-				$this->cache->set(sha1($uid), $i, 300);
145
-				return $this->getUserObject($uid, $backend);
146
-			}
147
-		}
148
-		return null;
149
-	}
150
-
151
-	public function getDisplayName(string $uid): ?string {
152
-		return $this->displayNameCache->getDisplayName($uid);
153
-	}
154
-
155
-	/**
156
-	 * get or construct the user object
157
-	 *
158
-	 * @param string $uid
159
-	 * @param \OCP\UserInterface $backend
160
-	 * @param bool $cacheUser If false the newly created user object will not be cached
161
-	 * @return \OC\User\User
162
-	 */
163
-	public function getUserObject($uid, $backend, $cacheUser = true) {
164
-		if ($backend instanceof IGetRealUIDBackend) {
165
-			$uid = $backend->getRealUID($uid);
166
-		}
167
-
168
-		if (isset($this->cachedUsers[$uid])) {
169
-			return $this->cachedUsers[$uid];
170
-		}
171
-
172
-		$user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config);
173
-		if ($cacheUser) {
174
-			$this->cachedUsers[$uid] = $user;
175
-		}
176
-		return $user;
177
-	}
178
-
179
-	/**
180
-	 * check if a user exists
181
-	 *
182
-	 * @param string $uid
183
-	 * @return bool
184
-	 */
185
-	public function userExists($uid) {
186
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
187
-			return false;
188
-		}
189
-
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 IUser|false 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
-			$this->logger->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
-		$cachedBackend = $this->cache->get($loginName);
224
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
225
-			$backends = [$this->backends[$cachedBackend]];
226
-		} else {
227
-			$backends = $this->backends;
228
-		}
229
-		foreach ($backends as $backend) {
230
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
231
-				/** @var ICheckPasswordBackend $backend */
232
-				$uid = $backend->checkPassword($loginName, $password);
233
-				if ($uid !== false) {
234
-					return $this->getUserObject($uid, $backend);
235
-				}
236
-			}
237
-		}
238
-
239
-		// since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
240
-		// we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
241
-		// to contain urlencoded patterns by "accident".
242
-		$password = urldecode($password);
243
-
244
-		foreach ($backends as $backend) {
245
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
246
-				/** @var ICheckPasswordBackend|UserInterface $backend */
247
-				$uid = $backend->checkPassword($loginName, $password);
248
-				if ($uid !== false) {
249
-					return $this->getUserObject($uid, $backend);
250
-				}
251
-			}
252
-		}
253
-
254
-		return false;
255
-	}
256
-
257
-	public function search($pattern, $limit = null, $offset = null) {
258
-		$users = [];
259
-		foreach ($this->backends as $backend) {
260
-			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
261
-			if (is_array($backendUsers)) {
262
-				foreach ($backendUsers as $uid) {
263
-					$users[$uid] = new LazyUser($uid, $this, null, $backend);
264
-				}
265
-			}
266
-		}
267
-
268
-		uasort($users, function (IUser $a, IUser $b) {
269
-			return strcasecmp($a->getUID(), $b->getUID());
270
-		});
271
-		return $users;
272
-	}
273
-
274
-	public function searchDisplayName($pattern, $limit = null, $offset = null) {
275
-		$users = [];
276
-		foreach ($this->backends as $backend) {
277
-			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
278
-			if (is_array($backendUsers)) {
279
-				foreach ($backendUsers as $uid => $displayName) {
280
-					$users[] = new LazyUser($uid, $this, $displayName, $backend);
281
-				}
282
-			}
283
-		}
284
-
285
-		usort($users, function (IUser $a, IUser $b) {
286
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
287
-		});
288
-		return $users;
289
-	}
290
-
291
-	/**
292
-	 * @return IUser[]
293
-	 */
294
-	public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
295
-		$users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
296
-		$users = array_combine(
297
-			$users,
298
-			array_map(
299
-				fn (string $uid): IUser => new LazyUser($uid, $this),
300
-				$users
301
-			)
302
-		);
303
-		if ($search !== '') {
304
-			$users = array_filter(
305
-				$users,
306
-				function (IUser $user) use ($search): bool {
307
-					try {
308
-						return mb_stripos($user->getUID(), $search) !== false
309
-						|| mb_stripos($user->getDisplayName(), $search) !== false
310
-						|| mb_stripos($user->getEMailAddress() ?? '', $search) !== false;
311
-					} catch (NoUserException $ex) {
312
-						$this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]);
313
-						return false;
314
-					}
315
-				});
316
-		}
317
-
318
-		$tempLimit = ($limit === null ? null : $limit + $offset);
319
-		foreach ($this->backends as $backend) {
320
-			if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
321
-				break;
322
-			}
323
-			if ($backend instanceof IProvideEnabledStateBackend) {
324
-				$backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search);
325
-				foreach ($backendUsers as $uid) {
326
-					$users[$uid] = new LazyUser($uid, $this, null, $backend);
327
-				}
328
-			}
329
-		}
330
-
331
-		return array_slice($users, $offset, $limit);
332
-	}
333
-
334
-	/**
335
-	 * Search known users (from phonebook sync) by displayName
336
-	 *
337
-	 * @param string $searcher
338
-	 * @param string $pattern
339
-	 * @param int|null $limit
340
-	 * @param int|null $offset
341
-	 * @return IUser[]
342
-	 */
343
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
344
-		$users = [];
345
-		foreach ($this->backends as $backend) {
346
-			if ($backend instanceof ISearchKnownUsersBackend) {
347
-				$backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
348
-			} else {
349
-				// Better than nothing, but filtering after pagination can remove lots of results.
350
-				$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
351
-			}
352
-			if (is_array($backendUsers)) {
353
-				foreach ($backendUsers as $uid => $displayName) {
354
-					$users[] = $this->getUserObject($uid, $backend);
355
-				}
356
-			}
357
-		}
358
-
359
-		usort($users, function ($a, $b) {
360
-			/**
361
-			 * @var IUser $a
362
-			 * @var IUser $b
363
-			 */
364
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
365
-		});
366
-		return $users;
367
-	}
368
-
369
-	/**
370
-	 * @param string $uid
371
-	 * @param string $password
372
-	 * @return false|IUser the created user or false
373
-	 * @throws \InvalidArgumentException
374
-	 * @throws HintException
375
-	 */
376
-	public function createUser($uid, $password) {
377
-		// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
378
-		/** @var IAssertion $assertion */
379
-		$assertion = \OC::$server->get(IAssertion::class);
380
-		$assertion->createUserIsLegit();
381
-
382
-		$localBackends = [];
383
-		foreach ($this->backends as $backend) {
384
-			if ($backend instanceof Database) {
385
-				// First check if there is another user backend
386
-				$localBackends[] = $backend;
387
-				continue;
388
-			}
389
-
390
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
391
-				return $this->createUserFromBackend($uid, $password, $backend);
392
-			}
393
-		}
394
-
395
-		foreach ($localBackends as $backend) {
396
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
397
-				return $this->createUserFromBackend($uid, $password, $backend);
398
-			}
399
-		}
400
-
401
-		return false;
402
-	}
403
-
404
-	/**
405
-	 * @param string $uid
406
-	 * @param string $password
407
-	 * @param UserInterface $backend
408
-	 * @return IUser|false
409
-	 * @throws \InvalidArgumentException
410
-	 */
411
-	public function createUserFromBackend($uid, $password, UserInterface $backend) {
412
-		$l = \OCP\Util::getL10N('lib');
413
-
414
-		$this->validateUserId($uid, true);
415
-
416
-		// No empty password
417
-		if (trim($password) === '') {
418
-			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
419
-		}
420
-
421
-		// Check if user already exists
422
-		if ($this->userExists($uid)) {
423
-			throw new \InvalidArgumentException($l->t('The Login is already being used'));
424
-		}
425
-
426
-		/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
427
-		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
428
-		$this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
429
-		$state = $backend->createUser($uid, $password);
430
-		if ($state === false) {
431
-			throw new \InvalidArgumentException($l->t('Could not create account'));
432
-		}
433
-		$user = $this->getUserObject($uid, $backend);
434
-		if ($user instanceof IUser) {
435
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
436
-			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
437
-			$this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
438
-			return $user;
439
-		}
440
-		return false;
441
-	}
442
-
443
-	/**
444
-	 * returns how many users per backend exist (if supported by backend)
445
-	 *
446
-	 * @return array<string, int> an array of backend class as key and count number as value
447
-	 */
448
-	public function countUsers(bool $onlyMappedUsers = false) {
449
-		$userCountStatistics = [];
450
-		foreach ($this->backends as $backend) {
451
-			$name = $backend instanceof IUserBackend
452
-				? $backend->getBackendName()
453
-				: get_class($backend);
454
-
455
-			if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
456
-				$userCountStatistics[$name] = $backend->countMappedUsers();
457
-				continue;
458
-			}
459
-			if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
460
-				/** @var ICountUsersBackend|IUserBackend $backend */
461
-				$backendUsers = $backend->countUsers();
462
-				if ($backendUsers !== false) {
463
-					if (isset($userCountStatistics[$name])) {
464
-						$userCountStatistics[$name] += $backendUsers;
465
-					} else {
466
-						$userCountStatistics[$name] = $backendUsers;
467
-					}
468
-				}
469
-			}
470
-		}
471
-
472
-		return $userCountStatistics;
473
-	}
474
-
475
-	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
476
-		$userCount = false;
477
-
478
-		foreach ($this->backends as $backend) {
479
-			if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
480
-				$backendUsers = $backend->countMappedUsers();
481
-			} elseif ($backend instanceof ILimitAwareCountUsersBackend) {
482
-				$backendUsers = $backend->countUsers($limit);
483
-			} elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
484
-				/** @var ICountUsersBackend $backend */
485
-				$backendUsers = $backend->countUsers();
486
-			} else {
487
-				$this->logger->debug('Skip backend for user count: ' . get_class($backend));
488
-				continue;
489
-			}
490
-			if ($backendUsers !== false) {
491
-				$userCount = (int)$userCount + $backendUsers;
492
-				if ($limit > 0) {
493
-					if ($userCount >= $limit) {
494
-						break;
495
-					}
496
-					$limit -= $userCount;
497
-				}
498
-			} else {
499
-				$this->logger->warning('Can not determine user count for ' . get_class($backend));
500
-			}
501
-		}
502
-		return $userCount;
503
-	}
504
-
505
-	/**
506
-	 * returns how many users per backend exist in the requested groups (if supported by backend)
507
-	 *
508
-	 * @param IGroup[] $groups an array of groups to search in
509
-	 * @param int $limit limit to stop counting
510
-	 * @return array{int,int} total number of users, and number of disabled users in the given groups, below $limit. If limit is reached, -1 is returned for number of disabled users
511
-	 */
512
-	public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array {
513
-		$users = [];
514
-		$disabled = [];
515
-		foreach ($groups as $group) {
516
-			foreach ($group->getUsers() as $user) {
517
-				$users[$user->getUID()] = 1;
518
-				if (!$user->isEnabled()) {
519
-					$disabled[$user->getUID()] = 1;
520
-				}
521
-				if (count($users) >= $limit) {
522
-					return [count($users),-1];
523
-				}
524
-			}
525
-		}
526
-		return [count($users),count($disabled)];
527
-	}
528
-
529
-	/**
530
-	 * The callback is executed for each user on each backend.
531
-	 * If the callback returns false no further users will be retrieved.
532
-	 *
533
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
534
-	 * @param string $search
535
-	 * @param boolean $onlySeen when true only users that have a lastLogin entry
536
-	 *                          in the preferences table will be affected
537
-	 * @since 9.0.0
538
-	 */
539
-	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
540
-		if ($onlySeen) {
541
-			$this->callForSeenUsers($callback);
542
-		} else {
543
-			foreach ($this->getBackends() as $backend) {
544
-				$limit = 500;
545
-				$offset = 0;
546
-				do {
547
-					$users = $backend->getUsers($search, $limit, $offset);
548
-					foreach ($users as $uid) {
549
-						if (!$backend->userExists($uid)) {
550
-							continue;
551
-						}
552
-						$user = $this->getUserObject($uid, $backend, false);
553
-						$return = $callback($user);
554
-						if ($return === false) {
555
-							break;
556
-						}
557
-					}
558
-					$offset += $limit;
559
-				} while (count($users) >= $limit);
560
-			}
561
-		}
562
-	}
563
-
564
-	/**
565
-	 * returns how many users are disabled
566
-	 *
567
-	 * @return int
568
-	 * @since 12.0.0
569
-	 */
570
-	public function countDisabledUsers(): int {
571
-		$queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
572
-		$queryBuilder->select($queryBuilder->func()->count('*'))
573
-			->from('preferences')
574
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
575
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
576
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
577
-
578
-
579
-		$result = $queryBuilder->executeQuery();
580
-		$count = $result->fetchOne();
581
-		$result->closeCursor();
582
-
583
-		if ($count !== false) {
584
-			$count = (int)$count;
585
-		} else {
586
-			$count = 0;
587
-		}
588
-
589
-		return $count;
590
-	}
591
-
592
-	/**
593
-	 * returns how many users have logged in once
594
-	 *
595
-	 * @return int
596
-	 * @since 11.0.0
597
-	 */
598
-	public function countSeenUsers() {
599
-		$queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
600
-		$queryBuilder->select($queryBuilder->func()->count('*'))
601
-			->from('preferences')
602
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
603
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
604
-
605
-		$query = $queryBuilder->executeQuery();
606
-
607
-		$result = (int)$query->fetchOne();
608
-		$query->closeCursor();
609
-
610
-		return $result;
611
-	}
612
-
613
-	public function callForSeenUsers(\Closure $callback) {
614
-		$users = $this->getSeenUsers();
615
-		foreach ($users as $user) {
616
-			$return = $callback($user);
617
-			if ($return === false) {
618
-				return;
619
-			}
620
-		}
621
-	}
622
-
623
-	/**
624
-	 * Getting all userIds that have a lastLogin value requires checking the
625
-	 * value in php because on oracle you cannot use a clob in a where clause,
626
-	 * preventing us from doing a not null or length(value) > 0 check.
627
-	 *
628
-	 * @param int $limit
629
-	 * @param int $offset
630
-	 * @return string[] with user ids
631
-	 */
632
-	private function getSeenUserIds($limit = null, $offset = null) {
633
-		$queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
634
-		$queryBuilder->select(['userid'])
635
-			->from('preferences')
636
-			->where($queryBuilder->expr()->eq(
637
-				'appid', $queryBuilder->createNamedParameter('login'))
638
-			)
639
-			->andWhere($queryBuilder->expr()->eq(
640
-				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
641
-			)
642
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
643
-			);
644
-
645
-		if ($limit !== null) {
646
-			$queryBuilder->setMaxResults($limit);
647
-		}
648
-		if ($offset !== null) {
649
-			$queryBuilder->setFirstResult($offset);
650
-		}
651
-		$query = $queryBuilder->executeQuery();
652
-		$result = [];
653
-
654
-		while ($row = $query->fetch()) {
655
-			$result[] = $row['userid'];
656
-		}
657
-
658
-		$query->closeCursor();
659
-
660
-		return $result;
661
-	}
662
-
663
-	/**
664
-	 * @internal Only for mocks it in unit tests.
665
-	 */
666
-	public function getUserConfig(): IUserConfig {
667
-		return \OCP\Server::get(IUserConfig::class);
668
-	}
669
-
670
-	/**
671
-	 * @param string $email
672
-	 * @return IUser[]
673
-	 * @since 9.1.0
674
-	 */
675
-	public function getByEmail($email): array {
676
-		$users = [];
677
-		$userConfig = $this->getUserConfig();
678
-		// looking for 'email' only (and not primary_mail) is intentional
679
-		$userIds = $userConfig->searchUsersByValueString('settings', 'email', $email, caseInsensitive: true);
680
-		foreach ($userIds as $userId) {
681
-			$user = $this->get($userId);
682
-			if ($user !== null) {
683
-				$users[] = $user;
684
-			}
685
-		}
686
-		return $users;
687
-	}
688
-
689
-	/**
690
-	 * @param string $uid
691
-	 * @param bool $checkDataDirectory
692
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
693
-	 * @since 26.0.0
694
-	 */
695
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
696
-		$l = Server::get(IFactory::class)->get('lib');
697
-
698
-		// Check the ID for bad characters
699
-		// Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
700
-		if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
701
-			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:'
702
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
703
-		}
704
-
705
-		// No empty user ID
706
-		if (trim($uid) === '') {
707
-			throw new \InvalidArgumentException($l->t('A valid Login must be provided'));
708
-		}
709
-
710
-		// No whitespace at the beginning or at the end
711
-		if (trim($uid) !== $uid) {
712
-			throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end'));
713
-		}
714
-
715
-		// User ID only consists of 1 or 2 dots (directory traversal)
716
-		if ($uid === '.' || $uid === '..') {
717
-			throw new \InvalidArgumentException($l->t('Login must not consist of dots only'));
718
-		}
719
-
720
-		// User ID is too long
721
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
722
-			// TRANSLATORS User ID is too long
723
-			throw new \InvalidArgumentException($l->t('Username is too long'));
724
-		}
725
-
726
-		if (!$this->verifyUid($uid, $checkDataDirectory)) {
727
-			throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user'));
728
-		}
729
-	}
730
-
731
-	/**
732
-	 * Gets the list of user ids sorted by lastLogin, from most recent to least recent
733
-	 *
734
-	 * @param int|null $limit how many users to fetch (default: 25, max: 100)
735
-	 * @param int $offset from which offset to fetch
736
-	 * @param string $search search users based on search params
737
-	 * @return list<string> list of user IDs
738
-	 */
739
-	public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
740
-		// We can't load all users who already logged in
741
-		$limit = min(100, $limit ?: 25);
742
-
743
-		$connection = Server::get(IDBConnection::class);
744
-		$queryBuilder = $connection->getQueryBuilder();
745
-		$queryBuilder->select('pref_login.userid')
746
-			->from('preferences', 'pref_login')
747
-			->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login')))
748
-			->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin')))
749
-			->setFirstResult($offset)
750
-			->setMaxResults($limit)
751
-		;
752
-
753
-		// Oracle don't want to run ORDER BY on CLOB column
754
-		$loginOrder = $connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE
755
-			? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT)
756
-			: 'pref_login.configvalue';
757
-		$queryBuilder
758
-			->orderBy($loginOrder, 'DESC')
759
-			->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC');
760
-
761
-		if ($search !== '') {
762
-			$displayNameMatches = $this->searchDisplayName($search);
763
-			$matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches);
764
-
765
-			$queryBuilder
766
-				->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX(
767
-					$queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'),
768
-					$queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')),
769
-					$queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')),
770
-				))
771
-				->andWhere($queryBuilder->expr()->orX(
772
-					$queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)),
773
-				));
774
-		}
775
-
776
-		/** @var list<string> */
777
-		$list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
778
-
779
-		return $list;
780
-	}
781
-
782
-	private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
783
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
784
-
785
-		if (\in_array($uid, [
786
-			'.htaccess',
787
-			'files_external',
788
-			'__groupfolders',
789
-			'.ncdata',
790
-			'owncloud.log',
791
-			'nextcloud.log',
792
-			'updater.log',
793
-			'audit.log',
794
-			$appdata], true)) {
795
-			return false;
796
-		}
797
-
798
-		if (!$checkDataDirectory) {
799
-			return true;
800
-		}
801
-
802
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
803
-
804
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
805
-	}
806
-
807
-	public function getDisplayNameCache(): DisplayNameCache {
808
-		return $this->displayNameCache;
809
-	}
810
-
811
-	public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
812
-		$maxBatchSize = 1000;
813
-
814
-		do {
815
-			if ($limit !== null) {
816
-				$batchSize = min($limit, $maxBatchSize);
817
-				$limit -= $batchSize;
818
-			} else {
819
-				$batchSize = $maxBatchSize;
820
-			}
821
-
822
-			$userIds = $this->getSeenUserIds($batchSize, $offset);
823
-			$offset += $batchSize;
824
-
825
-			foreach ($userIds as $userId) {
826
-				foreach ($this->backends as $backend) {
827
-					if ($backend->userExists($userId)) {
828
-						$user = new LazyUser($userId, $this, null, $backend);
829
-						yield $userId => $user;
830
-						break;
831
-					}
832
-				}
833
-			}
834
-		} while (count($userIds) === $batchSize && $limit !== 0);
835
-	}
836
-
837
-	public function getExistingUser(string $userId, ?string $displayName = null): IUser {
838
-		return new LazyUser($userId, $this, $displayName);
839
-	}
57
+    /**
58
+     * @var UserInterface[] $backends
59
+     */
60
+    private array $backends = [];
61
+
62
+    /**
63
+     * @var array<string,\OC\User\User> $cachedUsers
64
+     */
65
+    private array $cachedUsers = [];
66
+
67
+    private ICache $cache;
68
+
69
+    private DisplayNameCache $displayNameCache;
70
+
71
+    // This constructor can't autoload any class requiring a DB connection.
72
+    public function __construct(
73
+        private IConfig $config,
74
+        ICacheFactory $cacheFactory,
75
+        private IEventDispatcher $eventDispatcher,
76
+        private LoggerInterface $logger,
77
+    ) {
78
+        $this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
79
+        $this->listen('\OC\User', 'postDelete', function (IUser $user): void {
80
+            unset($this->cachedUsers[$user->getUID()]);
81
+        });
82
+        $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
83
+    }
84
+
85
+    /**
86
+     * Get the active backends
87
+     * @return UserInterface[]
88
+     */
89
+    public function getBackends(): array {
90
+        return $this->backends;
91
+    }
92
+
93
+    public function registerBackend(UserInterface $backend): void {
94
+        $this->backends[] = $backend;
95
+    }
96
+
97
+    public function removeBackend(UserInterface $backend): void {
98
+        $this->cachedUsers = [];
99
+        if (($i = array_search($backend, $this->backends)) !== false) {
100
+            unset($this->backends[$i]);
101
+        }
102
+    }
103
+
104
+    public function clearBackends(): void {
105
+        $this->cachedUsers = [];
106
+        $this->backends = [];
107
+    }
108
+
109
+    /**
110
+     * get a user by user id
111
+     *
112
+     * @param string $uid
113
+     * @return \OC\User\User|null Either the user or null if the specified user does not exist
114
+     */
115
+    public function get($uid) {
116
+        if (is_null($uid) || $uid === '' || $uid === false) {
117
+            return null;
118
+        }
119
+        if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
120
+            return $this->cachedUsers[$uid];
121
+        }
122
+
123
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
124
+            return null;
125
+        }
126
+
127
+        $cachedBackend = $this->cache->get(sha1($uid));
128
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
129
+            // Cache has the info of the user backend already, so ask that one directly
130
+            $backend = $this->backends[$cachedBackend];
131
+            if ($backend->userExists($uid)) {
132
+                return $this->getUserObject($uid, $backend);
133
+            }
134
+        }
135
+
136
+        foreach ($this->backends as $i => $backend) {
137
+            if ($i === $cachedBackend) {
138
+                // Tried that one already
139
+                continue;
140
+            }
141
+
142
+            if ($backend->userExists($uid)) {
143
+                // Hash $uid to ensure that only valid characters are used for the cache key
144
+                $this->cache->set(sha1($uid), $i, 300);
145
+                return $this->getUserObject($uid, $backend);
146
+            }
147
+        }
148
+        return null;
149
+    }
150
+
151
+    public function getDisplayName(string $uid): ?string {
152
+        return $this->displayNameCache->getDisplayName($uid);
153
+    }
154
+
155
+    /**
156
+     * get or construct the user object
157
+     *
158
+     * @param string $uid
159
+     * @param \OCP\UserInterface $backend
160
+     * @param bool $cacheUser If false the newly created user object will not be cached
161
+     * @return \OC\User\User
162
+     */
163
+    public function getUserObject($uid, $backend, $cacheUser = true) {
164
+        if ($backend instanceof IGetRealUIDBackend) {
165
+            $uid = $backend->getRealUID($uid);
166
+        }
167
+
168
+        if (isset($this->cachedUsers[$uid])) {
169
+            return $this->cachedUsers[$uid];
170
+        }
171
+
172
+        $user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config);
173
+        if ($cacheUser) {
174
+            $this->cachedUsers[$uid] = $user;
175
+        }
176
+        return $user;
177
+    }
178
+
179
+    /**
180
+     * check if a user exists
181
+     *
182
+     * @param string $uid
183
+     * @return bool
184
+     */
185
+    public function userExists($uid) {
186
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
187
+            return false;
188
+        }
189
+
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 IUser|false 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
+            $this->logger->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
+        $cachedBackend = $this->cache->get($loginName);
224
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
225
+            $backends = [$this->backends[$cachedBackend]];
226
+        } else {
227
+            $backends = $this->backends;
228
+        }
229
+        foreach ($backends as $backend) {
230
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
231
+                /** @var ICheckPasswordBackend $backend */
232
+                $uid = $backend->checkPassword($loginName, $password);
233
+                if ($uid !== false) {
234
+                    return $this->getUserObject($uid, $backend);
235
+                }
236
+            }
237
+        }
238
+
239
+        // since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
240
+        // we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
241
+        // to contain urlencoded patterns by "accident".
242
+        $password = urldecode($password);
243
+
244
+        foreach ($backends as $backend) {
245
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
246
+                /** @var ICheckPasswordBackend|UserInterface $backend */
247
+                $uid = $backend->checkPassword($loginName, $password);
248
+                if ($uid !== false) {
249
+                    return $this->getUserObject($uid, $backend);
250
+                }
251
+            }
252
+        }
253
+
254
+        return false;
255
+    }
256
+
257
+    public function search($pattern, $limit = null, $offset = null) {
258
+        $users = [];
259
+        foreach ($this->backends as $backend) {
260
+            $backendUsers = $backend->getUsers($pattern, $limit, $offset);
261
+            if (is_array($backendUsers)) {
262
+                foreach ($backendUsers as $uid) {
263
+                    $users[$uid] = new LazyUser($uid, $this, null, $backend);
264
+                }
265
+            }
266
+        }
267
+
268
+        uasort($users, function (IUser $a, IUser $b) {
269
+            return strcasecmp($a->getUID(), $b->getUID());
270
+        });
271
+        return $users;
272
+    }
273
+
274
+    public function searchDisplayName($pattern, $limit = null, $offset = null) {
275
+        $users = [];
276
+        foreach ($this->backends as $backend) {
277
+            $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
278
+            if (is_array($backendUsers)) {
279
+                foreach ($backendUsers as $uid => $displayName) {
280
+                    $users[] = new LazyUser($uid, $this, $displayName, $backend);
281
+                }
282
+            }
283
+        }
284
+
285
+        usort($users, function (IUser $a, IUser $b) {
286
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
287
+        });
288
+        return $users;
289
+    }
290
+
291
+    /**
292
+     * @return IUser[]
293
+     */
294
+    public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
295
+        $users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
296
+        $users = array_combine(
297
+            $users,
298
+            array_map(
299
+                fn (string $uid): IUser => new LazyUser($uid, $this),
300
+                $users
301
+            )
302
+        );
303
+        if ($search !== '') {
304
+            $users = array_filter(
305
+                $users,
306
+                function (IUser $user) use ($search): bool {
307
+                    try {
308
+                        return mb_stripos($user->getUID(), $search) !== false
309
+                        || mb_stripos($user->getDisplayName(), $search) !== false
310
+                        || mb_stripos($user->getEMailAddress() ?? '', $search) !== false;
311
+                    } catch (NoUserException $ex) {
312
+                        $this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]);
313
+                        return false;
314
+                    }
315
+                });
316
+        }
317
+
318
+        $tempLimit = ($limit === null ? null : $limit + $offset);
319
+        foreach ($this->backends as $backend) {
320
+            if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
321
+                break;
322
+            }
323
+            if ($backend instanceof IProvideEnabledStateBackend) {
324
+                $backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search);
325
+                foreach ($backendUsers as $uid) {
326
+                    $users[$uid] = new LazyUser($uid, $this, null, $backend);
327
+                }
328
+            }
329
+        }
330
+
331
+        return array_slice($users, $offset, $limit);
332
+    }
333
+
334
+    /**
335
+     * Search known users (from phonebook sync) by displayName
336
+     *
337
+     * @param string $searcher
338
+     * @param string $pattern
339
+     * @param int|null $limit
340
+     * @param int|null $offset
341
+     * @return IUser[]
342
+     */
343
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
344
+        $users = [];
345
+        foreach ($this->backends as $backend) {
346
+            if ($backend instanceof ISearchKnownUsersBackend) {
347
+                $backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
348
+            } else {
349
+                // Better than nothing, but filtering after pagination can remove lots of results.
350
+                $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
351
+            }
352
+            if (is_array($backendUsers)) {
353
+                foreach ($backendUsers as $uid => $displayName) {
354
+                    $users[] = $this->getUserObject($uid, $backend);
355
+                }
356
+            }
357
+        }
358
+
359
+        usort($users, function ($a, $b) {
360
+            /**
361
+             * @var IUser $a
362
+             * @var IUser $b
363
+             */
364
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
365
+        });
366
+        return $users;
367
+    }
368
+
369
+    /**
370
+     * @param string $uid
371
+     * @param string $password
372
+     * @return false|IUser the created user or false
373
+     * @throws \InvalidArgumentException
374
+     * @throws HintException
375
+     */
376
+    public function createUser($uid, $password) {
377
+        // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
378
+        /** @var IAssertion $assertion */
379
+        $assertion = \OC::$server->get(IAssertion::class);
380
+        $assertion->createUserIsLegit();
381
+
382
+        $localBackends = [];
383
+        foreach ($this->backends as $backend) {
384
+            if ($backend instanceof Database) {
385
+                // First check if there is another user backend
386
+                $localBackends[] = $backend;
387
+                continue;
388
+            }
389
+
390
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
391
+                return $this->createUserFromBackend($uid, $password, $backend);
392
+            }
393
+        }
394
+
395
+        foreach ($localBackends as $backend) {
396
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
397
+                return $this->createUserFromBackend($uid, $password, $backend);
398
+            }
399
+        }
400
+
401
+        return false;
402
+    }
403
+
404
+    /**
405
+     * @param string $uid
406
+     * @param string $password
407
+     * @param UserInterface $backend
408
+     * @return IUser|false
409
+     * @throws \InvalidArgumentException
410
+     */
411
+    public function createUserFromBackend($uid, $password, UserInterface $backend) {
412
+        $l = \OCP\Util::getL10N('lib');
413
+
414
+        $this->validateUserId($uid, true);
415
+
416
+        // No empty password
417
+        if (trim($password) === '') {
418
+            throw new \InvalidArgumentException($l->t('A valid password must be provided'));
419
+        }
420
+
421
+        // Check if user already exists
422
+        if ($this->userExists($uid)) {
423
+            throw new \InvalidArgumentException($l->t('The Login is already being used'));
424
+        }
425
+
426
+        /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
427
+        $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
428
+        $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
429
+        $state = $backend->createUser($uid, $password);
430
+        if ($state === false) {
431
+            throw new \InvalidArgumentException($l->t('Could not create account'));
432
+        }
433
+        $user = $this->getUserObject($uid, $backend);
434
+        if ($user instanceof IUser) {
435
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
436
+            $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
437
+            $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
438
+            return $user;
439
+        }
440
+        return false;
441
+    }
442
+
443
+    /**
444
+     * returns how many users per backend exist (if supported by backend)
445
+     *
446
+     * @return array<string, int> an array of backend class as key and count number as value
447
+     */
448
+    public function countUsers(bool $onlyMappedUsers = false) {
449
+        $userCountStatistics = [];
450
+        foreach ($this->backends as $backend) {
451
+            $name = $backend instanceof IUserBackend
452
+                ? $backend->getBackendName()
453
+                : get_class($backend);
454
+
455
+            if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
456
+                $userCountStatistics[$name] = $backend->countMappedUsers();
457
+                continue;
458
+            }
459
+            if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
460
+                /** @var ICountUsersBackend|IUserBackend $backend */
461
+                $backendUsers = $backend->countUsers();
462
+                if ($backendUsers !== false) {
463
+                    if (isset($userCountStatistics[$name])) {
464
+                        $userCountStatistics[$name] += $backendUsers;
465
+                    } else {
466
+                        $userCountStatistics[$name] = $backendUsers;
467
+                    }
468
+                }
469
+            }
470
+        }
471
+
472
+        return $userCountStatistics;
473
+    }
474
+
475
+    public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
476
+        $userCount = false;
477
+
478
+        foreach ($this->backends as $backend) {
479
+            if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
480
+                $backendUsers = $backend->countMappedUsers();
481
+            } elseif ($backend instanceof ILimitAwareCountUsersBackend) {
482
+                $backendUsers = $backend->countUsers($limit);
483
+            } elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
484
+                /** @var ICountUsersBackend $backend */
485
+                $backendUsers = $backend->countUsers();
486
+            } else {
487
+                $this->logger->debug('Skip backend for user count: ' . get_class($backend));
488
+                continue;
489
+            }
490
+            if ($backendUsers !== false) {
491
+                $userCount = (int)$userCount + $backendUsers;
492
+                if ($limit > 0) {
493
+                    if ($userCount >= $limit) {
494
+                        break;
495
+                    }
496
+                    $limit -= $userCount;
497
+                }
498
+            } else {
499
+                $this->logger->warning('Can not determine user count for ' . get_class($backend));
500
+            }
501
+        }
502
+        return $userCount;
503
+    }
504
+
505
+    /**
506
+     * returns how many users per backend exist in the requested groups (if supported by backend)
507
+     *
508
+     * @param IGroup[] $groups an array of groups to search in
509
+     * @param int $limit limit to stop counting
510
+     * @return array{int,int} total number of users, and number of disabled users in the given groups, below $limit. If limit is reached, -1 is returned for number of disabled users
511
+     */
512
+    public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array {
513
+        $users = [];
514
+        $disabled = [];
515
+        foreach ($groups as $group) {
516
+            foreach ($group->getUsers() as $user) {
517
+                $users[$user->getUID()] = 1;
518
+                if (!$user->isEnabled()) {
519
+                    $disabled[$user->getUID()] = 1;
520
+                }
521
+                if (count($users) >= $limit) {
522
+                    return [count($users),-1];
523
+                }
524
+            }
525
+        }
526
+        return [count($users),count($disabled)];
527
+    }
528
+
529
+    /**
530
+     * The callback is executed for each user on each backend.
531
+     * If the callback returns false no further users will be retrieved.
532
+     *
533
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
534
+     * @param string $search
535
+     * @param boolean $onlySeen when true only users that have a lastLogin entry
536
+     *                          in the preferences table will be affected
537
+     * @since 9.0.0
538
+     */
539
+    public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
540
+        if ($onlySeen) {
541
+            $this->callForSeenUsers($callback);
542
+        } else {
543
+            foreach ($this->getBackends() as $backend) {
544
+                $limit = 500;
545
+                $offset = 0;
546
+                do {
547
+                    $users = $backend->getUsers($search, $limit, $offset);
548
+                    foreach ($users as $uid) {
549
+                        if (!$backend->userExists($uid)) {
550
+                            continue;
551
+                        }
552
+                        $user = $this->getUserObject($uid, $backend, false);
553
+                        $return = $callback($user);
554
+                        if ($return === false) {
555
+                            break;
556
+                        }
557
+                    }
558
+                    $offset += $limit;
559
+                } while (count($users) >= $limit);
560
+            }
561
+        }
562
+    }
563
+
564
+    /**
565
+     * returns how many users are disabled
566
+     *
567
+     * @return int
568
+     * @since 12.0.0
569
+     */
570
+    public function countDisabledUsers(): int {
571
+        $queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
572
+        $queryBuilder->select($queryBuilder->func()->count('*'))
573
+            ->from('preferences')
574
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
575
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
576
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
577
+
578
+
579
+        $result = $queryBuilder->executeQuery();
580
+        $count = $result->fetchOne();
581
+        $result->closeCursor();
582
+
583
+        if ($count !== false) {
584
+            $count = (int)$count;
585
+        } else {
586
+            $count = 0;
587
+        }
588
+
589
+        return $count;
590
+    }
591
+
592
+    /**
593
+     * returns how many users have logged in once
594
+     *
595
+     * @return int
596
+     * @since 11.0.0
597
+     */
598
+    public function countSeenUsers() {
599
+        $queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
600
+        $queryBuilder->select($queryBuilder->func()->count('*'))
601
+            ->from('preferences')
602
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
603
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
604
+
605
+        $query = $queryBuilder->executeQuery();
606
+
607
+        $result = (int)$query->fetchOne();
608
+        $query->closeCursor();
609
+
610
+        return $result;
611
+    }
612
+
613
+    public function callForSeenUsers(\Closure $callback) {
614
+        $users = $this->getSeenUsers();
615
+        foreach ($users as $user) {
616
+            $return = $callback($user);
617
+            if ($return === false) {
618
+                return;
619
+            }
620
+        }
621
+    }
622
+
623
+    /**
624
+     * Getting all userIds that have a lastLogin value requires checking the
625
+     * value in php because on oracle you cannot use a clob in a where clause,
626
+     * preventing us from doing a not null or length(value) > 0 check.
627
+     *
628
+     * @param int $limit
629
+     * @param int $offset
630
+     * @return string[] with user ids
631
+     */
632
+    private function getSeenUserIds($limit = null, $offset = null) {
633
+        $queryBuilder = Server::get(IDBConnection::class)->getQueryBuilder();
634
+        $queryBuilder->select(['userid'])
635
+            ->from('preferences')
636
+            ->where($queryBuilder->expr()->eq(
637
+                'appid', $queryBuilder->createNamedParameter('login'))
638
+            )
639
+            ->andWhere($queryBuilder->expr()->eq(
640
+                'configkey', $queryBuilder->createNamedParameter('lastLogin'))
641
+            )
642
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
643
+            );
644
+
645
+        if ($limit !== null) {
646
+            $queryBuilder->setMaxResults($limit);
647
+        }
648
+        if ($offset !== null) {
649
+            $queryBuilder->setFirstResult($offset);
650
+        }
651
+        $query = $queryBuilder->executeQuery();
652
+        $result = [];
653
+
654
+        while ($row = $query->fetch()) {
655
+            $result[] = $row['userid'];
656
+        }
657
+
658
+        $query->closeCursor();
659
+
660
+        return $result;
661
+    }
662
+
663
+    /**
664
+     * @internal Only for mocks it in unit tests.
665
+     */
666
+    public function getUserConfig(): IUserConfig {
667
+        return \OCP\Server::get(IUserConfig::class);
668
+    }
669
+
670
+    /**
671
+     * @param string $email
672
+     * @return IUser[]
673
+     * @since 9.1.0
674
+     */
675
+    public function getByEmail($email): array {
676
+        $users = [];
677
+        $userConfig = $this->getUserConfig();
678
+        // looking for 'email' only (and not primary_mail) is intentional
679
+        $userIds = $userConfig->searchUsersByValueString('settings', 'email', $email, caseInsensitive: true);
680
+        foreach ($userIds as $userId) {
681
+            $user = $this->get($userId);
682
+            if ($user !== null) {
683
+                $users[] = $user;
684
+            }
685
+        }
686
+        return $users;
687
+    }
688
+
689
+    /**
690
+     * @param string $uid
691
+     * @param bool $checkDataDirectory
692
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
693
+     * @since 26.0.0
694
+     */
695
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
696
+        $l = Server::get(IFactory::class)->get('lib');
697
+
698
+        // Check the ID for bad characters
699
+        // Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
700
+        if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
701
+            throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:'
702
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
703
+        }
704
+
705
+        // No empty user ID
706
+        if (trim($uid) === '') {
707
+            throw new \InvalidArgumentException($l->t('A valid Login must be provided'));
708
+        }
709
+
710
+        // No whitespace at the beginning or at the end
711
+        if (trim($uid) !== $uid) {
712
+            throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end'));
713
+        }
714
+
715
+        // User ID only consists of 1 or 2 dots (directory traversal)
716
+        if ($uid === '.' || $uid === '..') {
717
+            throw new \InvalidArgumentException($l->t('Login must not consist of dots only'));
718
+        }
719
+
720
+        // User ID is too long
721
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
722
+            // TRANSLATORS User ID is too long
723
+            throw new \InvalidArgumentException($l->t('Username is too long'));
724
+        }
725
+
726
+        if (!$this->verifyUid($uid, $checkDataDirectory)) {
727
+            throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user'));
728
+        }
729
+    }
730
+
731
+    /**
732
+     * Gets the list of user ids sorted by lastLogin, from most recent to least recent
733
+     *
734
+     * @param int|null $limit how many users to fetch (default: 25, max: 100)
735
+     * @param int $offset from which offset to fetch
736
+     * @param string $search search users based on search params
737
+     * @return list<string> list of user IDs
738
+     */
739
+    public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
740
+        // We can't load all users who already logged in
741
+        $limit = min(100, $limit ?: 25);
742
+
743
+        $connection = Server::get(IDBConnection::class);
744
+        $queryBuilder = $connection->getQueryBuilder();
745
+        $queryBuilder->select('pref_login.userid')
746
+            ->from('preferences', 'pref_login')
747
+            ->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login')))
748
+            ->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin')))
749
+            ->setFirstResult($offset)
750
+            ->setMaxResults($limit)
751
+        ;
752
+
753
+        // Oracle don't want to run ORDER BY on CLOB column
754
+        $loginOrder = $connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE
755
+            ? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT)
756
+            : 'pref_login.configvalue';
757
+        $queryBuilder
758
+            ->orderBy($loginOrder, 'DESC')
759
+            ->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC');
760
+
761
+        if ($search !== '') {
762
+            $displayNameMatches = $this->searchDisplayName($search);
763
+            $matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches);
764
+
765
+            $queryBuilder
766
+                ->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX(
767
+                    $queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'),
768
+                    $queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')),
769
+                    $queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')),
770
+                ))
771
+                ->andWhere($queryBuilder->expr()->orX(
772
+                    $queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)),
773
+                ));
774
+        }
775
+
776
+        /** @var list<string> */
777
+        $list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
778
+
779
+        return $list;
780
+    }
781
+
782
+    private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
783
+        $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
784
+
785
+        if (\in_array($uid, [
786
+            '.htaccess',
787
+            'files_external',
788
+            '__groupfolders',
789
+            '.ncdata',
790
+            'owncloud.log',
791
+            'nextcloud.log',
792
+            'updater.log',
793
+            'audit.log',
794
+            $appdata], true)) {
795
+            return false;
796
+        }
797
+
798
+        if (!$checkDataDirectory) {
799
+            return true;
800
+        }
801
+
802
+        $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
803
+
804
+        return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
805
+    }
806
+
807
+    public function getDisplayNameCache(): DisplayNameCache {
808
+        return $this->displayNameCache;
809
+    }
810
+
811
+    public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
812
+        $maxBatchSize = 1000;
813
+
814
+        do {
815
+            if ($limit !== null) {
816
+                $batchSize = min($limit, $maxBatchSize);
817
+                $limit -= $batchSize;
818
+            } else {
819
+                $batchSize = $maxBatchSize;
820
+            }
821
+
822
+            $userIds = $this->getSeenUserIds($batchSize, $offset);
823
+            $offset += $batchSize;
824
+
825
+            foreach ($userIds as $userId) {
826
+                foreach ($this->backends as $backend) {
827
+                    if ($backend->userExists($userId)) {
828
+                        $user = new LazyUser($userId, $this, null, $backend);
829
+                        yield $userId => $user;
830
+                        break;
831
+                    }
832
+                }
833
+            }
834
+        } while (count($userIds) === $batchSize && $limit !== 0);
835
+    }
836
+
837
+    public function getExistingUser(string $userId, ?string $displayName = null): IUser {
838
+        return new LazyUser($userId, $this, $displayName);
839
+    }
840 840
 }
Please login to merge, or discard this patch.
tests/lib/User/ManagerTest.php 1 patch
Indentation   +728 added lines, -728 removed lines patch added patch discarded remove patch
@@ -33,733 +33,733 @@
 block discarded – undo
33 33
  */
34 34
 #[\PHPUnit\Framework\Attributes\Group('DB')]
35 35
 class ManagerTest extends TestCase {
36
-	private IConfig&MockObject $config;
37
-	private IEventDispatcher&MockObject $eventDispatcher;
38
-	private ICacheFactory&MockObject $cacheFactory;
39
-	private ICache&MockObject $cache;
40
-	private LoggerInterface&MockObject $logger;
41
-
42
-	protected function setUp(): void {
43
-		parent::setUp();
44
-
45
-		$this->config = $this->createMock(IConfig::class);
46
-		$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
47
-		$this->cacheFactory = $this->createMock(ICacheFactory::class);
48
-		$this->cache = $this->createMock(ICache::class);
49
-		$this->logger = $this->createMock(LoggerInterface::class);
50
-
51
-		$this->cacheFactory->method('createDistributed')
52
-			->willReturn($this->cache);
53
-	}
54
-
55
-	public function testGetBackends(): void {
56
-		$userDummyBackend = $this->createMock(\Test\Util\User\Dummy::class);
57
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
58
-		$manager->registerBackend($userDummyBackend);
59
-		$this->assertEquals([$userDummyBackend], $manager->getBackends());
60
-		$dummyDatabaseBackend = $this->createMock(Database::class);
61
-		$manager->registerBackend($dummyDatabaseBackend);
62
-		$this->assertEquals([$userDummyBackend, $dummyDatabaseBackend], $manager->getBackends());
63
-	}
64
-
65
-
66
-	public function testUserExistsSingleBackendExists(): void {
67
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
68
-		$backend->expects($this->once())
69
-			->method('userExists')
70
-			->with($this->equalTo('foo'))
71
-			->willReturn(true);
72
-
73
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
74
-		$manager->registerBackend($backend);
75
-
76
-		$this->assertTrue($manager->userExists('foo'));
77
-	}
78
-
79
-	public function testUserExistsTooLong(): void {
80
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
81
-		$backend->expects($this->never())
82
-			->method('userExists')
83
-			->with($this->equalTo('foo'))
84
-			->willReturn(true);
85
-
86
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
87
-		$manager->registerBackend($backend);
88
-
89
-		$this->assertFalse($manager->userExists('foo' . str_repeat('a', 62)));
90
-	}
91
-
92
-	public function testUserExistsSingleBackendNotExists(): void {
93
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
94
-		$backend->expects($this->once())
95
-			->method('userExists')
96
-			->with($this->equalTo('foo'))
97
-			->willReturn(false);
98
-
99
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
100
-		$manager->registerBackend($backend);
101
-
102
-		$this->assertFalse($manager->userExists('foo'));
103
-	}
104
-
105
-	public function testUserExistsNoBackends(): void {
106
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
107
-
108
-		$this->assertFalse($manager->userExists('foo'));
109
-	}
110
-
111
-	public function testUserExistsTwoBackendsSecondExists(): void {
112
-		$backend1 = $this->createMock(\Test\Util\User\Dummy::class);
113
-		$backend1->expects($this->once())
114
-			->method('userExists')
115
-			->with($this->equalTo('foo'))
116
-			->willReturn(false);
117
-
118
-		$backend2 = $this->createMock(\Test\Util\User\Dummy::class);
119
-		$backend2->expects($this->once())
120
-			->method('userExists')
121
-			->with($this->equalTo('foo'))
122
-			->willReturn(true);
123
-
124
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
125
-		$manager->registerBackend($backend1);
126
-		$manager->registerBackend($backend2);
127
-
128
-		$this->assertTrue($manager->userExists('foo'));
129
-	}
130
-
131
-	public function testUserExistsTwoBackendsFirstExists(): void {
132
-		$backend1 = $this->createMock(\Test\Util\User\Dummy::class);
133
-		$backend1->expects($this->once())
134
-			->method('userExists')
135
-			->with($this->equalTo('foo'))
136
-			->willReturn(true);
137
-
138
-		$backend2 = $this->createMock(\Test\Util\User\Dummy::class);
139
-		$backend2->expects($this->never())
140
-			->method('userExists');
141
-
142
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
143
-		$manager->registerBackend($backend1);
144
-		$manager->registerBackend($backend2);
145
-
146
-		$this->assertTrue($manager->userExists('foo'));
147
-	}
148
-
149
-	public function testCheckPassword(): void {
150
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
151
-		$backend->expects($this->once())
152
-			->method('checkPassword')
153
-			->with($this->equalTo('foo'), $this->equalTo('bar'))
154
-			->willReturn(true);
155
-
156
-		$backend->expects($this->any())
157
-			->method('implementsActions')
158
-			->willReturnCallback(function ($actions) {
159
-				if ($actions === BACKEND::CHECK_PASSWORD) {
160
-					return true;
161
-				} else {
162
-					return false;
163
-				}
164
-			});
165
-
166
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
167
-		$manager->registerBackend($backend);
168
-
169
-		$user = $manager->checkPassword('foo', 'bar');
170
-		$this->assertTrue($user instanceof User);
171
-	}
172
-
173
-	public function testCheckPasswordNotSupported(): void {
174
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
175
-		$backend->expects($this->never())
176
-			->method('checkPassword');
177
-
178
-		$backend->expects($this->any())
179
-			->method('implementsActions')
180
-			->willReturn(false);
181
-
182
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
183
-		$manager->registerBackend($backend);
184
-
185
-		$this->assertFalse($manager->checkPassword('foo', 'bar'));
186
-	}
187
-
188
-	public function testGetOneBackendExists(): void {
189
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
190
-		$backend->expects($this->once())
191
-			->method('userExists')
192
-			->with($this->equalTo('foo'))
193
-			->willReturn(true);
194
-		$backend->expects($this->never())
195
-			->method('loginName2UserName');
196
-
197
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
198
-		$manager->registerBackend($backend);
199
-
200
-		$this->assertEquals('foo', $manager->get('foo')->getUID());
201
-	}
202
-
203
-	public function testGetOneBackendNotExists(): void {
204
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
205
-		$backend->expects($this->once())
206
-			->method('userExists')
207
-			->with($this->equalTo('foo'))
208
-			->willReturn(false);
209
-
210
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
211
-		$manager->registerBackend($backend);
212
-
213
-		$this->assertEquals(null, $manager->get('foo'));
214
-	}
215
-
216
-	public function testGetTooLong(): void {
217
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
218
-		$backend->expects($this->never())
219
-			->method('userExists')
220
-			->with($this->equalTo('foo'))
221
-			->willReturn(false);
222
-
223
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
224
-		$manager->registerBackend($backend);
225
-
226
-		$this->assertEquals(null, $manager->get('foo' . str_repeat('a', 62)));
227
-	}
228
-
229
-	public function testGetOneBackendDoNotTranslateLoginNames(): void {
230
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
231
-		$backend->expects($this->once())
232
-			->method('userExists')
233
-			->with($this->equalTo('bLeNdEr'))
234
-			->willReturn(true);
235
-		$backend->expects($this->never())
236
-			->method('loginName2UserName');
237
-
238
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
239
-		$manager->registerBackend($backend);
240
-
241
-		$this->assertEquals('bLeNdEr', $manager->get('bLeNdEr')->getUID());
242
-	}
243
-
244
-	public function testSearchOneBackend(): void {
245
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
246
-		$backend->expects($this->once())
247
-			->method('getUsers')
248
-			->with($this->equalTo('fo'))
249
-			->willReturn(['foo', 'afoo', 'Afoo1', 'Bfoo']);
250
-		$backend->expects($this->never())
251
-			->method('loginName2UserName');
252
-
253
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
254
-		$manager->registerBackend($backend);
255
-
256
-		$result = $manager->search('fo');
257
-		$this->assertEquals(4, count($result));
258
-		$this->assertEquals('afoo', array_shift($result)->getUID());
259
-		$this->assertEquals('Afoo1', array_shift($result)->getUID());
260
-		$this->assertEquals('Bfoo', array_shift($result)->getUID());
261
-		$this->assertEquals('foo', array_shift($result)->getUID());
262
-	}
263
-
264
-	public function testSearchTwoBackendLimitOffset(): void {
265
-		$backend1 = $this->createMock(\Test\Util\User\Dummy::class);
266
-		$backend1->expects($this->once())
267
-			->method('getUsers')
268
-			->with($this->equalTo('fo'), $this->equalTo(3), $this->equalTo(1))
269
-			->willReturn(['foo1', 'foo2']);
270
-		$backend1->expects($this->never())
271
-			->method('loginName2UserName');
272
-
273
-		$backend2 = $this->createMock(\Test\Util\User\Dummy::class);
274
-		$backend2->expects($this->once())
275
-			->method('getUsers')
276
-			->with($this->equalTo('fo'), $this->equalTo(3), $this->equalTo(1))
277
-			->willReturn(['foo3']);
278
-		$backend2->expects($this->never())
279
-			->method('loginName2UserName');
280
-
281
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
282
-		$manager->registerBackend($backend1);
283
-		$manager->registerBackend($backend2);
284
-
285
-		$result = $manager->search('fo', 3, 1);
286
-		$this->assertEquals(3, count($result));
287
-		$this->assertEquals('foo1', array_shift($result)->getUID());
288
-		$this->assertEquals('foo2', array_shift($result)->getUID());
289
-		$this->assertEquals('foo3', array_shift($result)->getUID());
290
-	}
291
-
292
-	public static function dataCreateUserInvalid(): array {
293
-		return [
294
-			['te?st', 'foo', 'Only the following characters are allowed in a username:'
295
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
296
-			["te\tst", '', 'Only the following characters are allowed in a username:'
297
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
298
-			["te\nst", '', 'Only the following characters are allowed in a username:'
299
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
300
-			["te\rst", '', 'Only the following characters are allowed in a username:'
301
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
302
-			["te\0st", '', 'Only the following characters are allowed in a username:'
303
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
304
-			["te\x0Bst", '', 'Only the following characters are allowed in a username:'
305
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
306
-			["te\xe2st", '', 'Only the following characters are allowed in a username:'
307
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
308
-			["te\x80st", '', 'Only the following characters are allowed in a username:'
309
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
310
-			["te\x8bst", '', 'Only the following characters are allowed in a username:'
311
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
312
-			['', 'foo', 'A valid username must be provided'],
313
-			[' ', 'foo', 'A valid username must be provided'],
314
-			[' test', 'foo', 'Username contains whitespace at the beginning or at the end'],
315
-			['test ', 'foo', 'Username contains whitespace at the beginning or at the end'],
316
-			['.', 'foo', 'Username must not consist of dots only'],
317
-			['..', 'foo', 'Username must not consist of dots only'],
318
-			['.test', '', 'A valid password must be provided'],
319
-			['test', '', 'A valid password must be provided'],
320
-			['test' . str_repeat('a', 61), '', 'Login is too long'],
321
-		];
322
-	}
323
-
324
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataCreateUserInvalid')]
325
-	public function testCreateUserInvalid($uid, $password, $exception): void {
326
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
327
-		$backend->expects($this->once())
328
-			->method('implementsActions')
329
-			->with(\OC\User\Backend::CREATE_USER)
330
-			->willReturn(true);
331
-
332
-
333
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
334
-		$manager->registerBackend($backend);
335
-
336
-		$this->expectException(\InvalidArgumentException::class, $exception);
337
-		$manager->createUser($uid, $password);
338
-	}
339
-
340
-	public function testCreateUserSingleBackendNotExists(): void {
341
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
342
-		$backend->expects($this->any())
343
-			->method('implementsActions')
344
-			->willReturn(true);
345
-
346
-		$backend->expects($this->once())
347
-			->method('createUser')
348
-			->with($this->equalTo('foo'), $this->equalTo('bar'));
349
-
350
-		$backend->expects($this->once())
351
-			->method('userExists')
352
-			->with($this->equalTo('foo'))
353
-			->willReturn(false);
354
-		$backend->expects($this->never())
355
-			->method('loginName2UserName');
356
-
357
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
358
-		$manager->registerBackend($backend);
359
-
360
-		$user = $manager->createUser('foo', 'bar');
361
-		$this->assertEquals('foo', $user->getUID());
362
-	}
363
-
364
-
365
-	public function testCreateUserSingleBackendExists(): void {
366
-		$this->expectException(\Exception::class);
367
-
368
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
369
-		$backend->expects($this->any())
370
-			->method('implementsActions')
371
-			->willReturn(true);
372
-
373
-		$backend->expects($this->never())
374
-			->method('createUser');
375
-
376
-		$backend->expects($this->once())
377
-			->method('userExists')
378
-			->with($this->equalTo('foo'))
379
-			->willReturn(true);
380
-
381
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
382
-		$manager->registerBackend($backend);
383
-
384
-		$manager->createUser('foo', 'bar');
385
-	}
386
-
387
-	public function testCreateUserSingleBackendNotSupported(): void {
388
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
389
-		$backend->expects($this->any())
390
-			->method('implementsActions')
391
-			->willReturn(false);
392
-
393
-		$backend->expects($this->never())
394
-			->method('createUser');
395
-
396
-		$backend->expects($this->never())
397
-			->method('userExists');
398
-
399
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
400
-		$manager->registerBackend($backend);
401
-
402
-		$this->assertFalse($manager->createUser('foo', 'bar'));
403
-	}
404
-
405
-	public function testCreateUserNoBackends(): void {
406
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
407
-
408
-		$this->assertFalse($manager->createUser('foo', 'bar'));
409
-	}
410
-
411
-
412
-	public function testCreateUserFromBackendWithBackendError(): void {
413
-		$this->expectException(\InvalidArgumentException::class);
414
-		$this->expectExceptionMessage('Could not create account');
415
-
416
-		/** @var \Test\Util\User\Dummy&MockObject $backend */
417
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
418
-		$backend
419
-			->expects($this->once())
420
-			->method('createUser')
421
-			->with('MyUid', 'MyPassword')
422
-			->willReturn(false);
423
-
424
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
425
-		$manager->createUserFromBackend('MyUid', 'MyPassword', $backend);
426
-	}
427
-
428
-
429
-	public function testCreateUserTwoBackendExists(): void {
430
-		$this->expectException(\Exception::class);
431
-
432
-		$backend1 = $this->createMock(\Test\Util\User\Dummy::class);
433
-		$backend1->expects($this->any())
434
-			->method('implementsActions')
435
-			->willReturn(true);
436
-
437
-		$backend1->expects($this->never())
438
-			->method('createUser');
439
-
440
-		$backend1->expects($this->once())
441
-			->method('userExists')
442
-			->with($this->equalTo('foo'))
443
-			->willReturn(false);
444
-
445
-		$backend2 = $this->createMock(\Test\Util\User\Dummy::class);
446
-		$backend2->expects($this->any())
447
-			->method('implementsActions')
448
-			->willReturn(true);
449
-
450
-		$backend2->expects($this->never())
451
-			->method('createUser');
452
-
453
-		$backend2->expects($this->once())
454
-			->method('userExists')
455
-			->with($this->equalTo('foo'))
456
-			->willReturn(true);
457
-
458
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
459
-		$manager->registerBackend($backend1);
460
-		$manager->registerBackend($backend2);
36
+    private IConfig&MockObject $config;
37
+    private IEventDispatcher&MockObject $eventDispatcher;
38
+    private ICacheFactory&MockObject $cacheFactory;
39
+    private ICache&MockObject $cache;
40
+    private LoggerInterface&MockObject $logger;
41
+
42
+    protected function setUp(): void {
43
+        parent::setUp();
44
+
45
+        $this->config = $this->createMock(IConfig::class);
46
+        $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
47
+        $this->cacheFactory = $this->createMock(ICacheFactory::class);
48
+        $this->cache = $this->createMock(ICache::class);
49
+        $this->logger = $this->createMock(LoggerInterface::class);
50
+
51
+        $this->cacheFactory->method('createDistributed')
52
+            ->willReturn($this->cache);
53
+    }
54
+
55
+    public function testGetBackends(): void {
56
+        $userDummyBackend = $this->createMock(\Test\Util\User\Dummy::class);
57
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
58
+        $manager->registerBackend($userDummyBackend);
59
+        $this->assertEquals([$userDummyBackend], $manager->getBackends());
60
+        $dummyDatabaseBackend = $this->createMock(Database::class);
61
+        $manager->registerBackend($dummyDatabaseBackend);
62
+        $this->assertEquals([$userDummyBackend, $dummyDatabaseBackend], $manager->getBackends());
63
+    }
64
+
65
+
66
+    public function testUserExistsSingleBackendExists(): void {
67
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
68
+        $backend->expects($this->once())
69
+            ->method('userExists')
70
+            ->with($this->equalTo('foo'))
71
+            ->willReturn(true);
72
+
73
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
74
+        $manager->registerBackend($backend);
75
+
76
+        $this->assertTrue($manager->userExists('foo'));
77
+    }
78
+
79
+    public function testUserExistsTooLong(): void {
80
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
81
+        $backend->expects($this->never())
82
+            ->method('userExists')
83
+            ->with($this->equalTo('foo'))
84
+            ->willReturn(true);
85
+
86
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
87
+        $manager->registerBackend($backend);
88
+
89
+        $this->assertFalse($manager->userExists('foo' . str_repeat('a', 62)));
90
+    }
91
+
92
+    public function testUserExistsSingleBackendNotExists(): void {
93
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
94
+        $backend->expects($this->once())
95
+            ->method('userExists')
96
+            ->with($this->equalTo('foo'))
97
+            ->willReturn(false);
98
+
99
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
100
+        $manager->registerBackend($backend);
101
+
102
+        $this->assertFalse($manager->userExists('foo'));
103
+    }
104
+
105
+    public function testUserExistsNoBackends(): void {
106
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
107
+
108
+        $this->assertFalse($manager->userExists('foo'));
109
+    }
110
+
111
+    public function testUserExistsTwoBackendsSecondExists(): void {
112
+        $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
113
+        $backend1->expects($this->once())
114
+            ->method('userExists')
115
+            ->with($this->equalTo('foo'))
116
+            ->willReturn(false);
117
+
118
+        $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
119
+        $backend2->expects($this->once())
120
+            ->method('userExists')
121
+            ->with($this->equalTo('foo'))
122
+            ->willReturn(true);
123
+
124
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
125
+        $manager->registerBackend($backend1);
126
+        $manager->registerBackend($backend2);
127
+
128
+        $this->assertTrue($manager->userExists('foo'));
129
+    }
130
+
131
+    public function testUserExistsTwoBackendsFirstExists(): void {
132
+        $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
133
+        $backend1->expects($this->once())
134
+            ->method('userExists')
135
+            ->with($this->equalTo('foo'))
136
+            ->willReturn(true);
137
+
138
+        $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
139
+        $backend2->expects($this->never())
140
+            ->method('userExists');
141
+
142
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
143
+        $manager->registerBackend($backend1);
144
+        $manager->registerBackend($backend2);
145
+
146
+        $this->assertTrue($manager->userExists('foo'));
147
+    }
148
+
149
+    public function testCheckPassword(): void {
150
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
151
+        $backend->expects($this->once())
152
+            ->method('checkPassword')
153
+            ->with($this->equalTo('foo'), $this->equalTo('bar'))
154
+            ->willReturn(true);
155
+
156
+        $backend->expects($this->any())
157
+            ->method('implementsActions')
158
+            ->willReturnCallback(function ($actions) {
159
+                if ($actions === BACKEND::CHECK_PASSWORD) {
160
+                    return true;
161
+                } else {
162
+                    return false;
163
+                }
164
+            });
165
+
166
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
167
+        $manager->registerBackend($backend);
168
+
169
+        $user = $manager->checkPassword('foo', 'bar');
170
+        $this->assertTrue($user instanceof User);
171
+    }
172
+
173
+    public function testCheckPasswordNotSupported(): void {
174
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
175
+        $backend->expects($this->never())
176
+            ->method('checkPassword');
177
+
178
+        $backend->expects($this->any())
179
+            ->method('implementsActions')
180
+            ->willReturn(false);
181
+
182
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
183
+        $manager->registerBackend($backend);
184
+
185
+        $this->assertFalse($manager->checkPassword('foo', 'bar'));
186
+    }
187
+
188
+    public function testGetOneBackendExists(): void {
189
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
190
+        $backend->expects($this->once())
191
+            ->method('userExists')
192
+            ->with($this->equalTo('foo'))
193
+            ->willReturn(true);
194
+        $backend->expects($this->never())
195
+            ->method('loginName2UserName');
196
+
197
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
198
+        $manager->registerBackend($backend);
199
+
200
+        $this->assertEquals('foo', $manager->get('foo')->getUID());
201
+    }
202
+
203
+    public function testGetOneBackendNotExists(): void {
204
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
205
+        $backend->expects($this->once())
206
+            ->method('userExists')
207
+            ->with($this->equalTo('foo'))
208
+            ->willReturn(false);
209
+
210
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
211
+        $manager->registerBackend($backend);
212
+
213
+        $this->assertEquals(null, $manager->get('foo'));
214
+    }
215
+
216
+    public function testGetTooLong(): void {
217
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
218
+        $backend->expects($this->never())
219
+            ->method('userExists')
220
+            ->with($this->equalTo('foo'))
221
+            ->willReturn(false);
222
+
223
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
224
+        $manager->registerBackend($backend);
225
+
226
+        $this->assertEquals(null, $manager->get('foo' . str_repeat('a', 62)));
227
+    }
228
+
229
+    public function testGetOneBackendDoNotTranslateLoginNames(): void {
230
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
231
+        $backend->expects($this->once())
232
+            ->method('userExists')
233
+            ->with($this->equalTo('bLeNdEr'))
234
+            ->willReturn(true);
235
+        $backend->expects($this->never())
236
+            ->method('loginName2UserName');
237
+
238
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
239
+        $manager->registerBackend($backend);
240
+
241
+        $this->assertEquals('bLeNdEr', $manager->get('bLeNdEr')->getUID());
242
+    }
243
+
244
+    public function testSearchOneBackend(): void {
245
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
246
+        $backend->expects($this->once())
247
+            ->method('getUsers')
248
+            ->with($this->equalTo('fo'))
249
+            ->willReturn(['foo', 'afoo', 'Afoo1', 'Bfoo']);
250
+        $backend->expects($this->never())
251
+            ->method('loginName2UserName');
252
+
253
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
254
+        $manager->registerBackend($backend);
255
+
256
+        $result = $manager->search('fo');
257
+        $this->assertEquals(4, count($result));
258
+        $this->assertEquals('afoo', array_shift($result)->getUID());
259
+        $this->assertEquals('Afoo1', array_shift($result)->getUID());
260
+        $this->assertEquals('Bfoo', array_shift($result)->getUID());
261
+        $this->assertEquals('foo', array_shift($result)->getUID());
262
+    }
263
+
264
+    public function testSearchTwoBackendLimitOffset(): void {
265
+        $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
266
+        $backend1->expects($this->once())
267
+            ->method('getUsers')
268
+            ->with($this->equalTo('fo'), $this->equalTo(3), $this->equalTo(1))
269
+            ->willReturn(['foo1', 'foo2']);
270
+        $backend1->expects($this->never())
271
+            ->method('loginName2UserName');
272
+
273
+        $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
274
+        $backend2->expects($this->once())
275
+            ->method('getUsers')
276
+            ->with($this->equalTo('fo'), $this->equalTo(3), $this->equalTo(1))
277
+            ->willReturn(['foo3']);
278
+        $backend2->expects($this->never())
279
+            ->method('loginName2UserName');
280
+
281
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
282
+        $manager->registerBackend($backend1);
283
+        $manager->registerBackend($backend2);
284
+
285
+        $result = $manager->search('fo', 3, 1);
286
+        $this->assertEquals(3, count($result));
287
+        $this->assertEquals('foo1', array_shift($result)->getUID());
288
+        $this->assertEquals('foo2', array_shift($result)->getUID());
289
+        $this->assertEquals('foo3', array_shift($result)->getUID());
290
+    }
291
+
292
+    public static function dataCreateUserInvalid(): array {
293
+        return [
294
+            ['te?st', 'foo', 'Only the following characters are allowed in a username:'
295
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
296
+            ["te\tst", '', 'Only the following characters are allowed in a username:'
297
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
298
+            ["te\nst", '', 'Only the following characters are allowed in a username:'
299
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
300
+            ["te\rst", '', 'Only the following characters are allowed in a username:'
301
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
302
+            ["te\0st", '', 'Only the following characters are allowed in a username:'
303
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
304
+            ["te\x0Bst", '', 'Only the following characters are allowed in a username:'
305
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
306
+            ["te\xe2st", '', 'Only the following characters are allowed in a username:'
307
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
308
+            ["te\x80st", '', 'Only the following characters are allowed in a username:'
309
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
310
+            ["te\x8bst", '', 'Only the following characters are allowed in a username:'
311
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'],
312
+            ['', 'foo', 'A valid username must be provided'],
313
+            [' ', 'foo', 'A valid username must be provided'],
314
+            [' test', 'foo', 'Username contains whitespace at the beginning or at the end'],
315
+            ['test ', 'foo', 'Username contains whitespace at the beginning or at the end'],
316
+            ['.', 'foo', 'Username must not consist of dots only'],
317
+            ['..', 'foo', 'Username must not consist of dots only'],
318
+            ['.test', '', 'A valid password must be provided'],
319
+            ['test', '', 'A valid password must be provided'],
320
+            ['test' . str_repeat('a', 61), '', 'Login is too long'],
321
+        ];
322
+    }
323
+
324
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataCreateUserInvalid')]
325
+    public function testCreateUserInvalid($uid, $password, $exception): void {
326
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
327
+        $backend->expects($this->once())
328
+            ->method('implementsActions')
329
+            ->with(\OC\User\Backend::CREATE_USER)
330
+            ->willReturn(true);
331
+
332
+
333
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
334
+        $manager->registerBackend($backend);
335
+
336
+        $this->expectException(\InvalidArgumentException::class, $exception);
337
+        $manager->createUser($uid, $password);
338
+    }
339
+
340
+    public function testCreateUserSingleBackendNotExists(): void {
341
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
342
+        $backend->expects($this->any())
343
+            ->method('implementsActions')
344
+            ->willReturn(true);
345
+
346
+        $backend->expects($this->once())
347
+            ->method('createUser')
348
+            ->with($this->equalTo('foo'), $this->equalTo('bar'));
349
+
350
+        $backend->expects($this->once())
351
+            ->method('userExists')
352
+            ->with($this->equalTo('foo'))
353
+            ->willReturn(false);
354
+        $backend->expects($this->never())
355
+            ->method('loginName2UserName');
356
+
357
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
358
+        $manager->registerBackend($backend);
359
+
360
+        $user = $manager->createUser('foo', 'bar');
361
+        $this->assertEquals('foo', $user->getUID());
362
+    }
363
+
364
+
365
+    public function testCreateUserSingleBackendExists(): void {
366
+        $this->expectException(\Exception::class);
367
+
368
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
369
+        $backend->expects($this->any())
370
+            ->method('implementsActions')
371
+            ->willReturn(true);
372
+
373
+        $backend->expects($this->never())
374
+            ->method('createUser');
375
+
376
+        $backend->expects($this->once())
377
+            ->method('userExists')
378
+            ->with($this->equalTo('foo'))
379
+            ->willReturn(true);
380
+
381
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
382
+        $manager->registerBackend($backend);
383
+
384
+        $manager->createUser('foo', 'bar');
385
+    }
386
+
387
+    public function testCreateUserSingleBackendNotSupported(): void {
388
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
389
+        $backend->expects($this->any())
390
+            ->method('implementsActions')
391
+            ->willReturn(false);
392
+
393
+        $backend->expects($this->never())
394
+            ->method('createUser');
395
+
396
+        $backend->expects($this->never())
397
+            ->method('userExists');
398
+
399
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
400
+        $manager->registerBackend($backend);
401
+
402
+        $this->assertFalse($manager->createUser('foo', 'bar'));
403
+    }
404
+
405
+    public function testCreateUserNoBackends(): void {
406
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
407
+
408
+        $this->assertFalse($manager->createUser('foo', 'bar'));
409
+    }
410
+
411
+
412
+    public function testCreateUserFromBackendWithBackendError(): void {
413
+        $this->expectException(\InvalidArgumentException::class);
414
+        $this->expectExceptionMessage('Could not create account');
415
+
416
+        /** @var \Test\Util\User\Dummy&MockObject $backend */
417
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
418
+        $backend
419
+            ->expects($this->once())
420
+            ->method('createUser')
421
+            ->with('MyUid', 'MyPassword')
422
+            ->willReturn(false);
423
+
424
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
425
+        $manager->createUserFromBackend('MyUid', 'MyPassword', $backend);
426
+    }
427
+
428
+
429
+    public function testCreateUserTwoBackendExists(): void {
430
+        $this->expectException(\Exception::class);
431
+
432
+        $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
433
+        $backend1->expects($this->any())
434
+            ->method('implementsActions')
435
+            ->willReturn(true);
436
+
437
+        $backend1->expects($this->never())
438
+            ->method('createUser');
439
+
440
+        $backend1->expects($this->once())
441
+            ->method('userExists')
442
+            ->with($this->equalTo('foo'))
443
+            ->willReturn(false);
444
+
445
+        $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
446
+        $backend2->expects($this->any())
447
+            ->method('implementsActions')
448
+            ->willReturn(true);
449
+
450
+        $backend2->expects($this->never())
451
+            ->method('createUser');
452
+
453
+        $backend2->expects($this->once())
454
+            ->method('userExists')
455
+            ->with($this->equalTo('foo'))
456
+            ->willReturn(true);
457
+
458
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
459
+        $manager->registerBackend($backend1);
460
+        $manager->registerBackend($backend2);
461 461
 
462
-		$manager->createUser('foo', 'bar');
463
-	}
464
-
465
-	public function testCountUsersNoBackend(): void {
466
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
467
-
468
-		$result = $manager->countUsers();
469
-		$this->assertTrue(is_array($result));
470
-		$this->assertTrue(empty($result));
471
-	}
472
-
473
-	public function testCountUsersOneBackend(): void {
474
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
475
-		$backend->expects($this->once())
476
-			->method('countUsers')
477
-			->willReturn(7);
478
-
479
-		$backend->expects($this->once())
480
-			->method('implementsActions')
481
-			->with(BACKEND::COUNT_USERS)
482
-			->willReturn(true);
483
-
484
-		$backend->expects($this->once())
485
-			->method('getBackendName')
486
-			->willReturn('Mock_Test_Util_User_Dummy');
487
-
488
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
489
-		$manager->registerBackend($backend);
490
-
491
-		$result = $manager->countUsers();
492
-		$keys = array_keys($result);
493
-		$this->assertTrue(strpos($keys[0], 'Mock_Test_Util_User_Dummy') !== false);
494
-
495
-		$users = array_shift($result);
496
-		$this->assertEquals(7, $users);
497
-	}
498
-
499
-	public function testCountUsersTwoBackends(): void {
500
-		/**
501
-		 * @var \Test\Util\User\Dummy&MockObject $backend
502
-		 */
503
-		$backend1 = $this->createMock(\Test\Util\User\Dummy::class);
504
-		$backend1->expects($this->once())
505
-			->method('countUsers')
506
-			->willReturn(7);
507
-
508
-		$backend1->expects($this->once())
509
-			->method('implementsActions')
510
-			->with(BACKEND::COUNT_USERS)
511
-			->willReturn(true);
512
-		$backend1->expects($this->once())
513
-			->method('getBackendName')
514
-			->willReturn('Mock_Test_Util_User_Dummy');
515
-
516
-		$backend2 = $this->createMock(\Test\Util\User\Dummy::class);
517
-		$backend2->expects($this->once())
518
-			->method('countUsers')
519
-			->willReturn(16);
520
-
521
-		$backend2->expects($this->once())
522
-			->method('implementsActions')
523
-			->with(BACKEND::COUNT_USERS)
524
-			->willReturn(true);
525
-		$backend2->expects($this->once())
526
-			->method('getBackendName')
527
-			->willReturn('Mock_Test_Util_User_Dummy');
528
-
529
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
530
-		$manager->registerBackend($backend1);
531
-		$manager->registerBackend($backend2);
532
-
533
-		$result = $manager->countUsers();
534
-		//because the backends have the same class name, only one value expected
535
-		$this->assertEquals(1, count($result));
536
-		$keys = array_keys($result);
537
-		$this->assertTrue(strpos($keys[0], 'Mock_Test_Util_User_Dummy') !== false);
538
-
539
-		$users = array_shift($result);
540
-		//users from backends shall be summed up
541
-		$this->assertEquals(7 + 16, $users);
542
-	}
543
-
544
-	public function testCountUsersOnlyDisabled(): void {
545
-		$manager = Server::get(IUserManager::class);
546
-		// count other users in the db before adding our own
547
-		$countBefore = $manager->countDisabledUsers();
548
-
549
-		//Add test users
550
-		$user1 = $manager->createUser('testdisabledcount1', 'testdisabledcount1');
551
-
552
-		$user2 = $manager->createUser('testdisabledcount2', 'testdisabledcount2');
553
-		$user2->setEnabled(false);
554
-
555
-		$user3 = $manager->createUser('testdisabledcount3', 'testdisabledcount3');
556
-
557
-		$user4 = $manager->createUser('testdisabledcount4', 'testdisabledcount4');
558
-		$user4->setEnabled(false);
559
-
560
-		$this->assertEquals($countBefore + 2, $manager->countDisabledUsers());
561
-
562
-		//cleanup
563
-		$user1->delete();
564
-		$user2->delete();
565
-		$user3->delete();
566
-		$user4->delete();
567
-	}
568
-
569
-	public function testCountUsersOnlySeen(): void {
570
-		$manager = Server::get(IUserManager::class);
571
-		// count other users in the db before adding our own
572
-		$countBefore = $manager->countSeenUsers();
573
-
574
-		//Add test users
575
-		$user1 = $manager->createUser('testseencount1', 'testseencount1');
576
-		$user1->updateLastLoginTimestamp();
577
-
578
-		$user2 = $manager->createUser('testseencount2', 'testseencount2');
579
-		$user2->updateLastLoginTimestamp();
580
-
581
-		$user3 = $manager->createUser('testseencount3', 'testseencount3');
582
-
583
-		$user4 = $manager->createUser('testseencount4', 'testseencount4');
584
-		$user4->updateLastLoginTimestamp();
585
-
586
-		$this->assertEquals($countBefore + 3, $manager->countSeenUsers());
587
-
588
-		//cleanup
589
-		$user1->delete();
590
-		$user2->delete();
591
-		$user3->delete();
592
-		$user4->delete();
593
-	}
594
-
595
-	public function testCallForSeenUsers(): void {
596
-		$manager = Server::get(IUserManager::class);
597
-		// count other users in the db before adding our own
598
-		$count = 0;
599
-		$function = function (IUser $user) use (&$count): void {
600
-			$count++;
601
-		};
602
-		$manager->callForAllUsers($function, '', true);
603
-		$countBefore = $count;
604
-
605
-		//Add test users
606
-		$user1 = $manager->createUser('testseen1', 'testseen10');
607
-		$user1->updateLastLoginTimestamp();
608
-
609
-		$user2 = $manager->createUser('testseen2', 'testseen20');
610
-		$user2->updateLastLoginTimestamp();
611
-
612
-		$user3 = $manager->createUser('testseen3', 'testseen30');
613
-
614
-		$user4 = $manager->createUser('testseen4', 'testseen40');
615
-		$user4->updateLastLoginTimestamp();
616
-
617
-		$count = 0;
618
-		$manager->callForAllUsers($function, '', true);
619
-
620
-		$this->assertEquals($countBefore + 3, $count);
621
-
622
-		//cleanup
623
-		$user1->delete();
624
-		$user2->delete();
625
-		$user3->delete();
626
-		$user4->delete();
627
-	}
628
-
629
-	#[\PHPUnit\Framework\Attributes\RunInSeparateProcess]
630
-	#[\PHPUnit\Framework\Attributes\PreserveGlobalState(enabled: false)]
631
-	public function testRecentlyActive(): void {
632
-		$config = Server::get(IConfig::class);
633
-		$manager = Server::get(IUserManager::class);
634
-
635
-		// Create some users
636
-		$now = (string)time();
637
-		$user1 = $manager->createUser('test_active_1', 'test_active_1');
638
-		$config->setUserValue('test_active_1', 'login', 'lastLogin', $now);
639
-		$user1->setDisplayName('test active 1');
640
-		$user1->setSystemEMailAddress('[email protected]');
641
-
642
-		$user2 = $manager->createUser('TEST_ACTIVE_2_FRED', 'TEST_ACTIVE_2');
643
-		$config->setUserValue('TEST_ACTIVE_2_FRED', 'login', 'lastLogin', $now);
644
-		$user2->setDisplayName('TEST ACTIVE 2 UPPER');
645
-		$user2->setSystemEMailAddress('[email protected]');
646
-
647
-		$user3 = $manager->createUser('test_active_3', 'test_active_3');
648
-		$config->setUserValue('test_active_3', 'login', 'lastLogin', $now + 1);
649
-		$user3->setDisplayName('test active 3');
650
-
651
-		$user4 = $manager->createUser('test_active_4', 'test_active_4');
652
-		$config->setUserValue('test_active_4', 'login', 'lastLogin', $now);
653
-		$user4->setDisplayName('Test Active 4');
654
-
655
-		$user5 = $manager->createUser('test_inactive_1', 'test_inactive_1');
656
-		$user5->setDisplayName('Test Inactive 1');
657
-		$user2->setSystemEMailAddress('[email protected]');
658
-
659
-		// Search recently active
660
-		//  - No search, case-insensitive order
661
-		$users = $manager->getLastLoggedInUsers(4);
662
-		$this->assertEquals(['test_active_3', 'test_active_1', 'TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
663
-		//  - Search, case-insensitive order
664
-		$users = $manager->getLastLoggedInUsers(search: 'act');
665
-		$this->assertEquals(['test_active_3', 'test_active_1', 'TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
666
-		//  - No search with offset
667
-		$users = $manager->getLastLoggedInUsers(2, 2);
668
-		$this->assertEquals(['TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
669
-		//  - Case insensitive search (email)
670
-		$users = $manager->getLastLoggedInUsers(search: 'active.com');
671
-		$this->assertEquals(['test_active_1', 'TEST_ACTIVE_2_FRED'], $users);
672
-		//  - Case insensitive search (display name)
673
-		$users = $manager->getLastLoggedInUsers(search: 'upper');
674
-		$this->assertEquals(['TEST_ACTIVE_2_FRED'], $users);
675
-		//  - Case insensitive search (uid)
676
-		$users = $manager->getLastLoggedInUsers(search: 'fred');
677
-		$this->assertEquals(['TEST_ACTIVE_2_FRED'], $users);
678
-
679
-		// Delete users and config keys
680
-		$user1->delete();
681
-		$user2->delete();
682
-		$user3->delete();
683
-		$user4->delete();
684
-		$user5->delete();
685
-	}
686
-
687
-	public function testDeleteUser(): void {
688
-		/** @var AllConfig&MockObject */
689
-		$config = $this->getMockBuilder(AllConfig::class)
690
-			->disableOriginalConstructor()
691
-			->getMock();
692
-		$config
693
-			->expects($this->any())
694
-			->method('getUserValue')
695
-			->willReturnArgument(3);
696
-		$config
697
-			->expects($this->any())
698
-			->method('getAppValue')
699
-			->willReturnArgument(2);
700
-
701
-		$manager = new Manager($config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
702
-		$backend = new \Test\Util\User\Dummy();
703
-
704
-		$manager->registerBackend($backend);
705
-		$backend->createUser('foo', 'bar');
706
-		$this->assertTrue($manager->userExists('foo'));
707
-		$manager->get('foo')->delete();
708
-		$this->assertFalse($manager->userExists('foo'));
709
-	}
710
-
711
-	public function testGetByEmail(): void {
712
-		$userConfig = $this->createMock(IUserConfig::class);
713
-		$userConfig->expects($this->once())
714
-			->method('searchUsersByValueString')
715
-			->with('settings', 'email', '[email protected]')
716
-			->willReturnCallback(function () {
717
-				yield 'uid1';
718
-				yield 'uid99';
719
-				yield 'uid2';
720
-			});
721
-
722
-		$manager = $this->getMockBuilder(Manager::class)
723
-			->setConstructorArgs([$this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger])
724
-			->onlyMethods(['getUserConfig', 'get'])
725
-			->getMock();
726
-		$manager->method('getUserConfig')->willReturn($userConfig);
727
-		$manager->expects($this->exactly(3))
728
-			->method('get')
729
-			->willReturnCallback(function (string $uid): ?IUser {
730
-				if ($uid === 'uid99') {
731
-					return null;
732
-				}
733
-				$user = $this->createMock(IUser::class);
734
-				$user->method('getUID')->willReturn($uid);
735
-				return $user;
736
-			});
737
-
738
-		$users = $manager->getByEmail('[email protected]');
739
-		$this->assertCount(2, $users);
740
-		$this->assertEquals('uid1', $users[0]->getUID());
741
-		$this->assertEquals('uid2', $users[1]->getUID());
742
-	}
743
-
744
-	public function testGetExistingUser() {
745
-		$backend = $this->createMock(\Test\Util\User\Dummy::class);
746
-		$backend->method('userExists')
747
-			->with('foobar')
748
-			->willReturn(true);
749
-		$backend->method('getDisplayName')
750
-			->willReturn('Foo Bar');
751
-		$backend->method('implementsActions')
752
-			->willReturnCallback(fn (int $action) => $action === Backend::GET_DISPLAYNAME);
753
-
754
-		$manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
755
-		$manager->registerBackend($backend);
756
-
757
-		$user = $manager->getExistingUser('foobar');
758
-		$this->assertEquals('foobar', $user->getUID());
759
-		$this->assertEquals('Foo Bar', $user->getDisplayName());
760
-
761
-		$user = $manager->getExistingUser('nobody', 'None');
762
-		$this->assertEquals('nobody', $user->getUID());
763
-		$this->assertEquals('None', $user->getDisplayName());
764
-	}
462
+        $manager->createUser('foo', 'bar');
463
+    }
464
+
465
+    public function testCountUsersNoBackend(): void {
466
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
467
+
468
+        $result = $manager->countUsers();
469
+        $this->assertTrue(is_array($result));
470
+        $this->assertTrue(empty($result));
471
+    }
472
+
473
+    public function testCountUsersOneBackend(): void {
474
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
475
+        $backend->expects($this->once())
476
+            ->method('countUsers')
477
+            ->willReturn(7);
478
+
479
+        $backend->expects($this->once())
480
+            ->method('implementsActions')
481
+            ->with(BACKEND::COUNT_USERS)
482
+            ->willReturn(true);
483
+
484
+        $backend->expects($this->once())
485
+            ->method('getBackendName')
486
+            ->willReturn('Mock_Test_Util_User_Dummy');
487
+
488
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
489
+        $manager->registerBackend($backend);
490
+
491
+        $result = $manager->countUsers();
492
+        $keys = array_keys($result);
493
+        $this->assertTrue(strpos($keys[0], 'Mock_Test_Util_User_Dummy') !== false);
494
+
495
+        $users = array_shift($result);
496
+        $this->assertEquals(7, $users);
497
+    }
498
+
499
+    public function testCountUsersTwoBackends(): void {
500
+        /**
501
+         * @var \Test\Util\User\Dummy&MockObject $backend
502
+         */
503
+        $backend1 = $this->createMock(\Test\Util\User\Dummy::class);
504
+        $backend1->expects($this->once())
505
+            ->method('countUsers')
506
+            ->willReturn(7);
507
+
508
+        $backend1->expects($this->once())
509
+            ->method('implementsActions')
510
+            ->with(BACKEND::COUNT_USERS)
511
+            ->willReturn(true);
512
+        $backend1->expects($this->once())
513
+            ->method('getBackendName')
514
+            ->willReturn('Mock_Test_Util_User_Dummy');
515
+
516
+        $backend2 = $this->createMock(\Test\Util\User\Dummy::class);
517
+        $backend2->expects($this->once())
518
+            ->method('countUsers')
519
+            ->willReturn(16);
520
+
521
+        $backend2->expects($this->once())
522
+            ->method('implementsActions')
523
+            ->with(BACKEND::COUNT_USERS)
524
+            ->willReturn(true);
525
+        $backend2->expects($this->once())
526
+            ->method('getBackendName')
527
+            ->willReturn('Mock_Test_Util_User_Dummy');
528
+
529
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
530
+        $manager->registerBackend($backend1);
531
+        $manager->registerBackend($backend2);
532
+
533
+        $result = $manager->countUsers();
534
+        //because the backends have the same class name, only one value expected
535
+        $this->assertEquals(1, count($result));
536
+        $keys = array_keys($result);
537
+        $this->assertTrue(strpos($keys[0], 'Mock_Test_Util_User_Dummy') !== false);
538
+
539
+        $users = array_shift($result);
540
+        //users from backends shall be summed up
541
+        $this->assertEquals(7 + 16, $users);
542
+    }
543
+
544
+    public function testCountUsersOnlyDisabled(): void {
545
+        $manager = Server::get(IUserManager::class);
546
+        // count other users in the db before adding our own
547
+        $countBefore = $manager->countDisabledUsers();
548
+
549
+        //Add test users
550
+        $user1 = $manager->createUser('testdisabledcount1', 'testdisabledcount1');
551
+
552
+        $user2 = $manager->createUser('testdisabledcount2', 'testdisabledcount2');
553
+        $user2->setEnabled(false);
554
+
555
+        $user3 = $manager->createUser('testdisabledcount3', 'testdisabledcount3');
556
+
557
+        $user4 = $manager->createUser('testdisabledcount4', 'testdisabledcount4');
558
+        $user4->setEnabled(false);
559
+
560
+        $this->assertEquals($countBefore + 2, $manager->countDisabledUsers());
561
+
562
+        //cleanup
563
+        $user1->delete();
564
+        $user2->delete();
565
+        $user3->delete();
566
+        $user4->delete();
567
+    }
568
+
569
+    public function testCountUsersOnlySeen(): void {
570
+        $manager = Server::get(IUserManager::class);
571
+        // count other users in the db before adding our own
572
+        $countBefore = $manager->countSeenUsers();
573
+
574
+        //Add test users
575
+        $user1 = $manager->createUser('testseencount1', 'testseencount1');
576
+        $user1->updateLastLoginTimestamp();
577
+
578
+        $user2 = $manager->createUser('testseencount2', 'testseencount2');
579
+        $user2->updateLastLoginTimestamp();
580
+
581
+        $user3 = $manager->createUser('testseencount3', 'testseencount3');
582
+
583
+        $user4 = $manager->createUser('testseencount4', 'testseencount4');
584
+        $user4->updateLastLoginTimestamp();
585
+
586
+        $this->assertEquals($countBefore + 3, $manager->countSeenUsers());
587
+
588
+        //cleanup
589
+        $user1->delete();
590
+        $user2->delete();
591
+        $user3->delete();
592
+        $user4->delete();
593
+    }
594
+
595
+    public function testCallForSeenUsers(): void {
596
+        $manager = Server::get(IUserManager::class);
597
+        // count other users in the db before adding our own
598
+        $count = 0;
599
+        $function = function (IUser $user) use (&$count): void {
600
+            $count++;
601
+        };
602
+        $manager->callForAllUsers($function, '', true);
603
+        $countBefore = $count;
604
+
605
+        //Add test users
606
+        $user1 = $manager->createUser('testseen1', 'testseen10');
607
+        $user1->updateLastLoginTimestamp();
608
+
609
+        $user2 = $manager->createUser('testseen2', 'testseen20');
610
+        $user2->updateLastLoginTimestamp();
611
+
612
+        $user3 = $manager->createUser('testseen3', 'testseen30');
613
+
614
+        $user4 = $manager->createUser('testseen4', 'testseen40');
615
+        $user4->updateLastLoginTimestamp();
616
+
617
+        $count = 0;
618
+        $manager->callForAllUsers($function, '', true);
619
+
620
+        $this->assertEquals($countBefore + 3, $count);
621
+
622
+        //cleanup
623
+        $user1->delete();
624
+        $user2->delete();
625
+        $user3->delete();
626
+        $user4->delete();
627
+    }
628
+
629
+    #[\PHPUnit\Framework\Attributes\RunInSeparateProcess]
630
+    #[\PHPUnit\Framework\Attributes\PreserveGlobalState(enabled: false)]
631
+    public function testRecentlyActive(): void {
632
+        $config = Server::get(IConfig::class);
633
+        $manager = Server::get(IUserManager::class);
634
+
635
+        // Create some users
636
+        $now = (string)time();
637
+        $user1 = $manager->createUser('test_active_1', 'test_active_1');
638
+        $config->setUserValue('test_active_1', 'login', 'lastLogin', $now);
639
+        $user1->setDisplayName('test active 1');
640
+        $user1->setSystemEMailAddress('[email protected]');
641
+
642
+        $user2 = $manager->createUser('TEST_ACTIVE_2_FRED', 'TEST_ACTIVE_2');
643
+        $config->setUserValue('TEST_ACTIVE_2_FRED', 'login', 'lastLogin', $now);
644
+        $user2->setDisplayName('TEST ACTIVE 2 UPPER');
645
+        $user2->setSystemEMailAddress('[email protected]');
646
+
647
+        $user3 = $manager->createUser('test_active_3', 'test_active_3');
648
+        $config->setUserValue('test_active_3', 'login', 'lastLogin', $now + 1);
649
+        $user3->setDisplayName('test active 3');
650
+
651
+        $user4 = $manager->createUser('test_active_4', 'test_active_4');
652
+        $config->setUserValue('test_active_4', 'login', 'lastLogin', $now);
653
+        $user4->setDisplayName('Test Active 4');
654
+
655
+        $user5 = $manager->createUser('test_inactive_1', 'test_inactive_1');
656
+        $user5->setDisplayName('Test Inactive 1');
657
+        $user2->setSystemEMailAddress('[email protected]');
658
+
659
+        // Search recently active
660
+        //  - No search, case-insensitive order
661
+        $users = $manager->getLastLoggedInUsers(4);
662
+        $this->assertEquals(['test_active_3', 'test_active_1', 'TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
663
+        //  - Search, case-insensitive order
664
+        $users = $manager->getLastLoggedInUsers(search: 'act');
665
+        $this->assertEquals(['test_active_3', 'test_active_1', 'TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
666
+        //  - No search with offset
667
+        $users = $manager->getLastLoggedInUsers(2, 2);
668
+        $this->assertEquals(['TEST_ACTIVE_2_FRED', 'test_active_4'], $users);
669
+        //  - Case insensitive search (email)
670
+        $users = $manager->getLastLoggedInUsers(search: 'active.com');
671
+        $this->assertEquals(['test_active_1', 'TEST_ACTIVE_2_FRED'], $users);
672
+        //  - Case insensitive search (display name)
673
+        $users = $manager->getLastLoggedInUsers(search: 'upper');
674
+        $this->assertEquals(['TEST_ACTIVE_2_FRED'], $users);
675
+        //  - Case insensitive search (uid)
676
+        $users = $manager->getLastLoggedInUsers(search: 'fred');
677
+        $this->assertEquals(['TEST_ACTIVE_2_FRED'], $users);
678
+
679
+        // Delete users and config keys
680
+        $user1->delete();
681
+        $user2->delete();
682
+        $user3->delete();
683
+        $user4->delete();
684
+        $user5->delete();
685
+    }
686
+
687
+    public function testDeleteUser(): void {
688
+        /** @var AllConfig&MockObject */
689
+        $config = $this->getMockBuilder(AllConfig::class)
690
+            ->disableOriginalConstructor()
691
+            ->getMock();
692
+        $config
693
+            ->expects($this->any())
694
+            ->method('getUserValue')
695
+            ->willReturnArgument(3);
696
+        $config
697
+            ->expects($this->any())
698
+            ->method('getAppValue')
699
+            ->willReturnArgument(2);
700
+
701
+        $manager = new Manager($config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
702
+        $backend = new \Test\Util\User\Dummy();
703
+
704
+        $manager->registerBackend($backend);
705
+        $backend->createUser('foo', 'bar');
706
+        $this->assertTrue($manager->userExists('foo'));
707
+        $manager->get('foo')->delete();
708
+        $this->assertFalse($manager->userExists('foo'));
709
+    }
710
+
711
+    public function testGetByEmail(): void {
712
+        $userConfig = $this->createMock(IUserConfig::class);
713
+        $userConfig->expects($this->once())
714
+            ->method('searchUsersByValueString')
715
+            ->with('settings', 'email', '[email protected]')
716
+            ->willReturnCallback(function () {
717
+                yield 'uid1';
718
+                yield 'uid99';
719
+                yield 'uid2';
720
+            });
721
+
722
+        $manager = $this->getMockBuilder(Manager::class)
723
+            ->setConstructorArgs([$this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger])
724
+            ->onlyMethods(['getUserConfig', 'get'])
725
+            ->getMock();
726
+        $manager->method('getUserConfig')->willReturn($userConfig);
727
+        $manager->expects($this->exactly(3))
728
+            ->method('get')
729
+            ->willReturnCallback(function (string $uid): ?IUser {
730
+                if ($uid === 'uid99') {
731
+                    return null;
732
+                }
733
+                $user = $this->createMock(IUser::class);
734
+                $user->method('getUID')->willReturn($uid);
735
+                return $user;
736
+            });
737
+
738
+        $users = $manager->getByEmail('[email protected]');
739
+        $this->assertCount(2, $users);
740
+        $this->assertEquals('uid1', $users[0]->getUID());
741
+        $this->assertEquals('uid2', $users[1]->getUID());
742
+    }
743
+
744
+    public function testGetExistingUser() {
745
+        $backend = $this->createMock(\Test\Util\User\Dummy::class);
746
+        $backend->method('userExists')
747
+            ->with('foobar')
748
+            ->willReturn(true);
749
+        $backend->method('getDisplayName')
750
+            ->willReturn('Foo Bar');
751
+        $backend->method('implementsActions')
752
+            ->willReturnCallback(fn (int $action) => $action === Backend::GET_DISPLAYNAME);
753
+
754
+        $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger);
755
+        $manager->registerBackend($backend);
756
+
757
+        $user = $manager->getExistingUser('foobar');
758
+        $this->assertEquals('foobar', $user->getUID());
759
+        $this->assertEquals('Foo Bar', $user->getDisplayName());
760
+
761
+        $user = $manager->getExistingUser('nobody', 'None');
762
+        $this->assertEquals('nobody', $user->getUID());
763
+        $this->assertEquals('None', $user->getDisplayName());
764
+    }
765 765
 }
Please login to merge, or discard this patch.