Completed
Push — master ( 623b8e...7e8926 )
by
unknown
30:25 queued 08:36
created
lib/public/IUserManager.php 1 patch
Indentation   +221 added lines, -221 removed lines patch added patch discarded remove patch
@@ -24,225 +24,225 @@
 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;
31
-
32
-	/**
33
-	 * register a user backend
34
-	 *
35
-	 * @since 8.0.0
36
-	 * @return void
37
-	 */
38
-	public function registerBackend(UserInterface $backend);
39
-
40
-	/**
41
-	 * Get the active backends
42
-	 * @return UserInterface[]
43
-	 * @since 8.0.0
44
-	 */
45
-	public function getBackends();
46
-
47
-	/**
48
-	 * remove a user backend
49
-	 *
50
-	 * @since 8.0.0
51
-	 * @return void
52
-	 */
53
-	public function removeBackend(UserInterface $backend);
54
-
55
-	/**
56
-	 * remove all user backends
57
-	 * @since 8.0.0
58
-	 * @return void
59
-	 */
60
-	public function clearBackends();
61
-
62
-	/**
63
-	 * get a user by user id
64
-	 *
65
-	 * @param string $uid
66
-	 * @return \OCP\IUser|null Either the user or null if the specified user does not exist
67
-	 * @since 8.0.0
68
-	 */
69
-	public function get($uid);
70
-
71
-	/**
72
-	 * Get the display name of a user
73
-	 *
74
-	 * @param string $uid
75
-	 * @return string|null
76
-	 * @since 25.0.0
77
-	 */
78
-	public function getDisplayName(string $uid): ?string;
79
-
80
-	/**
81
-	 * check if a user exists
82
-	 *
83
-	 * @param string $uid
84
-	 * @return bool
85
-	 * @since 8.0.0
86
-	 */
87
-	public function userExists($uid);
88
-
89
-	/**
90
-	 * Check if the password is valid for the user
91
-	 *
92
-	 * @param string $loginName
93
-	 * @param string $password
94
-	 * @return IUser|false the User object on success, false otherwise
95
-	 * @since 8.0.0
96
-	 */
97
-	public function checkPassword($loginName, $password);
98
-
99
-	/**
100
-	 * search by user id
101
-	 *
102
-	 * @param string $pattern
103
-	 * @param int $limit
104
-	 * @param int $offset
105
-	 * @return \OCP\IUser[]
106
-	 * @since 8.0.0
107
-	 * @deprecated 27.0.0, use searchDisplayName instead
108
-	 */
109
-	public function search($pattern, $limit = null, $offset = null);
110
-
111
-	/**
112
-	 * search by displayName
113
-	 *
114
-	 * @param string $pattern
115
-	 * @param int $limit
116
-	 * @param int $offset
117
-	 * @return list<IUser>
118
-	 * @since 8.0.0
119
-	 */
120
-	public function searchDisplayName($pattern, $limit = null, $offset = null);
121
-
122
-	/**
123
-	 * @return IUser[]
124
-	 * @since 28.0.0
125
-	 * @since 30.0.0 $search parameter added
126
-	 */
127
-	public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
128
-
129
-	/**
130
-	 * Search known users (from phonebook sync) by displayName
131
-	 *
132
-	 * @param string $searcher
133
-	 * @param string $pattern
134
-	 * @param int|null $limit
135
-	 * @param int|null $offset
136
-	 * @return IUser[]
137
-	 * @since 21.0.1
138
-	 */
139
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
140
-
141
-	/**
142
-	 * @param string $uid
143
-	 * @param string $password
144
-	 * @throws \InvalidArgumentException
145
-	 * @return false|\OCP\IUser the created user or false
146
-	 * @since 8.0.0
147
-	 */
148
-	public function createUser($uid, $password);
149
-
150
-	/**
151
-	 * @param string $uid
152
-	 * @param string $password
153
-	 * @param UserInterface $backend
154
-	 * @return IUser|null
155
-	 * @throws \InvalidArgumentException
156
-	 * @since 12.0.0
157
-	 */
158
-	public function createUserFromBackend($uid, $password, UserInterface $backend);
159
-
160
-	/**
161
-	 * Get how many users per backend exist (if supported by backend)
162
-	 *
163
-	 * @return array<string, int> an array of backend class name as key and count number as value
164
-	 * @since 8.0.0
165
-	 */
166
-	public function countUsers();
167
-
168
-	/**
169
-	 * Get how many users exists in total, whithin limit
170
-	 *
171
-	 * @param int $limit Limit the count to avoid resource waste. 0 to disable
172
-	 * @param bool $onlyMappedUsers Count mapped users instead of all users for compatible backends
173
-	 *
174
-	 * @since 31.0.0
175
-	 */
176
-	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false;
177
-
178
-	/**
179
-	 * @param \Closure $callback
180
-	 * @psalm-param \Closure(\OCP\IUser):void $callback
181
-	 * @param string $search
182
-	 * @since 9.0.0
183
-	 */
184
-	public function callForAllUsers(\Closure $callback, $search = '');
185
-
186
-	/**
187
-	 * returns how many users have logged in once
188
-	 *
189
-	 * @return int
190
-	 * @since 11.0.0
191
-	 */
192
-	public function countDisabledUsers();
193
-
194
-	/**
195
-	 * returns how many users have logged in once
196
-	 *
197
-	 * @return int
198
-	 * @since 11.0.0
199
-	 */
200
-	public function countSeenUsers();
201
-
202
-	/**
203
-	 * @param \Closure $callback
204
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
205
-	 * @since 11.0.0
206
-	 */
207
-	public function callForSeenUsers(\Closure $callback);
208
-
209
-	/**
210
-	 * returns all users having the provided email set as system email address
211
-	 *
212
-	 * @param string $email
213
-	 * @return IUser[]
214
-	 * @since 9.1.0
215
-	 */
216
-	public function getByEmail($email);
217
-
218
-	/**
219
-	 * @param string $uid The user ID to validate
220
-	 * @param bool $checkDataDirectory Whether it should be checked if files for the ID exist inside the data directory
221
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the ID is not valid
222
-	 * @since 26.0.0
223
-	 */
224
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void;
225
-
226
-	/**
227
-	 * Gets the list of users sorted by lastLogin, from most recent to least recent
228
-	 *
229
-	 * @param int|null $limit how many records to fetch
230
-	 * @param int $offset from which offset to fetch
231
-	 * @param string $search search users based on search params
232
-	 * @return list<string> list of user IDs
233
-	 * @since 30.0.0
234
-	 */
235
-	public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
236
-
237
-	/**
238
-	 * Gets the list of users.
239
-	 * An iterator is returned allowing the caller to stop the iteration at any time.
240
-	 * The offset argument allows the caller to continue the iteration at a specific offset.
241
-	 *
242
-	 * @param int $offset from which offset to fetch
243
-	 * @param int|null $limit maximum number of records to fetch
244
-	 * @return \Iterator<IUser> list of IUser object
245
-	 * @since 32.0.0
246
-	 */
247
-	public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator;
27
+    /**
28
+     * @since 26.0.0
29
+     */
30
+    public const MAX_PASSWORD_LENGTH = 469;
31
+
32
+    /**
33
+     * register a user backend
34
+     *
35
+     * @since 8.0.0
36
+     * @return void
37
+     */
38
+    public function registerBackend(UserInterface $backend);
39
+
40
+    /**
41
+     * Get the active backends
42
+     * @return UserInterface[]
43
+     * @since 8.0.0
44
+     */
45
+    public function getBackends();
46
+
47
+    /**
48
+     * remove a user backend
49
+     *
50
+     * @since 8.0.0
51
+     * @return void
52
+     */
53
+    public function removeBackend(UserInterface $backend);
54
+
55
+    /**
56
+     * remove all user backends
57
+     * @since 8.0.0
58
+     * @return void
59
+     */
60
+    public function clearBackends();
61
+
62
+    /**
63
+     * get a user by user id
64
+     *
65
+     * @param string $uid
66
+     * @return \OCP\IUser|null Either the user or null if the specified user does not exist
67
+     * @since 8.0.0
68
+     */
69
+    public function get($uid);
70
+
71
+    /**
72
+     * Get the display name of a user
73
+     *
74
+     * @param string $uid
75
+     * @return string|null
76
+     * @since 25.0.0
77
+     */
78
+    public function getDisplayName(string $uid): ?string;
79
+
80
+    /**
81
+     * check if a user exists
82
+     *
83
+     * @param string $uid
84
+     * @return bool
85
+     * @since 8.0.0
86
+     */
87
+    public function userExists($uid);
88
+
89
+    /**
90
+     * Check if the password is valid for the user
91
+     *
92
+     * @param string $loginName
93
+     * @param string $password
94
+     * @return IUser|false the User object on success, false otherwise
95
+     * @since 8.0.0
96
+     */
97
+    public function checkPassword($loginName, $password);
98
+
99
+    /**
100
+     * search by user id
101
+     *
102
+     * @param string $pattern
103
+     * @param int $limit
104
+     * @param int $offset
105
+     * @return \OCP\IUser[]
106
+     * @since 8.0.0
107
+     * @deprecated 27.0.0, use searchDisplayName instead
108
+     */
109
+    public function search($pattern, $limit = null, $offset = null);
110
+
111
+    /**
112
+     * search by displayName
113
+     *
114
+     * @param string $pattern
115
+     * @param int $limit
116
+     * @param int $offset
117
+     * @return list<IUser>
118
+     * @since 8.0.0
119
+     */
120
+    public function searchDisplayName($pattern, $limit = null, $offset = null);
121
+
122
+    /**
123
+     * @return IUser[]
124
+     * @since 28.0.0
125
+     * @since 30.0.0 $search parameter added
126
+     */
127
+    public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
128
+
129
+    /**
130
+     * Search known users (from phonebook sync) by displayName
131
+     *
132
+     * @param string $searcher
133
+     * @param string $pattern
134
+     * @param int|null $limit
135
+     * @param int|null $offset
136
+     * @return IUser[]
137
+     * @since 21.0.1
138
+     */
139
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
140
+
141
+    /**
142
+     * @param string $uid
143
+     * @param string $password
144
+     * @throws \InvalidArgumentException
145
+     * @return false|\OCP\IUser the created user or false
146
+     * @since 8.0.0
147
+     */
148
+    public function createUser($uid, $password);
149
+
150
+    /**
151
+     * @param string $uid
152
+     * @param string $password
153
+     * @param UserInterface $backend
154
+     * @return IUser|null
155
+     * @throws \InvalidArgumentException
156
+     * @since 12.0.0
157
+     */
158
+    public function createUserFromBackend($uid, $password, UserInterface $backend);
159
+
160
+    /**
161
+     * Get how many users per backend exist (if supported by backend)
162
+     *
163
+     * @return array<string, int> an array of backend class name as key and count number as value
164
+     * @since 8.0.0
165
+     */
166
+    public function countUsers();
167
+
168
+    /**
169
+     * Get how many users exists in total, whithin limit
170
+     *
171
+     * @param int $limit Limit the count to avoid resource waste. 0 to disable
172
+     * @param bool $onlyMappedUsers Count mapped users instead of all users for compatible backends
173
+     *
174
+     * @since 31.0.0
175
+     */
176
+    public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false;
177
+
178
+    /**
179
+     * @param \Closure $callback
180
+     * @psalm-param \Closure(\OCP\IUser):void $callback
181
+     * @param string $search
182
+     * @since 9.0.0
183
+     */
184
+    public function callForAllUsers(\Closure $callback, $search = '');
185
+
186
+    /**
187
+     * returns how many users have logged in once
188
+     *
189
+     * @return int
190
+     * @since 11.0.0
191
+     */
192
+    public function countDisabledUsers();
193
+
194
+    /**
195
+     * returns how many users have logged in once
196
+     *
197
+     * @return int
198
+     * @since 11.0.0
199
+     */
200
+    public function countSeenUsers();
201
+
202
+    /**
203
+     * @param \Closure $callback
204
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
205
+     * @since 11.0.0
206
+     */
207
+    public function callForSeenUsers(\Closure $callback);
208
+
209
+    /**
210
+     * returns all users having the provided email set as system email address
211
+     *
212
+     * @param string $email
213
+     * @return IUser[]
214
+     * @since 9.1.0
215
+     */
216
+    public function getByEmail($email);
217
+
218
+    /**
219
+     * @param string $uid The user ID to validate
220
+     * @param bool $checkDataDirectory Whether it should be checked if files for the ID exist inside the data directory
221
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the ID is not valid
222
+     * @since 26.0.0
223
+     */
224
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void;
225
+
226
+    /**
227
+     * Gets the list of users sorted by lastLogin, from most recent to least recent
228
+     *
229
+     * @param int|null $limit how many records to fetch
230
+     * @param int $offset from which offset to fetch
231
+     * @param string $search search users based on search params
232
+     * @return list<string> list of user IDs
233
+     * @since 30.0.0
234
+     */
235
+    public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array;
236
+
237
+    /**
238
+     * Gets the list of users.
239
+     * An iterator is returned allowing the caller to stop the iteration at any time.
240
+     * The offset argument allows the caller to continue the iteration at a specific offset.
241
+     *
242
+     * @param int $offset from which offset to fetch
243
+     * @param int|null $limit maximum number of records to fetch
244
+     * @return \Iterator<IUser> list of IUser object
245
+     * @since 32.0.0
246
+     */
247
+    public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator;
248 248
 }
Please login to merge, or discard this patch.
lib/public/IGroupManager.php 1 patch
Indentation   +117 added lines, -117 removed lines patch added patch discarded remove patch
@@ -23,121 +23,121 @@
 block discarded – undo
23 23
  * @since 8.0.0
24 24
  */
25 25
 interface IGroupManager {
26
-	/**
27
-	 * Checks whether a given backend is used
28
-	 *
29
-	 * @param string $backendClass Full classname including complete namespace
30
-	 * @return bool
31
-	 * @since 8.1.0
32
-	 */
33
-	public function isBackendUsed($backendClass);
34
-
35
-	/**
36
-	 * @param \OCP\GroupInterface $backend
37
-	 * @since 8.0.0
38
-	 */
39
-	public function addBackend($backend);
40
-
41
-	/**
42
-	 * @since 8.0.0
43
-	 */
44
-	public function clearBackends();
45
-
46
-	/**
47
-	 * Get the active backends
48
-	 * @return \OCP\GroupInterface[]
49
-	 * @since 13.0.0
50
-	 */
51
-	public function getBackends();
52
-
53
-	/**
54
-	 * @param string $gid
55
-	 * @return \OCP\IGroup|null
56
-	 * @since 8.0.0
57
-	 */
58
-	public function get($gid);
59
-
60
-	/**
61
-	 * @param string $gid
62
-	 * @return bool
63
-	 * @since 8.0.0
64
-	 */
65
-	public function groupExists($gid);
66
-
67
-	/**
68
-	 * @param string $gid
69
-	 * @return \OCP\IGroup|null
70
-	 * @since 8.0.0
71
-	 */
72
-	public function createGroup($gid);
73
-
74
-	/**
75
-	 * @param string $search
76
-	 * @param ?int $limit
77
-	 * @param ?int $offset
78
-	 * @return list<IGroup>
79
-	 * @since 8.0.0
80
-	 */
81
-	public function search(string $search, ?int $limit = null, ?int $offset = 0);
82
-
83
-	/**
84
-	 * @param \OCP\IUser|null $user
85
-	 * @return \OCP\IGroup[]
86
-	 * @since 8.0.0
87
-	 */
88
-	public function getUserGroups(?IUser $user = null);
89
-
90
-	/**
91
-	 * @param \OCP\IUser $user
92
-	 * @return string[] with group names
93
-	 * @since 8.0.0
94
-	 */
95
-	public function getUserGroupIds(IUser $user): array;
96
-
97
-	/**
98
-	 * get a list of all display names in a group
99
-	 *
100
-	 * @param string $gid
101
-	 * @param string $search
102
-	 * @param int $limit
103
-	 * @param int $offset
104
-	 * @return array an array of display names (value) and user ids (key)
105
-	 * @since 8.0.0
106
-	 */
107
-	public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0);
108
-
109
-	/**
110
-	 * Checks if a userId is in the admin group
111
-	 * @param string $userId
112
-	 * @return bool if admin
113
-	 * @since 8.0.0
114
-	 */
115
-	public function isAdmin($userId);
116
-
117
-	/**
118
-	 * Checks if a userId is eligible to users administration delegation
119
-	 * @param string $userId
120
-	 * @return bool if delegated admin
121
-	 * @since 30.0.0
122
-	 */
123
-	public function isDelegatedAdmin(string $userId): bool;
124
-
125
-	/**
126
-	 * Checks if a userId is in a group
127
-	 * @param string $userId
128
-	 * @param string $group
129
-	 * @return bool if in group
130
-	 * @since 8.0.0
131
-	 */
132
-	public function isInGroup($userId, $group);
133
-
134
-	/**
135
-	 * Get the display name of a Nextcloud group
136
-	 *
137
-	 * @param string $groupId
138
-	 * @return ?string display name, if any
139
-	 *
140
-	 * @since 26.0.0
141
-	 */
142
-	public function getDisplayName(string $groupId): ?string;
26
+    /**
27
+     * Checks whether a given backend is used
28
+     *
29
+     * @param string $backendClass Full classname including complete namespace
30
+     * @return bool
31
+     * @since 8.1.0
32
+     */
33
+    public function isBackendUsed($backendClass);
34
+
35
+    /**
36
+     * @param \OCP\GroupInterface $backend
37
+     * @since 8.0.0
38
+     */
39
+    public function addBackend($backend);
40
+
41
+    /**
42
+     * @since 8.0.0
43
+     */
44
+    public function clearBackends();
45
+
46
+    /**
47
+     * Get the active backends
48
+     * @return \OCP\GroupInterface[]
49
+     * @since 13.0.0
50
+     */
51
+    public function getBackends();
52
+
53
+    /**
54
+     * @param string $gid
55
+     * @return \OCP\IGroup|null
56
+     * @since 8.0.0
57
+     */
58
+    public function get($gid);
59
+
60
+    /**
61
+     * @param string $gid
62
+     * @return bool
63
+     * @since 8.0.0
64
+     */
65
+    public function groupExists($gid);
66
+
67
+    /**
68
+     * @param string $gid
69
+     * @return \OCP\IGroup|null
70
+     * @since 8.0.0
71
+     */
72
+    public function createGroup($gid);
73
+
74
+    /**
75
+     * @param string $search
76
+     * @param ?int $limit
77
+     * @param ?int $offset
78
+     * @return list<IGroup>
79
+     * @since 8.0.0
80
+     */
81
+    public function search(string $search, ?int $limit = null, ?int $offset = 0);
82
+
83
+    /**
84
+     * @param \OCP\IUser|null $user
85
+     * @return \OCP\IGroup[]
86
+     * @since 8.0.0
87
+     */
88
+    public function getUserGroups(?IUser $user = null);
89
+
90
+    /**
91
+     * @param \OCP\IUser $user
92
+     * @return string[] with group names
93
+     * @since 8.0.0
94
+     */
95
+    public function getUserGroupIds(IUser $user): array;
96
+
97
+    /**
98
+     * get a list of all display names in a group
99
+     *
100
+     * @param string $gid
101
+     * @param string $search
102
+     * @param int $limit
103
+     * @param int $offset
104
+     * @return array an array of display names (value) and user ids (key)
105
+     * @since 8.0.0
106
+     */
107
+    public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0);
108
+
109
+    /**
110
+     * Checks if a userId is in the admin group
111
+     * @param string $userId
112
+     * @return bool if admin
113
+     * @since 8.0.0
114
+     */
115
+    public function isAdmin($userId);
116
+
117
+    /**
118
+     * Checks if a userId is eligible to users administration delegation
119
+     * @param string $userId
120
+     * @return bool if delegated admin
121
+     * @since 30.0.0
122
+     */
123
+    public function isDelegatedAdmin(string $userId): bool;
124
+
125
+    /**
126
+     * Checks if a userId is in a group
127
+     * @param string $userId
128
+     * @param string $group
129
+     * @return bool if in group
130
+     * @since 8.0.0
131
+     */
132
+    public function isInGroup($userId, $group);
133
+
134
+    /**
135
+     * Get the display name of a Nextcloud group
136
+     *
137
+     * @param string $groupId
138
+     * @return ?string display name, if any
139
+     *
140
+     * @since 26.0.0
141
+     */
142
+    public function getDisplayName(string $groupId): ?string;
143 143
 }
Please login to merge, or discard this patch.
lib/private/User/Manager.php 1 patch
Indentation   +768 added lines, -768 removed lines patch added patch discarded remove patch
@@ -53,772 +53,772 @@
 block discarded – undo
53 53
  * @package OC\User
54 54
  */
55 55
 class Manager extends PublicEmitter implements IUserManager {
56
-	/**
57
-	 * @var UserInterface[] $backends
58
-	 */
59
-	private array $backends = [];
60
-
61
-	/**
62
-	 * @var array<string,\OC\User\User> $cachedUsers
63
-	 */
64
-	private array $cachedUsers = [];
65
-
66
-	private ICache $cache;
67
-
68
-	private DisplayNameCache $displayNameCache;
69
-
70
-	public function __construct(
71
-		private IConfig $config,
72
-		ICacheFactory $cacheFactory,
73
-		private IEventDispatcher $eventDispatcher,
74
-		private LoggerInterface $logger,
75
-	) {
76
-		$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
77
-		$this->listen('\OC\User', 'postDelete', function (IUser $user): void {
78
-			unset($this->cachedUsers[$user->getUID()]);
79
-		});
80
-		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
81
-	}
82
-
83
-	/**
84
-	 * Get the active backends
85
-	 * @return UserInterface[]
86
-	 */
87
-	public function getBackends(): array {
88
-		return $this->backends;
89
-	}
90
-
91
-	public function registerBackend(UserInterface $backend): void {
92
-		$this->backends[] = $backend;
93
-	}
94
-
95
-	public function removeBackend(UserInterface $backend): void {
96
-		$this->cachedUsers = [];
97
-		if (($i = array_search($backend, $this->backends)) !== false) {
98
-			unset($this->backends[$i]);
99
-		}
100
-	}
101
-
102
-	public function clearBackends(): void {
103
-		$this->cachedUsers = [];
104
-		$this->backends = [];
105
-	}
106
-
107
-	/**
108
-	 * get a user by user id
109
-	 *
110
-	 * @param string $uid
111
-	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
112
-	 */
113
-	public function get($uid) {
114
-		if (is_null($uid) || $uid === '' || $uid === false) {
115
-			return null;
116
-		}
117
-		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
118
-			return $this->cachedUsers[$uid];
119
-		}
120
-
121
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
122
-			return null;
123
-		}
124
-
125
-		$cachedBackend = $this->cache->get(sha1($uid));
126
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
127
-			// Cache has the info of the user backend already, so ask that one directly
128
-			$backend = $this->backends[$cachedBackend];
129
-			if ($backend->userExists($uid)) {
130
-				return $this->getUserObject($uid, $backend);
131
-			}
132
-		}
133
-
134
-		foreach ($this->backends as $i => $backend) {
135
-			if ($i === $cachedBackend) {
136
-				// Tried that one already
137
-				continue;
138
-			}
139
-
140
-			if ($backend->userExists($uid)) {
141
-				// Hash $uid to ensure that only valid characters are used for the cache key
142
-				$this->cache->set(sha1($uid), $i, 300);
143
-				return $this->getUserObject($uid, $backend);
144
-			}
145
-		}
146
-		return null;
147
-	}
148
-
149
-	public function getDisplayName(string $uid): ?string {
150
-		return $this->displayNameCache->getDisplayName($uid);
151
-	}
152
-
153
-	/**
154
-	 * get or construct the user object
155
-	 *
156
-	 * @param string $uid
157
-	 * @param \OCP\UserInterface $backend
158
-	 * @param bool $cacheUser If false the newly created user object will not be cached
159
-	 * @return \OC\User\User
160
-	 */
161
-	public function getUserObject($uid, $backend, $cacheUser = true) {
162
-		if ($backend instanceof IGetRealUIDBackend) {
163
-			$uid = $backend->getRealUID($uid);
164
-		}
165
-
166
-		if (isset($this->cachedUsers[$uid])) {
167
-			return $this->cachedUsers[$uid];
168
-		}
169
-
170
-		$user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config);
171
-		if ($cacheUser) {
172
-			$this->cachedUsers[$uid] = $user;
173
-		}
174
-		return $user;
175
-	}
176
-
177
-	/**
178
-	 * check if a user exists
179
-	 *
180
-	 * @param string $uid
181
-	 * @return bool
182
-	 */
183
-	public function userExists($uid) {
184
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
185
-			return false;
186
-		}
187
-
188
-		$user = $this->get($uid);
189
-		return ($user !== null);
190
-	}
191
-
192
-	/**
193
-	 * Check if the password is valid for the user
194
-	 *
195
-	 * @param string $loginName
196
-	 * @param string $password
197
-	 * @return IUser|false the User object on success, false otherwise
198
-	 */
199
-	public function checkPassword($loginName, $password) {
200
-		$result = $this->checkPasswordNoLogging($loginName, $password);
201
-
202
-		if ($result === false) {
203
-			$this->logger->warning('Login failed: \'' . $loginName . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
204
-		}
205
-
206
-		return $result;
207
-	}
208
-
209
-	/**
210
-	 * Check if the password is valid for the user
211
-	 *
212
-	 * @internal
213
-	 * @param string $loginName
214
-	 * @param string $password
215
-	 * @return IUser|false the User object on success, false otherwise
216
-	 */
217
-	public function checkPasswordNoLogging($loginName, $password) {
218
-		$loginName = str_replace("\0", '', $loginName);
219
-		$password = str_replace("\0", '', $password);
220
-
221
-		$cachedBackend = $this->cache->get($loginName);
222
-		if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
223
-			$backends = [$this->backends[$cachedBackend]];
224
-		} else {
225
-			$backends = $this->backends;
226
-		}
227
-		foreach ($backends as $backend) {
228
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
229
-				/** @var ICheckPasswordBackend $backend */
230
-				$uid = $backend->checkPassword($loginName, $password);
231
-				if ($uid !== false) {
232
-					return $this->getUserObject($uid, $backend);
233
-				}
234
-			}
235
-		}
236
-
237
-		// since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
238
-		// we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
239
-		// to contain urlencoded patterns by "accident".
240
-		$password = urldecode($password);
241
-
242
-		foreach ($backends as $backend) {
243
-			if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
244
-				/** @var ICheckPasswordBackend|UserInterface $backend */
245
-				$uid = $backend->checkPassword($loginName, $password);
246
-				if ($uid !== false) {
247
-					return $this->getUserObject($uid, $backend);
248
-				}
249
-			}
250
-		}
251
-
252
-		return false;
253
-	}
254
-
255
-	public function search($pattern, $limit = null, $offset = null) {
256
-		$users = [];
257
-		foreach ($this->backends as $backend) {
258
-			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
259
-			if (is_array($backendUsers)) {
260
-				foreach ($backendUsers as $uid) {
261
-					$users[$uid] = new LazyUser($uid, $this, null, $backend);
262
-				}
263
-			}
264
-		}
265
-
266
-		uasort($users, function (IUser $a, IUser $b) {
267
-			return strcasecmp($a->getUID(), $b->getUID());
268
-		});
269
-		return $users;
270
-	}
271
-
272
-	public function searchDisplayName($pattern, $limit = null, $offset = null) {
273
-		$users = [];
274
-		foreach ($this->backends as $backend) {
275
-			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
276
-			if (is_array($backendUsers)) {
277
-				foreach ($backendUsers as $uid => $displayName) {
278
-					$users[] = new LazyUser($uid, $this, $displayName, $backend);
279
-				}
280
-			}
281
-		}
282
-
283
-		usort($users, function (IUser $a, IUser $b) {
284
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
285
-		});
286
-		return $users;
287
-	}
288
-
289
-	/**
290
-	 * @return IUser[]
291
-	 */
292
-	public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
293
-		$users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
294
-		$users = array_combine(
295
-			$users,
296
-			array_map(
297
-				fn (string $uid): IUser => new LazyUser($uid, $this),
298
-				$users
299
-			)
300
-		);
301
-		if ($search !== '') {
302
-			$users = array_filter(
303
-				$users,
304
-				function (IUser $user) use ($search): bool {
305
-					try {
306
-						return mb_stripos($user->getUID(), $search) !== false
307
-						|| mb_stripos($user->getDisplayName(), $search) !== false
308
-						|| mb_stripos($user->getEMailAddress() ?? '', $search) !== false;
309
-					} catch (NoUserException $ex) {
310
-						$this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]);
311
-						return false;
312
-					}
313
-				});
314
-		}
315
-
316
-		$tempLimit = ($limit === null ? null : $limit + $offset);
317
-		foreach ($this->backends as $backend) {
318
-			if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
319
-				break;
320
-			}
321
-			if ($backend instanceof IProvideEnabledStateBackend) {
322
-				$backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search);
323
-				foreach ($backendUsers as $uid) {
324
-					$users[$uid] = new LazyUser($uid, $this, null, $backend);
325
-				}
326
-			}
327
-		}
328
-
329
-		return array_slice($users, $offset, $limit);
330
-	}
331
-
332
-	/**
333
-	 * Search known users (from phonebook sync) by displayName
334
-	 *
335
-	 * @param string $searcher
336
-	 * @param string $pattern
337
-	 * @param int|null $limit
338
-	 * @param int|null $offset
339
-	 * @return IUser[]
340
-	 */
341
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
342
-		$users = [];
343
-		foreach ($this->backends as $backend) {
344
-			if ($backend instanceof ISearchKnownUsersBackend) {
345
-				$backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
346
-			} else {
347
-				// Better than nothing, but filtering after pagination can remove lots of results.
348
-				$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
349
-			}
350
-			if (is_array($backendUsers)) {
351
-				foreach ($backendUsers as $uid => $displayName) {
352
-					$users[] = $this->getUserObject($uid, $backend);
353
-				}
354
-			}
355
-		}
356
-
357
-		usort($users, function ($a, $b) {
358
-			/**
359
-			 * @var IUser $a
360
-			 * @var IUser $b
361
-			 */
362
-			return strcasecmp($a->getDisplayName(), $b->getDisplayName());
363
-		});
364
-		return $users;
365
-	}
366
-
367
-	/**
368
-	 * @param string $uid
369
-	 * @param string $password
370
-	 * @return false|IUser the created user or false
371
-	 * @throws \InvalidArgumentException
372
-	 * @throws HintException
373
-	 */
374
-	public function createUser($uid, $password) {
375
-		// DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
376
-		/** @var IAssertion $assertion */
377
-		$assertion = \OC::$server->get(IAssertion::class);
378
-		$assertion->createUserIsLegit();
379
-
380
-		$localBackends = [];
381
-		foreach ($this->backends as $backend) {
382
-			if ($backend instanceof Database) {
383
-				// First check if there is another user backend
384
-				$localBackends[] = $backend;
385
-				continue;
386
-			}
387
-
388
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
389
-				return $this->createUserFromBackend($uid, $password, $backend);
390
-			}
391
-		}
392
-
393
-		foreach ($localBackends as $backend) {
394
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
395
-				return $this->createUserFromBackend($uid, $password, $backend);
396
-			}
397
-		}
398
-
399
-		return false;
400
-	}
401
-
402
-	/**
403
-	 * @param string $uid
404
-	 * @param string $password
405
-	 * @param UserInterface $backend
406
-	 * @return IUser|false
407
-	 * @throws \InvalidArgumentException
408
-	 */
409
-	public function createUserFromBackend($uid, $password, UserInterface $backend) {
410
-		$l = \OCP\Util::getL10N('lib');
411
-
412
-		$this->validateUserId($uid, true);
413
-
414
-		// No empty password
415
-		if (trim($password) === '') {
416
-			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
417
-		}
418
-
419
-		// Check if user already exists
420
-		if ($this->userExists($uid)) {
421
-			throw new \InvalidArgumentException($l->t('The Login is already being used'));
422
-		}
423
-
424
-		/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
425
-		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
426
-		$this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
427
-		$state = $backend->createUser($uid, $password);
428
-		if ($state === false) {
429
-			throw new \InvalidArgumentException($l->t('Could not create account'));
430
-		}
431
-		$user = $this->getUserObject($uid, $backend);
432
-		if ($user instanceof IUser) {
433
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
434
-			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
435
-			$this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
436
-			return $user;
437
-		}
438
-		return false;
439
-	}
440
-
441
-	/**
442
-	 * returns how many users per backend exist (if supported by backend)
443
-	 *
444
-	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
445
-	 *                             entry in the preferences table will be affected
446
-	 * @return array<string, int> an array of backend class as key and count number as value
447
-	 */
448
-	public function countUsers() {
449
-		$userCountStatistics = [];
450
-		foreach ($this->backends as $backend) {
451
-			if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
452
-				/** @var ICountUsersBackend|IUserBackend $backend */
453
-				$backendUsers = $backend->countUsers();
454
-				if ($backendUsers !== false) {
455
-					if ($backend instanceof IUserBackend) {
456
-						$name = $backend->getBackendName();
457
-					} else {
458
-						$name = get_class($backend);
459
-					}
460
-					if (isset($userCountStatistics[$name])) {
461
-						$userCountStatistics[$name] += $backendUsers;
462
-					} else {
463
-						$userCountStatistics[$name] = $backendUsers;
464
-					}
465
-				}
466
-			}
467
-		}
468
-		return $userCountStatistics;
469
-	}
470
-
471
-	public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
472
-		$userCount = false;
473
-
474
-		foreach ($this->backends as $backend) {
475
-			if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
476
-				$backendUsers = $backend->countMappedUsers();
477
-			} elseif ($backend instanceof ILimitAwareCountUsersBackend) {
478
-				$backendUsers = $backend->countUsers($limit);
479
-			} elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
480
-				/** @var ICountUsersBackend $backend */
481
-				$backendUsers = $backend->countUsers();
482
-			} else {
483
-				$this->logger->debug('Skip backend for user count: ' . get_class($backend));
484
-				continue;
485
-			}
486
-			if ($backendUsers !== false) {
487
-				$userCount = (int)$userCount + $backendUsers;
488
-				if ($limit > 0) {
489
-					if ($userCount >= $limit) {
490
-						break;
491
-					}
492
-					$limit -= $userCount;
493
-				}
494
-			} else {
495
-				$this->logger->warning('Can not determine user count for ' . get_class($backend));
496
-			}
497
-		}
498
-		return $userCount;
499
-	}
500
-
501
-	/**
502
-	 * returns how many users per backend exist in the requested groups (if supported by backend)
503
-	 *
504
-	 * @param IGroup[] $groups an array of groups to search in
505
-	 * @param int $limit limit to stop counting
506
-	 * @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
507
-	 */
508
-	public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array {
509
-		$users = [];
510
-		$disabled = [];
511
-		foreach ($groups as $group) {
512
-			foreach ($group->getUsers() as $user) {
513
-				$users[$user->getUID()] = 1;
514
-				if (!$user->isEnabled()) {
515
-					$disabled[$user->getUID()] = 1;
516
-				}
517
-				if (count($users) >= $limit) {
518
-					return [count($users),-1];
519
-				}
520
-			}
521
-		}
522
-		return [count($users),count($disabled)];
523
-	}
524
-
525
-	/**
526
-	 * The callback is executed for each user on each backend.
527
-	 * If the callback returns false no further users will be retrieved.
528
-	 *
529
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
530
-	 * @param string $search
531
-	 * @param boolean $onlySeen when true only users that have a lastLogin entry
532
-	 *                          in the preferences table will be affected
533
-	 * @since 9.0.0
534
-	 */
535
-	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
536
-		if ($onlySeen) {
537
-			$this->callForSeenUsers($callback);
538
-		} else {
539
-			foreach ($this->getBackends() as $backend) {
540
-				$limit = 500;
541
-				$offset = 0;
542
-				do {
543
-					$users = $backend->getUsers($search, $limit, $offset);
544
-					foreach ($users as $uid) {
545
-						if (!$backend->userExists($uid)) {
546
-							continue;
547
-						}
548
-						$user = $this->getUserObject($uid, $backend, false);
549
-						$return = $callback($user);
550
-						if ($return === false) {
551
-							break;
552
-						}
553
-					}
554
-					$offset += $limit;
555
-				} while (count($users) >= $limit);
556
-			}
557
-		}
558
-	}
559
-
560
-	/**
561
-	 * returns how many users are disabled
562
-	 *
563
-	 * @return int
564
-	 * @since 12.0.0
565
-	 */
566
-	public function countDisabledUsers(): int {
567
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
568
-		$queryBuilder->select($queryBuilder->func()->count('*'))
569
-			->from('preferences')
570
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
571
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
572
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
573
-
574
-
575
-		$result = $queryBuilder->executeQuery();
576
-		$count = $result->fetchOne();
577
-		$result->closeCursor();
578
-
579
-		if ($count !== false) {
580
-			$count = (int)$count;
581
-		} else {
582
-			$count = 0;
583
-		}
584
-
585
-		return $count;
586
-	}
587
-
588
-	/**
589
-	 * returns how many users have logged in once
590
-	 *
591
-	 * @return int
592
-	 * @since 11.0.0
593
-	 */
594
-	public function countSeenUsers() {
595
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
596
-		$queryBuilder->select($queryBuilder->func()->count('*'))
597
-			->from('preferences')
598
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
599
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
600
-
601
-		$query = $queryBuilder->executeQuery();
602
-
603
-		$result = (int)$query->fetchOne();
604
-		$query->closeCursor();
605
-
606
-		return $result;
607
-	}
608
-
609
-	public function callForSeenUsers(\Closure $callback) {
610
-		$users = $this->getSeenUsers();
611
-		foreach ($users as $user) {
612
-			$return = $callback($user);
613
-			if ($return === false) {
614
-				return;
615
-			}
616
-		}
617
-	}
618
-
619
-	/**
620
-	 * Getting all userIds that have a lastLogin value requires checking the
621
-	 * value in php because on oracle you cannot use a clob in a where clause,
622
-	 * preventing us from doing a not null or length(value) > 0 check.
623
-	 *
624
-	 * @param int $limit
625
-	 * @param int $offset
626
-	 * @return string[] with user ids
627
-	 */
628
-	private function getSeenUserIds($limit = null, $offset = null) {
629
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
630
-		$queryBuilder->select(['userid'])
631
-			->from('preferences')
632
-			->where($queryBuilder->expr()->eq(
633
-				'appid', $queryBuilder->createNamedParameter('login'))
634
-			)
635
-			->andWhere($queryBuilder->expr()->eq(
636
-				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
637
-			)
638
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
639
-			);
640
-
641
-		if ($limit !== null) {
642
-			$queryBuilder->setMaxResults($limit);
643
-		}
644
-		if ($offset !== null) {
645
-			$queryBuilder->setFirstResult($offset);
646
-		}
647
-		$query = $queryBuilder->executeQuery();
648
-		$result = [];
649
-
650
-		while ($row = $query->fetch()) {
651
-			$result[] = $row['userid'];
652
-		}
653
-
654
-		$query->closeCursor();
655
-
656
-		return $result;
657
-	}
658
-
659
-	/**
660
-	 * @param string $email
661
-	 * @return IUser[]
662
-	 * @since 9.1.0
663
-	 */
664
-	public function getByEmail($email) {
665
-		// looking for 'email' only (and not primary_mail) is intentional
666
-		$userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
667
-
668
-		$users = array_map(function ($uid) {
669
-			return $this->get($uid);
670
-		}, $userIds);
671
-
672
-		return array_values(array_filter($users, function ($u) {
673
-			return ($u instanceof IUser);
674
-		}));
675
-	}
676
-
677
-	/**
678
-	 * @param string $uid
679
-	 * @param bool $checkDataDirectory
680
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
681
-	 * @since 26.0.0
682
-	 */
683
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
684
-		$l = Server::get(IFactory::class)->get('lib');
685
-
686
-		// Check the ID for bad characters
687
-		// Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
688
-		if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
689
-			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:'
690
-				. ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
691
-		}
692
-
693
-		// No empty user ID
694
-		if (trim($uid) === '') {
695
-			throw new \InvalidArgumentException($l->t('A valid Login must be provided'));
696
-		}
697
-
698
-		// No whitespace at the beginning or at the end
699
-		if (trim($uid) !== $uid) {
700
-			throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end'));
701
-		}
702
-
703
-		// User ID only consists of 1 or 2 dots (directory traversal)
704
-		if ($uid === '.' || $uid === '..') {
705
-			throw new \InvalidArgumentException($l->t('Login must not consist of dots only'));
706
-		}
707
-
708
-		// User ID is too long
709
-		if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
710
-			// TRANSLATORS User ID is too long
711
-			throw new \InvalidArgumentException($l->t('Username is too long'));
712
-		}
713
-
714
-		if (!$this->verifyUid($uid, $checkDataDirectory)) {
715
-			throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user'));
716
-		}
717
-	}
718
-
719
-	/**
720
-	 * Gets the list of user ids sorted by lastLogin, from most recent to least recent
721
-	 *
722
-	 * @param int|null $limit how many users to fetch (default: 25, max: 100)
723
-	 * @param int $offset from which offset to fetch
724
-	 * @param string $search search users based on search params
725
-	 * @return list<string> list of user IDs
726
-	 */
727
-	public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
728
-		// We can't load all users who already logged in
729
-		$limit = min(100, $limit ?: 25);
730
-
731
-		$connection = \OC::$server->getDatabaseConnection();
732
-		$queryBuilder = $connection->getQueryBuilder();
733
-		$queryBuilder->select('pref_login.userid')
734
-			->from('preferences', 'pref_login')
735
-			->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login')))
736
-			->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin')))
737
-			->setFirstResult($offset)
738
-			->setMaxResults($limit)
739
-		;
740
-
741
-		// Oracle don't want to run ORDER BY on CLOB column
742
-		$loginOrder = $connection->getDatabasePlatform() instanceof OraclePlatform
743
-			? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT)
744
-			: 'pref_login.configvalue';
745
-		$queryBuilder
746
-			->orderBy($loginOrder, 'DESC')
747
-			->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC');
748
-
749
-		if ($search !== '') {
750
-			$displayNameMatches = $this->searchDisplayName($search);
751
-			$matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches);
752
-
753
-			$queryBuilder
754
-				->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX(
755
-					$queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'),
756
-					$queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')),
757
-					$queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')),
758
-				))
759
-				->andWhere($queryBuilder->expr()->orX(
760
-					$queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)),
761
-				));
762
-		}
763
-
764
-		/** @var list<string> */
765
-		$list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
766
-
767
-		return $list;
768
-	}
769
-
770
-	private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
771
-		$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
772
-
773
-		if (\in_array($uid, [
774
-			'.htaccess',
775
-			'files_external',
776
-			'__groupfolders',
777
-			'.ncdata',
778
-			'owncloud.log',
779
-			'nextcloud.log',
780
-			'updater.log',
781
-			'audit.log',
782
-			$appdata], true)) {
783
-			return false;
784
-		}
785
-
786
-		if (!$checkDataDirectory) {
787
-			return true;
788
-		}
789
-
790
-		$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
791
-
792
-		return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
793
-	}
794
-
795
-	public function getDisplayNameCache(): DisplayNameCache {
796
-		return $this->displayNameCache;
797
-	}
798
-
799
-	public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
800
-		$maxBatchSize = 1000;
801
-
802
-		do {
803
-			if ($limit !== null) {
804
-				$batchSize = min($limit, $maxBatchSize);
805
-				$limit -= $batchSize;
806
-			} else {
807
-				$batchSize = $maxBatchSize;
808
-			}
809
-
810
-			$userIds = $this->getSeenUserIds($batchSize, $offset);
811
-			$offset += $batchSize;
812
-
813
-			foreach ($userIds as $userId) {
814
-				foreach ($this->backends as $backend) {
815
-					if ($backend->userExists($userId)) {
816
-						$user = new LazyUser($userId, $this, null, $backend);
817
-						yield $user;
818
-						break;
819
-					}
820
-				}
821
-			}
822
-		} while (count($userIds) === $batchSize && $limit !== 0);
823
-	}
56
+    /**
57
+     * @var UserInterface[] $backends
58
+     */
59
+    private array $backends = [];
60
+
61
+    /**
62
+     * @var array<string,\OC\User\User> $cachedUsers
63
+     */
64
+    private array $cachedUsers = [];
65
+
66
+    private ICache $cache;
67
+
68
+    private DisplayNameCache $displayNameCache;
69
+
70
+    public function __construct(
71
+        private IConfig $config,
72
+        ICacheFactory $cacheFactory,
73
+        private IEventDispatcher $eventDispatcher,
74
+        private LoggerInterface $logger,
75
+    ) {
76
+        $this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
77
+        $this->listen('\OC\User', 'postDelete', function (IUser $user): void {
78
+            unset($this->cachedUsers[$user->getUID()]);
79
+        });
80
+        $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
81
+    }
82
+
83
+    /**
84
+     * Get the active backends
85
+     * @return UserInterface[]
86
+     */
87
+    public function getBackends(): array {
88
+        return $this->backends;
89
+    }
90
+
91
+    public function registerBackend(UserInterface $backend): void {
92
+        $this->backends[] = $backend;
93
+    }
94
+
95
+    public function removeBackend(UserInterface $backend): void {
96
+        $this->cachedUsers = [];
97
+        if (($i = array_search($backend, $this->backends)) !== false) {
98
+            unset($this->backends[$i]);
99
+        }
100
+    }
101
+
102
+    public function clearBackends(): void {
103
+        $this->cachedUsers = [];
104
+        $this->backends = [];
105
+    }
106
+
107
+    /**
108
+     * get a user by user id
109
+     *
110
+     * @param string $uid
111
+     * @return \OC\User\User|null Either the user or null if the specified user does not exist
112
+     */
113
+    public function get($uid) {
114
+        if (is_null($uid) || $uid === '' || $uid === false) {
115
+            return null;
116
+        }
117
+        if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
118
+            return $this->cachedUsers[$uid];
119
+        }
120
+
121
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
122
+            return null;
123
+        }
124
+
125
+        $cachedBackend = $this->cache->get(sha1($uid));
126
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
127
+            // Cache has the info of the user backend already, so ask that one directly
128
+            $backend = $this->backends[$cachedBackend];
129
+            if ($backend->userExists($uid)) {
130
+                return $this->getUserObject($uid, $backend);
131
+            }
132
+        }
133
+
134
+        foreach ($this->backends as $i => $backend) {
135
+            if ($i === $cachedBackend) {
136
+                // Tried that one already
137
+                continue;
138
+            }
139
+
140
+            if ($backend->userExists($uid)) {
141
+                // Hash $uid to ensure that only valid characters are used for the cache key
142
+                $this->cache->set(sha1($uid), $i, 300);
143
+                return $this->getUserObject($uid, $backend);
144
+            }
145
+        }
146
+        return null;
147
+    }
148
+
149
+    public function getDisplayName(string $uid): ?string {
150
+        return $this->displayNameCache->getDisplayName($uid);
151
+    }
152
+
153
+    /**
154
+     * get or construct the user object
155
+     *
156
+     * @param string $uid
157
+     * @param \OCP\UserInterface $backend
158
+     * @param bool $cacheUser If false the newly created user object will not be cached
159
+     * @return \OC\User\User
160
+     */
161
+    public function getUserObject($uid, $backend, $cacheUser = true) {
162
+        if ($backend instanceof IGetRealUIDBackend) {
163
+            $uid = $backend->getRealUID($uid);
164
+        }
165
+
166
+        if (isset($this->cachedUsers[$uid])) {
167
+            return $this->cachedUsers[$uid];
168
+        }
169
+
170
+        $user = new User($uid, $backend, $this->eventDispatcher, $this, $this->config);
171
+        if ($cacheUser) {
172
+            $this->cachedUsers[$uid] = $user;
173
+        }
174
+        return $user;
175
+    }
176
+
177
+    /**
178
+     * check if a user exists
179
+     *
180
+     * @param string $uid
181
+     * @return bool
182
+     */
183
+    public function userExists($uid) {
184
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
185
+            return false;
186
+        }
187
+
188
+        $user = $this->get($uid);
189
+        return ($user !== null);
190
+    }
191
+
192
+    /**
193
+     * Check if the password is valid for the user
194
+     *
195
+     * @param string $loginName
196
+     * @param string $password
197
+     * @return IUser|false the User object on success, false otherwise
198
+     */
199
+    public function checkPassword($loginName, $password) {
200
+        $result = $this->checkPasswordNoLogging($loginName, $password);
201
+
202
+        if ($result === false) {
203
+            $this->logger->warning('Login failed: \'' . $loginName . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
204
+        }
205
+
206
+        return $result;
207
+    }
208
+
209
+    /**
210
+     * Check if the password is valid for the user
211
+     *
212
+     * @internal
213
+     * @param string $loginName
214
+     * @param string $password
215
+     * @return IUser|false the User object on success, false otherwise
216
+     */
217
+    public function checkPasswordNoLogging($loginName, $password) {
218
+        $loginName = str_replace("\0", '', $loginName);
219
+        $password = str_replace("\0", '', $password);
220
+
221
+        $cachedBackend = $this->cache->get($loginName);
222
+        if ($cachedBackend !== null && isset($this->backends[$cachedBackend])) {
223
+            $backends = [$this->backends[$cachedBackend]];
224
+        } else {
225
+            $backends = $this->backends;
226
+        }
227
+        foreach ($backends as $backend) {
228
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
229
+                /** @var ICheckPasswordBackend $backend */
230
+                $uid = $backend->checkPassword($loginName, $password);
231
+                if ($uid !== false) {
232
+                    return $this->getUserObject($uid, $backend);
233
+                }
234
+            }
235
+        }
236
+
237
+        // since http basic auth doesn't provide a standard way of handling non ascii password we allow password to be urlencoded
238
+        // we only do this decoding after using the plain password fails to maintain compatibility with any password that happens
239
+        // to contain urlencoded patterns by "accident".
240
+        $password = urldecode($password);
241
+
242
+        foreach ($backends as $backend) {
243
+            if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) {
244
+                /** @var ICheckPasswordBackend|UserInterface $backend */
245
+                $uid = $backend->checkPassword($loginName, $password);
246
+                if ($uid !== false) {
247
+                    return $this->getUserObject($uid, $backend);
248
+                }
249
+            }
250
+        }
251
+
252
+        return false;
253
+    }
254
+
255
+    public function search($pattern, $limit = null, $offset = null) {
256
+        $users = [];
257
+        foreach ($this->backends as $backend) {
258
+            $backendUsers = $backend->getUsers($pattern, $limit, $offset);
259
+            if (is_array($backendUsers)) {
260
+                foreach ($backendUsers as $uid) {
261
+                    $users[$uid] = new LazyUser($uid, $this, null, $backend);
262
+                }
263
+            }
264
+        }
265
+
266
+        uasort($users, function (IUser $a, IUser $b) {
267
+            return strcasecmp($a->getUID(), $b->getUID());
268
+        });
269
+        return $users;
270
+    }
271
+
272
+    public function searchDisplayName($pattern, $limit = null, $offset = null) {
273
+        $users = [];
274
+        foreach ($this->backends as $backend) {
275
+            $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
276
+            if (is_array($backendUsers)) {
277
+                foreach ($backendUsers as $uid => $displayName) {
278
+                    $users[] = new LazyUser($uid, $this, $displayName, $backend);
279
+                }
280
+            }
281
+        }
282
+
283
+        usort($users, function (IUser $a, IUser $b) {
284
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
285
+        });
286
+        return $users;
287
+    }
288
+
289
+    /**
290
+     * @return IUser[]
291
+     */
292
+    public function getDisabledUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
293
+        $users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
294
+        $users = array_combine(
295
+            $users,
296
+            array_map(
297
+                fn (string $uid): IUser => new LazyUser($uid, $this),
298
+                $users
299
+            )
300
+        );
301
+        if ($search !== '') {
302
+            $users = array_filter(
303
+                $users,
304
+                function (IUser $user) use ($search): bool {
305
+                    try {
306
+                        return mb_stripos($user->getUID(), $search) !== false
307
+                        || mb_stripos($user->getDisplayName(), $search) !== false
308
+                        || mb_stripos($user->getEMailAddress() ?? '', $search) !== false;
309
+                    } catch (NoUserException $ex) {
310
+                        $this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]);
311
+                        return false;
312
+                    }
313
+                });
314
+        }
315
+
316
+        $tempLimit = ($limit === null ? null : $limit + $offset);
317
+        foreach ($this->backends as $backend) {
318
+            if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
319
+                break;
320
+            }
321
+            if ($backend instanceof IProvideEnabledStateBackend) {
322
+                $backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)), 0, $search);
323
+                foreach ($backendUsers as $uid) {
324
+                    $users[$uid] = new LazyUser($uid, $this, null, $backend);
325
+                }
326
+            }
327
+        }
328
+
329
+        return array_slice($users, $offset, $limit);
330
+    }
331
+
332
+    /**
333
+     * Search known users (from phonebook sync) by displayName
334
+     *
335
+     * @param string $searcher
336
+     * @param string $pattern
337
+     * @param int|null $limit
338
+     * @param int|null $offset
339
+     * @return IUser[]
340
+     */
341
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array {
342
+        $users = [];
343
+        foreach ($this->backends as $backend) {
344
+            if ($backend instanceof ISearchKnownUsersBackend) {
345
+                $backendUsers = $backend->searchKnownUsersByDisplayName($searcher, $pattern, $limit, $offset);
346
+            } else {
347
+                // Better than nothing, but filtering after pagination can remove lots of results.
348
+                $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
349
+            }
350
+            if (is_array($backendUsers)) {
351
+                foreach ($backendUsers as $uid => $displayName) {
352
+                    $users[] = $this->getUserObject($uid, $backend);
353
+                }
354
+            }
355
+        }
356
+
357
+        usort($users, function ($a, $b) {
358
+            /**
359
+             * @var IUser $a
360
+             * @var IUser $b
361
+             */
362
+            return strcasecmp($a->getDisplayName(), $b->getDisplayName());
363
+        });
364
+        return $users;
365
+    }
366
+
367
+    /**
368
+     * @param string $uid
369
+     * @param string $password
370
+     * @return false|IUser the created user or false
371
+     * @throws \InvalidArgumentException
372
+     * @throws HintException
373
+     */
374
+    public function createUser($uid, $password) {
375
+        // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency
376
+        /** @var IAssertion $assertion */
377
+        $assertion = \OC::$server->get(IAssertion::class);
378
+        $assertion->createUserIsLegit();
379
+
380
+        $localBackends = [];
381
+        foreach ($this->backends as $backend) {
382
+            if ($backend instanceof Database) {
383
+                // First check if there is another user backend
384
+                $localBackends[] = $backend;
385
+                continue;
386
+            }
387
+
388
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
389
+                return $this->createUserFromBackend($uid, $password, $backend);
390
+            }
391
+        }
392
+
393
+        foreach ($localBackends as $backend) {
394
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
395
+                return $this->createUserFromBackend($uid, $password, $backend);
396
+            }
397
+        }
398
+
399
+        return false;
400
+    }
401
+
402
+    /**
403
+     * @param string $uid
404
+     * @param string $password
405
+     * @param UserInterface $backend
406
+     * @return IUser|false
407
+     * @throws \InvalidArgumentException
408
+     */
409
+    public function createUserFromBackend($uid, $password, UserInterface $backend) {
410
+        $l = \OCP\Util::getL10N('lib');
411
+
412
+        $this->validateUserId($uid, true);
413
+
414
+        // No empty password
415
+        if (trim($password) === '') {
416
+            throw new \InvalidArgumentException($l->t('A valid password must be provided'));
417
+        }
418
+
419
+        // Check if user already exists
420
+        if ($this->userExists($uid)) {
421
+            throw new \InvalidArgumentException($l->t('The Login is already being used'));
422
+        }
423
+
424
+        /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
425
+        $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
426
+        $this->eventDispatcher->dispatchTyped(new BeforeUserCreatedEvent($uid, $password));
427
+        $state = $backend->createUser($uid, $password);
428
+        if ($state === false) {
429
+            throw new \InvalidArgumentException($l->t('Could not create account'));
430
+        }
431
+        $user = $this->getUserObject($uid, $backend);
432
+        if ($user instanceof IUser) {
433
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
434
+            $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
435
+            $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password));
436
+            return $user;
437
+        }
438
+        return false;
439
+    }
440
+
441
+    /**
442
+     * returns how many users per backend exist (if supported by backend)
443
+     *
444
+     * @param boolean $hasLoggedIn when true only users that have a lastLogin
445
+     *                             entry in the preferences table will be affected
446
+     * @return array<string, int> an array of backend class as key and count number as value
447
+     */
448
+    public function countUsers() {
449
+        $userCountStatistics = [];
450
+        foreach ($this->backends as $backend) {
451
+            if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
452
+                /** @var ICountUsersBackend|IUserBackend $backend */
453
+                $backendUsers = $backend->countUsers();
454
+                if ($backendUsers !== false) {
455
+                    if ($backend instanceof IUserBackend) {
456
+                        $name = $backend->getBackendName();
457
+                    } else {
458
+                        $name = get_class($backend);
459
+                    }
460
+                    if (isset($userCountStatistics[$name])) {
461
+                        $userCountStatistics[$name] += $backendUsers;
462
+                    } else {
463
+                        $userCountStatistics[$name] = $backendUsers;
464
+                    }
465
+                }
466
+            }
467
+        }
468
+        return $userCountStatistics;
469
+    }
470
+
471
+    public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false {
472
+        $userCount = false;
473
+
474
+        foreach ($this->backends as $backend) {
475
+            if ($onlyMappedUsers && $backend instanceof ICountMappedUsersBackend) {
476
+                $backendUsers = $backend->countMappedUsers();
477
+            } elseif ($backend instanceof ILimitAwareCountUsersBackend) {
478
+                $backendUsers = $backend->countUsers($limit);
479
+            } elseif ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) {
480
+                /** @var ICountUsersBackend $backend */
481
+                $backendUsers = $backend->countUsers();
482
+            } else {
483
+                $this->logger->debug('Skip backend for user count: ' . get_class($backend));
484
+                continue;
485
+            }
486
+            if ($backendUsers !== false) {
487
+                $userCount = (int)$userCount + $backendUsers;
488
+                if ($limit > 0) {
489
+                    if ($userCount >= $limit) {
490
+                        break;
491
+                    }
492
+                    $limit -= $userCount;
493
+                }
494
+            } else {
495
+                $this->logger->warning('Can not determine user count for ' . get_class($backend));
496
+            }
497
+        }
498
+        return $userCount;
499
+    }
500
+
501
+    /**
502
+     * returns how many users per backend exist in the requested groups (if supported by backend)
503
+     *
504
+     * @param IGroup[] $groups an array of groups to search in
505
+     * @param int $limit limit to stop counting
506
+     * @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
507
+     */
508
+    public function countUsersAndDisabledUsersOfGroups(array $groups, int $limit): array {
509
+        $users = [];
510
+        $disabled = [];
511
+        foreach ($groups as $group) {
512
+            foreach ($group->getUsers() as $user) {
513
+                $users[$user->getUID()] = 1;
514
+                if (!$user->isEnabled()) {
515
+                    $disabled[$user->getUID()] = 1;
516
+                }
517
+                if (count($users) >= $limit) {
518
+                    return [count($users),-1];
519
+                }
520
+            }
521
+        }
522
+        return [count($users),count($disabled)];
523
+    }
524
+
525
+    /**
526
+     * The callback is executed for each user on each backend.
527
+     * If the callback returns false no further users will be retrieved.
528
+     *
529
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
530
+     * @param string $search
531
+     * @param boolean $onlySeen when true only users that have a lastLogin entry
532
+     *                          in the preferences table will be affected
533
+     * @since 9.0.0
534
+     */
535
+    public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
536
+        if ($onlySeen) {
537
+            $this->callForSeenUsers($callback);
538
+        } else {
539
+            foreach ($this->getBackends() as $backend) {
540
+                $limit = 500;
541
+                $offset = 0;
542
+                do {
543
+                    $users = $backend->getUsers($search, $limit, $offset);
544
+                    foreach ($users as $uid) {
545
+                        if (!$backend->userExists($uid)) {
546
+                            continue;
547
+                        }
548
+                        $user = $this->getUserObject($uid, $backend, false);
549
+                        $return = $callback($user);
550
+                        if ($return === false) {
551
+                            break;
552
+                        }
553
+                    }
554
+                    $offset += $limit;
555
+                } while (count($users) >= $limit);
556
+            }
557
+        }
558
+    }
559
+
560
+    /**
561
+     * returns how many users are disabled
562
+     *
563
+     * @return int
564
+     * @since 12.0.0
565
+     */
566
+    public function countDisabledUsers(): int {
567
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
568
+        $queryBuilder->select($queryBuilder->func()->count('*'))
569
+            ->from('preferences')
570
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
571
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
572
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
573
+
574
+
575
+        $result = $queryBuilder->executeQuery();
576
+        $count = $result->fetchOne();
577
+        $result->closeCursor();
578
+
579
+        if ($count !== false) {
580
+            $count = (int)$count;
581
+        } else {
582
+            $count = 0;
583
+        }
584
+
585
+        return $count;
586
+    }
587
+
588
+    /**
589
+     * returns how many users have logged in once
590
+     *
591
+     * @return int
592
+     * @since 11.0.0
593
+     */
594
+    public function countSeenUsers() {
595
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
596
+        $queryBuilder->select($queryBuilder->func()->count('*'))
597
+            ->from('preferences')
598
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
599
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')));
600
+
601
+        $query = $queryBuilder->executeQuery();
602
+
603
+        $result = (int)$query->fetchOne();
604
+        $query->closeCursor();
605
+
606
+        return $result;
607
+    }
608
+
609
+    public function callForSeenUsers(\Closure $callback) {
610
+        $users = $this->getSeenUsers();
611
+        foreach ($users as $user) {
612
+            $return = $callback($user);
613
+            if ($return === false) {
614
+                return;
615
+            }
616
+        }
617
+    }
618
+
619
+    /**
620
+     * Getting all userIds that have a lastLogin value requires checking the
621
+     * value in php because on oracle you cannot use a clob in a where clause,
622
+     * preventing us from doing a not null or length(value) > 0 check.
623
+     *
624
+     * @param int $limit
625
+     * @param int $offset
626
+     * @return string[] with user ids
627
+     */
628
+    private function getSeenUserIds($limit = null, $offset = null) {
629
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
630
+        $queryBuilder->select(['userid'])
631
+            ->from('preferences')
632
+            ->where($queryBuilder->expr()->eq(
633
+                'appid', $queryBuilder->createNamedParameter('login'))
634
+            )
635
+            ->andWhere($queryBuilder->expr()->eq(
636
+                'configkey', $queryBuilder->createNamedParameter('lastLogin'))
637
+            )
638
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
639
+            );
640
+
641
+        if ($limit !== null) {
642
+            $queryBuilder->setMaxResults($limit);
643
+        }
644
+        if ($offset !== null) {
645
+            $queryBuilder->setFirstResult($offset);
646
+        }
647
+        $query = $queryBuilder->executeQuery();
648
+        $result = [];
649
+
650
+        while ($row = $query->fetch()) {
651
+            $result[] = $row['userid'];
652
+        }
653
+
654
+        $query->closeCursor();
655
+
656
+        return $result;
657
+    }
658
+
659
+    /**
660
+     * @param string $email
661
+     * @return IUser[]
662
+     * @since 9.1.0
663
+     */
664
+    public function getByEmail($email) {
665
+        // looking for 'email' only (and not primary_mail) is intentional
666
+        $userIds = $this->config->getUsersForUserValueCaseInsensitive('settings', 'email', $email);
667
+
668
+        $users = array_map(function ($uid) {
669
+            return $this->get($uid);
670
+        }, $userIds);
671
+
672
+        return array_values(array_filter($users, function ($u) {
673
+            return ($u instanceof IUser);
674
+        }));
675
+    }
676
+
677
+    /**
678
+     * @param string $uid
679
+     * @param bool $checkDataDirectory
680
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
681
+     * @since 26.0.0
682
+     */
683
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
684
+        $l = Server::get(IFactory::class)->get('lib');
685
+
686
+        // Check the ID for bad characters
687
+        // Allowed are: "a-z", "A-Z", "0-9", spaces and "_.@-'"
688
+        if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
689
+            throw new \InvalidArgumentException($l->t('Only the following characters are allowed in an Login:'
690
+                . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'));
691
+        }
692
+
693
+        // No empty user ID
694
+        if (trim($uid) === '') {
695
+            throw new \InvalidArgumentException($l->t('A valid Login must be provided'));
696
+        }
697
+
698
+        // No whitespace at the beginning or at the end
699
+        if (trim($uid) !== $uid) {
700
+            throw new \InvalidArgumentException($l->t('Login contains whitespace at the beginning or at the end'));
701
+        }
702
+
703
+        // User ID only consists of 1 or 2 dots (directory traversal)
704
+        if ($uid === '.' || $uid === '..') {
705
+            throw new \InvalidArgumentException($l->t('Login must not consist of dots only'));
706
+        }
707
+
708
+        // User ID is too long
709
+        if (strlen($uid) > IUser::MAX_USERID_LENGTH) {
710
+            // TRANSLATORS User ID is too long
711
+            throw new \InvalidArgumentException($l->t('Username is too long'));
712
+        }
713
+
714
+        if (!$this->verifyUid($uid, $checkDataDirectory)) {
715
+            throw new \InvalidArgumentException($l->t('Login is invalid because files already exist for this user'));
716
+        }
717
+    }
718
+
719
+    /**
720
+     * Gets the list of user ids sorted by lastLogin, from most recent to least recent
721
+     *
722
+     * @param int|null $limit how many users to fetch (default: 25, max: 100)
723
+     * @param int $offset from which offset to fetch
724
+     * @param string $search search users based on search params
725
+     * @return list<string> list of user IDs
726
+     */
727
+    public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array {
728
+        // We can't load all users who already logged in
729
+        $limit = min(100, $limit ?: 25);
730
+
731
+        $connection = \OC::$server->getDatabaseConnection();
732
+        $queryBuilder = $connection->getQueryBuilder();
733
+        $queryBuilder->select('pref_login.userid')
734
+            ->from('preferences', 'pref_login')
735
+            ->where($queryBuilder->expr()->eq('pref_login.appid', $queryBuilder->expr()->literal('login')))
736
+            ->andWhere($queryBuilder->expr()->eq('pref_login.configkey', $queryBuilder->expr()->literal('lastLogin')))
737
+            ->setFirstResult($offset)
738
+            ->setMaxResults($limit)
739
+        ;
740
+
741
+        // Oracle don't want to run ORDER BY on CLOB column
742
+        $loginOrder = $connection->getDatabasePlatform() instanceof OraclePlatform
743
+            ? $queryBuilder->expr()->castColumn('pref_login.configvalue', IQueryBuilder::PARAM_INT)
744
+            : 'pref_login.configvalue';
745
+        $queryBuilder
746
+            ->orderBy($loginOrder, 'DESC')
747
+            ->addOrderBy($queryBuilder->func()->lower('pref_login.userid'), 'ASC');
748
+
749
+        if ($search !== '') {
750
+            $displayNameMatches = $this->searchDisplayName($search);
751
+            $matchedUids = array_map(static fn (IUser $u): string => $u->getUID(), $displayNameMatches);
752
+
753
+            $queryBuilder
754
+                ->leftJoin('pref_login', 'preferences', 'pref_email', $queryBuilder->expr()->andX(
755
+                    $queryBuilder->expr()->eq('pref_login.userid', 'pref_email.userid'),
756
+                    $queryBuilder->expr()->eq('pref_email.appid', $queryBuilder->expr()->literal('settings')),
757
+                    $queryBuilder->expr()->eq('pref_email.configkey', $queryBuilder->expr()->literal('email')),
758
+                ))
759
+                ->andWhere($queryBuilder->expr()->orX(
760
+                    $queryBuilder->expr()->in('pref_login.userid', $queryBuilder->createNamedParameter($matchedUids, IQueryBuilder::PARAM_STR_ARRAY)),
761
+                ));
762
+        }
763
+
764
+        /** @var list<string> */
765
+        $list = $queryBuilder->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
766
+
767
+        return $list;
768
+    }
769
+
770
+    private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
771
+        $appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
772
+
773
+        if (\in_array($uid, [
774
+            '.htaccess',
775
+            'files_external',
776
+            '__groupfolders',
777
+            '.ncdata',
778
+            'owncloud.log',
779
+            'nextcloud.log',
780
+            'updater.log',
781
+            'audit.log',
782
+            $appdata], true)) {
783
+            return false;
784
+        }
785
+
786
+        if (!$checkDataDirectory) {
787
+            return true;
788
+        }
789
+
790
+        $dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
791
+
792
+        return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
793
+    }
794
+
795
+    public function getDisplayNameCache(): DisplayNameCache {
796
+        return $this->displayNameCache;
797
+    }
798
+
799
+    public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
800
+        $maxBatchSize = 1000;
801
+
802
+        do {
803
+            if ($limit !== null) {
804
+                $batchSize = min($limit, $maxBatchSize);
805
+                $limit -= $batchSize;
806
+            } else {
807
+                $batchSize = $maxBatchSize;
808
+            }
809
+
810
+            $userIds = $this->getSeenUserIds($batchSize, $offset);
811
+            $offset += $batchSize;
812
+
813
+            foreach ($userIds as $userId) {
814
+                foreach ($this->backends as $backend) {
815
+                    if ($backend->userExists($userId)) {
816
+                        $user = new LazyUser($userId, $this, null, $backend);
817
+                        yield $user;
818
+                        break;
819
+                    }
820
+                }
821
+            }
822
+        } while (count($userIds) === $batchSize && $limit !== 0);
823
+    }
824 824
 }
Please login to merge, or discard this patch.
lib/private/Group/Manager.php 1 patch
Indentation   +426 added lines, -426 removed lines patch added patch discarded remove patch
@@ -40,430 +40,430 @@
 block discarded – undo
40 40
  * @package OC\Group
41 41
  */
42 42
 class Manager extends PublicEmitter implements IGroupManager {
43
-	/** @var GroupInterface[] */
44
-	private $backends = [];
45
-
46
-	/** @var array<string, IGroup> */
47
-	private $cachedGroups = [];
48
-
49
-	/** @var array<string, list<string>> */
50
-	private $cachedUserGroups = [];
51
-
52
-	/** @var \OC\SubAdmin */
53
-	private $subAdmin = null;
54
-
55
-	private DisplayNameCache $displayNameCache;
56
-
57
-	private const MAX_GROUP_LENGTH = 255;
58
-
59
-	public function __construct(
60
-		private \OC\User\Manager $userManager,
61
-		private IEventDispatcher $dispatcher,
62
-		private LoggerInterface $logger,
63
-		ICacheFactory $cacheFactory,
64
-		private IRemoteAddress $remoteAddress,
65
-	) {
66
-		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
67
-
68
-		$this->listen('\OC\Group', 'postDelete', function (IGroup $group): void {
69
-			unset($this->cachedGroups[$group->getGID()]);
70
-			$this->cachedUserGroups = [];
71
-		});
72
-		$this->listen('\OC\Group', 'postAddUser', function (IGroup $group): void {
73
-			$this->cachedUserGroups = [];
74
-		});
75
-		$this->listen('\OC\Group', 'postRemoveUser', function (IGroup $group): void {
76
-			$this->cachedUserGroups = [];
77
-		});
78
-	}
79
-
80
-	/**
81
-	 * Checks whether a given backend is used
82
-	 *
83
-	 * @param string $backendClass Full classname including complete namespace
84
-	 * @return bool
85
-	 */
86
-	public function isBackendUsed($backendClass) {
87
-		$backendClass = strtolower(ltrim($backendClass, '\\'));
88
-
89
-		foreach ($this->backends as $backend) {
90
-			if (strtolower(get_class($backend)) === $backendClass) {
91
-				return true;
92
-			}
93
-		}
94
-
95
-		return false;
96
-	}
97
-
98
-	/**
99
-	 * @param \OCP\GroupInterface $backend
100
-	 */
101
-	public function addBackend($backend) {
102
-		$this->backends[] = $backend;
103
-		$this->clearCaches();
104
-	}
105
-
106
-	public function clearBackends() {
107
-		$this->backends = [];
108
-		$this->clearCaches();
109
-	}
110
-
111
-	/**
112
-	 * Get the active backends
113
-	 *
114
-	 * @return \OCP\GroupInterface[]
115
-	 */
116
-	public function getBackends() {
117
-		return $this->backends;
118
-	}
119
-
120
-
121
-	protected function clearCaches() {
122
-		$this->cachedGroups = [];
123
-		$this->cachedUserGroups = [];
124
-	}
125
-
126
-	/**
127
-	 * @param string $gid
128
-	 * @return IGroup|null
129
-	 */
130
-	public function get($gid) {
131
-		if (isset($this->cachedGroups[$gid])) {
132
-			return $this->cachedGroups[$gid];
133
-		}
134
-		return $this->getGroupObject($gid);
135
-	}
136
-
137
-	/**
138
-	 * @param string $gid
139
-	 * @param string $displayName
140
-	 * @return \OCP\IGroup|null
141
-	 */
142
-	protected function getGroupObject($gid, $displayName = null) {
143
-		$backends = [];
144
-		foreach ($this->backends as $backend) {
145
-			if ($backend->implementsActions(Backend::GROUP_DETAILS)) {
146
-				$groupData = $backend->getGroupDetails($gid);
147
-				if (is_array($groupData) && !empty($groupData)) {
148
-					// take the display name from the last backend that has a non-null one
149
-					if (is_null($displayName) && isset($groupData['displayName'])) {
150
-						$displayName = $groupData['displayName'];
151
-					}
152
-					$backends[] = $backend;
153
-				}
154
-			} elseif ($backend->groupExists($gid)) {
155
-				$backends[] = $backend;
156
-			}
157
-		}
158
-		if (count($backends) === 0) {
159
-			return null;
160
-		}
161
-		/** @var GroupInterface[] $backends */
162
-		$this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this, $displayName);
163
-		return $this->cachedGroups[$gid];
164
-	}
165
-
166
-	/**
167
-	 * @brief Batch method to create group objects
168
-	 *
169
-	 * @param list<string> $gids List of groupIds for which we want to create a IGroup object
170
-	 * @param array<string, string> $displayNames Array containing already know display name for a groupId
171
-	 * @return array<string, IGroup>
172
-	 */
173
-	protected function getGroupsObjects(array $gids, array $displayNames = []): array {
174
-		$backends = [];
175
-		$groups = [];
176
-		foreach ($gids as $gid) {
177
-			$backends[$gid] = [];
178
-			if (!isset($displayNames[$gid])) {
179
-				$displayNames[$gid] = null;
180
-			}
181
-		}
182
-		foreach ($this->backends as $backend) {
183
-			if ($backend instanceof IGroupDetailsBackend || $backend->implementsActions(GroupInterface::GROUP_DETAILS)) {
184
-				/** @var IGroupDetailsBackend $backend */
185
-				if ($backend instanceof IBatchMethodsBackend) {
186
-					$groupDatas = $backend->getGroupsDetails($gids);
187
-				} else {
188
-					$groupDatas = [];
189
-					foreach ($gids as $gid) {
190
-						$groupDatas[$gid] = $backend->getGroupDetails($gid);
191
-					}
192
-				}
193
-				foreach ($groupDatas as $gid => $groupData) {
194
-					if (!empty($groupData)) {
195
-						// take the display name from the last backend that has a non-null one
196
-						if (isset($groupData['displayName'])) {
197
-							$displayNames[$gid] = $groupData['displayName'];
198
-						}
199
-						$backends[$gid][] = $backend;
200
-					}
201
-				}
202
-			} else {
203
-				if ($backend instanceof IBatchMethodsBackend) {
204
-					$existingGroups = $backend->groupsExists($gids);
205
-				} else {
206
-					$existingGroups = array_filter($gids, fn (string $gid): bool => $backend->groupExists($gid));
207
-				}
208
-				foreach ($existingGroups as $group) {
209
-					$backends[$group][] = $backend;
210
-				}
211
-			}
212
-		}
213
-		foreach ($gids as $gid) {
214
-			if (count($backends[$gid]) === 0) {
215
-				continue;
216
-			}
217
-			$this->cachedGroups[$gid] = new Group($gid, $backends[$gid], $this->dispatcher, $this->userManager, $this, $displayNames[$gid]);
218
-			$groups[$gid] = $this->cachedGroups[$gid];
219
-		}
220
-		return $groups;
221
-	}
222
-
223
-	/**
224
-	 * @param string $gid
225
-	 * @return bool
226
-	 */
227
-	public function groupExists($gid) {
228
-		return $this->get($gid) instanceof IGroup;
229
-	}
230
-
231
-	/**
232
-	 * @param string $gid
233
-	 * @return IGroup|null
234
-	 */
235
-	public function createGroup($gid) {
236
-		if ($gid === '' || $gid === null) {
237
-			return null;
238
-		} elseif ($group = $this->get($gid)) {
239
-			return $group;
240
-		} elseif (mb_strlen($gid) > self::MAX_GROUP_LENGTH) {
241
-			throw new \Exception('Group name is limited to ' . self::MAX_GROUP_LENGTH . ' characters');
242
-		} else {
243
-			$this->dispatcher->dispatchTyped(new BeforeGroupCreatedEvent($gid));
244
-			$this->emit('\OC\Group', 'preCreate', [$gid]);
245
-			foreach ($this->backends as $backend) {
246
-				if ($backend->implementsActions(Backend::CREATE_GROUP)) {
247
-					if ($backend instanceof ICreateNamedGroupBackend) {
248
-						$groupName = $gid;
249
-						if (($gid = $backend->createGroup($groupName)) !== null) {
250
-							$group = $this->getGroupObject($gid);
251
-							$this->dispatcher->dispatchTyped(new GroupCreatedEvent($group));
252
-							$this->emit('\OC\Group', 'postCreate', [$group]);
253
-							return $group;
254
-						}
255
-					} elseif ($backend->createGroup($gid)) {
256
-						$group = $this->getGroupObject($gid);
257
-						$this->dispatcher->dispatchTyped(new GroupCreatedEvent($group));
258
-						$this->emit('\OC\Group', 'postCreate', [$group]);
259
-						return $group;
260
-					}
261
-				}
262
-			}
263
-			return null;
264
-		}
265
-	}
266
-
267
-	public function search(string $search, ?int $limit = null, ?int $offset = 0) {
268
-		$groups = [];
269
-		foreach ($this->backends as $backend) {
270
-			$groupIds = $backend->getGroups($search, $limit ?? -1, $offset ?? 0);
271
-			$newGroups = $this->getGroupsObjects($groupIds);
272
-			foreach ($newGroups as $groupId => $group) {
273
-				$groups[$groupId] = $group;
274
-			}
275
-			if (!is_null($limit) && $limit <= 0) {
276
-				return array_values($groups);
277
-			}
278
-		}
279
-		return array_values($groups);
280
-	}
281
-
282
-	/**
283
-	 * @param IUser|null $user
284
-	 * @return \OC\Group\Group[]
285
-	 */
286
-	public function getUserGroups(?IUser $user = null) {
287
-		if (!$user instanceof IUser) {
288
-			return [];
289
-		}
290
-		return $this->getUserIdGroups($user->getUID());
291
-	}
292
-
293
-	/**
294
-	 * @param string $uid the user id
295
-	 * @return \OC\Group\Group[]
296
-	 */
297
-	public function getUserIdGroups(string $uid): array {
298
-		$groups = [];
299
-
300
-		foreach ($this->getUserIdGroupIds($uid) as $groupId) {
301
-			$aGroup = $this->get($groupId);
302
-			if ($aGroup instanceof IGroup) {
303
-				$groups[$groupId] = $aGroup;
304
-			} else {
305
-				$this->logger->debug('User "' . $uid . '" belongs to deleted group: "' . $groupId . '"', ['app' => 'core']);
306
-			}
307
-		}
308
-
309
-		return $groups;
310
-	}
311
-
312
-	/**
313
-	 * Checks if a userId is in the admin group
314
-	 *
315
-	 * @param string $userId
316
-	 * @return bool if admin
317
-	 */
318
-	public function isAdmin($userId) {
319
-		if (!$this->remoteAddress->allowsAdminActions()) {
320
-			return false;
321
-		}
322
-
323
-		foreach ($this->backends as $backend) {
324
-			if (is_string($userId) && $backend->implementsActions(Backend::IS_ADMIN) && $backend->isAdmin($userId)) {
325
-				return true;
326
-			}
327
-		}
328
-		return $this->isInGroup($userId, 'admin');
329
-	}
330
-
331
-	public function isDelegatedAdmin(string $userId): bool {
332
-		if (!$this->remoteAddress->allowsAdminActions()) {
333
-			return false;
334
-		}
335
-
336
-		// Check if the user as admin delegation for users listing
337
-		$authorizedGroupMapper = \OCP\Server::get(AuthorizedGroupMapper::class);
338
-		$user = $this->userManager->get($userId);
339
-		$authorizedClasses = $authorizedGroupMapper->findAllClassesForUser($user);
340
-		return in_array(\OCA\Settings\Settings\Admin\Users::class, $authorizedClasses, true);
341
-	}
342
-
343
-	/**
344
-	 * Checks if a userId is in a group
345
-	 *
346
-	 * @param string $userId
347
-	 * @param string $group
348
-	 * @return bool if in group
349
-	 */
350
-	public function isInGroup($userId, $group) {
351
-		return in_array($group, $this->getUserIdGroupIds($userId));
352
-	}
353
-
354
-	/**
355
-	 * get a list of group ids for a user
356
-	 *
357
-	 * @param IUser $user
358
-	 * @return list<string> with group ids
359
-	 */
360
-	public function getUserGroupIds(IUser $user): array {
361
-		return $this->getUserIdGroupIds($user->getUID());
362
-	}
363
-
364
-	/**
365
-	 * @param string $uid the user id
366
-	 * @return list<string>
367
-	 */
368
-	private function getUserIdGroupIds(string $uid): array {
369
-		if (!isset($this->cachedUserGroups[$uid])) {
370
-			$groups = [];
371
-			foreach ($this->backends as $backend) {
372
-				if ($groupIds = $backend->getUserGroups($uid)) {
373
-					$groups = array_merge($groups, $groupIds);
374
-				}
375
-			}
376
-			$this->cachedUserGroups[$uid] = $groups;
377
-		}
378
-
379
-		return $this->cachedUserGroups[$uid];
380
-	}
381
-
382
-	/**
383
-	 * @param string $groupId
384
-	 * @return ?string
385
-	 */
386
-	public function getDisplayName(string $groupId): ?string {
387
-		return $this->displayNameCache->getDisplayName($groupId);
388
-	}
389
-
390
-	/**
391
-	 * get an array of groupid and displayName for a user
392
-	 *
393
-	 * @param IUser $user
394
-	 * @return array ['displayName' => displayname]
395
-	 */
396
-	public function getUserGroupNames(IUser $user) {
397
-		return array_map(function ($group) {
398
-			return ['displayName' => $this->displayNameCache->getDisplayName($group->getGID())];
399
-		}, $this->getUserGroups($user));
400
-	}
401
-
402
-	/**
403
-	 * get a list of all display names in a group
404
-	 *
405
-	 * @param string $gid
406
-	 * @param string $search
407
-	 * @param int $limit
408
-	 * @param int $offset
409
-	 * @return array an array of display names (value) and user ids (key)
410
-	 */
411
-	public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) {
412
-		$group = $this->get($gid);
413
-		if (is_null($group)) {
414
-			return [];
415
-		}
416
-
417
-		$search = trim($search);
418
-		$groupUsers = [];
419
-
420
-		if (!empty($search)) {
421
-			// only user backends have the capability to do a complex search for users
422
-			$searchOffset = 0;
423
-			$searchLimit = $limit * 100;
424
-			if ($limit === -1) {
425
-				$searchLimit = 500;
426
-			}
427
-
428
-			do {
429
-				$filteredUsers = $this->userManager->searchDisplayName($search, $searchLimit, $searchOffset);
430
-				foreach ($filteredUsers as $filteredUser) {
431
-					if ($group->inGroup($filteredUser)) {
432
-						$groupUsers[] = $filteredUser;
433
-					}
434
-				}
435
-				$searchOffset += $searchLimit;
436
-			} while (count($groupUsers) < $searchLimit + $offset && count($filteredUsers) >= $searchLimit);
437
-
438
-			if ($limit === -1) {
439
-				$groupUsers = array_slice($groupUsers, $offset);
440
-			} else {
441
-				$groupUsers = array_slice($groupUsers, $offset, $limit);
442
-			}
443
-		} else {
444
-			$groupUsers = $group->searchUsers('', $limit, $offset);
445
-		}
446
-
447
-		$matchingUsers = [];
448
-		foreach ($groupUsers as $groupUser) {
449
-			$matchingUsers[(string)$groupUser->getUID()] = $groupUser->getDisplayName();
450
-		}
451
-		return $matchingUsers;
452
-	}
453
-
454
-	/**
455
-	 * @return \OC\SubAdmin
456
-	 */
457
-	public function getSubAdmin() {
458
-		if (!$this->subAdmin) {
459
-			$this->subAdmin = new \OC\SubAdmin(
460
-				$this->userManager,
461
-				$this,
462
-				\OC::$server->getDatabaseConnection(),
463
-				$this->dispatcher
464
-			);
465
-		}
466
-
467
-		return $this->subAdmin;
468
-	}
43
+    /** @var GroupInterface[] */
44
+    private $backends = [];
45
+
46
+    /** @var array<string, IGroup> */
47
+    private $cachedGroups = [];
48
+
49
+    /** @var array<string, list<string>> */
50
+    private $cachedUserGroups = [];
51
+
52
+    /** @var \OC\SubAdmin */
53
+    private $subAdmin = null;
54
+
55
+    private DisplayNameCache $displayNameCache;
56
+
57
+    private const MAX_GROUP_LENGTH = 255;
58
+
59
+    public function __construct(
60
+        private \OC\User\Manager $userManager,
61
+        private IEventDispatcher $dispatcher,
62
+        private LoggerInterface $logger,
63
+        ICacheFactory $cacheFactory,
64
+        private IRemoteAddress $remoteAddress,
65
+    ) {
66
+        $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
67
+
68
+        $this->listen('\OC\Group', 'postDelete', function (IGroup $group): void {
69
+            unset($this->cachedGroups[$group->getGID()]);
70
+            $this->cachedUserGroups = [];
71
+        });
72
+        $this->listen('\OC\Group', 'postAddUser', function (IGroup $group): void {
73
+            $this->cachedUserGroups = [];
74
+        });
75
+        $this->listen('\OC\Group', 'postRemoveUser', function (IGroup $group): void {
76
+            $this->cachedUserGroups = [];
77
+        });
78
+    }
79
+
80
+    /**
81
+     * Checks whether a given backend is used
82
+     *
83
+     * @param string $backendClass Full classname including complete namespace
84
+     * @return bool
85
+     */
86
+    public function isBackendUsed($backendClass) {
87
+        $backendClass = strtolower(ltrim($backendClass, '\\'));
88
+
89
+        foreach ($this->backends as $backend) {
90
+            if (strtolower(get_class($backend)) === $backendClass) {
91
+                return true;
92
+            }
93
+        }
94
+
95
+        return false;
96
+    }
97
+
98
+    /**
99
+     * @param \OCP\GroupInterface $backend
100
+     */
101
+    public function addBackend($backend) {
102
+        $this->backends[] = $backend;
103
+        $this->clearCaches();
104
+    }
105
+
106
+    public function clearBackends() {
107
+        $this->backends = [];
108
+        $this->clearCaches();
109
+    }
110
+
111
+    /**
112
+     * Get the active backends
113
+     *
114
+     * @return \OCP\GroupInterface[]
115
+     */
116
+    public function getBackends() {
117
+        return $this->backends;
118
+    }
119
+
120
+
121
+    protected function clearCaches() {
122
+        $this->cachedGroups = [];
123
+        $this->cachedUserGroups = [];
124
+    }
125
+
126
+    /**
127
+     * @param string $gid
128
+     * @return IGroup|null
129
+     */
130
+    public function get($gid) {
131
+        if (isset($this->cachedGroups[$gid])) {
132
+            return $this->cachedGroups[$gid];
133
+        }
134
+        return $this->getGroupObject($gid);
135
+    }
136
+
137
+    /**
138
+     * @param string $gid
139
+     * @param string $displayName
140
+     * @return \OCP\IGroup|null
141
+     */
142
+    protected function getGroupObject($gid, $displayName = null) {
143
+        $backends = [];
144
+        foreach ($this->backends as $backend) {
145
+            if ($backend->implementsActions(Backend::GROUP_DETAILS)) {
146
+                $groupData = $backend->getGroupDetails($gid);
147
+                if (is_array($groupData) && !empty($groupData)) {
148
+                    // take the display name from the last backend that has a non-null one
149
+                    if (is_null($displayName) && isset($groupData['displayName'])) {
150
+                        $displayName = $groupData['displayName'];
151
+                    }
152
+                    $backends[] = $backend;
153
+                }
154
+            } elseif ($backend->groupExists($gid)) {
155
+                $backends[] = $backend;
156
+            }
157
+        }
158
+        if (count($backends) === 0) {
159
+            return null;
160
+        }
161
+        /** @var GroupInterface[] $backends */
162
+        $this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this, $displayName);
163
+        return $this->cachedGroups[$gid];
164
+    }
165
+
166
+    /**
167
+     * @brief Batch method to create group objects
168
+     *
169
+     * @param list<string> $gids List of groupIds for which we want to create a IGroup object
170
+     * @param array<string, string> $displayNames Array containing already know display name for a groupId
171
+     * @return array<string, IGroup>
172
+     */
173
+    protected function getGroupsObjects(array $gids, array $displayNames = []): array {
174
+        $backends = [];
175
+        $groups = [];
176
+        foreach ($gids as $gid) {
177
+            $backends[$gid] = [];
178
+            if (!isset($displayNames[$gid])) {
179
+                $displayNames[$gid] = null;
180
+            }
181
+        }
182
+        foreach ($this->backends as $backend) {
183
+            if ($backend instanceof IGroupDetailsBackend || $backend->implementsActions(GroupInterface::GROUP_DETAILS)) {
184
+                /** @var IGroupDetailsBackend $backend */
185
+                if ($backend instanceof IBatchMethodsBackend) {
186
+                    $groupDatas = $backend->getGroupsDetails($gids);
187
+                } else {
188
+                    $groupDatas = [];
189
+                    foreach ($gids as $gid) {
190
+                        $groupDatas[$gid] = $backend->getGroupDetails($gid);
191
+                    }
192
+                }
193
+                foreach ($groupDatas as $gid => $groupData) {
194
+                    if (!empty($groupData)) {
195
+                        // take the display name from the last backend that has a non-null one
196
+                        if (isset($groupData['displayName'])) {
197
+                            $displayNames[$gid] = $groupData['displayName'];
198
+                        }
199
+                        $backends[$gid][] = $backend;
200
+                    }
201
+                }
202
+            } else {
203
+                if ($backend instanceof IBatchMethodsBackend) {
204
+                    $existingGroups = $backend->groupsExists($gids);
205
+                } else {
206
+                    $existingGroups = array_filter($gids, fn (string $gid): bool => $backend->groupExists($gid));
207
+                }
208
+                foreach ($existingGroups as $group) {
209
+                    $backends[$group][] = $backend;
210
+                }
211
+            }
212
+        }
213
+        foreach ($gids as $gid) {
214
+            if (count($backends[$gid]) === 0) {
215
+                continue;
216
+            }
217
+            $this->cachedGroups[$gid] = new Group($gid, $backends[$gid], $this->dispatcher, $this->userManager, $this, $displayNames[$gid]);
218
+            $groups[$gid] = $this->cachedGroups[$gid];
219
+        }
220
+        return $groups;
221
+    }
222
+
223
+    /**
224
+     * @param string $gid
225
+     * @return bool
226
+     */
227
+    public function groupExists($gid) {
228
+        return $this->get($gid) instanceof IGroup;
229
+    }
230
+
231
+    /**
232
+     * @param string $gid
233
+     * @return IGroup|null
234
+     */
235
+    public function createGroup($gid) {
236
+        if ($gid === '' || $gid === null) {
237
+            return null;
238
+        } elseif ($group = $this->get($gid)) {
239
+            return $group;
240
+        } elseif (mb_strlen($gid) > self::MAX_GROUP_LENGTH) {
241
+            throw new \Exception('Group name is limited to ' . self::MAX_GROUP_LENGTH . ' characters');
242
+        } else {
243
+            $this->dispatcher->dispatchTyped(new BeforeGroupCreatedEvent($gid));
244
+            $this->emit('\OC\Group', 'preCreate', [$gid]);
245
+            foreach ($this->backends as $backend) {
246
+                if ($backend->implementsActions(Backend::CREATE_GROUP)) {
247
+                    if ($backend instanceof ICreateNamedGroupBackend) {
248
+                        $groupName = $gid;
249
+                        if (($gid = $backend->createGroup($groupName)) !== null) {
250
+                            $group = $this->getGroupObject($gid);
251
+                            $this->dispatcher->dispatchTyped(new GroupCreatedEvent($group));
252
+                            $this->emit('\OC\Group', 'postCreate', [$group]);
253
+                            return $group;
254
+                        }
255
+                    } elseif ($backend->createGroup($gid)) {
256
+                        $group = $this->getGroupObject($gid);
257
+                        $this->dispatcher->dispatchTyped(new GroupCreatedEvent($group));
258
+                        $this->emit('\OC\Group', 'postCreate', [$group]);
259
+                        return $group;
260
+                    }
261
+                }
262
+            }
263
+            return null;
264
+        }
265
+    }
266
+
267
+    public function search(string $search, ?int $limit = null, ?int $offset = 0) {
268
+        $groups = [];
269
+        foreach ($this->backends as $backend) {
270
+            $groupIds = $backend->getGroups($search, $limit ?? -1, $offset ?? 0);
271
+            $newGroups = $this->getGroupsObjects($groupIds);
272
+            foreach ($newGroups as $groupId => $group) {
273
+                $groups[$groupId] = $group;
274
+            }
275
+            if (!is_null($limit) && $limit <= 0) {
276
+                return array_values($groups);
277
+            }
278
+        }
279
+        return array_values($groups);
280
+    }
281
+
282
+    /**
283
+     * @param IUser|null $user
284
+     * @return \OC\Group\Group[]
285
+     */
286
+    public function getUserGroups(?IUser $user = null) {
287
+        if (!$user instanceof IUser) {
288
+            return [];
289
+        }
290
+        return $this->getUserIdGroups($user->getUID());
291
+    }
292
+
293
+    /**
294
+     * @param string $uid the user id
295
+     * @return \OC\Group\Group[]
296
+     */
297
+    public function getUserIdGroups(string $uid): array {
298
+        $groups = [];
299
+
300
+        foreach ($this->getUserIdGroupIds($uid) as $groupId) {
301
+            $aGroup = $this->get($groupId);
302
+            if ($aGroup instanceof IGroup) {
303
+                $groups[$groupId] = $aGroup;
304
+            } else {
305
+                $this->logger->debug('User "' . $uid . '" belongs to deleted group: "' . $groupId . '"', ['app' => 'core']);
306
+            }
307
+        }
308
+
309
+        return $groups;
310
+    }
311
+
312
+    /**
313
+     * Checks if a userId is in the admin group
314
+     *
315
+     * @param string $userId
316
+     * @return bool if admin
317
+     */
318
+    public function isAdmin($userId) {
319
+        if (!$this->remoteAddress->allowsAdminActions()) {
320
+            return false;
321
+        }
322
+
323
+        foreach ($this->backends as $backend) {
324
+            if (is_string($userId) && $backend->implementsActions(Backend::IS_ADMIN) && $backend->isAdmin($userId)) {
325
+                return true;
326
+            }
327
+        }
328
+        return $this->isInGroup($userId, 'admin');
329
+    }
330
+
331
+    public function isDelegatedAdmin(string $userId): bool {
332
+        if (!$this->remoteAddress->allowsAdminActions()) {
333
+            return false;
334
+        }
335
+
336
+        // Check if the user as admin delegation for users listing
337
+        $authorizedGroupMapper = \OCP\Server::get(AuthorizedGroupMapper::class);
338
+        $user = $this->userManager->get($userId);
339
+        $authorizedClasses = $authorizedGroupMapper->findAllClassesForUser($user);
340
+        return in_array(\OCA\Settings\Settings\Admin\Users::class, $authorizedClasses, true);
341
+    }
342
+
343
+    /**
344
+     * Checks if a userId is in a group
345
+     *
346
+     * @param string $userId
347
+     * @param string $group
348
+     * @return bool if in group
349
+     */
350
+    public function isInGroup($userId, $group) {
351
+        return in_array($group, $this->getUserIdGroupIds($userId));
352
+    }
353
+
354
+    /**
355
+     * get a list of group ids for a user
356
+     *
357
+     * @param IUser $user
358
+     * @return list<string> with group ids
359
+     */
360
+    public function getUserGroupIds(IUser $user): array {
361
+        return $this->getUserIdGroupIds($user->getUID());
362
+    }
363
+
364
+    /**
365
+     * @param string $uid the user id
366
+     * @return list<string>
367
+     */
368
+    private function getUserIdGroupIds(string $uid): array {
369
+        if (!isset($this->cachedUserGroups[$uid])) {
370
+            $groups = [];
371
+            foreach ($this->backends as $backend) {
372
+                if ($groupIds = $backend->getUserGroups($uid)) {
373
+                    $groups = array_merge($groups, $groupIds);
374
+                }
375
+            }
376
+            $this->cachedUserGroups[$uid] = $groups;
377
+        }
378
+
379
+        return $this->cachedUserGroups[$uid];
380
+    }
381
+
382
+    /**
383
+     * @param string $groupId
384
+     * @return ?string
385
+     */
386
+    public function getDisplayName(string $groupId): ?string {
387
+        return $this->displayNameCache->getDisplayName($groupId);
388
+    }
389
+
390
+    /**
391
+     * get an array of groupid and displayName for a user
392
+     *
393
+     * @param IUser $user
394
+     * @return array ['displayName' => displayname]
395
+     */
396
+    public function getUserGroupNames(IUser $user) {
397
+        return array_map(function ($group) {
398
+            return ['displayName' => $this->displayNameCache->getDisplayName($group->getGID())];
399
+        }, $this->getUserGroups($user));
400
+    }
401
+
402
+    /**
403
+     * get a list of all display names in a group
404
+     *
405
+     * @param string $gid
406
+     * @param string $search
407
+     * @param int $limit
408
+     * @param int $offset
409
+     * @return array an array of display names (value) and user ids (key)
410
+     */
411
+    public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) {
412
+        $group = $this->get($gid);
413
+        if (is_null($group)) {
414
+            return [];
415
+        }
416
+
417
+        $search = trim($search);
418
+        $groupUsers = [];
419
+
420
+        if (!empty($search)) {
421
+            // only user backends have the capability to do a complex search for users
422
+            $searchOffset = 0;
423
+            $searchLimit = $limit * 100;
424
+            if ($limit === -1) {
425
+                $searchLimit = 500;
426
+            }
427
+
428
+            do {
429
+                $filteredUsers = $this->userManager->searchDisplayName($search, $searchLimit, $searchOffset);
430
+                foreach ($filteredUsers as $filteredUser) {
431
+                    if ($group->inGroup($filteredUser)) {
432
+                        $groupUsers[] = $filteredUser;
433
+                    }
434
+                }
435
+                $searchOffset += $searchLimit;
436
+            } while (count($groupUsers) < $searchLimit + $offset && count($filteredUsers) >= $searchLimit);
437
+
438
+            if ($limit === -1) {
439
+                $groupUsers = array_slice($groupUsers, $offset);
440
+            } else {
441
+                $groupUsers = array_slice($groupUsers, $offset, $limit);
442
+            }
443
+        } else {
444
+            $groupUsers = $group->searchUsers('', $limit, $offset);
445
+        }
446
+
447
+        $matchingUsers = [];
448
+        foreach ($groupUsers as $groupUser) {
449
+            $matchingUsers[(string)$groupUser->getUID()] = $groupUser->getDisplayName();
450
+        }
451
+        return $matchingUsers;
452
+    }
453
+
454
+    /**
455
+     * @return \OC\SubAdmin
456
+     */
457
+    public function getSubAdmin() {
458
+        if (!$this->subAdmin) {
459
+            $this->subAdmin = new \OC\SubAdmin(
460
+                $this->userManager,
461
+                $this,
462
+                \OC::$server->getDatabaseConnection(),
463
+                $this->dispatcher
464
+            );
465
+        }
466
+
467
+        return $this->subAdmin;
468
+    }
469 469
 }
Please login to merge, or discard this patch.
apps/provisioning_api/lib/Controller/GroupsController.php 2 patches
Indentation   +287 added lines, -287 removed lines patch added patch discarded remove patch
@@ -39,316 +39,316 @@
 block discarded – undo
39 39
  */
40 40
 class GroupsController extends AUserDataOCSController {
41 41
 
42
-	public function __construct(
43
-		string $appName,
44
-		IRequest $request,
45
-		IUserManager $userManager,
46
-		IConfig $config,
47
-		IGroupManager $groupManager,
48
-		IUserSession $userSession,
49
-		IAccountManager $accountManager,
50
-		ISubAdmin $subAdminManager,
51
-		IFactory $l10nFactory,
52
-		IRootFolder $rootFolder,
53
-		private LoggerInterface $logger,
54
-	) {
55
-		parent::__construct($appName,
56
-			$request,
57
-			$userManager,
58
-			$config,
59
-			$groupManager,
60
-			$userSession,
61
-			$accountManager,
62
-			$subAdminManager,
63
-			$l10nFactory,
64
-			$rootFolder,
65
-		);
66
-	}
42
+    public function __construct(
43
+        string $appName,
44
+        IRequest $request,
45
+        IUserManager $userManager,
46
+        IConfig $config,
47
+        IGroupManager $groupManager,
48
+        IUserSession $userSession,
49
+        IAccountManager $accountManager,
50
+        ISubAdmin $subAdminManager,
51
+        IFactory $l10nFactory,
52
+        IRootFolder $rootFolder,
53
+        private LoggerInterface $logger,
54
+    ) {
55
+        parent::__construct($appName,
56
+            $request,
57
+            $userManager,
58
+            $config,
59
+            $groupManager,
60
+            $userSession,
61
+            $accountManager,
62
+            $subAdminManager,
63
+            $l10nFactory,
64
+            $rootFolder,
65
+        );
66
+    }
67 67
 
68
-	/**
69
-	 * Get a list of groups
70
-	 *
71
-	 * @param string $search Text to search for
72
-	 * @param ?int $limit Limit the amount of groups returned
73
-	 * @param int $offset Offset for searching for groups
74
-	 * @return DataResponse<Http::STATUS_OK, array{groups: list<string>}, array{}>
75
-	 *
76
-	 * 200: Groups returned
77
-	 */
78
-	#[NoAdminRequired]
79
-	public function getGroups(string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
80
-		$groups = $this->groupManager->search($search, $limit, $offset);
81
-		$groups = array_map(function ($group) {
82
-			/** @var IGroup $group */
83
-			return $group->getGID();
84
-		}, $groups);
68
+    /**
69
+     * Get a list of groups
70
+     *
71
+     * @param string $search Text to search for
72
+     * @param ?int $limit Limit the amount of groups returned
73
+     * @param int $offset Offset for searching for groups
74
+     * @return DataResponse<Http::STATUS_OK, array{groups: list<string>}, array{}>
75
+     *
76
+     * 200: Groups returned
77
+     */
78
+    #[NoAdminRequired]
79
+    public function getGroups(string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
80
+        $groups = $this->groupManager->search($search, $limit, $offset);
81
+        $groups = array_map(function ($group) {
82
+            /** @var IGroup $group */
83
+            return $group->getGID();
84
+        }, $groups);
85 85
 
86
-		return new DataResponse(['groups' => $groups]);
87
-	}
86
+        return new DataResponse(['groups' => $groups]);
87
+    }
88 88
 
89
-	/**
90
-	 * Get a list of groups details
91
-	 *
92
-	 * @param string $search Text to search for
93
-	 * @param ?int $limit Limit the amount of groups returned
94
-	 * @param int $offset Offset for searching for groups
95
-	 * @return DataResponse<Http::STATUS_OK, array{groups: list<Provisioning_APIGroupDetails>}, array{}>
96
-	 *
97
-	 * 200: Groups details returned
98
-	 */
99
-	#[NoAdminRequired]
100
-	#[AuthorizedAdminSetting(settings: Sharing::class)]
101
-	#[AuthorizedAdminSetting(settings: Users::class)]
102
-	public function getGroupsDetails(string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
103
-		$groups = $this->groupManager->search($search, $limit, $offset);
104
-		$groups = array_map(function ($group) {
105
-			/** @var IGroup $group */
106
-			return [
107
-				'id' => $group->getGID(),
108
-				'displayname' => $group->getDisplayName(),
109
-				'usercount' => $group->count(),
110
-				'disabled' => $group->countDisabled(),
111
-				'canAdd' => $group->canAddUser(),
112
-				'canRemove' => $group->canRemoveUser(),
113
-			];
114
-		}, $groups);
89
+    /**
90
+     * Get a list of groups details
91
+     *
92
+     * @param string $search Text to search for
93
+     * @param ?int $limit Limit the amount of groups returned
94
+     * @param int $offset Offset for searching for groups
95
+     * @return DataResponse<Http::STATUS_OK, array{groups: list<Provisioning_APIGroupDetails>}, array{}>
96
+     *
97
+     * 200: Groups details returned
98
+     */
99
+    #[NoAdminRequired]
100
+    #[AuthorizedAdminSetting(settings: Sharing::class)]
101
+    #[AuthorizedAdminSetting(settings: Users::class)]
102
+    public function getGroupsDetails(string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
103
+        $groups = $this->groupManager->search($search, $limit, $offset);
104
+        $groups = array_map(function ($group) {
105
+            /** @var IGroup $group */
106
+            return [
107
+                'id' => $group->getGID(),
108
+                'displayname' => $group->getDisplayName(),
109
+                'usercount' => $group->count(),
110
+                'disabled' => $group->countDisabled(),
111
+                'canAdd' => $group->canAddUser(),
112
+                'canRemove' => $group->canRemoveUser(),
113
+            ];
114
+        }, $groups);
115 115
 
116
-		return new DataResponse(['groups' => $groups]);
117
-	}
116
+        return new DataResponse(['groups' => $groups]);
117
+    }
118 118
 
119
-	/**
120
-	 * Get a list of users in the specified group
121
-	 *
122
-	 * @param string $groupId ID of the group
123
-	 * @return DataResponse<Http::STATUS_OK, array{users: list<string>}, array{}>
124
-	 * @throws OCSException
125
-	 *
126
-	 * @deprecated 14 Use getGroupUsers
127
-	 *
128
-	 * 200: Group users returned
129
-	 */
130
-	#[NoAdminRequired]
131
-	public function getGroup(string $groupId): DataResponse {
132
-		return $this->getGroupUsers($groupId);
133
-	}
119
+    /**
120
+     * Get a list of users in the specified group
121
+     *
122
+     * @param string $groupId ID of the group
123
+     * @return DataResponse<Http::STATUS_OK, array{users: list<string>}, array{}>
124
+     * @throws OCSException
125
+     *
126
+     * @deprecated 14 Use getGroupUsers
127
+     *
128
+     * 200: Group users returned
129
+     */
130
+    #[NoAdminRequired]
131
+    public function getGroup(string $groupId): DataResponse {
132
+        return $this->getGroupUsers($groupId);
133
+    }
134 134
 
135
-	/**
136
-	 * @NoSubAdminRequired
137
-	 *
138
-	 * Get a list of users in the specified group
139
-	 *
140
-	 * @param string $groupId ID of the group
141
-	 * @return DataResponse<Http::STATUS_OK, array{users: list<string>}, array{}>
142
-	 * @throws OCSException
143
-	 * @throws OCSNotFoundException Group not found
144
-	 * @throws OCSForbiddenException Missing permissions to get users in the group
145
-	 *
146
-	 * 200: User IDs returned
147
-	 */
148
-	#[NoAdminRequired]
149
-	public function getGroupUsers(string $groupId): DataResponse {
150
-		$groupId = urldecode($groupId);
135
+    /**
136
+     * @NoSubAdminRequired
137
+     *
138
+     * Get a list of users in the specified group
139
+     *
140
+     * @param string $groupId ID of the group
141
+     * @return DataResponse<Http::STATUS_OK, array{users: list<string>}, array{}>
142
+     * @throws OCSException
143
+     * @throws OCSNotFoundException Group not found
144
+     * @throws OCSForbiddenException Missing permissions to get users in the group
145
+     *
146
+     * 200: User IDs returned
147
+     */
148
+    #[NoAdminRequired]
149
+    public function getGroupUsers(string $groupId): DataResponse {
150
+        $groupId = urldecode($groupId);
151 151
 
152
-		$user = $this->userSession->getUser();
153
-		$isSubadminOfGroup = false;
152
+        $user = $this->userSession->getUser();
153
+        $isSubadminOfGroup = false;
154 154
 
155
-		// Check the group exists
156
-		$group = $this->groupManager->get($groupId);
157
-		if ($group !== null) {
158
-			$isSubadminOfGroup = $this->groupManager->getSubAdmin()->isSubAdminOfGroup($user, $group);
159
-			$isMember = $this->groupManager->isInGroup($user->getUID(), $group->getGID());
160
-		} else {
161
-			throw new OCSNotFoundException('The requested group could not be found');
162
-		}
155
+        // Check the group exists
156
+        $group = $this->groupManager->get($groupId);
157
+        if ($group !== null) {
158
+            $isSubadminOfGroup = $this->groupManager->getSubAdmin()->isSubAdminOfGroup($user, $group);
159
+            $isMember = $this->groupManager->isInGroup($user->getUID(), $group->getGID());
160
+        } else {
161
+            throw new OCSNotFoundException('The requested group could not be found');
162
+        }
163 163
 
164
-		// Check subadmin has access to this group
165
-		$isAdmin = $this->groupManager->isAdmin($user->getUID());
166
-		$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($user->getUID());
167
-		if ($isAdmin || $isDelegatedAdmin || $isSubadminOfGroup || $isMember) {
168
-			$users = $this->groupManager->get($groupId)->getUsers();
169
-			$users = array_map(function ($user) {
170
-				/** @var IUser $user */
171
-				return $user->getUID();
172
-			}, $users);
173
-			/** @var list<string> $users */
174
-			$users = array_values($users);
175
-			return new DataResponse(['users' => $users]);
176
-		}
164
+        // Check subadmin has access to this group
165
+        $isAdmin = $this->groupManager->isAdmin($user->getUID());
166
+        $isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($user->getUID());
167
+        if ($isAdmin || $isDelegatedAdmin || $isSubadminOfGroup || $isMember) {
168
+            $users = $this->groupManager->get($groupId)->getUsers();
169
+            $users = array_map(function ($user) {
170
+                /** @var IUser $user */
171
+                return $user->getUID();
172
+            }, $users);
173
+            /** @var list<string> $users */
174
+            $users = array_values($users);
175
+            return new DataResponse(['users' => $users]);
176
+        }
177 177
 
178
-		throw new OCSForbiddenException();
179
-	}
178
+        throw new OCSForbiddenException();
179
+    }
180 180
 
181
-	/**
182
-	 * Get a list of users details in the specified group
183
-	 *
184
-	 * @param string $groupId ID of the group
185
-	 * @param string $search Text to search for
186
-	 * @param int|null $limit Limit the amount of groups returned
187
-	 * @param int $offset Offset for searching for groups
188
-	 *
189
-	 * @return DataResponse<Http::STATUS_OK, array{users: array<string, Provisioning_APIUserDetails|array{id: string}>}, array{}>
190
-	 * @throws OCSException
191
-	 *
192
-	 * 200: Group users details returned
193
-	 */
194
-	#[NoAdminRequired]
195
-	public function getGroupUsersDetails(string $groupId, string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
196
-		$groupId = urldecode($groupId);
197
-		$currentUser = $this->userSession->getUser();
181
+    /**
182
+     * Get a list of users details in the specified group
183
+     *
184
+     * @param string $groupId ID of the group
185
+     * @param string $search Text to search for
186
+     * @param int|null $limit Limit the amount of groups returned
187
+     * @param int $offset Offset for searching for groups
188
+     *
189
+     * @return DataResponse<Http::STATUS_OK, array{users: array<string, Provisioning_APIUserDetails|array{id: string}>}, array{}>
190
+     * @throws OCSException
191
+     *
192
+     * 200: Group users details returned
193
+     */
194
+    #[NoAdminRequired]
195
+    public function getGroupUsersDetails(string $groupId, string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
196
+        $groupId = urldecode($groupId);
197
+        $currentUser = $this->userSession->getUser();
198 198
 
199
-		// Check the group exists
200
-		$group = $this->groupManager->get($groupId);
201
-		if ($group !== null) {
202
-			$isSubadminOfGroup = $this->groupManager->getSubAdmin()->isSubAdminOfGroup($currentUser, $group);
203
-		} else {
204
-			throw new OCSException('The requested group could not be found', OCSController::RESPOND_NOT_FOUND);
205
-		}
199
+        // Check the group exists
200
+        $group = $this->groupManager->get($groupId);
201
+        if ($group !== null) {
202
+            $isSubadminOfGroup = $this->groupManager->getSubAdmin()->isSubAdminOfGroup($currentUser, $group);
203
+        } else {
204
+            throw new OCSException('The requested group could not be found', OCSController::RESPOND_NOT_FOUND);
205
+        }
206 206
 
207
-		// Check subadmin has access to this group
208
-		$isAdmin = $this->groupManager->isAdmin($currentUser->getUID());
209
-		$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($currentUser->getUID());
210
-		if ($isAdmin || $isDelegatedAdmin || $isSubadminOfGroup) {
211
-			$users = $group->searchUsers($search, $limit, $offset);
207
+        // Check subadmin has access to this group
208
+        $isAdmin = $this->groupManager->isAdmin($currentUser->getUID());
209
+        $isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($currentUser->getUID());
210
+        if ($isAdmin || $isDelegatedAdmin || $isSubadminOfGroup) {
211
+            $users = $group->searchUsers($search, $limit, $offset);
212 212
 
213
-			// Extract required number
214
-			$usersDetails = [];
215
-			foreach ($users as $user) {
216
-				try {
217
-					/** @var IUser $user */
218
-					$userId = (string)$user->getUID();
219
-					$userData = $this->getUserData($userId);
220
-					// Do not insert empty entry
221
-					if ($userData !== null) {
222
-						$usersDetails[$userId] = $userData;
223
-					} else {
224
-						// Logged user does not have permissions to see this user
225
-						// only showing its id
226
-						$usersDetails[$userId] = ['id' => $userId];
227
-					}
228
-				} catch (OCSNotFoundException $e) {
229
-					// continue if a users ceased to exist.
230
-				}
231
-			}
232
-			return new DataResponse(['users' => $usersDetails]);
233
-		}
213
+            // Extract required number
214
+            $usersDetails = [];
215
+            foreach ($users as $user) {
216
+                try {
217
+                    /** @var IUser $user */
218
+                    $userId = (string)$user->getUID();
219
+                    $userData = $this->getUserData($userId);
220
+                    // Do not insert empty entry
221
+                    if ($userData !== null) {
222
+                        $usersDetails[$userId] = $userData;
223
+                    } else {
224
+                        // Logged user does not have permissions to see this user
225
+                        // only showing its id
226
+                        $usersDetails[$userId] = ['id' => $userId];
227
+                    }
228
+                } catch (OCSNotFoundException $e) {
229
+                    // continue if a users ceased to exist.
230
+                }
231
+            }
232
+            return new DataResponse(['users' => $usersDetails]);
233
+        }
234 234
 
235
-		throw new OCSException('The requested group could not be found', OCSController::RESPOND_NOT_FOUND);
236
-	}
235
+        throw new OCSException('The requested group could not be found', OCSController::RESPOND_NOT_FOUND);
236
+    }
237 237
 
238
-	/**
239
-	 * Create a new group
240
-	 *
241
-	 * @param string $groupid ID of the group
242
-	 * @param string $displayname Display name of the group
243
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
244
-	 * @throws OCSException
245
-	 *
246
-	 * 200: Group created successfully
247
-	 */
248
-	#[AuthorizedAdminSetting(settings:Users::class)]
249
-	#[PasswordConfirmationRequired]
250
-	public function addGroup(string $groupid, string $displayname = ''): DataResponse {
251
-		// Validate name
252
-		if (empty($groupid)) {
253
-			$this->logger->error('Group name not supplied', ['app' => 'provisioning_api']);
254
-			throw new OCSException('Invalid group name', 101);
255
-		}
256
-		// Check if it exists
257
-		if ($this->groupManager->groupExists($groupid)) {
258
-			throw new OCSException('group exists', 102);
259
-		}
260
-		$group = $this->groupManager->createGroup($groupid);
261
-		if ($group === null) {
262
-			throw new OCSException('Not supported by backend', 103);
263
-		}
264
-		if ($displayname !== '') {
265
-			$group->setDisplayName($displayname);
266
-		}
267
-		return new DataResponse();
268
-	}
238
+    /**
239
+     * Create a new group
240
+     *
241
+     * @param string $groupid ID of the group
242
+     * @param string $displayname Display name of the group
243
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
244
+     * @throws OCSException
245
+     *
246
+     * 200: Group created successfully
247
+     */
248
+    #[AuthorizedAdminSetting(settings:Users::class)]
249
+    #[PasswordConfirmationRequired]
250
+    public function addGroup(string $groupid, string $displayname = ''): DataResponse {
251
+        // Validate name
252
+        if (empty($groupid)) {
253
+            $this->logger->error('Group name not supplied', ['app' => 'provisioning_api']);
254
+            throw new OCSException('Invalid group name', 101);
255
+        }
256
+        // Check if it exists
257
+        if ($this->groupManager->groupExists($groupid)) {
258
+            throw new OCSException('group exists', 102);
259
+        }
260
+        $group = $this->groupManager->createGroup($groupid);
261
+        if ($group === null) {
262
+            throw new OCSException('Not supported by backend', 103);
263
+        }
264
+        if ($displayname !== '') {
265
+            $group->setDisplayName($displayname);
266
+        }
267
+        return new DataResponse();
268
+    }
269 269
 
270
-	/**
271
-	 * Update a group
272
-	 *
273
-	 * @param string $groupId ID of the group
274
-	 * @param string $key Key to update, only 'displayname'
275
-	 * @param string $value New value for the key
276
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
277
-	 * @throws OCSException
278
-	 *
279
-	 * 200: Group updated successfully
280
-	 */
281
-	#[AuthorizedAdminSetting(settings:Users::class)]
282
-	#[PasswordConfirmationRequired]
283
-	public function updateGroup(string $groupId, string $key, string $value): DataResponse {
284
-		$groupId = urldecode($groupId);
270
+    /**
271
+     * Update a group
272
+     *
273
+     * @param string $groupId ID of the group
274
+     * @param string $key Key to update, only 'displayname'
275
+     * @param string $value New value for the key
276
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
277
+     * @throws OCSException
278
+     *
279
+     * 200: Group updated successfully
280
+     */
281
+    #[AuthorizedAdminSetting(settings:Users::class)]
282
+    #[PasswordConfirmationRequired]
283
+    public function updateGroup(string $groupId, string $key, string $value): DataResponse {
284
+        $groupId = urldecode($groupId);
285 285
 
286
-		if ($key === 'displayname') {
287
-			$group = $this->groupManager->get($groupId);
288
-			if ($group === null) {
289
-				throw new OCSException('Group does not exist', OCSController::RESPOND_NOT_FOUND);
290
-			}
291
-			if ($group->setDisplayName($value)) {
292
-				return new DataResponse();
293
-			}
286
+        if ($key === 'displayname') {
287
+            $group = $this->groupManager->get($groupId);
288
+            if ($group === null) {
289
+                throw new OCSException('Group does not exist', OCSController::RESPOND_NOT_FOUND);
290
+            }
291
+            if ($group->setDisplayName($value)) {
292
+                return new DataResponse();
293
+            }
294 294
 
295
-			throw new OCSException('Not supported by backend', 101);
296
-		} else {
297
-			throw new OCSException('', OCSController::RESPOND_UNKNOWN_ERROR);
298
-		}
299
-	}
295
+            throw new OCSException('Not supported by backend', 101);
296
+        } else {
297
+            throw new OCSException('', OCSController::RESPOND_UNKNOWN_ERROR);
298
+        }
299
+    }
300 300
 
301
-	/**
302
-	 * Delete a group
303
-	 *
304
-	 * @param string $groupId ID of the group
305
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
306
-	 * @throws OCSException
307
-	 *
308
-	 * 200: Group deleted successfully
309
-	 */
310
-	#[AuthorizedAdminSetting(settings:Users::class)]
311
-	#[PasswordConfirmationRequired]
312
-	public function deleteGroup(string $groupId): DataResponse {
313
-		$groupId = urldecode($groupId);
301
+    /**
302
+     * Delete a group
303
+     *
304
+     * @param string $groupId ID of the group
305
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
306
+     * @throws OCSException
307
+     *
308
+     * 200: Group deleted successfully
309
+     */
310
+    #[AuthorizedAdminSetting(settings:Users::class)]
311
+    #[PasswordConfirmationRequired]
312
+    public function deleteGroup(string $groupId): DataResponse {
313
+        $groupId = urldecode($groupId);
314 314
 
315
-		// Check it exists
316
-		if (!$this->groupManager->groupExists($groupId)) {
317
-			throw new OCSException('', 101);
318
-		} elseif ($groupId === 'admin' || !$this->groupManager->get($groupId)->delete()) {
319
-			// Cannot delete admin group
320
-			throw new OCSException('', 102);
321
-		}
315
+        // Check it exists
316
+        if (!$this->groupManager->groupExists($groupId)) {
317
+            throw new OCSException('', 101);
318
+        } elseif ($groupId === 'admin' || !$this->groupManager->get($groupId)->delete()) {
319
+            // Cannot delete admin group
320
+            throw new OCSException('', 102);
321
+        }
322 322
 
323
-		return new DataResponse();
324
-	}
323
+        return new DataResponse();
324
+    }
325 325
 
326
-	/**
327
-	 * Get the list of user IDs that are a subadmin of the group
328
-	 *
329
-	 * @param string $groupId ID of the group
330
-	 * @return DataResponse<Http::STATUS_OK, list<string>, array{}>
331
-	 * @throws OCSException
332
-	 *
333
-	 * 200: Sub admins returned
334
-	 */
335
-	#[AuthorizedAdminSetting(settings:Users::class)]
336
-	public function getSubAdminsOfGroup(string $groupId): DataResponse {
337
-		// Check group exists
338
-		$targetGroup = $this->groupManager->get($groupId);
339
-		if ($targetGroup === null) {
340
-			throw new OCSException('Group does not exist', 101);
341
-		}
326
+    /**
327
+     * Get the list of user IDs that are a subadmin of the group
328
+     *
329
+     * @param string $groupId ID of the group
330
+     * @return DataResponse<Http::STATUS_OK, list<string>, array{}>
331
+     * @throws OCSException
332
+     *
333
+     * 200: Sub admins returned
334
+     */
335
+    #[AuthorizedAdminSetting(settings:Users::class)]
336
+    public function getSubAdminsOfGroup(string $groupId): DataResponse {
337
+        // Check group exists
338
+        $targetGroup = $this->groupManager->get($groupId);
339
+        if ($targetGroup === null) {
340
+            throw new OCSException('Group does not exist', 101);
341
+        }
342 342
 
343
-		/** @var IUser[] $subadmins */
344
-		$subadmins = $this->groupManager->getSubAdmin()->getGroupsSubAdmins($targetGroup);
345
-		// New class returns IUser[] so convert back
346
-		/** @var list<string> $uids */
347
-		$uids = [];
348
-		foreach ($subadmins as $user) {
349
-			$uids[] = $user->getUID();
350
-		}
343
+        /** @var IUser[] $subadmins */
344
+        $subadmins = $this->groupManager->getSubAdmin()->getGroupsSubAdmins($targetGroup);
345
+        // New class returns IUser[] so convert back
346
+        /** @var list<string> $uids */
347
+        $uids = [];
348
+        foreach ($subadmins as $user) {
349
+            $uids[] = $user->getUID();
350
+        }
351 351
 
352
-		return new DataResponse($uids);
353
-	}
352
+        return new DataResponse($uids);
353
+    }
354 354
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
 	#[NoAdminRequired]
79 79
 	public function getGroups(string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
80 80
 		$groups = $this->groupManager->search($search, $limit, $offset);
81
-		$groups = array_map(function ($group) {
81
+		$groups = array_map(function($group) {
82 82
 			/** @var IGroup $group */
83 83
 			return $group->getGID();
84 84
 		}, $groups);
@@ -101,7 +101,7 @@  discard block
 block discarded – undo
101 101
 	#[AuthorizedAdminSetting(settings: Users::class)]
102 102
 	public function getGroupsDetails(string $search = '', ?int $limit = null, int $offset = 0): DataResponse {
103 103
 		$groups = $this->groupManager->search($search, $limit, $offset);
104
-		$groups = array_map(function ($group) {
104
+		$groups = array_map(function($group) {
105 105
 			/** @var IGroup $group */
106 106
 			return [
107 107
 				'id' => $group->getGID(),
@@ -166,7 +166,7 @@  discard block
 block discarded – undo
166 166
 		$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($user->getUID());
167 167
 		if ($isAdmin || $isDelegatedAdmin || $isSubadminOfGroup || $isMember) {
168 168
 			$users = $this->groupManager->get($groupId)->getUsers();
169
-			$users = array_map(function ($user) {
169
+			$users = array_map(function($user) {
170 170
 				/** @var IUser $user */
171 171
 				return $user->getUID();
172 172
 			}, $users);
@@ -215,7 +215,7 @@  discard block
 block discarded – undo
215 215
 			foreach ($users as $user) {
216 216
 				try {
217 217
 					/** @var IUser $user */
218
-					$userId = (string)$user->getUID();
218
+					$userId = (string) $user->getUID();
219 219
 					$userData = $this->getUserData($userId);
220 220
 					// Do not insert empty entry
221 221
 					if ($userData !== null) {
Please login to merge, or discard this patch.