Passed
Push — master ( 84a353...212138 )
by Blizzz
12:21 queued 10s
created
apps/user_ldap/lib/User_Proxy.php 2 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -197,7 +197,7 @@  discard block
 block discarded – undo
197 197
 	 * @return string|false
198 198
 	 */
199 199
 	public function loginName2UserName($loginName) {
200
-		$id = 'LOGINNAME,' . $loginName;
200
+		$id = 'LOGINNAME,'.$loginName;
201 201
 		return $this->handleRequest($id, 'loginName2UserName', [$loginName]);
202 202
 	}
203 203
 	
@@ -208,7 +208,7 @@  discard block
 block discarded – undo
208 208
 	 * @return string|false with the username
209 209
 	 */
210 210
 	public function dn2UserName($dn) {
211
-		$id = 'DN,' . $dn;
211
+		$id = 'DN,'.$dn;
212 212
 		return $this->handleRequest($id, 'dn2UserName', [$dn]);
213 213
 	}
214 214
 
Please login to merge, or discard this patch.
Indentation   +329 added lines, -329 removed lines patch added patch discarded remove patch
@@ -38,358 +38,358 @@
 block discarded – undo
38 38
 use OCP\Notification\IManager as INotificationManager;
39 39
 
40 40
 class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP {
41
-	private $backends = [];
42
-	/** @var User_LDAP */
43
-	private $refBackend = null;
41
+    private $backends = [];
42
+    /** @var User_LDAP */
43
+    private $refBackend = null;
44 44
 
45
-	/**
46
-	 * Constructor
47
-	 *
48
-	 * @param array $serverConfigPrefixes array containing the config Prefixes
49
-	 * @param ILDAPWrapper $ldap
50
-	 * @param IConfig $ocConfig
51
-	 * @param INotificationManager $notificationManager
52
-	 * @param IUserSession $userSession
53
-	 */
54
-	public function __construct(
55
-		array $serverConfigPrefixes,
56
-		ILDAPWrapper $ldap,
57
-		IConfig $ocConfig,
58
-		INotificationManager $notificationManager,
59
-		IUserSession $userSession,
60
-		UserPluginManager $userPluginManager
61
-	) {
62
-		parent::__construct($ldap);
63
-		foreach ($serverConfigPrefixes as $configPrefix) {
64
-			$this->backends[$configPrefix] =
65
-				new User_LDAP($this->getAccess($configPrefix), $ocConfig, $notificationManager, $userSession, $userPluginManager);
45
+    /**
46
+     * Constructor
47
+     *
48
+     * @param array $serverConfigPrefixes array containing the config Prefixes
49
+     * @param ILDAPWrapper $ldap
50
+     * @param IConfig $ocConfig
51
+     * @param INotificationManager $notificationManager
52
+     * @param IUserSession $userSession
53
+     */
54
+    public function __construct(
55
+        array $serverConfigPrefixes,
56
+        ILDAPWrapper $ldap,
57
+        IConfig $ocConfig,
58
+        INotificationManager $notificationManager,
59
+        IUserSession $userSession,
60
+        UserPluginManager $userPluginManager
61
+    ) {
62
+        parent::__construct($ldap);
63
+        foreach ($serverConfigPrefixes as $configPrefix) {
64
+            $this->backends[$configPrefix] =
65
+                new User_LDAP($this->getAccess($configPrefix), $ocConfig, $notificationManager, $userSession, $userPluginManager);
66 66
 
67
-			if (is_null($this->refBackend)) {
68
-				$this->refBackend = &$this->backends[$configPrefix];
69
-			}
70
-		}
71
-	}
67
+            if (is_null($this->refBackend)) {
68
+                $this->refBackend = &$this->backends[$configPrefix];
69
+            }
70
+        }
71
+    }
72 72
 
73
-	/**
74
-	 * Tries the backends one after the other until a positive result is returned from the specified method
75
-	 *
76
-	 * @param string $uid the uid connected to the request
77
-	 * @param string $method the method of the user backend that shall be called
78
-	 * @param array $parameters an array of parameters to be passed
79
-	 * @return mixed the result of the method or false
80
-	 */
81
-	protected function walkBackends($uid, $method, $parameters) {
82
-		$cacheKey = $this->getUserCacheKey($uid);
83
-		foreach ($this->backends as $configPrefix => $backend) {
84
-			$instance = $backend;
85
-			if (!method_exists($instance, $method)
86
-				&& method_exists($this->getAccess($configPrefix), $method)) {
87
-				$instance = $this->getAccess($configPrefix);
88
-			}
89
-			if ($result = call_user_func_array([$instance, $method], $parameters)) {
90
-				if (!$this->isSingleBackend()) {
91
-					$this->writeToCache($cacheKey, $configPrefix);
92
-				}
93
-				return $result;
94
-			}
95
-		}
96
-		return false;
97
-	}
73
+    /**
74
+     * Tries the backends one after the other until a positive result is returned from the specified method
75
+     *
76
+     * @param string $uid the uid connected to the request
77
+     * @param string $method the method of the user backend that shall be called
78
+     * @param array $parameters an array of parameters to be passed
79
+     * @return mixed the result of the method or false
80
+     */
81
+    protected function walkBackends($uid, $method, $parameters) {
82
+        $cacheKey = $this->getUserCacheKey($uid);
83
+        foreach ($this->backends as $configPrefix => $backend) {
84
+            $instance = $backend;
85
+            if (!method_exists($instance, $method)
86
+                && method_exists($this->getAccess($configPrefix), $method)) {
87
+                $instance = $this->getAccess($configPrefix);
88
+            }
89
+            if ($result = call_user_func_array([$instance, $method], $parameters)) {
90
+                if (!$this->isSingleBackend()) {
91
+                    $this->writeToCache($cacheKey, $configPrefix);
92
+                }
93
+                return $result;
94
+            }
95
+        }
96
+        return false;
97
+    }
98 98
 
99
-	/**
100
-	 * Asks the backend connected to the server that supposely takes care of the uid from the request.
101
-	 *
102
-	 * @param string $uid the uid connected to the request
103
-	 * @param string $method the method of the user backend that shall be called
104
-	 * @param array $parameters an array of parameters to be passed
105
-	 * @param mixed $passOnWhen the result matches this variable
106
-	 * @return mixed the result of the method or false
107
-	 */
108
-	protected function callOnLastSeenOn($uid, $method, $parameters, $passOnWhen) {
109
-		$cacheKey = $this->getUserCacheKey($uid);
110
-		$prefix = $this->getFromCache($cacheKey);
111
-		//in case the uid has been found in the past, try this stored connection first
112
-		if (!is_null($prefix)) {
113
-			if (isset($this->backends[$prefix])) {
114
-				$instance = $this->backends[$prefix];
115
-				if (!method_exists($instance, $method)
116
-					&& method_exists($this->getAccess($prefix), $method)) {
117
-					$instance = $this->getAccess($prefix);
118
-				}
119
-				$result = call_user_func_array([$instance, $method], $parameters);
120
-				if ($result === $passOnWhen) {
121
-					//not found here, reset cache to null if user vanished
122
-					//because sometimes methods return false with a reason
123
-					$userExists = call_user_func_array(
124
-						[$this->backends[$prefix], 'userExistsOnLDAP'],
125
-						[$uid]
126
-					);
127
-					if (!$userExists) {
128
-						$this->writeToCache($cacheKey, null);
129
-					}
130
-				}
131
-				return $result;
132
-			}
133
-		}
134
-		return false;
135
-	}
99
+    /**
100
+     * Asks the backend connected to the server that supposely takes care of the uid from the request.
101
+     *
102
+     * @param string $uid the uid connected to the request
103
+     * @param string $method the method of the user backend that shall be called
104
+     * @param array $parameters an array of parameters to be passed
105
+     * @param mixed $passOnWhen the result matches this variable
106
+     * @return mixed the result of the method or false
107
+     */
108
+    protected function callOnLastSeenOn($uid, $method, $parameters, $passOnWhen) {
109
+        $cacheKey = $this->getUserCacheKey($uid);
110
+        $prefix = $this->getFromCache($cacheKey);
111
+        //in case the uid has been found in the past, try this stored connection first
112
+        if (!is_null($prefix)) {
113
+            if (isset($this->backends[$prefix])) {
114
+                $instance = $this->backends[$prefix];
115
+                if (!method_exists($instance, $method)
116
+                    && method_exists($this->getAccess($prefix), $method)) {
117
+                    $instance = $this->getAccess($prefix);
118
+                }
119
+                $result = call_user_func_array([$instance, $method], $parameters);
120
+                if ($result === $passOnWhen) {
121
+                    //not found here, reset cache to null if user vanished
122
+                    //because sometimes methods return false with a reason
123
+                    $userExists = call_user_func_array(
124
+                        [$this->backends[$prefix], 'userExistsOnLDAP'],
125
+                        [$uid]
126
+                    );
127
+                    if (!$userExists) {
128
+                        $this->writeToCache($cacheKey, null);
129
+                    }
130
+                }
131
+                return $result;
132
+            }
133
+        }
134
+        return false;
135
+    }
136 136
 
137
-	protected function activeBackends(): int {
138
-		return count($this->backends);
139
-	}
137
+    protected function activeBackends(): int {
138
+        return count($this->backends);
139
+    }
140 140
 
141
-	/**
142
-	 * Check if backend implements actions
143
-	 *
144
-	 * @param int $actions bitwise-or'ed actions
145
-	 * @return boolean
146
-	 *
147
-	 * Returns the supported actions as int to be
148
-	 * compared with \OC\User\Backend::CREATE_USER etc.
149
-	 */
150
-	public function implementsActions($actions) {
151
-		//it's the same across all our user backends obviously
152
-		return $this->refBackend->implementsActions($actions);
153
-	}
141
+    /**
142
+     * Check if backend implements actions
143
+     *
144
+     * @param int $actions bitwise-or'ed actions
145
+     * @return boolean
146
+     *
147
+     * Returns the supported actions as int to be
148
+     * compared with \OC\User\Backend::CREATE_USER etc.
149
+     */
150
+    public function implementsActions($actions) {
151
+        //it's the same across all our user backends obviously
152
+        return $this->refBackend->implementsActions($actions);
153
+    }
154 154
 
155
-	/**
156
-	 * Backend name to be shown in user management
157
-	 *
158
-	 * @return string the name of the backend to be shown
159
-	 */
160
-	public function getBackendName() {
161
-		return $this->refBackend->getBackendName();
162
-	}
155
+    /**
156
+     * Backend name to be shown in user management
157
+     *
158
+     * @return string the name of the backend to be shown
159
+     */
160
+    public function getBackendName() {
161
+        return $this->refBackend->getBackendName();
162
+    }
163 163
 
164
-	/**
165
-	 * Get a list of all users
166
-	 *
167
-	 * @param string $search
168
-	 * @param null|int $limit
169
-	 * @param null|int $offset
170
-	 * @return string[] an array of all uids
171
-	 */
172
-	public function getUsers($search = '', $limit = 10, $offset = 0) {
173
-		//we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends
174
-		$users = [];
175
-		foreach ($this->backends as $backend) {
176
-			$backendUsers = $backend->getUsers($search, $limit, $offset);
177
-			if (is_array($backendUsers)) {
178
-				$users = array_merge($users, $backendUsers);
179
-			}
180
-		}
181
-		return $users;
182
-	}
164
+    /**
165
+     * Get a list of all users
166
+     *
167
+     * @param string $search
168
+     * @param null|int $limit
169
+     * @param null|int $offset
170
+     * @return string[] an array of all uids
171
+     */
172
+    public function getUsers($search = '', $limit = 10, $offset = 0) {
173
+        //we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends
174
+        $users = [];
175
+        foreach ($this->backends as $backend) {
176
+            $backendUsers = $backend->getUsers($search, $limit, $offset);
177
+            if (is_array($backendUsers)) {
178
+                $users = array_merge($users, $backendUsers);
179
+            }
180
+        }
181
+        return $users;
182
+    }
183 183
 
184
-	/**
185
-	 * check if a user exists
186
-	 *
187
-	 * @param string $uid the username
188
-	 * @return boolean
189
-	 */
190
-	public function userExists($uid) {
191
-		$existsOnLDAP = false;
192
-		$existsLocally = $this->handleRequest($uid, 'userExists', [$uid]);
193
-		if ($existsLocally) {
194
-			$existsOnLDAP = $this->userExistsOnLDAP($uid);
195
-		}
196
-		if ($existsLocally && !$existsOnLDAP) {
197
-			try {
198
-				$user = $this->getLDAPAccess($uid)->userManager->get($uid);
199
-				if ($user instanceof User) {
200
-					$user->markUser();
201
-				}
202
-			} catch (\Exception $e) {
203
-				// ignore
204
-			}
205
-		}
206
-		return $existsLocally;
207
-	}
184
+    /**
185
+     * check if a user exists
186
+     *
187
+     * @param string $uid the username
188
+     * @return boolean
189
+     */
190
+    public function userExists($uid) {
191
+        $existsOnLDAP = false;
192
+        $existsLocally = $this->handleRequest($uid, 'userExists', [$uid]);
193
+        if ($existsLocally) {
194
+            $existsOnLDAP = $this->userExistsOnLDAP($uid);
195
+        }
196
+        if ($existsLocally && !$existsOnLDAP) {
197
+            try {
198
+                $user = $this->getLDAPAccess($uid)->userManager->get($uid);
199
+                if ($user instanceof User) {
200
+                    $user->markUser();
201
+                }
202
+            } catch (\Exception $e) {
203
+                // ignore
204
+            }
205
+        }
206
+        return $existsLocally;
207
+    }
208 208
 
209
-	/**
210
-	 * check if a user exists on LDAP
211
-	 *
212
-	 * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
213
-	 * name or an instance of that user
214
-	 * @return boolean
215
-	 */
216
-	public function userExistsOnLDAP($user) {
217
-		$id = ($user instanceof User) ? $user->getUsername() : $user;
218
-		return $this->handleRequest($id, 'userExistsOnLDAP', [$user]);
219
-	}
209
+    /**
210
+     * check if a user exists on LDAP
211
+     *
212
+     * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
213
+     * name or an instance of that user
214
+     * @return boolean
215
+     */
216
+    public function userExistsOnLDAP($user) {
217
+        $id = ($user instanceof User) ? $user->getUsername() : $user;
218
+        return $this->handleRequest($id, 'userExistsOnLDAP', [$user]);
219
+    }
220 220
 
221
-	/**
222
-	 * Check if the password is correct
223
-	 *
224
-	 * @param string $uid The username
225
-	 * @param string $password The password
226
-	 * @return bool
227
-	 *
228
-	 * Check if the password is correct without logging in the user
229
-	 */
230
-	public function checkPassword($uid, $password) {
231
-		return $this->handleRequest($uid, 'checkPassword', [$uid, $password]);
232
-	}
221
+    /**
222
+     * Check if the password is correct
223
+     *
224
+     * @param string $uid The username
225
+     * @param string $password The password
226
+     * @return bool
227
+     *
228
+     * Check if the password is correct without logging in the user
229
+     */
230
+    public function checkPassword($uid, $password) {
231
+        return $this->handleRequest($uid, 'checkPassword', [$uid, $password]);
232
+    }
233 233
 
234
-	/**
235
-	 * returns the username for the given login name, if available
236
-	 *
237
-	 * @param string $loginName
238
-	 * @return string|false
239
-	 */
240
-	public function loginName2UserName($loginName) {
241
-		$id = 'LOGINNAME,' . $loginName;
242
-		return $this->handleRequest($id, 'loginName2UserName', [$loginName]);
243
-	}
234
+    /**
235
+     * returns the username for the given login name, if available
236
+     *
237
+     * @param string $loginName
238
+     * @return string|false
239
+     */
240
+    public function loginName2UserName($loginName) {
241
+        $id = 'LOGINNAME,' . $loginName;
242
+        return $this->handleRequest($id, 'loginName2UserName', [$loginName]);
243
+    }
244 244
 
245
-	/**
246
-	 * returns the username for the given LDAP DN, if available
247
-	 *
248
-	 * @param string $dn
249
-	 * @return string|false with the username
250
-	 */
251
-	public function dn2UserName($dn) {
252
-		$id = 'DN,' . $dn;
253
-		return $this->handleRequest($id, 'dn2UserName', [$dn]);
254
-	}
245
+    /**
246
+     * returns the username for the given LDAP DN, if available
247
+     *
248
+     * @param string $dn
249
+     * @return string|false with the username
250
+     */
251
+    public function dn2UserName($dn) {
252
+        $id = 'DN,' . $dn;
253
+        return $this->handleRequest($id, 'dn2UserName', [$dn]);
254
+    }
255 255
 
256
-	/**
257
-	 * get the user's home directory
258
-	 *
259
-	 * @param string $uid the username
260
-	 * @return boolean
261
-	 */
262
-	public function getHome($uid) {
263
-		return $this->handleRequest($uid, 'getHome', [$uid]);
264
-	}
256
+    /**
257
+     * get the user's home directory
258
+     *
259
+     * @param string $uid the username
260
+     * @return boolean
261
+     */
262
+    public function getHome($uid) {
263
+        return $this->handleRequest($uid, 'getHome', [$uid]);
264
+    }
265 265
 
266
-	/**
267
-	 * get display name of the user
268
-	 *
269
-	 * @param string $uid user ID of the user
270
-	 * @return string display name
271
-	 */
272
-	public function getDisplayName($uid) {
273
-		return $this->handleRequest($uid, 'getDisplayName', [$uid]);
274
-	}
266
+    /**
267
+     * get display name of the user
268
+     *
269
+     * @param string $uid user ID of the user
270
+     * @return string display name
271
+     */
272
+    public function getDisplayName($uid) {
273
+        return $this->handleRequest($uid, 'getDisplayName', [$uid]);
274
+    }
275 275
 
276
-	/**
277
-	 * set display name of the user
278
-	 *
279
-	 * @param string $uid user ID of the user
280
-	 * @param string $displayName new display name
281
-	 * @return string display name
282
-	 */
283
-	public function setDisplayName($uid, $displayName) {
284
-		return $this->handleRequest($uid, 'setDisplayName', [$uid, $displayName]);
285
-	}
276
+    /**
277
+     * set display name of the user
278
+     *
279
+     * @param string $uid user ID of the user
280
+     * @param string $displayName new display name
281
+     * @return string display name
282
+     */
283
+    public function setDisplayName($uid, $displayName) {
284
+        return $this->handleRequest($uid, 'setDisplayName', [$uid, $displayName]);
285
+    }
286 286
 
287
-	/**
288
-	 * checks whether the user is allowed to change his avatar in Nextcloud
289
-	 *
290
-	 * @param string $uid the Nextcloud user name
291
-	 * @return boolean either the user can or cannot
292
-	 */
293
-	public function canChangeAvatar($uid) {
294
-		return $this->handleRequest($uid, 'canChangeAvatar', [$uid], true);
295
-	}
287
+    /**
288
+     * checks whether the user is allowed to change his avatar in Nextcloud
289
+     *
290
+     * @param string $uid the Nextcloud user name
291
+     * @return boolean either the user can or cannot
292
+     */
293
+    public function canChangeAvatar($uid) {
294
+        return $this->handleRequest($uid, 'canChangeAvatar', [$uid], true);
295
+    }
296 296
 
297
-	/**
298
-	 * Get a list of all display names and user ids.
299
-	 *
300
-	 * @param string $search
301
-	 * @param string|null $limit
302
-	 * @param string|null $offset
303
-	 * @return array an array of all displayNames (value) and the corresponding uids (key)
304
-	 */
305
-	public function getDisplayNames($search = '', $limit = null, $offset = null) {
306
-		//we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends
307
-		$users = [];
308
-		foreach ($this->backends as $backend) {
309
-			$backendUsers = $backend->getDisplayNames($search, $limit, $offset);
310
-			if (is_array($backendUsers)) {
311
-				$users = $users + $backendUsers;
312
-			}
313
-		}
314
-		return $users;
315
-	}
297
+    /**
298
+     * Get a list of all display names and user ids.
299
+     *
300
+     * @param string $search
301
+     * @param string|null $limit
302
+     * @param string|null $offset
303
+     * @return array an array of all displayNames (value) and the corresponding uids (key)
304
+     */
305
+    public function getDisplayNames($search = '', $limit = null, $offset = null) {
306
+        //we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends
307
+        $users = [];
308
+        foreach ($this->backends as $backend) {
309
+            $backendUsers = $backend->getDisplayNames($search, $limit, $offset);
310
+            if (is_array($backendUsers)) {
311
+                $users = $users + $backendUsers;
312
+            }
313
+        }
314
+        return $users;
315
+    }
316 316
 
317
-	/**
318
-	 * delete a user
319
-	 *
320
-	 * @param string $uid The username of the user to delete
321
-	 * @return bool
322
-	 *
323
-	 * Deletes a user
324
-	 */
325
-	public function deleteUser($uid) {
326
-		return $this->handleRequest($uid, 'deleteUser', [$uid]);
327
-	}
317
+    /**
318
+     * delete a user
319
+     *
320
+     * @param string $uid The username of the user to delete
321
+     * @return bool
322
+     *
323
+     * Deletes a user
324
+     */
325
+    public function deleteUser($uid) {
326
+        return $this->handleRequest($uid, 'deleteUser', [$uid]);
327
+    }
328 328
 
329
-	/**
330
-	 * Set password
331
-	 *
332
-	 * @param string $uid The username
333
-	 * @param string $password The new password
334
-	 * @return bool
335
-	 *
336
-	 */
337
-	public function setPassword($uid, $password) {
338
-		return $this->handleRequest($uid, 'setPassword', [$uid, $password]);
339
-	}
329
+    /**
330
+     * Set password
331
+     *
332
+     * @param string $uid The username
333
+     * @param string $password The new password
334
+     * @return bool
335
+     *
336
+     */
337
+    public function setPassword($uid, $password) {
338
+        return $this->handleRequest($uid, 'setPassword', [$uid, $password]);
339
+    }
340 340
 
341
-	/**
342
-	 * @return bool
343
-	 */
344
-	public function hasUserListings() {
345
-		return $this->refBackend->hasUserListings();
346
-	}
341
+    /**
342
+     * @return bool
343
+     */
344
+    public function hasUserListings() {
345
+        return $this->refBackend->hasUserListings();
346
+    }
347 347
 
348
-	/**
349
-	 * Count the number of users
350
-	 *
351
-	 * @return int|bool
352
-	 */
353
-	public function countUsers() {
354
-		$users = false;
355
-		foreach ($this->backends as $backend) {
356
-			$backendUsers = $backend->countUsers();
357
-			if ($backendUsers !== false) {
358
-				$users += $backendUsers;
359
-			}
360
-		}
361
-		return $users;
362
-	}
348
+    /**
349
+     * Count the number of users
350
+     *
351
+     * @return int|bool
352
+     */
353
+    public function countUsers() {
354
+        $users = false;
355
+        foreach ($this->backends as $backend) {
356
+            $backendUsers = $backend->countUsers();
357
+            if ($backendUsers !== false) {
358
+                $users += $backendUsers;
359
+            }
360
+        }
361
+        return $users;
362
+    }
363 363
 
364
-	/**
365
-	 * Return access for LDAP interaction.
366
-	 *
367
-	 * @param string $uid
368
-	 * @return Access instance of Access for LDAP interaction
369
-	 */
370
-	public function getLDAPAccess($uid) {
371
-		return $this->handleRequest($uid, 'getLDAPAccess', [$uid]);
372
-	}
364
+    /**
365
+     * Return access for LDAP interaction.
366
+     *
367
+     * @param string $uid
368
+     * @return Access instance of Access for LDAP interaction
369
+     */
370
+    public function getLDAPAccess($uid) {
371
+        return $this->handleRequest($uid, 'getLDAPAccess', [$uid]);
372
+    }
373 373
 
374
-	/**
375
-	 * Return a new LDAP connection for the specified user.
376
-	 * The connection needs to be closed manually.
377
-	 *
378
-	 * @param string $uid
379
-	 * @return resource of the LDAP connection
380
-	 */
381
-	public function getNewLDAPConnection($uid) {
382
-		return $this->handleRequest($uid, 'getNewLDAPConnection', [$uid]);
383
-	}
374
+    /**
375
+     * Return a new LDAP connection for the specified user.
376
+     * The connection needs to be closed manually.
377
+     *
378
+     * @param string $uid
379
+     * @return resource of the LDAP connection
380
+     */
381
+    public function getNewLDAPConnection($uid) {
382
+        return $this->handleRequest($uid, 'getNewLDAPConnection', [$uid]);
383
+    }
384 384
 
385
-	/**
386
-	 * Creates a new user in LDAP
387
-	 *
388
-	 * @param $username
389
-	 * @param $password
390
-	 * @return bool
391
-	 */
392
-	public function createUser($username, $password) {
393
-		return $this->handleRequest($username, 'createUser', [$username, $password]);
394
-	}
385
+    /**
386
+     * Creates a new user in LDAP
387
+     *
388
+     * @param $username
389
+     * @param $password
390
+     * @return bool
391
+     */
392
+    public function createUser($username, $password) {
393
+        return $this->handleRequest($username, 'createUser', [$username, $password]);
394
+    }
395 395
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Group_Proxy.php 1 patch
Indentation   +244 added lines, -244 removed lines patch added patch discarded remove patch
@@ -31,273 +31,273 @@
 block discarded – undo
31 31
 use OCP\Group\Backend\IGetDisplayNameBackend;
32 32
 
33 33
 class Group_Proxy extends Proxy implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend {
34
-	private $backends = [];
35
-	private $refBackend = null;
34
+    private $backends = [];
35
+    private $refBackend = null;
36 36
 
37
-	/**
38
-	 * Constructor
39
-	 *
40
-	 * @param string[] $serverConfigPrefixes array containing the config Prefixes
41
-	 */
42
-	public function __construct($serverConfigPrefixes, ILDAPWrapper $ldap, GroupPluginManager $groupPluginManager) {
43
-		parent::__construct($ldap);
44
-		foreach ($serverConfigPrefixes as $configPrefix) {
45
-			$this->backends[$configPrefix] =
46
-				new \OCA\User_LDAP\Group_LDAP($this->getAccess($configPrefix), $groupPluginManager);
47
-			if (is_null($this->refBackend)) {
48
-				$this->refBackend = &$this->backends[$configPrefix];
49
-			}
50
-		}
51
-	}
37
+    /**
38
+     * Constructor
39
+     *
40
+     * @param string[] $serverConfigPrefixes array containing the config Prefixes
41
+     */
42
+    public function __construct($serverConfigPrefixes, ILDAPWrapper $ldap, GroupPluginManager $groupPluginManager) {
43
+        parent::__construct($ldap);
44
+        foreach ($serverConfigPrefixes as $configPrefix) {
45
+            $this->backends[$configPrefix] =
46
+                new \OCA\User_LDAP\Group_LDAP($this->getAccess($configPrefix), $groupPluginManager);
47
+            if (is_null($this->refBackend)) {
48
+                $this->refBackend = &$this->backends[$configPrefix];
49
+            }
50
+        }
51
+    }
52 52
 
53
-	/**
54
-	 * Tries the backends one after the other until a positive result is returned from the specified method
55
-	 *
56
-	 * @param string $gid the gid connected to the request
57
-	 * @param string $method the method of the group backend that shall be called
58
-	 * @param array $parameters an array of parameters to be passed
59
-	 * @return mixed, the result of the method or false
60
-	 */
61
-	protected function walkBackends($gid, $method, $parameters) {
62
-		$cacheKey = $this->getGroupCacheKey($gid);
63
-		foreach ($this->backends as $configPrefix => $backend) {
64
-			if ($result = call_user_func_array([$backend, $method], $parameters)) {
65
-				if (!$this->isSingleBackend()) {
66
-					$this->writeToCache($cacheKey, $configPrefix);
67
-				}
68
-				return $result;
69
-			}
70
-		}
71
-		return false;
72
-	}
53
+    /**
54
+     * Tries the backends one after the other until a positive result is returned from the specified method
55
+     *
56
+     * @param string $gid the gid connected to the request
57
+     * @param string $method the method of the group backend that shall be called
58
+     * @param array $parameters an array of parameters to be passed
59
+     * @return mixed, the result of the method or false
60
+     */
61
+    protected function walkBackends($gid, $method, $parameters) {
62
+        $cacheKey = $this->getGroupCacheKey($gid);
63
+        foreach ($this->backends as $configPrefix => $backend) {
64
+            if ($result = call_user_func_array([$backend, $method], $parameters)) {
65
+                if (!$this->isSingleBackend()) {
66
+                    $this->writeToCache($cacheKey, $configPrefix);
67
+                }
68
+                return $result;
69
+            }
70
+        }
71
+        return false;
72
+    }
73 73
 
74
-	/**
75
-	 * Asks the backend connected to the server that supposely takes care of the gid from the request.
76
-	 *
77
-	 * @param string $gid the gid connected to the request
78
-	 * @param string $method the method of the group backend that shall be called
79
-	 * @param array $parameters an array of parameters to be passed
80
-	 * @param mixed $passOnWhen the result matches this variable
81
-	 * @return mixed, the result of the method or false
82
-	 */
83
-	protected function callOnLastSeenOn($gid, $method, $parameters, $passOnWhen) {
84
-		$cacheKey = $this->getGroupCacheKey($gid);
85
-		$prefix = $this->getFromCache($cacheKey);
86
-		//in case the uid has been found in the past, try this stored connection first
87
-		if (!is_null($prefix)) {
88
-			if (isset($this->backends[$prefix])) {
89
-				$result = call_user_func_array([$this->backends[$prefix], $method], $parameters);
90
-				if ($result === $passOnWhen) {
91
-					//not found here, reset cache to null if group vanished
92
-					//because sometimes methods return false with a reason
93
-					$groupExists = call_user_func_array(
94
-						[$this->backends[$prefix], 'groupExists'],
95
-						[$gid]
96
-					);
97
-					if (!$groupExists) {
98
-						$this->writeToCache($cacheKey, null);
99
-					}
100
-				}
101
-				return $result;
102
-			}
103
-		}
104
-		return false;
105
-	}
74
+    /**
75
+     * Asks the backend connected to the server that supposely takes care of the gid from the request.
76
+     *
77
+     * @param string $gid the gid connected to the request
78
+     * @param string $method the method of the group backend that shall be called
79
+     * @param array $parameters an array of parameters to be passed
80
+     * @param mixed $passOnWhen the result matches this variable
81
+     * @return mixed, the result of the method or false
82
+     */
83
+    protected function callOnLastSeenOn($gid, $method, $parameters, $passOnWhen) {
84
+        $cacheKey = $this->getGroupCacheKey($gid);
85
+        $prefix = $this->getFromCache($cacheKey);
86
+        //in case the uid has been found in the past, try this stored connection first
87
+        if (!is_null($prefix)) {
88
+            if (isset($this->backends[$prefix])) {
89
+                $result = call_user_func_array([$this->backends[$prefix], $method], $parameters);
90
+                if ($result === $passOnWhen) {
91
+                    //not found here, reset cache to null if group vanished
92
+                    //because sometimes methods return false with a reason
93
+                    $groupExists = call_user_func_array(
94
+                        [$this->backends[$prefix], 'groupExists'],
95
+                        [$gid]
96
+                    );
97
+                    if (!$groupExists) {
98
+                        $this->writeToCache($cacheKey, null);
99
+                    }
100
+                }
101
+                return $result;
102
+            }
103
+        }
104
+        return false;
105
+    }
106 106
 
107
-	protected function activeBackends(): int {
108
-		return count($this->backends);
109
-	}
107
+    protected function activeBackends(): int {
108
+        return count($this->backends);
109
+    }
110 110
 
111
-	/**
112
-	 * is user in group?
113
-	 *
114
-	 * @param string $uid uid of the user
115
-	 * @param string $gid gid of the group
116
-	 * @return bool
117
-	 *
118
-	 * Checks whether the user is member of a group or not.
119
-	 */
120
-	public function inGroup($uid, $gid) {
121
-		return $this->handleRequest($gid, 'inGroup', [$uid, $gid]);
122
-	}
111
+    /**
112
+     * is user in group?
113
+     *
114
+     * @param string $uid uid of the user
115
+     * @param string $gid gid of the group
116
+     * @return bool
117
+     *
118
+     * Checks whether the user is member of a group or not.
119
+     */
120
+    public function inGroup($uid, $gid) {
121
+        return $this->handleRequest($gid, 'inGroup', [$uid, $gid]);
122
+    }
123 123
 
124
-	/**
125
-	 * Get all groups a user belongs to
126
-	 *
127
-	 * @param string $uid Name of the user
128
-	 * @return string[] with group names
129
-	 *
130
-	 * This function fetches all groups a user belongs to. It does not check
131
-	 * if the user exists at all.
132
-	 */
133
-	public function getUserGroups($uid) {
134
-		$groups = [];
124
+    /**
125
+     * Get all groups a user belongs to
126
+     *
127
+     * @param string $uid Name of the user
128
+     * @return string[] with group names
129
+     *
130
+     * This function fetches all groups a user belongs to. It does not check
131
+     * if the user exists at all.
132
+     */
133
+    public function getUserGroups($uid) {
134
+        $groups = [];
135 135
 
136
-		foreach ($this->backends as $backend) {
137
-			$backendGroups = $backend->getUserGroups($uid);
138
-			if (is_array($backendGroups)) {
139
-				$groups = array_merge($groups, $backendGroups);
140
-			}
141
-		}
136
+        foreach ($this->backends as $backend) {
137
+            $backendGroups = $backend->getUserGroups($uid);
138
+            if (is_array($backendGroups)) {
139
+                $groups = array_merge($groups, $backendGroups);
140
+            }
141
+        }
142 142
 
143
-		return $groups;
144
-	}
143
+        return $groups;
144
+    }
145 145
 
146
-	/**
147
-	 * get a list of all users in a group
148
-	 *
149
-	 * @return string[] with user ids
150
-	 */
151
-	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
152
-		$users = [];
146
+    /**
147
+     * get a list of all users in a group
148
+     *
149
+     * @return string[] with user ids
150
+     */
151
+    public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
152
+        $users = [];
153 153
 
154
-		foreach ($this->backends as $backend) {
155
-			$backendUsers = $backend->usersInGroup($gid, $search, $limit, $offset);
156
-			if (is_array($backendUsers)) {
157
-				$users = array_merge($users, $backendUsers);
158
-			}
159
-		}
154
+        foreach ($this->backends as $backend) {
155
+            $backendUsers = $backend->usersInGroup($gid, $search, $limit, $offset);
156
+            if (is_array($backendUsers)) {
157
+                $users = array_merge($users, $backendUsers);
158
+            }
159
+        }
160 160
 
161
-		return $users;
162
-	}
161
+        return $users;
162
+    }
163 163
 
164
-	/**
165
-	 * @param string $gid
166
-	 * @return bool
167
-	 */
168
-	public function createGroup($gid) {
169
-		return $this->handleRequest(
170
-			$gid, 'createGroup', [$gid]);
171
-	}
164
+    /**
165
+     * @param string $gid
166
+     * @return bool
167
+     */
168
+    public function createGroup($gid) {
169
+        return $this->handleRequest(
170
+            $gid, 'createGroup', [$gid]);
171
+    }
172 172
 
173
-	/**
174
-	 * delete a group
175
-	 *
176
-	 * @param string $gid gid of the group to delete
177
-	 * @return bool
178
-	 */
179
-	public function deleteGroup($gid) {
180
-		return $this->handleRequest(
181
-			$gid, 'deleteGroup', [$gid]);
182
-	}
173
+    /**
174
+     * delete a group
175
+     *
176
+     * @param string $gid gid of the group to delete
177
+     * @return bool
178
+     */
179
+    public function deleteGroup($gid) {
180
+        return $this->handleRequest(
181
+            $gid, 'deleteGroup', [$gid]);
182
+    }
183 183
 
184
-	/**
185
-	 * Add a user to a group
186
-	 *
187
-	 * @param string $uid Name of the user to add to group
188
-	 * @param string $gid Name of the group in which add the user
189
-	 * @return bool
190
-	 *
191
-	 * Adds a user to a group.
192
-	 */
193
-	public function addToGroup($uid, $gid) {
194
-		return $this->handleRequest(
195
-			$gid, 'addToGroup', [$uid, $gid]);
196
-	}
184
+    /**
185
+     * Add a user to a group
186
+     *
187
+     * @param string $uid Name of the user to add to group
188
+     * @param string $gid Name of the group in which add the user
189
+     * @return bool
190
+     *
191
+     * Adds a user to a group.
192
+     */
193
+    public function addToGroup($uid, $gid) {
194
+        return $this->handleRequest(
195
+            $gid, 'addToGroup', [$uid, $gid]);
196
+    }
197 197
 
198
-	/**
199
-	 * Removes a user from a group
200
-	 *
201
-	 * @param string $uid Name of the user to remove from group
202
-	 * @param string $gid Name of the group from which remove the user
203
-	 * @return bool
204
-	 *
205
-	 * removes the user from a group.
206
-	 */
207
-	public function removeFromGroup($uid, $gid) {
208
-		return $this->handleRequest(
209
-			$gid, 'removeFromGroup', [$uid, $gid]);
210
-	}
198
+    /**
199
+     * Removes a user from a group
200
+     *
201
+     * @param string $uid Name of the user to remove from group
202
+     * @param string $gid Name of the group from which remove the user
203
+     * @return bool
204
+     *
205
+     * removes the user from a group.
206
+     */
207
+    public function removeFromGroup($uid, $gid) {
208
+        return $this->handleRequest(
209
+            $gid, 'removeFromGroup', [$uid, $gid]);
210
+    }
211 211
 
212
-	/**
213
-	 * returns the number of users in a group, who match the search term
214
-	 *
215
-	 * @param string $gid the internal group name
216
-	 * @param string $search optional, a search string
217
-	 * @return int|bool
218
-	 */
219
-	public function countUsersInGroup($gid, $search = '') {
220
-		return $this->handleRequest(
221
-			$gid, 'countUsersInGroup', [$gid, $search]);
222
-	}
212
+    /**
213
+     * returns the number of users in a group, who match the search term
214
+     *
215
+     * @param string $gid the internal group name
216
+     * @param string $search optional, a search string
217
+     * @return int|bool
218
+     */
219
+    public function countUsersInGroup($gid, $search = '') {
220
+        return $this->handleRequest(
221
+            $gid, 'countUsersInGroup', [$gid, $search]);
222
+    }
223 223
 
224
-	/**
225
-	 * get an array with group details
226
-	 *
227
-	 * @param string $gid
228
-	 * @return array|false
229
-	 */
230
-	public function getGroupDetails($gid) {
231
-		return $this->handleRequest(
232
-			$gid, 'getGroupDetails', [$gid]);
233
-	}
224
+    /**
225
+     * get an array with group details
226
+     *
227
+     * @param string $gid
228
+     * @return array|false
229
+     */
230
+    public function getGroupDetails($gid) {
231
+        return $this->handleRequest(
232
+            $gid, 'getGroupDetails', [$gid]);
233
+    }
234 234
 
235
-	/**
236
-	 * get a list of all groups
237
-	 *
238
-	 * @return string[] with group names
239
-	 *
240
-	 * Returns a list with all groups
241
-	 */
242
-	public function getGroups($search = '', $limit = -1, $offset = 0) {
243
-		$groups = [];
235
+    /**
236
+     * get a list of all groups
237
+     *
238
+     * @return string[] with group names
239
+     *
240
+     * Returns a list with all groups
241
+     */
242
+    public function getGroups($search = '', $limit = -1, $offset = 0) {
243
+        $groups = [];
244 244
 
245
-		foreach ($this->backends as $backend) {
246
-			$backendGroups = $backend->getGroups($search, $limit, $offset);
247
-			if (is_array($backendGroups)) {
248
-				$groups = array_merge($groups, $backendGroups);
249
-			}
250
-		}
245
+        foreach ($this->backends as $backend) {
246
+            $backendGroups = $backend->getGroups($search, $limit, $offset);
247
+            if (is_array($backendGroups)) {
248
+                $groups = array_merge($groups, $backendGroups);
249
+            }
250
+        }
251 251
 
252
-		return $groups;
253
-	}
252
+        return $groups;
253
+    }
254 254
 
255
-	/**
256
-	 * check if a group exists
257
-	 *
258
-	 * @param string $gid
259
-	 * @return bool
260
-	 */
261
-	public function groupExists($gid) {
262
-		return $this->handleRequest($gid, 'groupExists', [$gid]);
263
-	}
255
+    /**
256
+     * check if a group exists
257
+     *
258
+     * @param string $gid
259
+     * @return bool
260
+     */
261
+    public function groupExists($gid) {
262
+        return $this->handleRequest($gid, 'groupExists', [$gid]);
263
+    }
264 264
 
265
-	/**
266
-	 * Check if backend implements actions
267
-	 *
268
-	 * @param int $actions bitwise-or'ed actions
269
-	 * @return boolean
270
-	 *
271
-	 * Returns the supported actions as int to be
272
-	 * compared with \OCP\GroupInterface::CREATE_GROUP etc.
273
-	 */
274
-	public function implementsActions($actions) {
275
-		//it's the same across all our user backends obviously
276
-		return $this->refBackend->implementsActions($actions);
277
-	}
265
+    /**
266
+     * Check if backend implements actions
267
+     *
268
+     * @param int $actions bitwise-or'ed actions
269
+     * @return boolean
270
+     *
271
+     * Returns the supported actions as int to be
272
+     * compared with \OCP\GroupInterface::CREATE_GROUP etc.
273
+     */
274
+    public function implementsActions($actions) {
275
+        //it's the same across all our user backends obviously
276
+        return $this->refBackend->implementsActions($actions);
277
+    }
278 278
 
279
-	/**
280
-	 * Return access for LDAP interaction.
281
-	 *
282
-	 * @param string $gid
283
-	 * @return Access instance of Access for LDAP interaction
284
-	 */
285
-	public function getLDAPAccess($gid) {
286
-		return $this->handleRequest($gid, 'getLDAPAccess', [$gid]);
287
-	}
279
+    /**
280
+     * Return access for LDAP interaction.
281
+     *
282
+     * @param string $gid
283
+     * @return Access instance of Access for LDAP interaction
284
+     */
285
+    public function getLDAPAccess($gid) {
286
+        return $this->handleRequest($gid, 'getLDAPAccess', [$gid]);
287
+    }
288 288
 
289
-	/**
290
-	 * Return a new LDAP connection for the specified group.
291
-	 * The connection needs to be closed manually.
292
-	 *
293
-	 * @param string $gid
294
-	 * @return resource of the LDAP connection
295
-	 */
296
-	public function getNewLDAPConnection($gid) {
297
-		return $this->handleRequest($gid, 'getNewLDAPConnection', [$gid]);
298
-	}
289
+    /**
290
+     * Return a new LDAP connection for the specified group.
291
+     * The connection needs to be closed manually.
292
+     *
293
+     * @param string $gid
294
+     * @return resource of the LDAP connection
295
+     */
296
+    public function getNewLDAPConnection($gid) {
297
+        return $this->handleRequest($gid, 'getNewLDAPConnection', [$gid]);
298
+    }
299 299
 
300
-	public function getDisplayName(string $gid): string {
301
-		return $this->handleRequest($gid, 'getDisplayName', [$gid]);
302
-	}
300
+    public function getDisplayName(string $gid): string {
301
+        return $this->handleRequest($gid, 'getDisplayName', [$gid]);
302
+    }
303 303
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Group_LDAP.php 2 patches
Indentation   +1272 added lines, -1272 removed lines patch added patch discarded remove patch
@@ -49,1276 +49,1276 @@
 block discarded – undo
49 49
 use OCP\ILogger;
50 50
 
51 51
 class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLDAP, IGetDisplayNameBackend {
52
-	protected $enabled = false;
53
-
54
-	/**
55
-	 * @var string[] $cachedGroupMembers array of users with gid as key
56
-	 */
57
-	protected $cachedGroupMembers;
58
-
59
-	/**
60
-	 * @var string[] $cachedGroupsByMember array of groups with uid as key
61
-	 */
62
-	protected $cachedGroupsByMember;
63
-
64
-	/**
65
-	 * @var string[] $cachedNestedGroups array of groups with gid (DN) as key
66
-	 */
67
-	protected $cachedNestedGroups;
68
-
69
-	/** @var GroupPluginManager */
70
-	protected $groupPluginManager;
71
-
72
-	public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
73
-		parent::__construct($access);
74
-		$filter = $this->access->connection->ldapGroupFilter;
75
-		$gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
76
-		if (!empty($filter) && !empty($gassoc)) {
77
-			$this->enabled = true;
78
-		}
79
-
80
-		$this->cachedGroupMembers = new CappedMemoryCache();
81
-		$this->cachedGroupsByMember = new CappedMemoryCache();
82
-		$this->cachedNestedGroups = new CappedMemoryCache();
83
-		$this->groupPluginManager = $groupPluginManager;
84
-	}
85
-
86
-	/**
87
-	 * is user in group?
88
-	 *
89
-	 * @param string $uid uid of the user
90
-	 * @param string $gid gid of the group
91
-	 * @return bool
92
-	 *
93
-	 * Checks whether the user is member of a group or not.
94
-	 */
95
-	public function inGroup($uid, $gid) {
96
-		if (!$this->enabled) {
97
-			return false;
98
-		}
99
-		$cacheKey = 'inGroup' . $uid . ':' . $gid;
100
-		$inGroup = $this->access->connection->getFromCache($cacheKey);
101
-		if (!is_null($inGroup)) {
102
-			return (bool)$inGroup;
103
-		}
104
-
105
-		$userDN = $this->access->username2dn($uid);
106
-
107
-		if (isset($this->cachedGroupMembers[$gid])) {
108
-			$isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
109
-			return $isInGroup;
110
-		}
111
-
112
-		$cacheKeyMembers = 'inGroup-members:' . $gid;
113
-		$members = $this->access->connection->getFromCache($cacheKeyMembers);
114
-		if (!is_null($members)) {
115
-			$this->cachedGroupMembers[$gid] = $members;
116
-			$isInGroup = in_array($userDN, $members, true);
117
-			$this->access->connection->writeToCache($cacheKey, $isInGroup);
118
-			return $isInGroup;
119
-		}
120
-
121
-		$groupDN = $this->access->groupname2dn($gid);
122
-		// just in case
123
-		if (!$groupDN || !$userDN) {
124
-			$this->access->connection->writeToCache($cacheKey, false);
125
-			return false;
126
-		}
127
-
128
-		//check primary group first
129
-		if ($gid === $this->getUserPrimaryGroup($userDN)) {
130
-			$this->access->connection->writeToCache($cacheKey, true);
131
-			return true;
132
-		}
133
-
134
-		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
135
-		$members = $this->_groupMembers($groupDN);
136
-		if (!is_array($members) || count($members) === 0) {
137
-			$this->access->connection->writeToCache($cacheKey, false);
138
-			return false;
139
-		}
140
-
141
-		//extra work if we don't get back user DNs
142
-		if (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
143
-			$dns = [];
144
-			$filterParts = [];
145
-			$bytes = 0;
146
-			foreach ($members as $mid) {
147
-				$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
148
-				$filterParts[] = $filter;
149
-				$bytes += strlen($filter);
150
-				if ($bytes >= 9000000) {
151
-					// AD has a default input buffer of 10 MB, we do not want
152
-					// to take even the chance to exceed it
153
-					$filter = $this->access->combineFilterWithOr($filterParts);
154
-					$bytes = 0;
155
-					$filterParts = [];
156
-					$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
157
-					$dns = array_merge($dns, $users);
158
-				}
159
-			}
160
-			if (count($filterParts) > 0) {
161
-				$filter = $this->access->combineFilterWithOr($filterParts);
162
-				$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
163
-				$dns = array_merge($dns, $users);
164
-			}
165
-			$members = $dns;
166
-		}
167
-
168
-		$isInGroup = in_array($userDN, $members);
169
-		$this->access->connection->writeToCache($cacheKey, $isInGroup);
170
-		$this->access->connection->writeToCache($cacheKeyMembers, $members);
171
-		$this->cachedGroupMembers[$gid] = $members;
172
-
173
-		return $isInGroup;
174
-	}
175
-
176
-	/**
177
-	 * @param string $dnGroup
178
-	 * @return array
179
-	 *
180
-	 * For a group that has user membership defined by an LDAP search url attribute returns the users
181
-	 * that match the search url otherwise returns an empty array.
182
-	 */
183
-	public function getDynamicGroupMembers($dnGroup) {
184
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
185
-
186
-		if (empty($dynamicGroupMemberURL)) {
187
-			return [];
188
-		}
189
-
190
-		$dynamicMembers = [];
191
-		$memberURLs = $this->access->readAttribute(
192
-			$dnGroup,
193
-			$dynamicGroupMemberURL,
194
-			$this->access->connection->ldapGroupFilter
195
-		);
196
-		if ($memberURLs !== false) {
197
-			// this group has the 'memberURL' attribute so this is a dynamic group
198
-			// example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
199
-			// example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
200
-			$pos = strpos($memberURLs[0], '(');
201
-			if ($pos !== false) {
202
-				$memberUrlFilter = substr($memberURLs[0], $pos);
203
-				$foundMembers = $this->access->searchUsers($memberUrlFilter, 'dn');
204
-				$dynamicMembers = [];
205
-				foreach ($foundMembers as $value) {
206
-					$dynamicMembers[$value['dn'][0]] = 1;
207
-				}
208
-			} else {
209
-				\OCP\Util::writeLog('user_ldap', 'No search filter found on member url ' .
210
-					'of group ' . $dnGroup, ILogger::DEBUG);
211
-			}
212
-		}
213
-		return $dynamicMembers;
214
-	}
215
-
216
-	/**
217
-	 * @param string $dnGroup
218
-	 * @param array|null &$seen
219
-	 * @return array|mixed|null
220
-	 * @throws \OC\ServerNotAvailableException
221
-	 */
222
-	private function _groupMembers($dnGroup, &$seen = null) {
223
-		if ($seen === null) {
224
-			$seen = [];
225
-		}
226
-		$allMembers = [];
227
-		if (array_key_exists($dnGroup, $seen)) {
228
-			// avoid loops
229
-			return [];
230
-		}
231
-		// used extensively in cron job, caching makes sense for nested groups
232
-		$cacheKey = '_groupMembers' . $dnGroup;
233
-		$groupMembers = $this->access->connection->getFromCache($cacheKey);
234
-		if ($groupMembers !== null) {
235
-			return $groupMembers;
236
-		}
237
-		$seen[$dnGroup] = 1;
238
-		$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
239
-		if (is_array($members)) {
240
-			$fetcher = function ($memberDN, &$seen) {
241
-				return $this->_groupMembers($memberDN, $seen);
242
-			};
243
-			$allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members);
244
-		}
245
-
246
-		$allMembers += $this->getDynamicGroupMembers($dnGroup);
247
-
248
-		$this->access->connection->writeToCache($cacheKey, $allMembers);
249
-		return $allMembers;
250
-	}
251
-
252
-	/**
253
-	 * @param string $DN
254
-	 * @param array|null &$seen
255
-	 * @return array
256
-	 * @throws \OC\ServerNotAvailableException
257
-	 */
258
-	private function _getGroupDNsFromMemberOf($DN) {
259
-		$groups = $this->access->readAttribute($DN, 'memberOf');
260
-		if (!is_array($groups)) {
261
-			return [];
262
-		}
263
-
264
-		$fetcher = function ($groupDN) {
265
-			if (isset($this->cachedNestedGroups[$groupDN])) {
266
-				$nestedGroups = $this->cachedNestedGroups[$groupDN];
267
-			} else {
268
-				$nestedGroups = $this->access->readAttribute($groupDN, 'memberOf');
269
-				if (!is_array($nestedGroups)) {
270
-					$nestedGroups = [];
271
-				}
272
-				$this->cachedNestedGroups[$groupDN] = $nestedGroups;
273
-			}
274
-			return $nestedGroups;
275
-		};
276
-
277
-		$groups = $this->walkNestedGroups($DN, $fetcher, $groups);
278
-		return $this->filterValidGroups($groups);
279
-	}
280
-
281
-	/**
282
-	 * @param string $dn
283
-	 * @param \Closure $fetcher args: string $dn, array $seen, returns: string[] of dns
284
-	 * @param array $list
285
-	 * @return array
286
-	 */
287
-	private function walkNestedGroups(string $dn, \Closure $fetcher, array $list): array {
288
-		$nesting = (int)$this->access->connection->ldapNestedGroups;
289
-		// depending on the input, we either have a list of DNs or a list of LDAP records
290
-		// also, the output expects either DNs or records. Testing the first element should suffice.
291
-		$recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]);
292
-
293
-		if ($nesting !== 1) {
294
-			if ($recordMode) {
295
-				// the keys are numeric, but should hold the DN
296
-				return array_reduce($list, function ($transformed, $record) use ($dn) {
297
-					if ($record['dn'][0] != $dn) {
298
-						$transformed[$record['dn'][0]] = $record;
299
-					}
300
-					return $transformed;
301
-				}, []);
302
-			}
303
-			return $list;
304
-		}
305
-
306
-		$seen = [];
307
-		while ($record = array_pop($list)) {
308
-			$recordDN = $recordMode ? $record['dn'][0] : $record;
309
-			if ($recordDN === $dn || array_key_exists($recordDN, $seen)) {
310
-				// Prevent loops
311
-				continue;
312
-			}
313
-			$fetched = $fetcher($record, $seen);
314
-			$list = array_merge($list, $fetched);
315
-			$seen[$recordDN] = $record;
316
-		}
317
-
318
-		return $recordMode ? $seen : array_keys($seen);
319
-	}
320
-
321
-	/**
322
-	 * translates a gidNumber into an ownCloud internal name
323
-	 *
324
-	 * @param string $gid as given by gidNumber on POSIX LDAP
325
-	 * @param string $dn a DN that belongs to the same domain as the group
326
-	 * @return string|bool
327
-	 */
328
-	public function gidNumber2Name($gid, $dn) {
329
-		$cacheKey = 'gidNumberToName' . $gid;
330
-		$groupName = $this->access->connection->getFromCache($cacheKey);
331
-		if (!is_null($groupName) && isset($groupName)) {
332
-			return $groupName;
333
-		}
334
-
335
-		//we need to get the DN from LDAP
336
-		$filter = $this->access->combineFilterWithAnd([
337
-			$this->access->connection->ldapGroupFilter,
338
-			'objectClass=posixGroup',
339
-			$this->access->connection->ldapGidNumber . '=' . $gid
340
-		]);
341
-		$result = $this->access->searchGroups($filter, ['dn'], 1);
342
-		if (empty($result)) {
343
-			return false;
344
-		}
345
-		$dn = $result[0]['dn'][0];
346
-
347
-		//and now the group name
348
-		//NOTE once we have separate ownCloud group IDs and group names we can
349
-		//directly read the display name attribute instead of the DN
350
-		$name = $this->access->dn2groupname($dn);
351
-
352
-		$this->access->connection->writeToCache($cacheKey, $name);
353
-
354
-		return $name;
355
-	}
356
-
357
-	/**
358
-	 * returns the entry's gidNumber
359
-	 *
360
-	 * @param string $dn
361
-	 * @param string $attribute
362
-	 * @return string|bool
363
-	 */
364
-	private function getEntryGidNumber($dn, $attribute) {
365
-		$value = $this->access->readAttribute($dn, $attribute);
366
-		if (is_array($value) && !empty($value)) {
367
-			return $value[0];
368
-		}
369
-		return false;
370
-	}
371
-
372
-	/**
373
-	 * returns the group's primary ID
374
-	 *
375
-	 * @param string $dn
376
-	 * @return string|bool
377
-	 */
378
-	public function getGroupGidNumber($dn) {
379
-		return $this->getEntryGidNumber($dn, 'gidNumber');
380
-	}
381
-
382
-	/**
383
-	 * returns the user's gidNumber
384
-	 *
385
-	 * @param string $dn
386
-	 * @return string|bool
387
-	 */
388
-	public function getUserGidNumber($dn) {
389
-		$gidNumber = false;
390
-		if ($this->access->connection->hasGidNumber) {
391
-			$gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
392
-			if ($gidNumber === false) {
393
-				$this->access->connection->hasGidNumber = false;
394
-			}
395
-		}
396
-		return $gidNumber;
397
-	}
398
-
399
-	/**
400
-	 * returns a filter for a "users has specific gid" search or count operation
401
-	 *
402
-	 * @param string $groupDN
403
-	 * @param string $search
404
-	 * @return string
405
-	 * @throws \Exception
406
-	 */
407
-	private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
408
-		$groupID = $this->getGroupGidNumber($groupDN);
409
-		if ($groupID === false) {
410
-			throw new \Exception('Not a valid group');
411
-		}
412
-
413
-		$filterParts = [];
414
-		$filterParts[] = $this->access->getFilterForUserCount();
415
-		if ($search !== '') {
416
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
417
-		}
418
-		$filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID;
419
-
420
-		return $this->access->combineFilterWithAnd($filterParts);
421
-	}
422
-
423
-	/**
424
-	 * returns a list of users that have the given group as gid number
425
-	 *
426
-	 * @param string $groupDN
427
-	 * @param string $search
428
-	 * @param int $limit
429
-	 * @param int $offset
430
-	 * @return string[]
431
-	 */
432
-	public function getUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
433
-		try {
434
-			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
435
-			$users = $this->access->fetchListOfUsers(
436
-				$filter,
437
-				[$this->access->connection->ldapUserDisplayName, 'dn'],
438
-				$limit,
439
-				$offset
440
-			);
441
-			return $this->access->nextcloudUserNames($users);
442
-		} catch (\Exception $e) {
443
-			return [];
444
-		}
445
-	}
446
-
447
-	/**
448
-	 * returns the number of users that have the given group as gid number
449
-	 *
450
-	 * @param string $groupDN
451
-	 * @param string $search
452
-	 * @param int $limit
453
-	 * @param int $offset
454
-	 * @return int
455
-	 */
456
-	public function countUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
457
-		try {
458
-			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
459
-			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
460
-			return (int)$users;
461
-		} catch (\Exception $e) {
462
-			return 0;
463
-		}
464
-	}
465
-
466
-	/**
467
-	 * gets the gidNumber of a user
468
-	 *
469
-	 * @param string $dn
470
-	 * @return string
471
-	 */
472
-	public function getUserGroupByGid($dn) {
473
-		$groupID = $this->getUserGidNumber($dn);
474
-		if ($groupID !== false) {
475
-			$groupName = $this->gidNumber2Name($groupID, $dn);
476
-			if ($groupName !== false) {
477
-				return $groupName;
478
-			}
479
-		}
480
-
481
-		return false;
482
-	}
483
-
484
-	/**
485
-	 * translates a primary group ID into an Nextcloud internal name
486
-	 *
487
-	 * @param string $gid as given by primaryGroupID on AD
488
-	 * @param string $dn a DN that belongs to the same domain as the group
489
-	 * @return string|bool
490
-	 */
491
-	public function primaryGroupID2Name($gid, $dn) {
492
-		$cacheKey = 'primaryGroupIDtoName';
493
-		$groupNames = $this->access->connection->getFromCache($cacheKey);
494
-		if (!is_null($groupNames) && isset($groupNames[$gid])) {
495
-			return $groupNames[$gid];
496
-		}
497
-
498
-		$domainObjectSid = $this->access->getSID($dn);
499
-		if ($domainObjectSid === false) {
500
-			return false;
501
-		}
502
-
503
-		//we need to get the DN from LDAP
504
-		$filter = $this->access->combineFilterWithAnd([
505
-			$this->access->connection->ldapGroupFilter,
506
-			'objectsid=' . $domainObjectSid . '-' . $gid
507
-		]);
508
-		$result = $this->access->searchGroups($filter, ['dn'], 1);
509
-		if (empty($result)) {
510
-			return false;
511
-		}
512
-		$dn = $result[0]['dn'][0];
513
-
514
-		//and now the group name
515
-		//NOTE once we have separate Nextcloud group IDs and group names we can
516
-		//directly read the display name attribute instead of the DN
517
-		$name = $this->access->dn2groupname($dn);
518
-
519
-		$this->access->connection->writeToCache($cacheKey, $name);
520
-
521
-		return $name;
522
-	}
523
-
524
-	/**
525
-	 * returns the entry's primary group ID
526
-	 *
527
-	 * @param string $dn
528
-	 * @param string $attribute
529
-	 * @return string|bool
530
-	 */
531
-	private function getEntryGroupID($dn, $attribute) {
532
-		$value = $this->access->readAttribute($dn, $attribute);
533
-		if (is_array($value) && !empty($value)) {
534
-			return $value[0];
535
-		}
536
-		return false;
537
-	}
538
-
539
-	/**
540
-	 * returns the group's primary ID
541
-	 *
542
-	 * @param string $dn
543
-	 * @return string|bool
544
-	 */
545
-	public function getGroupPrimaryGroupID($dn) {
546
-		return $this->getEntryGroupID($dn, 'primaryGroupToken');
547
-	}
548
-
549
-	/**
550
-	 * returns the user's primary group ID
551
-	 *
552
-	 * @param string $dn
553
-	 * @return string|bool
554
-	 */
555
-	public function getUserPrimaryGroupIDs($dn) {
556
-		$primaryGroupID = false;
557
-		if ($this->access->connection->hasPrimaryGroups) {
558
-			$primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
559
-			if ($primaryGroupID === false) {
560
-				$this->access->connection->hasPrimaryGroups = false;
561
-			}
562
-		}
563
-		return $primaryGroupID;
564
-	}
565
-
566
-	/**
567
-	 * returns a filter for a "users in primary group" search or count operation
568
-	 *
569
-	 * @param string $groupDN
570
-	 * @param string $search
571
-	 * @return string
572
-	 * @throws \Exception
573
-	 */
574
-	private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
575
-		$groupID = $this->getGroupPrimaryGroupID($groupDN);
576
-		if ($groupID === false) {
577
-			throw new \Exception('Not a valid group');
578
-		}
579
-
580
-		$filterParts = [];
581
-		$filterParts[] = $this->access->getFilterForUserCount();
582
-		if ($search !== '') {
583
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
584
-		}
585
-		$filterParts[] = 'primaryGroupID=' . $groupID;
586
-
587
-		return $this->access->combineFilterWithAnd($filterParts);
588
-	}
589
-
590
-	/**
591
-	 * returns a list of users that have the given group as primary group
592
-	 *
593
-	 * @param string $groupDN
594
-	 * @param string $search
595
-	 * @param int $limit
596
-	 * @param int $offset
597
-	 * @return string[]
598
-	 */
599
-	public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
600
-		try {
601
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
602
-			$users = $this->access->fetchListOfUsers(
603
-				$filter,
604
-				[$this->access->connection->ldapUserDisplayName, 'dn'],
605
-				$limit,
606
-				$offset
607
-			);
608
-			return $this->access->nextcloudUserNames($users);
609
-		} catch (\Exception $e) {
610
-			return [];
611
-		}
612
-	}
613
-
614
-	/**
615
-	 * returns the number of users that have the given group as primary group
616
-	 *
617
-	 * @param string $groupDN
618
-	 * @param string $search
619
-	 * @param int $limit
620
-	 * @param int $offset
621
-	 * @return int
622
-	 */
623
-	public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
624
-		try {
625
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
626
-			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
627
-			return (int)$users;
628
-		} catch (\Exception $e) {
629
-			return 0;
630
-		}
631
-	}
632
-
633
-	/**
634
-	 * gets the primary group of a user
635
-	 *
636
-	 * @param string $dn
637
-	 * @return string
638
-	 */
639
-	public function getUserPrimaryGroup($dn) {
640
-		$groupID = $this->getUserPrimaryGroupIDs($dn);
641
-		if ($groupID !== false) {
642
-			$groupName = $this->primaryGroupID2Name($groupID, $dn);
643
-			if ($groupName !== false) {
644
-				return $groupName;
645
-			}
646
-		}
647
-
648
-		return false;
649
-	}
650
-
651
-	/**
652
-	 * Get all groups a user belongs to
653
-	 *
654
-	 * @param string $uid Name of the user
655
-	 * @return array with group names
656
-	 *
657
-	 * This function fetches all groups a user belongs to. It does not check
658
-	 * if the user exists at all.
659
-	 *
660
-	 * This function includes groups based on dynamic group membership.
661
-	 */
662
-	public function getUserGroups($uid) {
663
-		if (!$this->enabled) {
664
-			return [];
665
-		}
666
-		$cacheKey = 'getUserGroups' . $uid;
667
-		$userGroups = $this->access->connection->getFromCache($cacheKey);
668
-		if (!is_null($userGroups)) {
669
-			return $userGroups;
670
-		}
671
-		$userDN = $this->access->username2dn($uid);
672
-		if (!$userDN) {
673
-			$this->access->connection->writeToCache($cacheKey, []);
674
-			return [];
675
-		}
676
-
677
-		$groups = [];
678
-		$primaryGroup = $this->getUserPrimaryGroup($userDN);
679
-		$gidGroupName = $this->getUserGroupByGid($userDN);
680
-
681
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
682
-
683
-		if (!empty($dynamicGroupMemberURL)) {
684
-			// look through dynamic groups to add them to the result array if needed
685
-			$groupsToMatch = $this->access->fetchListOfGroups(
686
-				$this->access->connection->ldapGroupFilter, ['dn', $dynamicGroupMemberURL]);
687
-			foreach ($groupsToMatch as $dynamicGroup) {
688
-				if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
689
-					continue;
690
-				}
691
-				$pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
692
-				if ($pos !== false) {
693
-					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos);
694
-					// apply filter via ldap search to see if this user is in this
695
-					// dynamic group
696
-					$userMatch = $this->access->readAttribute(
697
-						$userDN,
698
-						$this->access->connection->ldapUserDisplayName,
699
-						$memberUrlFilter
700
-					);
701
-					if ($userMatch !== false) {
702
-						// match found so this user is in this group
703
-						$groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
704
-						if (is_string($groupName)) {
705
-							// be sure to never return false if the dn could not be
706
-							// resolved to a name, for whatever reason.
707
-							$groups[] = $groupName;
708
-						}
709
-					}
710
-				} else {
711
-					\OCP\Util::writeLog('user_ldap', 'No search filter found on member url ' .
712
-						'of group ' . print_r($dynamicGroup, true), ILogger::DEBUG);
713
-				}
714
-			}
715
-		}
716
-
717
-		// if possible, read out membership via memberOf. It's far faster than
718
-		// performing a search, which still is a fallback later.
719
-		// memberof doesn't support memberuid, so skip it here.
720
-		if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
721
-			&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
722
-			&& strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
723
-		) {
724
-			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
725
-			if (is_array($groupDNs)) {
726
-				foreach ($groupDNs as $dn) {
727
-					$groupName = $this->access->dn2groupname($dn);
728
-					if (is_string($groupName)) {
729
-						// be sure to never return false if the dn could not be
730
-						// resolved to a name, for whatever reason.
731
-						$groups[] = $groupName;
732
-					}
733
-				}
734
-			}
735
-
736
-			if ($primaryGroup !== false) {
737
-				$groups[] = $primaryGroup;
738
-			}
739
-			if ($gidGroupName !== false) {
740
-				$groups[] = $gidGroupName;
741
-			}
742
-			$this->access->connection->writeToCache($cacheKey, $groups);
743
-			return $groups;
744
-		}
745
-
746
-		//uniqueMember takes DN, memberuid the uid, so we need to distinguish
747
-		if ((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
748
-			|| (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
749
-		) {
750
-			$uid = $userDN;
751
-		} elseif (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
752
-			$result = $this->access->readAttribute($userDN, 'uid');
753
-			if ($result === false) {
754
-				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on ' .
755
-					$this->access->connection->ldapHost, ILogger::DEBUG);
756
-				$uid = false;
757
-			} else {
758
-				$uid = $result[0];
759
-			}
760
-		} else {
761
-			// just in case
762
-			$uid = $userDN;
763
-		}
764
-
765
-		if ($uid !== false) {
766
-			if (isset($this->cachedGroupsByMember[$uid])) {
767
-				$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
768
-			} else {
769
-				$groupsByMember = array_values($this->getGroupsByMember($uid));
770
-				$groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
771
-				$this->cachedGroupsByMember[$uid] = $groupsByMember;
772
-				$groups = array_merge($groups, $groupsByMember);
773
-			}
774
-		}
775
-
776
-		if ($primaryGroup !== false) {
777
-			$groups[] = $primaryGroup;
778
-		}
779
-		if ($gidGroupName !== false) {
780
-			$groups[] = $gidGroupName;
781
-		}
782
-
783
-		$groups = array_unique($groups, SORT_LOCALE_STRING);
784
-		$this->access->connection->writeToCache($cacheKey, $groups);
785
-
786
-		return $groups;
787
-	}
788
-
789
-	/**
790
-	 * @param string $dn
791
-	 * @param array|null &$seen
792
-	 * @return array
793
-	 */
794
-	private function getGroupsByMember($dn, &$seen = null) {
795
-		if ($seen === null) {
796
-			$seen = [];
797
-		}
798
-		if (array_key_exists($dn, $seen)) {
799
-			// avoid loops
800
-			return [];
801
-		}
802
-		$allGroups = [];
803
-		$seen[$dn] = true;
804
-		$filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
805
-		$groups = $this->access->fetchListOfGroups($filter,
806
-			[strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
807
-		if (is_array($groups)) {
808
-			$fetcher = function ($dn, &$seen) {
809
-				if (is_array($dn) && isset($dn['dn'][0])) {
810
-					$dn = $dn['dn'][0];
811
-				}
812
-				return $this->getGroupsByMember($dn, $seen);
813
-			};
814
-			$allGroups = $this->walkNestedGroups($dn, $fetcher, $groups);
815
-		}
816
-		$visibleGroups = $this->filterValidGroups($allGroups);
817
-		return array_intersect_key($allGroups, $visibleGroups);
818
-	}
819
-
820
-	/**
821
-	 * get a list of all users in a group
822
-	 *
823
-	 * @param string $gid
824
-	 * @param string $search
825
-	 * @param int $limit
826
-	 * @param int $offset
827
-	 * @return array with user ids
828
-	 * @throws \Exception
829
-	 */
830
-	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
831
-		if (!$this->enabled) {
832
-			return [];
833
-		}
834
-		if (!$this->groupExists($gid)) {
835
-			return [];
836
-		}
837
-		$search = $this->access->escapeFilterPart($search, true);
838
-		$cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset;
839
-		// check for cache of the exact query
840
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
841
-		if (!is_null($groupUsers)) {
842
-			return $groupUsers;
843
-		}
844
-
845
-		// check for cache of the query without limit and offset
846
-		$groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search);
847
-		if (!is_null($groupUsers)) {
848
-			$groupUsers = array_slice($groupUsers, $offset, $limit);
849
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
850
-			return $groupUsers;
851
-		}
852
-
853
-		if ($limit === -1) {
854
-			$limit = null;
855
-		}
856
-		$groupDN = $this->access->groupname2dn($gid);
857
-		if (!$groupDN) {
858
-			// group couldn't be found, return empty resultset
859
-			$this->access->connection->writeToCache($cacheKey, []);
860
-			return [];
861
-		}
862
-
863
-		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
864
-		$posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
865
-		$members = $this->_groupMembers($groupDN);
866
-		if (!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
867
-			//in case users could not be retrieved, return empty result set
868
-			$this->access->connection->writeToCache($cacheKey, []);
869
-			return [];
870
-		}
871
-
872
-		$groupUsers = [];
873
-		$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
874
-		$attrs = $this->access->userManager->getAttributes(true);
875
-		foreach ($members as $member) {
876
-			if ($isMemberUid) {
877
-				//we got uids, need to get their DNs to 'translate' them to user names
878
-				$filter = $this->access->combineFilterWithAnd([
879
-					str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
880
-					$this->access->combineFilterWithAnd([
881
-						$this->access->getFilterPartForUserSearch($search),
882
-						$this->access->connection->ldapUserFilter
883
-					])
884
-				]);
885
-				$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
886
-				if (count($ldap_users) < 1) {
887
-					continue;
888
-				}
889
-				$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
890
-			} else {
891
-				//we got DNs, check if we need to filter by search or we can give back all of them
892
-				$uid = $this->access->dn2username($member);
893
-				if (!$uid) {
894
-					continue;
895
-				}
896
-
897
-				$cacheKey = 'userExistsOnLDAP' . $uid;
898
-				$userExists = $this->access->connection->getFromCache($cacheKey);
899
-				if ($userExists === false) {
900
-					continue;
901
-				}
902
-				if ($userExists === null || $search !== '') {
903
-					if (!$this->access->readAttribute($member,
904
-						$this->access->connection->ldapUserDisplayName,
905
-						$this->access->combineFilterWithAnd([
906
-							$this->access->getFilterPartForUserSearch($search),
907
-							$this->access->connection->ldapUserFilter
908
-						]))) {
909
-						if ($search === '') {
910
-							$this->access->connection->writeToCache($cacheKey, false);
911
-						}
912
-						continue;
913
-					}
914
-					$this->access->connection->writeToCache($cacheKey, true);
915
-				}
916
-				$groupUsers[] = $uid;
917
-			}
918
-		}
919
-
920
-		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
921
-		natsort($groupUsers);
922
-		$this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers);
923
-		$groupUsers = array_slice($groupUsers, $offset, $limit);
924
-
925
-		$this->access->connection->writeToCache($cacheKey, $groupUsers);
926
-
927
-		return $groupUsers;
928
-	}
929
-
930
-	/**
931
-	 * returns the number of users in a group, who match the search term
932
-	 *
933
-	 * @param string $gid the internal group name
934
-	 * @param string $search optional, a search string
935
-	 * @return int|bool
936
-	 */
937
-	public function countUsersInGroup($gid, $search = '') {
938
-		if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
939
-			return $this->groupPluginManager->countUsersInGroup($gid, $search);
940
-		}
941
-
942
-		$cacheKey = 'countUsersInGroup-' . $gid . '-' . $search;
943
-		if (!$this->enabled || !$this->groupExists($gid)) {
944
-			return false;
945
-		}
946
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
947
-		if (!is_null($groupUsers)) {
948
-			return $groupUsers;
949
-		}
950
-
951
-		$groupDN = $this->access->groupname2dn($gid);
952
-		if (!$groupDN) {
953
-			// group couldn't be found, return empty result set
954
-			$this->access->connection->writeToCache($cacheKey, false);
955
-			return false;
956
-		}
957
-
958
-		$members = $this->_groupMembers($groupDN);
959
-		$primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
960
-		if (!$members && $primaryUserCount === 0) {
961
-			//in case users could not be retrieved, return empty result set
962
-			$this->access->connection->writeToCache($cacheKey, false);
963
-			return false;
964
-		}
965
-
966
-		if ($search === '') {
967
-			$groupUsers = count($members) + $primaryUserCount;
968
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
969
-			return $groupUsers;
970
-		}
971
-		$search = $this->access->escapeFilterPart($search, true);
972
-		$isMemberUid =
973
-			(strtolower($this->access->connection->ldapGroupMemberAssocAttr)
974
-				=== 'memberuid');
975
-
976
-		//we need to apply the search filter
977
-		//alternatives that need to be checked:
978
-		//a) get all users by search filter and array_intersect them
979
-		//b) a, but only when less than 1k 10k ?k users like it is
980
-		//c) put all DNs|uids in a LDAP filter, combine with the search string
981
-		//   and let it count.
982
-		//For now this is not important, because the only use of this method
983
-		//does not supply a search string
984
-		$groupUsers = [];
985
-		foreach ($members as $member) {
986
-			if ($isMemberUid) {
987
-				//we got uids, need to get their DNs to 'translate' them to user names
988
-				$filter = $this->access->combineFilterWithAnd([
989
-					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
990
-					$this->access->getFilterPartForUserSearch($search)
991
-				]);
992
-				$ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
993
-				if (count($ldap_users) < 1) {
994
-					continue;
995
-				}
996
-				$groupUsers[] = $this->access->dn2username($ldap_users[0]);
997
-			} else {
998
-				//we need to apply the search filter now
999
-				if (!$this->access->readAttribute($member,
1000
-					$this->access->connection->ldapUserDisplayName,
1001
-					$this->access->getFilterPartForUserSearch($search))) {
1002
-					continue;
1003
-				}
1004
-				// dn2username will also check if the users belong to the allowed base
1005
-				if ($ocname = $this->access->dn2username($member)) {
1006
-					$groupUsers[] = $ocname;
1007
-				}
1008
-			}
1009
-		}
1010
-
1011
-		//and get users that have the group as primary
1012
-		$primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
1013
-
1014
-		return count($groupUsers) + $primaryUsers;
1015
-	}
1016
-
1017
-	/**
1018
-	 * get a list of all groups
1019
-	 *
1020
-	 * @param string $search
1021
-	 * @param $limit
1022
-	 * @param int $offset
1023
-	 * @return array with group names
1024
-	 *
1025
-	 * Returns a list with all groups (used by getGroups)
1026
-	 */
1027
-	protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
1028
-		if (!$this->enabled) {
1029
-			return [];
1030
-		}
1031
-		$cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset;
1032
-
1033
-		//Check cache before driving unnecessary searches
1034
-		\OCP\Util::writeLog('user_ldap', 'getGroups ' . $cacheKey, ILogger::DEBUG);
1035
-		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
1036
-		if (!is_null($ldap_groups)) {
1037
-			return $ldap_groups;
1038
-		}
1039
-
1040
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
1041
-		// error. With a limit of 0, we get 0 results. So we pass null.
1042
-		if ($limit <= 0) {
1043
-			$limit = null;
1044
-		}
1045
-		$filter = $this->access->combineFilterWithAnd([
1046
-			$this->access->connection->ldapGroupFilter,
1047
-			$this->access->getFilterPartForGroupSearch($search)
1048
-		]);
1049
-		\OCP\Util::writeLog('user_ldap', 'getGroups Filter ' . $filter, ILogger::DEBUG);
1050
-		$ldap_groups = $this->access->fetchListOfGroups($filter,
1051
-			[$this->access->connection->ldapGroupDisplayName, 'dn'],
1052
-			$limit,
1053
-			$offset);
1054
-		$ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
1055
-
1056
-		$this->access->connection->writeToCache($cacheKey, $ldap_groups);
1057
-		return $ldap_groups;
1058
-	}
1059
-
1060
-	/**
1061
-	 * get a list of all groups using a paged search
1062
-	 *
1063
-	 * @param string $search
1064
-	 * @param int $limit
1065
-	 * @param int $offset
1066
-	 * @return array with group names
1067
-	 *
1068
-	 * Returns a list with all groups
1069
-	 * Uses a paged search if available to override a
1070
-	 * server side search limit.
1071
-	 * (active directory has a limit of 1000 by default)
1072
-	 */
1073
-	public function getGroups($search = '', $limit = -1, $offset = 0) {
1074
-		if (!$this->enabled) {
1075
-			return [];
1076
-		}
1077
-		$search = $this->access->escapeFilterPart($search, true);
1078
-		$pagingSize = (int)$this->access->connection->ldapPagingSize;
1079
-		if ($pagingSize <= 0) {
1080
-			return $this->getGroupsChunk($search, $limit, $offset);
1081
-		}
1082
-		$maxGroups = 100000; // limit max results (just for safety reasons)
1083
-		if ($limit > -1) {
1084
-			$overallLimit = min($limit + $offset, $maxGroups);
1085
-		} else {
1086
-			$overallLimit = $maxGroups;
1087
-		}
1088
-		$chunkOffset = $offset;
1089
-		$allGroups = [];
1090
-		while ($chunkOffset < $overallLimit) {
1091
-			$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1092
-			$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1093
-			$nread = count($ldapGroups);
1094
-			\OCP\Util::writeLog('user_ldap', 'getGroups(' . $search . '): read ' . $nread . ' at offset ' . $chunkOffset . ' (limit: ' . $chunkLimit . ')', ILogger::DEBUG);
1095
-			if ($nread) {
1096
-				$allGroups = array_merge($allGroups, $ldapGroups);
1097
-				$chunkOffset += $nread;
1098
-			}
1099
-			if ($nread < $chunkLimit) {
1100
-				break;
1101
-			}
1102
-		}
1103
-		return $allGroups;
1104
-	}
1105
-
1106
-	/**
1107
-	 * @param string $group
1108
-	 * @return bool
1109
-	 */
1110
-	public function groupMatchesFilter($group) {
1111
-		return (strripos($group, $this->groupSearch) !== false);
1112
-	}
1113
-
1114
-	/**
1115
-	 * check if a group exists
1116
-	 *
1117
-	 * @param string $gid
1118
-	 * @return bool
1119
-	 */
1120
-	public function groupExists($gid) {
1121
-		$groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
1122
-		if (!is_null($groupExists)) {
1123
-			return (bool)$groupExists;
1124
-		}
1125
-
1126
-		//getting dn, if false the group does not exist. If dn, it may be mapped
1127
-		//only, requires more checking.
1128
-		$dn = $this->access->groupname2dn($gid);
1129
-		if (!$dn) {
1130
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1131
-			return false;
1132
-		}
1133
-
1134
-		if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
1135
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1136
-			return false;
1137
-		}
1138
-
1139
-		//if group really still exists, we will be able to read its objectclass
1140
-		if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
1141
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1142
-			return false;
1143
-		}
1144
-
1145
-		$this->access->connection->writeToCache('groupExists' . $gid, true);
1146
-		return true;
1147
-	}
1148
-
1149
-	protected function filterValidGroups(array $listOfGroups): array {
1150
-		$validGroupDNs = [];
1151
-		foreach ($listOfGroups as $key => $item) {
1152
-			$dn = is_string($item) ? $item : $item['dn'][0];
1153
-			$gid = $this->access->dn2groupname($dn);
1154
-			if (!$gid) {
1155
-				continue;
1156
-			}
1157
-			if ($this->groupExists($gid)) {
1158
-				$validGroupDNs[$key] = $item;
1159
-			}
1160
-		}
1161
-		return $validGroupDNs;
1162
-	}
1163
-
1164
-	/**
1165
-	 * Check if backend implements actions
1166
-	 *
1167
-	 * @param int $actions bitwise-or'ed actions
1168
-	 * @return boolean
1169
-	 *
1170
-	 * Returns the supported actions as int to be
1171
-	 * compared with GroupInterface::CREATE_GROUP etc.
1172
-	 */
1173
-	public function implementsActions($actions) {
1174
-		return (bool)((GroupInterface::COUNT_USERS |
1175
-				$this->groupPluginManager->getImplementedActions()) & $actions);
1176
-	}
1177
-
1178
-	/**
1179
-	 * Return access for LDAP interaction.
1180
-	 *
1181
-	 * @return Access instance of Access for LDAP interaction
1182
-	 */
1183
-	public function getLDAPAccess($gid) {
1184
-		return $this->access;
1185
-	}
1186
-
1187
-	/**
1188
-	 * create a group
1189
-	 *
1190
-	 * @param string $gid
1191
-	 * @return bool
1192
-	 * @throws \Exception
1193
-	 */
1194
-	public function createGroup($gid) {
1195
-		if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1196
-			if ($dn = $this->groupPluginManager->createGroup($gid)) {
1197
-				//updates group mapping
1198
-				$uuid = $this->access->getUUID($dn, false);
1199
-				if (is_string($uuid)) {
1200
-					$this->access->mapAndAnnounceIfApplicable(
1201
-						$this->access->getGroupMapper(),
1202
-						$dn,
1203
-						$gid,
1204
-						$uuid,
1205
-						false
1206
-					);
1207
-					$this->access->cacheGroupExists($gid);
1208
-				}
1209
-			}
1210
-			return $dn != null;
1211
-		}
1212
-		throw new \Exception('Could not create group in LDAP backend.');
1213
-	}
1214
-
1215
-	/**
1216
-	 * delete a group
1217
-	 *
1218
-	 * @param string $gid gid of the group to delete
1219
-	 * @return bool
1220
-	 * @throws \Exception
1221
-	 */
1222
-	public function deleteGroup($gid) {
1223
-		if ($this->groupPluginManager->implementsActions(GroupInterface::DELETE_GROUP)) {
1224
-			if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1225
-				#delete group in nextcloud internal db
1226
-				$this->access->getGroupMapper()->unmap($gid);
1227
-				$this->access->connection->writeToCache("groupExists" . $gid, false);
1228
-			}
1229
-			return $ret;
1230
-		}
1231
-		throw new \Exception('Could not delete group in LDAP backend.');
1232
-	}
1233
-
1234
-	/**
1235
-	 * Add a user to a group
1236
-	 *
1237
-	 * @param string $uid Name of the user to add to group
1238
-	 * @param string $gid Name of the group in which add the user
1239
-	 * @return bool
1240
-	 * @throws \Exception
1241
-	 */
1242
-	public function addToGroup($uid, $gid) {
1243
-		if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1244
-			if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1245
-				$this->access->connection->clearCache();
1246
-				unset($this->cachedGroupMembers[$gid]);
1247
-			}
1248
-			return $ret;
1249
-		}
1250
-		throw new \Exception('Could not add user to group in LDAP backend.');
1251
-	}
1252
-
1253
-	/**
1254
-	 * Removes a user from a group
1255
-	 *
1256
-	 * @param string $uid Name of the user to remove from group
1257
-	 * @param string $gid Name of the group from which remove the user
1258
-	 * @return bool
1259
-	 * @throws \Exception
1260
-	 */
1261
-	public function removeFromGroup($uid, $gid) {
1262
-		if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1263
-			if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1264
-				$this->access->connection->clearCache();
1265
-				unset($this->cachedGroupMembers[$gid]);
1266
-			}
1267
-			return $ret;
1268
-		}
1269
-		throw new \Exception('Could not remove user from group in LDAP backend.');
1270
-	}
1271
-
1272
-	/**
1273
-	 * Gets group details
1274
-	 *
1275
-	 * @param string $gid Name of the group
1276
-	 * @return array | false
1277
-	 * @throws \Exception
1278
-	 */
1279
-	public function getGroupDetails($gid) {
1280
-		if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1281
-			return $this->groupPluginManager->getGroupDetails($gid);
1282
-		}
1283
-		throw new \Exception('Could not get group details in LDAP backend.');
1284
-	}
1285
-
1286
-	/**
1287
-	 * Return LDAP connection resource from a cloned connection.
1288
-	 * The cloned connection needs to be closed manually.
1289
-	 * of the current access.
1290
-	 *
1291
-	 * @param string $gid
1292
-	 * @return resource of the LDAP connection
1293
-	 */
1294
-	public function getNewLDAPConnection($gid) {
1295
-		$connection = clone $this->access->getConnection();
1296
-		return $connection->getConnectionResource();
1297
-	}
1298
-
1299
-	/**
1300
-	 * @throws \OC\ServerNotAvailableException
1301
-	 */
1302
-	public function getDisplayName(string $gid): string {
1303
-		if ($this->groupPluginManager instanceof IGetDisplayNameBackend) {
1304
-			return $this->groupPluginManager->getDisplayName($gid);
1305
-		}
1306
-
1307
-		$cacheKey = 'group_getDisplayName' . $gid;
1308
-		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
1309
-			return $displayName;
1310
-		}
1311
-
1312
-		$displayName = $this->access->readAttribute(
1313
-			$this->access->groupname2dn($gid),
1314
-			$this->access->connection->ldapGroupDisplayName);
1315
-
1316
-		if ($displayName && (count($displayName) > 0)) {
1317
-			$displayName = $displayName[0];
1318
-			$this->access->connection->writeToCache($cacheKey, $displayName);
1319
-			return $displayName;
1320
-		}
1321
-
1322
-		return '';
1323
-	}
52
+    protected $enabled = false;
53
+
54
+    /**
55
+     * @var string[] $cachedGroupMembers array of users with gid as key
56
+     */
57
+    protected $cachedGroupMembers;
58
+
59
+    /**
60
+     * @var string[] $cachedGroupsByMember array of groups with uid as key
61
+     */
62
+    protected $cachedGroupsByMember;
63
+
64
+    /**
65
+     * @var string[] $cachedNestedGroups array of groups with gid (DN) as key
66
+     */
67
+    protected $cachedNestedGroups;
68
+
69
+    /** @var GroupPluginManager */
70
+    protected $groupPluginManager;
71
+
72
+    public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
73
+        parent::__construct($access);
74
+        $filter = $this->access->connection->ldapGroupFilter;
75
+        $gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
76
+        if (!empty($filter) && !empty($gassoc)) {
77
+            $this->enabled = true;
78
+        }
79
+
80
+        $this->cachedGroupMembers = new CappedMemoryCache();
81
+        $this->cachedGroupsByMember = new CappedMemoryCache();
82
+        $this->cachedNestedGroups = new CappedMemoryCache();
83
+        $this->groupPluginManager = $groupPluginManager;
84
+    }
85
+
86
+    /**
87
+     * is user in group?
88
+     *
89
+     * @param string $uid uid of the user
90
+     * @param string $gid gid of the group
91
+     * @return bool
92
+     *
93
+     * Checks whether the user is member of a group or not.
94
+     */
95
+    public function inGroup($uid, $gid) {
96
+        if (!$this->enabled) {
97
+            return false;
98
+        }
99
+        $cacheKey = 'inGroup' . $uid . ':' . $gid;
100
+        $inGroup = $this->access->connection->getFromCache($cacheKey);
101
+        if (!is_null($inGroup)) {
102
+            return (bool)$inGroup;
103
+        }
104
+
105
+        $userDN = $this->access->username2dn($uid);
106
+
107
+        if (isset($this->cachedGroupMembers[$gid])) {
108
+            $isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
109
+            return $isInGroup;
110
+        }
111
+
112
+        $cacheKeyMembers = 'inGroup-members:' . $gid;
113
+        $members = $this->access->connection->getFromCache($cacheKeyMembers);
114
+        if (!is_null($members)) {
115
+            $this->cachedGroupMembers[$gid] = $members;
116
+            $isInGroup = in_array($userDN, $members, true);
117
+            $this->access->connection->writeToCache($cacheKey, $isInGroup);
118
+            return $isInGroup;
119
+        }
120
+
121
+        $groupDN = $this->access->groupname2dn($gid);
122
+        // just in case
123
+        if (!$groupDN || !$userDN) {
124
+            $this->access->connection->writeToCache($cacheKey, false);
125
+            return false;
126
+        }
127
+
128
+        //check primary group first
129
+        if ($gid === $this->getUserPrimaryGroup($userDN)) {
130
+            $this->access->connection->writeToCache($cacheKey, true);
131
+            return true;
132
+        }
133
+
134
+        //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
135
+        $members = $this->_groupMembers($groupDN);
136
+        if (!is_array($members) || count($members) === 0) {
137
+            $this->access->connection->writeToCache($cacheKey, false);
138
+            return false;
139
+        }
140
+
141
+        //extra work if we don't get back user DNs
142
+        if (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
143
+            $dns = [];
144
+            $filterParts = [];
145
+            $bytes = 0;
146
+            foreach ($members as $mid) {
147
+                $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
148
+                $filterParts[] = $filter;
149
+                $bytes += strlen($filter);
150
+                if ($bytes >= 9000000) {
151
+                    // AD has a default input buffer of 10 MB, we do not want
152
+                    // to take even the chance to exceed it
153
+                    $filter = $this->access->combineFilterWithOr($filterParts);
154
+                    $bytes = 0;
155
+                    $filterParts = [];
156
+                    $users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
157
+                    $dns = array_merge($dns, $users);
158
+                }
159
+            }
160
+            if (count($filterParts) > 0) {
161
+                $filter = $this->access->combineFilterWithOr($filterParts);
162
+                $users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
163
+                $dns = array_merge($dns, $users);
164
+            }
165
+            $members = $dns;
166
+        }
167
+
168
+        $isInGroup = in_array($userDN, $members);
169
+        $this->access->connection->writeToCache($cacheKey, $isInGroup);
170
+        $this->access->connection->writeToCache($cacheKeyMembers, $members);
171
+        $this->cachedGroupMembers[$gid] = $members;
172
+
173
+        return $isInGroup;
174
+    }
175
+
176
+    /**
177
+     * @param string $dnGroup
178
+     * @return array
179
+     *
180
+     * For a group that has user membership defined by an LDAP search url attribute returns the users
181
+     * that match the search url otherwise returns an empty array.
182
+     */
183
+    public function getDynamicGroupMembers($dnGroup) {
184
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
185
+
186
+        if (empty($dynamicGroupMemberURL)) {
187
+            return [];
188
+        }
189
+
190
+        $dynamicMembers = [];
191
+        $memberURLs = $this->access->readAttribute(
192
+            $dnGroup,
193
+            $dynamicGroupMemberURL,
194
+            $this->access->connection->ldapGroupFilter
195
+        );
196
+        if ($memberURLs !== false) {
197
+            // this group has the 'memberURL' attribute so this is a dynamic group
198
+            // example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
199
+            // example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
200
+            $pos = strpos($memberURLs[0], '(');
201
+            if ($pos !== false) {
202
+                $memberUrlFilter = substr($memberURLs[0], $pos);
203
+                $foundMembers = $this->access->searchUsers($memberUrlFilter, 'dn');
204
+                $dynamicMembers = [];
205
+                foreach ($foundMembers as $value) {
206
+                    $dynamicMembers[$value['dn'][0]] = 1;
207
+                }
208
+            } else {
209
+                \OCP\Util::writeLog('user_ldap', 'No search filter found on member url ' .
210
+                    'of group ' . $dnGroup, ILogger::DEBUG);
211
+            }
212
+        }
213
+        return $dynamicMembers;
214
+    }
215
+
216
+    /**
217
+     * @param string $dnGroup
218
+     * @param array|null &$seen
219
+     * @return array|mixed|null
220
+     * @throws \OC\ServerNotAvailableException
221
+     */
222
+    private function _groupMembers($dnGroup, &$seen = null) {
223
+        if ($seen === null) {
224
+            $seen = [];
225
+        }
226
+        $allMembers = [];
227
+        if (array_key_exists($dnGroup, $seen)) {
228
+            // avoid loops
229
+            return [];
230
+        }
231
+        // used extensively in cron job, caching makes sense for nested groups
232
+        $cacheKey = '_groupMembers' . $dnGroup;
233
+        $groupMembers = $this->access->connection->getFromCache($cacheKey);
234
+        if ($groupMembers !== null) {
235
+            return $groupMembers;
236
+        }
237
+        $seen[$dnGroup] = 1;
238
+        $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
239
+        if (is_array($members)) {
240
+            $fetcher = function ($memberDN, &$seen) {
241
+                return $this->_groupMembers($memberDN, $seen);
242
+            };
243
+            $allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members);
244
+        }
245
+
246
+        $allMembers += $this->getDynamicGroupMembers($dnGroup);
247
+
248
+        $this->access->connection->writeToCache($cacheKey, $allMembers);
249
+        return $allMembers;
250
+    }
251
+
252
+    /**
253
+     * @param string $DN
254
+     * @param array|null &$seen
255
+     * @return array
256
+     * @throws \OC\ServerNotAvailableException
257
+     */
258
+    private function _getGroupDNsFromMemberOf($DN) {
259
+        $groups = $this->access->readAttribute($DN, 'memberOf');
260
+        if (!is_array($groups)) {
261
+            return [];
262
+        }
263
+
264
+        $fetcher = function ($groupDN) {
265
+            if (isset($this->cachedNestedGroups[$groupDN])) {
266
+                $nestedGroups = $this->cachedNestedGroups[$groupDN];
267
+            } else {
268
+                $nestedGroups = $this->access->readAttribute($groupDN, 'memberOf');
269
+                if (!is_array($nestedGroups)) {
270
+                    $nestedGroups = [];
271
+                }
272
+                $this->cachedNestedGroups[$groupDN] = $nestedGroups;
273
+            }
274
+            return $nestedGroups;
275
+        };
276
+
277
+        $groups = $this->walkNestedGroups($DN, $fetcher, $groups);
278
+        return $this->filterValidGroups($groups);
279
+    }
280
+
281
+    /**
282
+     * @param string $dn
283
+     * @param \Closure $fetcher args: string $dn, array $seen, returns: string[] of dns
284
+     * @param array $list
285
+     * @return array
286
+     */
287
+    private function walkNestedGroups(string $dn, \Closure $fetcher, array $list): array {
288
+        $nesting = (int)$this->access->connection->ldapNestedGroups;
289
+        // depending on the input, we either have a list of DNs or a list of LDAP records
290
+        // also, the output expects either DNs or records. Testing the first element should suffice.
291
+        $recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]);
292
+
293
+        if ($nesting !== 1) {
294
+            if ($recordMode) {
295
+                // the keys are numeric, but should hold the DN
296
+                return array_reduce($list, function ($transformed, $record) use ($dn) {
297
+                    if ($record['dn'][0] != $dn) {
298
+                        $transformed[$record['dn'][0]] = $record;
299
+                    }
300
+                    return $transformed;
301
+                }, []);
302
+            }
303
+            return $list;
304
+        }
305
+
306
+        $seen = [];
307
+        while ($record = array_pop($list)) {
308
+            $recordDN = $recordMode ? $record['dn'][0] : $record;
309
+            if ($recordDN === $dn || array_key_exists($recordDN, $seen)) {
310
+                // Prevent loops
311
+                continue;
312
+            }
313
+            $fetched = $fetcher($record, $seen);
314
+            $list = array_merge($list, $fetched);
315
+            $seen[$recordDN] = $record;
316
+        }
317
+
318
+        return $recordMode ? $seen : array_keys($seen);
319
+    }
320
+
321
+    /**
322
+     * translates a gidNumber into an ownCloud internal name
323
+     *
324
+     * @param string $gid as given by gidNumber on POSIX LDAP
325
+     * @param string $dn a DN that belongs to the same domain as the group
326
+     * @return string|bool
327
+     */
328
+    public function gidNumber2Name($gid, $dn) {
329
+        $cacheKey = 'gidNumberToName' . $gid;
330
+        $groupName = $this->access->connection->getFromCache($cacheKey);
331
+        if (!is_null($groupName) && isset($groupName)) {
332
+            return $groupName;
333
+        }
334
+
335
+        //we need to get the DN from LDAP
336
+        $filter = $this->access->combineFilterWithAnd([
337
+            $this->access->connection->ldapGroupFilter,
338
+            'objectClass=posixGroup',
339
+            $this->access->connection->ldapGidNumber . '=' . $gid
340
+        ]);
341
+        $result = $this->access->searchGroups($filter, ['dn'], 1);
342
+        if (empty($result)) {
343
+            return false;
344
+        }
345
+        $dn = $result[0]['dn'][0];
346
+
347
+        //and now the group name
348
+        //NOTE once we have separate ownCloud group IDs and group names we can
349
+        //directly read the display name attribute instead of the DN
350
+        $name = $this->access->dn2groupname($dn);
351
+
352
+        $this->access->connection->writeToCache($cacheKey, $name);
353
+
354
+        return $name;
355
+    }
356
+
357
+    /**
358
+     * returns the entry's gidNumber
359
+     *
360
+     * @param string $dn
361
+     * @param string $attribute
362
+     * @return string|bool
363
+     */
364
+    private function getEntryGidNumber($dn, $attribute) {
365
+        $value = $this->access->readAttribute($dn, $attribute);
366
+        if (is_array($value) && !empty($value)) {
367
+            return $value[0];
368
+        }
369
+        return false;
370
+    }
371
+
372
+    /**
373
+     * returns the group's primary ID
374
+     *
375
+     * @param string $dn
376
+     * @return string|bool
377
+     */
378
+    public function getGroupGidNumber($dn) {
379
+        return $this->getEntryGidNumber($dn, 'gidNumber');
380
+    }
381
+
382
+    /**
383
+     * returns the user's gidNumber
384
+     *
385
+     * @param string $dn
386
+     * @return string|bool
387
+     */
388
+    public function getUserGidNumber($dn) {
389
+        $gidNumber = false;
390
+        if ($this->access->connection->hasGidNumber) {
391
+            $gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
392
+            if ($gidNumber === false) {
393
+                $this->access->connection->hasGidNumber = false;
394
+            }
395
+        }
396
+        return $gidNumber;
397
+    }
398
+
399
+    /**
400
+     * returns a filter for a "users has specific gid" search or count operation
401
+     *
402
+     * @param string $groupDN
403
+     * @param string $search
404
+     * @return string
405
+     * @throws \Exception
406
+     */
407
+    private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
408
+        $groupID = $this->getGroupGidNumber($groupDN);
409
+        if ($groupID === false) {
410
+            throw new \Exception('Not a valid group');
411
+        }
412
+
413
+        $filterParts = [];
414
+        $filterParts[] = $this->access->getFilterForUserCount();
415
+        if ($search !== '') {
416
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
417
+        }
418
+        $filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID;
419
+
420
+        return $this->access->combineFilterWithAnd($filterParts);
421
+    }
422
+
423
+    /**
424
+     * returns a list of users that have the given group as gid number
425
+     *
426
+     * @param string $groupDN
427
+     * @param string $search
428
+     * @param int $limit
429
+     * @param int $offset
430
+     * @return string[]
431
+     */
432
+    public function getUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
433
+        try {
434
+            $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
435
+            $users = $this->access->fetchListOfUsers(
436
+                $filter,
437
+                [$this->access->connection->ldapUserDisplayName, 'dn'],
438
+                $limit,
439
+                $offset
440
+            );
441
+            return $this->access->nextcloudUserNames($users);
442
+        } catch (\Exception $e) {
443
+            return [];
444
+        }
445
+    }
446
+
447
+    /**
448
+     * returns the number of users that have the given group as gid number
449
+     *
450
+     * @param string $groupDN
451
+     * @param string $search
452
+     * @param int $limit
453
+     * @param int $offset
454
+     * @return int
455
+     */
456
+    public function countUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
457
+        try {
458
+            $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
459
+            $users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
460
+            return (int)$users;
461
+        } catch (\Exception $e) {
462
+            return 0;
463
+        }
464
+    }
465
+
466
+    /**
467
+     * gets the gidNumber of a user
468
+     *
469
+     * @param string $dn
470
+     * @return string
471
+     */
472
+    public function getUserGroupByGid($dn) {
473
+        $groupID = $this->getUserGidNumber($dn);
474
+        if ($groupID !== false) {
475
+            $groupName = $this->gidNumber2Name($groupID, $dn);
476
+            if ($groupName !== false) {
477
+                return $groupName;
478
+            }
479
+        }
480
+
481
+        return false;
482
+    }
483
+
484
+    /**
485
+     * translates a primary group ID into an Nextcloud internal name
486
+     *
487
+     * @param string $gid as given by primaryGroupID on AD
488
+     * @param string $dn a DN that belongs to the same domain as the group
489
+     * @return string|bool
490
+     */
491
+    public function primaryGroupID2Name($gid, $dn) {
492
+        $cacheKey = 'primaryGroupIDtoName';
493
+        $groupNames = $this->access->connection->getFromCache($cacheKey);
494
+        if (!is_null($groupNames) && isset($groupNames[$gid])) {
495
+            return $groupNames[$gid];
496
+        }
497
+
498
+        $domainObjectSid = $this->access->getSID($dn);
499
+        if ($domainObjectSid === false) {
500
+            return false;
501
+        }
502
+
503
+        //we need to get the DN from LDAP
504
+        $filter = $this->access->combineFilterWithAnd([
505
+            $this->access->connection->ldapGroupFilter,
506
+            'objectsid=' . $domainObjectSid . '-' . $gid
507
+        ]);
508
+        $result = $this->access->searchGroups($filter, ['dn'], 1);
509
+        if (empty($result)) {
510
+            return false;
511
+        }
512
+        $dn = $result[0]['dn'][0];
513
+
514
+        //and now the group name
515
+        //NOTE once we have separate Nextcloud group IDs and group names we can
516
+        //directly read the display name attribute instead of the DN
517
+        $name = $this->access->dn2groupname($dn);
518
+
519
+        $this->access->connection->writeToCache($cacheKey, $name);
520
+
521
+        return $name;
522
+    }
523
+
524
+    /**
525
+     * returns the entry's primary group ID
526
+     *
527
+     * @param string $dn
528
+     * @param string $attribute
529
+     * @return string|bool
530
+     */
531
+    private function getEntryGroupID($dn, $attribute) {
532
+        $value = $this->access->readAttribute($dn, $attribute);
533
+        if (is_array($value) && !empty($value)) {
534
+            return $value[0];
535
+        }
536
+        return false;
537
+    }
538
+
539
+    /**
540
+     * returns the group's primary ID
541
+     *
542
+     * @param string $dn
543
+     * @return string|bool
544
+     */
545
+    public function getGroupPrimaryGroupID($dn) {
546
+        return $this->getEntryGroupID($dn, 'primaryGroupToken');
547
+    }
548
+
549
+    /**
550
+     * returns the user's primary group ID
551
+     *
552
+     * @param string $dn
553
+     * @return string|bool
554
+     */
555
+    public function getUserPrimaryGroupIDs($dn) {
556
+        $primaryGroupID = false;
557
+        if ($this->access->connection->hasPrimaryGroups) {
558
+            $primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
559
+            if ($primaryGroupID === false) {
560
+                $this->access->connection->hasPrimaryGroups = false;
561
+            }
562
+        }
563
+        return $primaryGroupID;
564
+    }
565
+
566
+    /**
567
+     * returns a filter for a "users in primary group" search or count operation
568
+     *
569
+     * @param string $groupDN
570
+     * @param string $search
571
+     * @return string
572
+     * @throws \Exception
573
+     */
574
+    private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
575
+        $groupID = $this->getGroupPrimaryGroupID($groupDN);
576
+        if ($groupID === false) {
577
+            throw new \Exception('Not a valid group');
578
+        }
579
+
580
+        $filterParts = [];
581
+        $filterParts[] = $this->access->getFilterForUserCount();
582
+        if ($search !== '') {
583
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
584
+        }
585
+        $filterParts[] = 'primaryGroupID=' . $groupID;
586
+
587
+        return $this->access->combineFilterWithAnd($filterParts);
588
+    }
589
+
590
+    /**
591
+     * returns a list of users that have the given group as primary group
592
+     *
593
+     * @param string $groupDN
594
+     * @param string $search
595
+     * @param int $limit
596
+     * @param int $offset
597
+     * @return string[]
598
+     */
599
+    public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
600
+        try {
601
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
602
+            $users = $this->access->fetchListOfUsers(
603
+                $filter,
604
+                [$this->access->connection->ldapUserDisplayName, 'dn'],
605
+                $limit,
606
+                $offset
607
+            );
608
+            return $this->access->nextcloudUserNames($users);
609
+        } catch (\Exception $e) {
610
+            return [];
611
+        }
612
+    }
613
+
614
+    /**
615
+     * returns the number of users that have the given group as primary group
616
+     *
617
+     * @param string $groupDN
618
+     * @param string $search
619
+     * @param int $limit
620
+     * @param int $offset
621
+     * @return int
622
+     */
623
+    public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
624
+        try {
625
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
626
+            $users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
627
+            return (int)$users;
628
+        } catch (\Exception $e) {
629
+            return 0;
630
+        }
631
+    }
632
+
633
+    /**
634
+     * gets the primary group of a user
635
+     *
636
+     * @param string $dn
637
+     * @return string
638
+     */
639
+    public function getUserPrimaryGroup($dn) {
640
+        $groupID = $this->getUserPrimaryGroupIDs($dn);
641
+        if ($groupID !== false) {
642
+            $groupName = $this->primaryGroupID2Name($groupID, $dn);
643
+            if ($groupName !== false) {
644
+                return $groupName;
645
+            }
646
+        }
647
+
648
+        return false;
649
+    }
650
+
651
+    /**
652
+     * Get all groups a user belongs to
653
+     *
654
+     * @param string $uid Name of the user
655
+     * @return array with group names
656
+     *
657
+     * This function fetches all groups a user belongs to. It does not check
658
+     * if the user exists at all.
659
+     *
660
+     * This function includes groups based on dynamic group membership.
661
+     */
662
+    public function getUserGroups($uid) {
663
+        if (!$this->enabled) {
664
+            return [];
665
+        }
666
+        $cacheKey = 'getUserGroups' . $uid;
667
+        $userGroups = $this->access->connection->getFromCache($cacheKey);
668
+        if (!is_null($userGroups)) {
669
+            return $userGroups;
670
+        }
671
+        $userDN = $this->access->username2dn($uid);
672
+        if (!$userDN) {
673
+            $this->access->connection->writeToCache($cacheKey, []);
674
+            return [];
675
+        }
676
+
677
+        $groups = [];
678
+        $primaryGroup = $this->getUserPrimaryGroup($userDN);
679
+        $gidGroupName = $this->getUserGroupByGid($userDN);
680
+
681
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
682
+
683
+        if (!empty($dynamicGroupMemberURL)) {
684
+            // look through dynamic groups to add them to the result array if needed
685
+            $groupsToMatch = $this->access->fetchListOfGroups(
686
+                $this->access->connection->ldapGroupFilter, ['dn', $dynamicGroupMemberURL]);
687
+            foreach ($groupsToMatch as $dynamicGroup) {
688
+                if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
689
+                    continue;
690
+                }
691
+                $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
692
+                if ($pos !== false) {
693
+                    $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos);
694
+                    // apply filter via ldap search to see if this user is in this
695
+                    // dynamic group
696
+                    $userMatch = $this->access->readAttribute(
697
+                        $userDN,
698
+                        $this->access->connection->ldapUserDisplayName,
699
+                        $memberUrlFilter
700
+                    );
701
+                    if ($userMatch !== false) {
702
+                        // match found so this user is in this group
703
+                        $groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
704
+                        if (is_string($groupName)) {
705
+                            // be sure to never return false if the dn could not be
706
+                            // resolved to a name, for whatever reason.
707
+                            $groups[] = $groupName;
708
+                        }
709
+                    }
710
+                } else {
711
+                    \OCP\Util::writeLog('user_ldap', 'No search filter found on member url ' .
712
+                        'of group ' . print_r($dynamicGroup, true), ILogger::DEBUG);
713
+                }
714
+            }
715
+        }
716
+
717
+        // if possible, read out membership via memberOf. It's far faster than
718
+        // performing a search, which still is a fallback later.
719
+        // memberof doesn't support memberuid, so skip it here.
720
+        if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
721
+            && (int)$this->access->connection->useMemberOfToDetectMembership === 1
722
+            && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
723
+        ) {
724
+            $groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
725
+            if (is_array($groupDNs)) {
726
+                foreach ($groupDNs as $dn) {
727
+                    $groupName = $this->access->dn2groupname($dn);
728
+                    if (is_string($groupName)) {
729
+                        // be sure to never return false if the dn could not be
730
+                        // resolved to a name, for whatever reason.
731
+                        $groups[] = $groupName;
732
+                    }
733
+                }
734
+            }
735
+
736
+            if ($primaryGroup !== false) {
737
+                $groups[] = $primaryGroup;
738
+            }
739
+            if ($gidGroupName !== false) {
740
+                $groups[] = $gidGroupName;
741
+            }
742
+            $this->access->connection->writeToCache($cacheKey, $groups);
743
+            return $groups;
744
+        }
745
+
746
+        //uniqueMember takes DN, memberuid the uid, so we need to distinguish
747
+        if ((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
748
+            || (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
749
+        ) {
750
+            $uid = $userDN;
751
+        } elseif (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
752
+            $result = $this->access->readAttribute($userDN, 'uid');
753
+            if ($result === false) {
754
+                \OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on ' .
755
+                    $this->access->connection->ldapHost, ILogger::DEBUG);
756
+                $uid = false;
757
+            } else {
758
+                $uid = $result[0];
759
+            }
760
+        } else {
761
+            // just in case
762
+            $uid = $userDN;
763
+        }
764
+
765
+        if ($uid !== false) {
766
+            if (isset($this->cachedGroupsByMember[$uid])) {
767
+                $groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
768
+            } else {
769
+                $groupsByMember = array_values($this->getGroupsByMember($uid));
770
+                $groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
771
+                $this->cachedGroupsByMember[$uid] = $groupsByMember;
772
+                $groups = array_merge($groups, $groupsByMember);
773
+            }
774
+        }
775
+
776
+        if ($primaryGroup !== false) {
777
+            $groups[] = $primaryGroup;
778
+        }
779
+        if ($gidGroupName !== false) {
780
+            $groups[] = $gidGroupName;
781
+        }
782
+
783
+        $groups = array_unique($groups, SORT_LOCALE_STRING);
784
+        $this->access->connection->writeToCache($cacheKey, $groups);
785
+
786
+        return $groups;
787
+    }
788
+
789
+    /**
790
+     * @param string $dn
791
+     * @param array|null &$seen
792
+     * @return array
793
+     */
794
+    private function getGroupsByMember($dn, &$seen = null) {
795
+        if ($seen === null) {
796
+            $seen = [];
797
+        }
798
+        if (array_key_exists($dn, $seen)) {
799
+            // avoid loops
800
+            return [];
801
+        }
802
+        $allGroups = [];
803
+        $seen[$dn] = true;
804
+        $filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
805
+        $groups = $this->access->fetchListOfGroups($filter,
806
+            [strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
807
+        if (is_array($groups)) {
808
+            $fetcher = function ($dn, &$seen) {
809
+                if (is_array($dn) && isset($dn['dn'][0])) {
810
+                    $dn = $dn['dn'][0];
811
+                }
812
+                return $this->getGroupsByMember($dn, $seen);
813
+            };
814
+            $allGroups = $this->walkNestedGroups($dn, $fetcher, $groups);
815
+        }
816
+        $visibleGroups = $this->filterValidGroups($allGroups);
817
+        return array_intersect_key($allGroups, $visibleGroups);
818
+    }
819
+
820
+    /**
821
+     * get a list of all users in a group
822
+     *
823
+     * @param string $gid
824
+     * @param string $search
825
+     * @param int $limit
826
+     * @param int $offset
827
+     * @return array with user ids
828
+     * @throws \Exception
829
+     */
830
+    public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
831
+        if (!$this->enabled) {
832
+            return [];
833
+        }
834
+        if (!$this->groupExists($gid)) {
835
+            return [];
836
+        }
837
+        $search = $this->access->escapeFilterPart($search, true);
838
+        $cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset;
839
+        // check for cache of the exact query
840
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
841
+        if (!is_null($groupUsers)) {
842
+            return $groupUsers;
843
+        }
844
+
845
+        // check for cache of the query without limit and offset
846
+        $groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search);
847
+        if (!is_null($groupUsers)) {
848
+            $groupUsers = array_slice($groupUsers, $offset, $limit);
849
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
850
+            return $groupUsers;
851
+        }
852
+
853
+        if ($limit === -1) {
854
+            $limit = null;
855
+        }
856
+        $groupDN = $this->access->groupname2dn($gid);
857
+        if (!$groupDN) {
858
+            // group couldn't be found, return empty resultset
859
+            $this->access->connection->writeToCache($cacheKey, []);
860
+            return [];
861
+        }
862
+
863
+        $primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
864
+        $posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
865
+        $members = $this->_groupMembers($groupDN);
866
+        if (!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
867
+            //in case users could not be retrieved, return empty result set
868
+            $this->access->connection->writeToCache($cacheKey, []);
869
+            return [];
870
+        }
871
+
872
+        $groupUsers = [];
873
+        $isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
874
+        $attrs = $this->access->userManager->getAttributes(true);
875
+        foreach ($members as $member) {
876
+            if ($isMemberUid) {
877
+                //we got uids, need to get their DNs to 'translate' them to user names
878
+                $filter = $this->access->combineFilterWithAnd([
879
+                    str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
880
+                    $this->access->combineFilterWithAnd([
881
+                        $this->access->getFilterPartForUserSearch($search),
882
+                        $this->access->connection->ldapUserFilter
883
+                    ])
884
+                ]);
885
+                $ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
886
+                if (count($ldap_users) < 1) {
887
+                    continue;
888
+                }
889
+                $groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
890
+            } else {
891
+                //we got DNs, check if we need to filter by search or we can give back all of them
892
+                $uid = $this->access->dn2username($member);
893
+                if (!$uid) {
894
+                    continue;
895
+                }
896
+
897
+                $cacheKey = 'userExistsOnLDAP' . $uid;
898
+                $userExists = $this->access->connection->getFromCache($cacheKey);
899
+                if ($userExists === false) {
900
+                    continue;
901
+                }
902
+                if ($userExists === null || $search !== '') {
903
+                    if (!$this->access->readAttribute($member,
904
+                        $this->access->connection->ldapUserDisplayName,
905
+                        $this->access->combineFilterWithAnd([
906
+                            $this->access->getFilterPartForUserSearch($search),
907
+                            $this->access->connection->ldapUserFilter
908
+                        ]))) {
909
+                        if ($search === '') {
910
+                            $this->access->connection->writeToCache($cacheKey, false);
911
+                        }
912
+                        continue;
913
+                    }
914
+                    $this->access->connection->writeToCache($cacheKey, true);
915
+                }
916
+                $groupUsers[] = $uid;
917
+            }
918
+        }
919
+
920
+        $groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
921
+        natsort($groupUsers);
922
+        $this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers);
923
+        $groupUsers = array_slice($groupUsers, $offset, $limit);
924
+
925
+        $this->access->connection->writeToCache($cacheKey, $groupUsers);
926
+
927
+        return $groupUsers;
928
+    }
929
+
930
+    /**
931
+     * returns the number of users in a group, who match the search term
932
+     *
933
+     * @param string $gid the internal group name
934
+     * @param string $search optional, a search string
935
+     * @return int|bool
936
+     */
937
+    public function countUsersInGroup($gid, $search = '') {
938
+        if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
939
+            return $this->groupPluginManager->countUsersInGroup($gid, $search);
940
+        }
941
+
942
+        $cacheKey = 'countUsersInGroup-' . $gid . '-' . $search;
943
+        if (!$this->enabled || !$this->groupExists($gid)) {
944
+            return false;
945
+        }
946
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
947
+        if (!is_null($groupUsers)) {
948
+            return $groupUsers;
949
+        }
950
+
951
+        $groupDN = $this->access->groupname2dn($gid);
952
+        if (!$groupDN) {
953
+            // group couldn't be found, return empty result set
954
+            $this->access->connection->writeToCache($cacheKey, false);
955
+            return false;
956
+        }
957
+
958
+        $members = $this->_groupMembers($groupDN);
959
+        $primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
960
+        if (!$members && $primaryUserCount === 0) {
961
+            //in case users could not be retrieved, return empty result set
962
+            $this->access->connection->writeToCache($cacheKey, false);
963
+            return false;
964
+        }
965
+
966
+        if ($search === '') {
967
+            $groupUsers = count($members) + $primaryUserCount;
968
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
969
+            return $groupUsers;
970
+        }
971
+        $search = $this->access->escapeFilterPart($search, true);
972
+        $isMemberUid =
973
+            (strtolower($this->access->connection->ldapGroupMemberAssocAttr)
974
+                === 'memberuid');
975
+
976
+        //we need to apply the search filter
977
+        //alternatives that need to be checked:
978
+        //a) get all users by search filter and array_intersect them
979
+        //b) a, but only when less than 1k 10k ?k users like it is
980
+        //c) put all DNs|uids in a LDAP filter, combine with the search string
981
+        //   and let it count.
982
+        //For now this is not important, because the only use of this method
983
+        //does not supply a search string
984
+        $groupUsers = [];
985
+        foreach ($members as $member) {
986
+            if ($isMemberUid) {
987
+                //we got uids, need to get their DNs to 'translate' them to user names
988
+                $filter = $this->access->combineFilterWithAnd([
989
+                    str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
990
+                    $this->access->getFilterPartForUserSearch($search)
991
+                ]);
992
+                $ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
993
+                if (count($ldap_users) < 1) {
994
+                    continue;
995
+                }
996
+                $groupUsers[] = $this->access->dn2username($ldap_users[0]);
997
+            } else {
998
+                //we need to apply the search filter now
999
+                if (!$this->access->readAttribute($member,
1000
+                    $this->access->connection->ldapUserDisplayName,
1001
+                    $this->access->getFilterPartForUserSearch($search))) {
1002
+                    continue;
1003
+                }
1004
+                // dn2username will also check if the users belong to the allowed base
1005
+                if ($ocname = $this->access->dn2username($member)) {
1006
+                    $groupUsers[] = $ocname;
1007
+                }
1008
+            }
1009
+        }
1010
+
1011
+        //and get users that have the group as primary
1012
+        $primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
1013
+
1014
+        return count($groupUsers) + $primaryUsers;
1015
+    }
1016
+
1017
+    /**
1018
+     * get a list of all groups
1019
+     *
1020
+     * @param string $search
1021
+     * @param $limit
1022
+     * @param int $offset
1023
+     * @return array with group names
1024
+     *
1025
+     * Returns a list with all groups (used by getGroups)
1026
+     */
1027
+    protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
1028
+        if (!$this->enabled) {
1029
+            return [];
1030
+        }
1031
+        $cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset;
1032
+
1033
+        //Check cache before driving unnecessary searches
1034
+        \OCP\Util::writeLog('user_ldap', 'getGroups ' . $cacheKey, ILogger::DEBUG);
1035
+        $ldap_groups = $this->access->connection->getFromCache($cacheKey);
1036
+        if (!is_null($ldap_groups)) {
1037
+            return $ldap_groups;
1038
+        }
1039
+
1040
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
1041
+        // error. With a limit of 0, we get 0 results. So we pass null.
1042
+        if ($limit <= 0) {
1043
+            $limit = null;
1044
+        }
1045
+        $filter = $this->access->combineFilterWithAnd([
1046
+            $this->access->connection->ldapGroupFilter,
1047
+            $this->access->getFilterPartForGroupSearch($search)
1048
+        ]);
1049
+        \OCP\Util::writeLog('user_ldap', 'getGroups Filter ' . $filter, ILogger::DEBUG);
1050
+        $ldap_groups = $this->access->fetchListOfGroups($filter,
1051
+            [$this->access->connection->ldapGroupDisplayName, 'dn'],
1052
+            $limit,
1053
+            $offset);
1054
+        $ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
1055
+
1056
+        $this->access->connection->writeToCache($cacheKey, $ldap_groups);
1057
+        return $ldap_groups;
1058
+    }
1059
+
1060
+    /**
1061
+     * get a list of all groups using a paged search
1062
+     *
1063
+     * @param string $search
1064
+     * @param int $limit
1065
+     * @param int $offset
1066
+     * @return array with group names
1067
+     *
1068
+     * Returns a list with all groups
1069
+     * Uses a paged search if available to override a
1070
+     * server side search limit.
1071
+     * (active directory has a limit of 1000 by default)
1072
+     */
1073
+    public function getGroups($search = '', $limit = -1, $offset = 0) {
1074
+        if (!$this->enabled) {
1075
+            return [];
1076
+        }
1077
+        $search = $this->access->escapeFilterPart($search, true);
1078
+        $pagingSize = (int)$this->access->connection->ldapPagingSize;
1079
+        if ($pagingSize <= 0) {
1080
+            return $this->getGroupsChunk($search, $limit, $offset);
1081
+        }
1082
+        $maxGroups = 100000; // limit max results (just for safety reasons)
1083
+        if ($limit > -1) {
1084
+            $overallLimit = min($limit + $offset, $maxGroups);
1085
+        } else {
1086
+            $overallLimit = $maxGroups;
1087
+        }
1088
+        $chunkOffset = $offset;
1089
+        $allGroups = [];
1090
+        while ($chunkOffset < $overallLimit) {
1091
+            $chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1092
+            $ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1093
+            $nread = count($ldapGroups);
1094
+            \OCP\Util::writeLog('user_ldap', 'getGroups(' . $search . '): read ' . $nread . ' at offset ' . $chunkOffset . ' (limit: ' . $chunkLimit . ')', ILogger::DEBUG);
1095
+            if ($nread) {
1096
+                $allGroups = array_merge($allGroups, $ldapGroups);
1097
+                $chunkOffset += $nread;
1098
+            }
1099
+            if ($nread < $chunkLimit) {
1100
+                break;
1101
+            }
1102
+        }
1103
+        return $allGroups;
1104
+    }
1105
+
1106
+    /**
1107
+     * @param string $group
1108
+     * @return bool
1109
+     */
1110
+    public function groupMatchesFilter($group) {
1111
+        return (strripos($group, $this->groupSearch) !== false);
1112
+    }
1113
+
1114
+    /**
1115
+     * check if a group exists
1116
+     *
1117
+     * @param string $gid
1118
+     * @return bool
1119
+     */
1120
+    public function groupExists($gid) {
1121
+        $groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
1122
+        if (!is_null($groupExists)) {
1123
+            return (bool)$groupExists;
1124
+        }
1125
+
1126
+        //getting dn, if false the group does not exist. If dn, it may be mapped
1127
+        //only, requires more checking.
1128
+        $dn = $this->access->groupname2dn($gid);
1129
+        if (!$dn) {
1130
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1131
+            return false;
1132
+        }
1133
+
1134
+        if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
1135
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1136
+            return false;
1137
+        }
1138
+
1139
+        //if group really still exists, we will be able to read its objectclass
1140
+        if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
1141
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1142
+            return false;
1143
+        }
1144
+
1145
+        $this->access->connection->writeToCache('groupExists' . $gid, true);
1146
+        return true;
1147
+    }
1148
+
1149
+    protected function filterValidGroups(array $listOfGroups): array {
1150
+        $validGroupDNs = [];
1151
+        foreach ($listOfGroups as $key => $item) {
1152
+            $dn = is_string($item) ? $item : $item['dn'][0];
1153
+            $gid = $this->access->dn2groupname($dn);
1154
+            if (!$gid) {
1155
+                continue;
1156
+            }
1157
+            if ($this->groupExists($gid)) {
1158
+                $validGroupDNs[$key] = $item;
1159
+            }
1160
+        }
1161
+        return $validGroupDNs;
1162
+    }
1163
+
1164
+    /**
1165
+     * Check if backend implements actions
1166
+     *
1167
+     * @param int $actions bitwise-or'ed actions
1168
+     * @return boolean
1169
+     *
1170
+     * Returns the supported actions as int to be
1171
+     * compared with GroupInterface::CREATE_GROUP etc.
1172
+     */
1173
+    public function implementsActions($actions) {
1174
+        return (bool)((GroupInterface::COUNT_USERS |
1175
+                $this->groupPluginManager->getImplementedActions()) & $actions);
1176
+    }
1177
+
1178
+    /**
1179
+     * Return access for LDAP interaction.
1180
+     *
1181
+     * @return Access instance of Access for LDAP interaction
1182
+     */
1183
+    public function getLDAPAccess($gid) {
1184
+        return $this->access;
1185
+    }
1186
+
1187
+    /**
1188
+     * create a group
1189
+     *
1190
+     * @param string $gid
1191
+     * @return bool
1192
+     * @throws \Exception
1193
+     */
1194
+    public function createGroup($gid) {
1195
+        if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1196
+            if ($dn = $this->groupPluginManager->createGroup($gid)) {
1197
+                //updates group mapping
1198
+                $uuid = $this->access->getUUID($dn, false);
1199
+                if (is_string($uuid)) {
1200
+                    $this->access->mapAndAnnounceIfApplicable(
1201
+                        $this->access->getGroupMapper(),
1202
+                        $dn,
1203
+                        $gid,
1204
+                        $uuid,
1205
+                        false
1206
+                    );
1207
+                    $this->access->cacheGroupExists($gid);
1208
+                }
1209
+            }
1210
+            return $dn != null;
1211
+        }
1212
+        throw new \Exception('Could not create group in LDAP backend.');
1213
+    }
1214
+
1215
+    /**
1216
+     * delete a group
1217
+     *
1218
+     * @param string $gid gid of the group to delete
1219
+     * @return bool
1220
+     * @throws \Exception
1221
+     */
1222
+    public function deleteGroup($gid) {
1223
+        if ($this->groupPluginManager->implementsActions(GroupInterface::DELETE_GROUP)) {
1224
+            if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1225
+                #delete group in nextcloud internal db
1226
+                $this->access->getGroupMapper()->unmap($gid);
1227
+                $this->access->connection->writeToCache("groupExists" . $gid, false);
1228
+            }
1229
+            return $ret;
1230
+        }
1231
+        throw new \Exception('Could not delete group in LDAP backend.');
1232
+    }
1233
+
1234
+    /**
1235
+     * Add a user to a group
1236
+     *
1237
+     * @param string $uid Name of the user to add to group
1238
+     * @param string $gid Name of the group in which add the user
1239
+     * @return bool
1240
+     * @throws \Exception
1241
+     */
1242
+    public function addToGroup($uid, $gid) {
1243
+        if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1244
+            if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1245
+                $this->access->connection->clearCache();
1246
+                unset($this->cachedGroupMembers[$gid]);
1247
+            }
1248
+            return $ret;
1249
+        }
1250
+        throw new \Exception('Could not add user to group in LDAP backend.');
1251
+    }
1252
+
1253
+    /**
1254
+     * Removes a user from a group
1255
+     *
1256
+     * @param string $uid Name of the user to remove from group
1257
+     * @param string $gid Name of the group from which remove the user
1258
+     * @return bool
1259
+     * @throws \Exception
1260
+     */
1261
+    public function removeFromGroup($uid, $gid) {
1262
+        if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1263
+            if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1264
+                $this->access->connection->clearCache();
1265
+                unset($this->cachedGroupMembers[$gid]);
1266
+            }
1267
+            return $ret;
1268
+        }
1269
+        throw new \Exception('Could not remove user from group in LDAP backend.');
1270
+    }
1271
+
1272
+    /**
1273
+     * Gets group details
1274
+     *
1275
+     * @param string $gid Name of the group
1276
+     * @return array | false
1277
+     * @throws \Exception
1278
+     */
1279
+    public function getGroupDetails($gid) {
1280
+        if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1281
+            return $this->groupPluginManager->getGroupDetails($gid);
1282
+        }
1283
+        throw new \Exception('Could not get group details in LDAP backend.');
1284
+    }
1285
+
1286
+    /**
1287
+     * Return LDAP connection resource from a cloned connection.
1288
+     * The cloned connection needs to be closed manually.
1289
+     * of the current access.
1290
+     *
1291
+     * @param string $gid
1292
+     * @return resource of the LDAP connection
1293
+     */
1294
+    public function getNewLDAPConnection($gid) {
1295
+        $connection = clone $this->access->getConnection();
1296
+        return $connection->getConnectionResource();
1297
+    }
1298
+
1299
+    /**
1300
+     * @throws \OC\ServerNotAvailableException
1301
+     */
1302
+    public function getDisplayName(string $gid): string {
1303
+        if ($this->groupPluginManager instanceof IGetDisplayNameBackend) {
1304
+            return $this->groupPluginManager->getDisplayName($gid);
1305
+        }
1306
+
1307
+        $cacheKey = 'group_getDisplayName' . $gid;
1308
+        if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
1309
+            return $displayName;
1310
+        }
1311
+
1312
+        $displayName = $this->access->readAttribute(
1313
+            $this->access->groupname2dn($gid),
1314
+            $this->access->connection->ldapGroupDisplayName);
1315
+
1316
+        if ($displayName && (count($displayName) > 0)) {
1317
+            $displayName = $displayName[0];
1318
+            $this->access->connection->writeToCache($cacheKey, $displayName);
1319
+            return $displayName;
1320
+        }
1321
+
1322
+        return '';
1323
+    }
1324 1324
 }
Please login to merge, or discard this patch.
Spacing   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -96,10 +96,10 @@  discard block
 block discarded – undo
96 96
 		if (!$this->enabled) {
97 97
 			return false;
98 98
 		}
99
-		$cacheKey = 'inGroup' . $uid . ':' . $gid;
99
+		$cacheKey = 'inGroup'.$uid.':'.$gid;
100 100
 		$inGroup = $this->access->connection->getFromCache($cacheKey);
101 101
 		if (!is_null($inGroup)) {
102
-			return (bool)$inGroup;
102
+			return (bool) $inGroup;
103 103
 		}
104 104
 
105 105
 		$userDN = $this->access->username2dn($uid);
@@ -109,7 +109,7 @@  discard block
 block discarded – undo
109 109
 			return $isInGroup;
110 110
 		}
111 111
 
112
-		$cacheKeyMembers = 'inGroup-members:' . $gid;
112
+		$cacheKeyMembers = 'inGroup-members:'.$gid;
113 113
 		$members = $this->access->connection->getFromCache($cacheKeyMembers);
114 114
 		if (!is_null($members)) {
115 115
 			$this->cachedGroupMembers[$gid] = $members;
@@ -206,8 +206,8 @@  discard block
 block discarded – undo
206 206
 					$dynamicMembers[$value['dn'][0]] = 1;
207 207
 				}
208 208
 			} else {
209
-				\OCP\Util::writeLog('user_ldap', 'No search filter found on member url ' .
210
-					'of group ' . $dnGroup, ILogger::DEBUG);
209
+				\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
210
+					'of group '.$dnGroup, ILogger::DEBUG);
211 211
 			}
212 212
 		}
213 213
 		return $dynamicMembers;
@@ -229,7 +229,7 @@  discard block
 block discarded – undo
229 229
 			return [];
230 230
 		}
231 231
 		// used extensively in cron job, caching makes sense for nested groups
232
-		$cacheKey = '_groupMembers' . $dnGroup;
232
+		$cacheKey = '_groupMembers'.$dnGroup;
233 233
 		$groupMembers = $this->access->connection->getFromCache($cacheKey);
234 234
 		if ($groupMembers !== null) {
235 235
 			return $groupMembers;
@@ -237,7 +237,7 @@  discard block
 block discarded – undo
237 237
 		$seen[$dnGroup] = 1;
238 238
 		$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
239 239
 		if (is_array($members)) {
240
-			$fetcher = function ($memberDN, &$seen) {
240
+			$fetcher = function($memberDN, &$seen) {
241 241
 				return $this->_groupMembers($memberDN, $seen);
242 242
 			};
243 243
 			$allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members);
@@ -261,7 +261,7 @@  discard block
 block discarded – undo
261 261
 			return [];
262 262
 		}
263 263
 
264
-		$fetcher = function ($groupDN) {
264
+		$fetcher = function($groupDN) {
265 265
 			if (isset($this->cachedNestedGroups[$groupDN])) {
266 266
 				$nestedGroups = $this->cachedNestedGroups[$groupDN];
267 267
 			} else {
@@ -285,7 +285,7 @@  discard block
 block discarded – undo
285 285
 	 * @return array
286 286
 	 */
287 287
 	private function walkNestedGroups(string $dn, \Closure $fetcher, array $list): array {
288
-		$nesting = (int)$this->access->connection->ldapNestedGroups;
288
+		$nesting = (int) $this->access->connection->ldapNestedGroups;
289 289
 		// depending on the input, we either have a list of DNs or a list of LDAP records
290 290
 		// also, the output expects either DNs or records. Testing the first element should suffice.
291 291
 		$recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]);
@@ -293,7 +293,7 @@  discard block
 block discarded – undo
293 293
 		if ($nesting !== 1) {
294 294
 			if ($recordMode) {
295 295
 				// the keys are numeric, but should hold the DN
296
-				return array_reduce($list, function ($transformed, $record) use ($dn) {
296
+				return array_reduce($list, function($transformed, $record) use ($dn) {
297 297
 					if ($record['dn'][0] != $dn) {
298 298
 						$transformed[$record['dn'][0]] = $record;
299 299
 					}
@@ -326,7 +326,7 @@  discard block
 block discarded – undo
326 326
 	 * @return string|bool
327 327
 	 */
328 328
 	public function gidNumber2Name($gid, $dn) {
329
-		$cacheKey = 'gidNumberToName' . $gid;
329
+		$cacheKey = 'gidNumberToName'.$gid;
330 330
 		$groupName = $this->access->connection->getFromCache($cacheKey);
331 331
 		if (!is_null($groupName) && isset($groupName)) {
332 332
 			return $groupName;
@@ -336,7 +336,7 @@  discard block
 block discarded – undo
336 336
 		$filter = $this->access->combineFilterWithAnd([
337 337
 			$this->access->connection->ldapGroupFilter,
338 338
 			'objectClass=posixGroup',
339
-			$this->access->connection->ldapGidNumber . '=' . $gid
339
+			$this->access->connection->ldapGidNumber.'='.$gid
340 340
 		]);
341 341
 		$result = $this->access->searchGroups($filter, ['dn'], 1);
342 342
 		if (empty($result)) {
@@ -415,7 +415,7 @@  discard block
 block discarded – undo
415 415
 		if ($search !== '') {
416 416
 			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
417 417
 		}
418
-		$filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID;
418
+		$filterParts[] = $this->access->connection->ldapGidNumber.'='.$groupID;
419 419
 
420 420
 		return $this->access->combineFilterWithAnd($filterParts);
421 421
 	}
@@ -457,7 +457,7 @@  discard block
 block discarded – undo
457 457
 		try {
458 458
 			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
459 459
 			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
460
-			return (int)$users;
460
+			return (int) $users;
461 461
 		} catch (\Exception $e) {
462 462
 			return 0;
463 463
 		}
@@ -503,7 +503,7 @@  discard block
 block discarded – undo
503 503
 		//we need to get the DN from LDAP
504 504
 		$filter = $this->access->combineFilterWithAnd([
505 505
 			$this->access->connection->ldapGroupFilter,
506
-			'objectsid=' . $domainObjectSid . '-' . $gid
506
+			'objectsid='.$domainObjectSid.'-'.$gid
507 507
 		]);
508 508
 		$result = $this->access->searchGroups($filter, ['dn'], 1);
509 509
 		if (empty($result)) {
@@ -582,7 +582,7 @@  discard block
 block discarded – undo
582 582
 		if ($search !== '') {
583 583
 			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
584 584
 		}
585
-		$filterParts[] = 'primaryGroupID=' . $groupID;
585
+		$filterParts[] = 'primaryGroupID='.$groupID;
586 586
 
587 587
 		return $this->access->combineFilterWithAnd($filterParts);
588 588
 	}
@@ -624,7 +624,7 @@  discard block
 block discarded – undo
624 624
 		try {
625 625
 			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
626 626
 			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
627
-			return (int)$users;
627
+			return (int) $users;
628 628
 		} catch (\Exception $e) {
629 629
 			return 0;
630 630
 		}
@@ -663,7 +663,7 @@  discard block
 block discarded – undo
663 663
 		if (!$this->enabled) {
664 664
 			return [];
665 665
 		}
666
-		$cacheKey = 'getUserGroups' . $uid;
666
+		$cacheKey = 'getUserGroups'.$uid;
667 667
 		$userGroups = $this->access->connection->getFromCache($cacheKey);
668 668
 		if (!is_null($userGroups)) {
669 669
 			return $userGroups;
@@ -708,8 +708,8 @@  discard block
 block discarded – undo
708 708
 						}
709 709
 					}
710 710
 				} else {
711
-					\OCP\Util::writeLog('user_ldap', 'No search filter found on member url ' .
712
-						'of group ' . print_r($dynamicGroup, true), ILogger::DEBUG);
711
+					\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
712
+						'of group '.print_r($dynamicGroup, true), ILogger::DEBUG);
713 713
 				}
714 714
 			}
715 715
 		}
@@ -717,8 +717,8 @@  discard block
 block discarded – undo
717 717
 		// if possible, read out membership via memberOf. It's far faster than
718 718
 		// performing a search, which still is a fallback later.
719 719
 		// memberof doesn't support memberuid, so skip it here.
720
-		if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
721
-			&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
720
+		if ((int) $this->access->connection->hasMemberOfFilterSupport === 1
721
+			&& (int) $this->access->connection->useMemberOfToDetectMembership === 1
722 722
 			&& strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
723 723
 		) {
724 724
 			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
@@ -751,7 +751,7 @@  discard block
 block discarded – undo
751 751
 		} elseif (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
752 752
 			$result = $this->access->readAttribute($userDN, 'uid');
753 753
 			if ($result === false) {
754
-				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on ' .
754
+				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN '.$userDN.' on '.
755 755
 					$this->access->connection->ldapHost, ILogger::DEBUG);
756 756
 				$uid = false;
757 757
 			} else {
@@ -801,11 +801,11 @@  discard block
 block discarded – undo
801 801
 		}
802 802
 		$allGroups = [];
803 803
 		$seen[$dn] = true;
804
-		$filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
804
+		$filter = $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn;
805 805
 		$groups = $this->access->fetchListOfGroups($filter,
806 806
 			[strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
807 807
 		if (is_array($groups)) {
808
-			$fetcher = function ($dn, &$seen) {
808
+			$fetcher = function($dn, &$seen) {
809 809
 				if (is_array($dn) && isset($dn['dn'][0])) {
810 810
 					$dn = $dn['dn'][0];
811 811
 				}
@@ -835,7 +835,7 @@  discard block
 block discarded – undo
835 835
 			return [];
836 836
 		}
837 837
 		$search = $this->access->escapeFilterPart($search, true);
838
-		$cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset;
838
+		$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
839 839
 		// check for cache of the exact query
840 840
 		$groupUsers = $this->access->connection->getFromCache($cacheKey);
841 841
 		if (!is_null($groupUsers)) {
@@ -843,7 +843,7 @@  discard block
 block discarded – undo
843 843
 		}
844 844
 
845 845
 		// check for cache of the query without limit and offset
846
-		$groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search);
846
+		$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
847 847
 		if (!is_null($groupUsers)) {
848 848
 			$groupUsers = array_slice($groupUsers, $offset, $limit);
849 849
 			$this->access->connection->writeToCache($cacheKey, $groupUsers);
@@ -894,7 +894,7 @@  discard block
 block discarded – undo
894 894
 					continue;
895 895
 				}
896 896
 
897
-				$cacheKey = 'userExistsOnLDAP' . $uid;
897
+				$cacheKey = 'userExistsOnLDAP'.$uid;
898 898
 				$userExists = $this->access->connection->getFromCache($cacheKey);
899 899
 				if ($userExists === false) {
900 900
 					continue;
@@ -919,7 +919,7 @@  discard block
 block discarded – undo
919 919
 
920 920
 		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
921 921
 		natsort($groupUsers);
922
-		$this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers);
922
+		$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
923 923
 		$groupUsers = array_slice($groupUsers, $offset, $limit);
924 924
 
925 925
 		$this->access->connection->writeToCache($cacheKey, $groupUsers);
@@ -939,7 +939,7 @@  discard block
 block discarded – undo
939 939
 			return $this->groupPluginManager->countUsersInGroup($gid, $search);
940 940
 		}
941 941
 
942
-		$cacheKey = 'countUsersInGroup-' . $gid . '-' . $search;
942
+		$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
943 943
 		if (!$this->enabled || !$this->groupExists($gid)) {
944 944
 			return false;
945 945
 		}
@@ -1028,10 +1028,10 @@  discard block
 block discarded – undo
1028 1028
 		if (!$this->enabled) {
1029 1029
 			return [];
1030 1030
 		}
1031
-		$cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset;
1031
+		$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
1032 1032
 
1033 1033
 		//Check cache before driving unnecessary searches
1034
-		\OCP\Util::writeLog('user_ldap', 'getGroups ' . $cacheKey, ILogger::DEBUG);
1034
+		\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, ILogger::DEBUG);
1035 1035
 		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
1036 1036
 		if (!is_null($ldap_groups)) {
1037 1037
 			return $ldap_groups;
@@ -1046,7 +1046,7 @@  discard block
 block discarded – undo
1046 1046
 			$this->access->connection->ldapGroupFilter,
1047 1047
 			$this->access->getFilterPartForGroupSearch($search)
1048 1048
 		]);
1049
-		\OCP\Util::writeLog('user_ldap', 'getGroups Filter ' . $filter, ILogger::DEBUG);
1049
+		\OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, ILogger::DEBUG);
1050 1050
 		$ldap_groups = $this->access->fetchListOfGroups($filter,
1051 1051
 			[$this->access->connection->ldapGroupDisplayName, 'dn'],
1052 1052
 			$limit,
@@ -1075,7 +1075,7 @@  discard block
 block discarded – undo
1075 1075
 			return [];
1076 1076
 		}
1077 1077
 		$search = $this->access->escapeFilterPart($search, true);
1078
-		$pagingSize = (int)$this->access->connection->ldapPagingSize;
1078
+		$pagingSize = (int) $this->access->connection->ldapPagingSize;
1079 1079
 		if ($pagingSize <= 0) {
1080 1080
 			return $this->getGroupsChunk($search, $limit, $offset);
1081 1081
 		}
@@ -1091,7 +1091,7 @@  discard block
 block discarded – undo
1091 1091
 			$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1092 1092
 			$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1093 1093
 			$nread = count($ldapGroups);
1094
-			\OCP\Util::writeLog('user_ldap', 'getGroups(' . $search . '): read ' . $nread . ' at offset ' . $chunkOffset . ' (limit: ' . $chunkLimit . ')', ILogger::DEBUG);
1094
+			\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', ILogger::DEBUG);
1095 1095
 			if ($nread) {
1096 1096
 				$allGroups = array_merge($allGroups, $ldapGroups);
1097 1097
 				$chunkOffset += $nread;
@@ -1118,31 +1118,31 @@  discard block
 block discarded – undo
1118 1118
 	 * @return bool
1119 1119
 	 */
1120 1120
 	public function groupExists($gid) {
1121
-		$groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
1121
+		$groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1122 1122
 		if (!is_null($groupExists)) {
1123
-			return (bool)$groupExists;
1123
+			return (bool) $groupExists;
1124 1124
 		}
1125 1125
 
1126 1126
 		//getting dn, if false the group does not exist. If dn, it may be mapped
1127 1127
 		//only, requires more checking.
1128 1128
 		$dn = $this->access->groupname2dn($gid);
1129 1129
 		if (!$dn) {
1130
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1130
+			$this->access->connection->writeToCache('groupExists'.$gid, false);
1131 1131
 			return false;
1132 1132
 		}
1133 1133
 
1134 1134
 		if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
1135
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1135
+			$this->access->connection->writeToCache('groupExists'.$gid, false);
1136 1136
 			return false;
1137 1137
 		}
1138 1138
 
1139 1139
 		//if group really still exists, we will be able to read its objectclass
1140 1140
 		if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
1141
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1141
+			$this->access->connection->writeToCache('groupExists'.$gid, false);
1142 1142
 			return false;
1143 1143
 		}
1144 1144
 
1145
-		$this->access->connection->writeToCache('groupExists' . $gid, true);
1145
+		$this->access->connection->writeToCache('groupExists'.$gid, true);
1146 1146
 		return true;
1147 1147
 	}
1148 1148
 
@@ -1171,7 +1171,7 @@  discard block
 block discarded – undo
1171 1171
 	 * compared with GroupInterface::CREATE_GROUP etc.
1172 1172
 	 */
1173 1173
 	public function implementsActions($actions) {
1174
-		return (bool)((GroupInterface::COUNT_USERS |
1174
+		return (bool) ((GroupInterface::COUNT_USERS |
1175 1175
 				$this->groupPluginManager->getImplementedActions()) & $actions);
1176 1176
 	}
1177 1177
 
@@ -1224,7 +1224,7 @@  discard block
 block discarded – undo
1224 1224
 			if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1225 1225
 				#delete group in nextcloud internal db
1226 1226
 				$this->access->getGroupMapper()->unmap($gid);
1227
-				$this->access->connection->writeToCache("groupExists" . $gid, false);
1227
+				$this->access->connection->writeToCache("groupExists".$gid, false);
1228 1228
 			}
1229 1229
 			return $ret;
1230 1230
 		}
@@ -1304,7 +1304,7 @@  discard block
 block discarded – undo
1304 1304
 			return $this->groupPluginManager->getDisplayName($gid);
1305 1305
 		}
1306 1306
 
1307
-		$cacheKey = 'group_getDisplayName' . $gid;
1307
+		$cacheKey = 'group_getDisplayName'.$gid;
1308 1308
 		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
1309 1309
 			return $displayName;
1310 1310
 		}
Please login to merge, or discard this patch.
apps/user_ldap/lib/Helper.php 2 patches
Indentation   +285 added lines, -285 removed lines patch added patch discarded remove patch
@@ -39,133 +39,133 @@  discard block
 block discarded – undo
39 39
 
40 40
 class Helper {
41 41
 
42
-	/** @var IConfig */
43
-	private $config;
44
-
45
-	/** @var CappedMemoryCache */
46
-	protected $sanitizeDnCache;
47
-
48
-	/**
49
-	 * Helper constructor.
50
-	 *
51
-	 * @param IConfig $config
52
-	 */
53
-	public function __construct(IConfig $config) {
54
-		$this->config = $config;
55
-		$this->sanitizeDnCache = new CappedMemoryCache(10000);
56
-	}
57
-
58
-	/**
59
-	 * returns prefixes for each saved LDAP/AD server configuration.
60
-	 *
61
-	 * @param bool $activeConfigurations optional, whether only active configuration shall be
62
-	 * retrieved, defaults to false
63
-	 * @return array with a list of the available prefixes
64
-	 *
65
-	 * Configuration prefixes are used to set up configurations for n LDAP or
66
-	 * AD servers. Since configuration is stored in the database, table
67
-	 * appconfig under appid user_ldap, the common identifiers in column
68
-	 * 'configkey' have a prefix. The prefix for the very first server
69
-	 * configuration is empty.
70
-	 * Configkey Examples:
71
-	 * Server 1: ldap_login_filter
72
-	 * Server 2: s1_ldap_login_filter
73
-	 * Server 3: s2_ldap_login_filter
74
-	 *
75
-	 * The prefix needs to be passed to the constructor of Connection class,
76
-	 * except the default (first) server shall be connected to.
77
-	 *
78
-	 */
79
-	public function getServerConfigurationPrefixes($activeConfigurations = false) {
80
-		$referenceConfigkey = 'ldap_configuration_active';
81
-
82
-		$keys = $this->getServersConfig($referenceConfigkey);
83
-
84
-		$prefixes = [];
85
-		foreach ($keys as $key) {
86
-			if ($activeConfigurations && $this->config->getAppValue('user_ldap', $key, '0') !== '1') {
87
-				continue;
88
-			}
89
-
90
-			$len = strlen($key) - strlen($referenceConfigkey);
91
-			$prefixes[] = substr($key, 0, $len);
92
-		}
93
-		asort($prefixes);
94
-
95
-		return $prefixes;
96
-	}
97
-
98
-	/**
99
-	 *
100
-	 * determines the host for every configured connection
101
-	 *
102
-	 * @return array an array with configprefix as keys
103
-	 *
104
-	 */
105
-	public function getServerConfigurationHosts() {
106
-		$referenceConfigkey = 'ldap_host';
107
-
108
-		$keys = $this->getServersConfig($referenceConfigkey);
109
-
110
-		$result = [];
111
-		foreach ($keys as $key) {
112
-			$len = strlen($key) - strlen($referenceConfigkey);
113
-			$prefix = substr($key, 0, $len);
114
-			$result[$prefix] = $this->config->getAppValue('user_ldap', $key);
115
-		}
116
-
117
-		return $result;
118
-	}
119
-
120
-	/**
121
-	 * return the next available configuration prefix
122
-	 *
123
-	 * @return string
124
-	 */
125
-	public function getNextServerConfigurationPrefix() {
126
-		$serverConnections = $this->getServerConfigurationPrefixes();
127
-
128
-		if (count($serverConnections) === 0) {
129
-			return 's01';
130
-		}
131
-
132
-		sort($serverConnections);
133
-		$lastKey = array_pop($serverConnections);
134
-		$lastNumber = (int)str_replace('s', '', $lastKey);
135
-		return 's' . str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT);
136
-	}
137
-
138
-	private function getServersConfig($value) {
139
-		$regex = '/' . $value . '$/S';
140
-
141
-		$keys = $this->config->getAppKeys('user_ldap');
142
-		$result = [];
143
-		foreach ($keys as $key) {
144
-			if (preg_match($regex, $key) === 1) {
145
-				$result[] = $key;
146
-			}
147
-		}
148
-
149
-		return $result;
150
-	}
151
-
152
-	/**
153
-	 * deletes a given saved LDAP/AD server configuration.
154
-	 *
155
-	 * @param string $prefix the configuration prefix of the config to delete
156
-	 * @return bool true on success, false otherwise
157
-	 */
158
-	public function deleteServerConfiguration($prefix) {
159
-		if (!in_array($prefix, self::getServerConfigurationPrefixes())) {
160
-			return false;
161
-		}
162
-
163
-		$saveOtherConfigurations = '';
164
-		if (empty($prefix)) {
165
-			$saveOtherConfigurations = 'AND `configkey` NOT LIKE \'s%\'';
166
-		}
167
-
168
-		$query = \OC_DB::prepare('
42
+    /** @var IConfig */
43
+    private $config;
44
+
45
+    /** @var CappedMemoryCache */
46
+    protected $sanitizeDnCache;
47
+
48
+    /**
49
+     * Helper constructor.
50
+     *
51
+     * @param IConfig $config
52
+     */
53
+    public function __construct(IConfig $config) {
54
+        $this->config = $config;
55
+        $this->sanitizeDnCache = new CappedMemoryCache(10000);
56
+    }
57
+
58
+    /**
59
+     * returns prefixes for each saved LDAP/AD server configuration.
60
+     *
61
+     * @param bool $activeConfigurations optional, whether only active configuration shall be
62
+     * retrieved, defaults to false
63
+     * @return array with a list of the available prefixes
64
+     *
65
+     * Configuration prefixes are used to set up configurations for n LDAP or
66
+     * AD servers. Since configuration is stored in the database, table
67
+     * appconfig under appid user_ldap, the common identifiers in column
68
+     * 'configkey' have a prefix. The prefix for the very first server
69
+     * configuration is empty.
70
+     * Configkey Examples:
71
+     * Server 1: ldap_login_filter
72
+     * Server 2: s1_ldap_login_filter
73
+     * Server 3: s2_ldap_login_filter
74
+     *
75
+     * The prefix needs to be passed to the constructor of Connection class,
76
+     * except the default (first) server shall be connected to.
77
+     *
78
+     */
79
+    public function getServerConfigurationPrefixes($activeConfigurations = false) {
80
+        $referenceConfigkey = 'ldap_configuration_active';
81
+
82
+        $keys = $this->getServersConfig($referenceConfigkey);
83
+
84
+        $prefixes = [];
85
+        foreach ($keys as $key) {
86
+            if ($activeConfigurations && $this->config->getAppValue('user_ldap', $key, '0') !== '1') {
87
+                continue;
88
+            }
89
+
90
+            $len = strlen($key) - strlen($referenceConfigkey);
91
+            $prefixes[] = substr($key, 0, $len);
92
+        }
93
+        asort($prefixes);
94
+
95
+        return $prefixes;
96
+    }
97
+
98
+    /**
99
+     *
100
+     * determines the host for every configured connection
101
+     *
102
+     * @return array an array with configprefix as keys
103
+     *
104
+     */
105
+    public function getServerConfigurationHosts() {
106
+        $referenceConfigkey = 'ldap_host';
107
+
108
+        $keys = $this->getServersConfig($referenceConfigkey);
109
+
110
+        $result = [];
111
+        foreach ($keys as $key) {
112
+            $len = strlen($key) - strlen($referenceConfigkey);
113
+            $prefix = substr($key, 0, $len);
114
+            $result[$prefix] = $this->config->getAppValue('user_ldap', $key);
115
+        }
116
+
117
+        return $result;
118
+    }
119
+
120
+    /**
121
+     * return the next available configuration prefix
122
+     *
123
+     * @return string
124
+     */
125
+    public function getNextServerConfigurationPrefix() {
126
+        $serverConnections = $this->getServerConfigurationPrefixes();
127
+
128
+        if (count($serverConnections) === 0) {
129
+            return 's01';
130
+        }
131
+
132
+        sort($serverConnections);
133
+        $lastKey = array_pop($serverConnections);
134
+        $lastNumber = (int)str_replace('s', '', $lastKey);
135
+        return 's' . str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT);
136
+    }
137
+
138
+    private function getServersConfig($value) {
139
+        $regex = '/' . $value . '$/S';
140
+
141
+        $keys = $this->config->getAppKeys('user_ldap');
142
+        $result = [];
143
+        foreach ($keys as $key) {
144
+            if (preg_match($regex, $key) === 1) {
145
+                $result[] = $key;
146
+            }
147
+        }
148
+
149
+        return $result;
150
+    }
151
+
152
+    /**
153
+     * deletes a given saved LDAP/AD server configuration.
154
+     *
155
+     * @param string $prefix the configuration prefix of the config to delete
156
+     * @return bool true on success, false otherwise
157
+     */
158
+    public function deleteServerConfiguration($prefix) {
159
+        if (!in_array($prefix, self::getServerConfigurationPrefixes())) {
160
+            return false;
161
+        }
162
+
163
+        $saveOtherConfigurations = '';
164
+        if (empty($prefix)) {
165
+            $saveOtherConfigurations = 'AND `configkey` NOT LIKE \'s%\'';
166
+        }
167
+
168
+        $query = \OC_DB::prepare('
169 169
 			DELETE
170 170
 			FROM `*PREFIX*appconfig`
171 171
 			WHERE `configkey` LIKE ?
@@ -173,162 +173,162 @@  discard block
 block discarded – undo
173 173
 				AND `appid` = \'user_ldap\'
174 174
 				AND `configkey` NOT IN (\'enabled\', \'installed_version\', \'types\', \'bgjUpdateGroupsLastRun\')
175 175
 		');
176
-		$delRows = $query->execute([$prefix . '%']);
177
-
178
-		if ($delRows === null) {
179
-			return false;
180
-		}
181
-
182
-		if ($delRows === 0) {
183
-			return false;
184
-		}
185
-
186
-		return true;
187
-	}
188
-
189
-	/**
190
-	 * checks whether there is one or more disabled LDAP configurations
191
-	 *
192
-	 * @return bool
193
-	 * @throws \Exception
194
-	 */
195
-	public function haveDisabledConfigurations() {
196
-		$all = $this->getServerConfigurationPrefixes(false);
197
-		$active = $this->getServerConfigurationPrefixes(true);
198
-
199
-		if (!is_array($all) || !is_array($active)) {
200
-			throw new \Exception('Unexpected Return Value');
201
-		}
202
-
203
-		return count($all) !== count($active) || count($all) === 0;
204
-	}
205
-
206
-	/**
207
-	 * extracts the domain from a given URL
208
-	 *
209
-	 * @param string $url the URL
210
-	 * @return string|false domain as string on success, false otherwise
211
-	 */
212
-	public function getDomainFromURL($url) {
213
-		$uinfo = parse_url($url);
214
-		if (!is_array($uinfo)) {
215
-			return false;
216
-		}
217
-
218
-		$domain = false;
219
-		if (isset($uinfo['host'])) {
220
-			$domain = $uinfo['host'];
221
-		} elseif (isset($uinfo['path'])) {
222
-			$domain = $uinfo['path'];
223
-		}
224
-
225
-		return $domain;
226
-	}
227
-
228
-	/**
229
-	 *
230
-	 * Set the LDAPProvider in the config
231
-	 *
232
-	 */
233
-	public function setLDAPProvider() {
234
-		$current = \OC::$server->getConfig()->getSystemValue('ldapProviderFactory', null);
235
-		if (is_null($current)) {
236
-			\OC::$server->getConfig()->setSystemValue('ldapProviderFactory', LDAPProviderFactory::class);
237
-		}
238
-	}
239
-
240
-	/**
241
-	 * sanitizes a DN received from the LDAP server
242
-	 *
243
-	 * @param array $dn the DN in question
244
-	 * @return array|string the sanitized DN
245
-	 */
246
-	public function sanitizeDN($dn) {
247
-		//treating multiple base DNs
248
-		if (is_array($dn)) {
249
-			$result = [];
250
-			foreach ($dn as $singleDN) {
251
-				$result[] = $this->sanitizeDN($singleDN);
252
-			}
253
-			return $result;
254
-		}
255
-
256
-		if (!is_string($dn)) {
257
-			throw new \LogicException('String expected ' . \gettype($dn) . ' given');
258
-		}
259
-
260
-		if (($sanitizedDn = $this->sanitizeDnCache->get($dn)) !== null) {
261
-			return $sanitizedDn;
262
-		}
263
-
264
-		//OID sometimes gives back DNs with whitespace after the comma
265
-		// a la "uid=foo, cn=bar, dn=..." We need to tackle this!
266
-		$sanitizedDn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
267
-
268
-		//make comparisons and everything work
269
-		$sanitizedDn = mb_strtolower($sanitizedDn, 'UTF-8');
270
-
271
-		//escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
272
-		//to use the DN in search filters, \ needs to be escaped to \5c additionally
273
-		//to use them in bases, we convert them back to simple backslashes in readAttribute()
274
-		$replacements = [
275
-			'\,' => '\5c2C',
276
-			'\=' => '\5c3D',
277
-			'\+' => '\5c2B',
278
-			'\<' => '\5c3C',
279
-			'\>' => '\5c3E',
280
-			'\;' => '\5c3B',
281
-			'\"' => '\5c22',
282
-			'\#' => '\5c23',
283
-			'(' => '\28',
284
-			')' => '\29',
285
-			'*' => '\2A',
286
-		];
287
-		$sanitizedDn = str_replace(array_keys($replacements), array_values($replacements), $sanitizedDn);
288
-		$this->sanitizeDnCache->set($dn, $sanitizedDn);
289
-
290
-		return $sanitizedDn;
291
-	}
292
-
293
-	/**
294
-	 * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
295
-	 *
296
-	 * @param string $dn the DN
297
-	 * @return string
298
-	 */
299
-	public function DNasBaseParameter($dn) {
300
-		return str_ireplace('\\5c', '\\', $dn);
301
-	}
302
-
303
-	/**
304
-	 * listens to a hook thrown by server2server sharing and replaces the given
305
-	 * login name by a username, if it matches an LDAP user.
306
-	 *
307
-	 * @param array $param
308
-	 * @throws \Exception
309
-	 */
310
-	public static function loginName2UserName($param) {
311
-		if (!isset($param['uid'])) {
312
-			throw new \Exception('key uid is expected to be set in $param');
313
-		}
314
-
315
-		//ain't it ironic?
316
-		$helper = new Helper(\OC::$server->getConfig());
317
-
318
-		$configPrefixes = $helper->getServerConfigurationPrefixes(true);
319
-		$ldapWrapper = new LDAP();
320
-		$ocConfig = \OC::$server->getConfig();
321
-		$notificationManager = \OC::$server->getNotificationManager();
322
-
323
-		$userSession = \OC::$server->getUserSession();
324
-		$userPluginManager = \OC::$server->query('LDAPUserPluginManager');
325
-
326
-		$userBackend = new User_Proxy(
327
-			$configPrefixes, $ldapWrapper, $ocConfig, $notificationManager, $userSession, $userPluginManager
328
-		);
329
-		$uid = $userBackend->loginName2UserName($param['uid']);
330
-		if ($uid !== false) {
331
-			$param['uid'] = $uid;
332
-		}
333
-	}
176
+        $delRows = $query->execute([$prefix . '%']);
177
+
178
+        if ($delRows === null) {
179
+            return false;
180
+        }
181
+
182
+        if ($delRows === 0) {
183
+            return false;
184
+        }
185
+
186
+        return true;
187
+    }
188
+
189
+    /**
190
+     * checks whether there is one or more disabled LDAP configurations
191
+     *
192
+     * @return bool
193
+     * @throws \Exception
194
+     */
195
+    public function haveDisabledConfigurations() {
196
+        $all = $this->getServerConfigurationPrefixes(false);
197
+        $active = $this->getServerConfigurationPrefixes(true);
198
+
199
+        if (!is_array($all) || !is_array($active)) {
200
+            throw new \Exception('Unexpected Return Value');
201
+        }
202
+
203
+        return count($all) !== count($active) || count($all) === 0;
204
+    }
205
+
206
+    /**
207
+     * extracts the domain from a given URL
208
+     *
209
+     * @param string $url the URL
210
+     * @return string|false domain as string on success, false otherwise
211
+     */
212
+    public function getDomainFromURL($url) {
213
+        $uinfo = parse_url($url);
214
+        if (!is_array($uinfo)) {
215
+            return false;
216
+        }
217
+
218
+        $domain = false;
219
+        if (isset($uinfo['host'])) {
220
+            $domain = $uinfo['host'];
221
+        } elseif (isset($uinfo['path'])) {
222
+            $domain = $uinfo['path'];
223
+        }
224
+
225
+        return $domain;
226
+    }
227
+
228
+    /**
229
+     *
230
+     * Set the LDAPProvider in the config
231
+     *
232
+     */
233
+    public function setLDAPProvider() {
234
+        $current = \OC::$server->getConfig()->getSystemValue('ldapProviderFactory', null);
235
+        if (is_null($current)) {
236
+            \OC::$server->getConfig()->setSystemValue('ldapProviderFactory', LDAPProviderFactory::class);
237
+        }
238
+    }
239
+
240
+    /**
241
+     * sanitizes a DN received from the LDAP server
242
+     *
243
+     * @param array $dn the DN in question
244
+     * @return array|string the sanitized DN
245
+     */
246
+    public function sanitizeDN($dn) {
247
+        //treating multiple base DNs
248
+        if (is_array($dn)) {
249
+            $result = [];
250
+            foreach ($dn as $singleDN) {
251
+                $result[] = $this->sanitizeDN($singleDN);
252
+            }
253
+            return $result;
254
+        }
255
+
256
+        if (!is_string($dn)) {
257
+            throw new \LogicException('String expected ' . \gettype($dn) . ' given');
258
+        }
259
+
260
+        if (($sanitizedDn = $this->sanitizeDnCache->get($dn)) !== null) {
261
+            return $sanitizedDn;
262
+        }
263
+
264
+        //OID sometimes gives back DNs with whitespace after the comma
265
+        // a la "uid=foo, cn=bar, dn=..." We need to tackle this!
266
+        $sanitizedDn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
267
+
268
+        //make comparisons and everything work
269
+        $sanitizedDn = mb_strtolower($sanitizedDn, 'UTF-8');
270
+
271
+        //escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
272
+        //to use the DN in search filters, \ needs to be escaped to \5c additionally
273
+        //to use them in bases, we convert them back to simple backslashes in readAttribute()
274
+        $replacements = [
275
+            '\,' => '\5c2C',
276
+            '\=' => '\5c3D',
277
+            '\+' => '\5c2B',
278
+            '\<' => '\5c3C',
279
+            '\>' => '\5c3E',
280
+            '\;' => '\5c3B',
281
+            '\"' => '\5c22',
282
+            '\#' => '\5c23',
283
+            '(' => '\28',
284
+            ')' => '\29',
285
+            '*' => '\2A',
286
+        ];
287
+        $sanitizedDn = str_replace(array_keys($replacements), array_values($replacements), $sanitizedDn);
288
+        $this->sanitizeDnCache->set($dn, $sanitizedDn);
289
+
290
+        return $sanitizedDn;
291
+    }
292
+
293
+    /**
294
+     * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
295
+     *
296
+     * @param string $dn the DN
297
+     * @return string
298
+     */
299
+    public function DNasBaseParameter($dn) {
300
+        return str_ireplace('\\5c', '\\', $dn);
301
+    }
302
+
303
+    /**
304
+     * listens to a hook thrown by server2server sharing and replaces the given
305
+     * login name by a username, if it matches an LDAP user.
306
+     *
307
+     * @param array $param
308
+     * @throws \Exception
309
+     */
310
+    public static function loginName2UserName($param) {
311
+        if (!isset($param['uid'])) {
312
+            throw new \Exception('key uid is expected to be set in $param');
313
+        }
314
+
315
+        //ain't it ironic?
316
+        $helper = new Helper(\OC::$server->getConfig());
317
+
318
+        $configPrefixes = $helper->getServerConfigurationPrefixes(true);
319
+        $ldapWrapper = new LDAP();
320
+        $ocConfig = \OC::$server->getConfig();
321
+        $notificationManager = \OC::$server->getNotificationManager();
322
+
323
+        $userSession = \OC::$server->getUserSession();
324
+        $userPluginManager = \OC::$server->query('LDAPUserPluginManager');
325
+
326
+        $userBackend = new User_Proxy(
327
+            $configPrefixes, $ldapWrapper, $ocConfig, $notificationManager, $userSession, $userPluginManager
328
+        );
329
+        $uid = $userBackend->loginName2UserName($param['uid']);
330
+        if ($uid !== false) {
331
+            $param['uid'] = $uid;
332
+        }
333
+    }
334 334
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -131,12 +131,12 @@  discard block
 block discarded – undo
131 131
 
132 132
 		sort($serverConnections);
133 133
 		$lastKey = array_pop($serverConnections);
134
-		$lastNumber = (int)str_replace('s', '', $lastKey);
135
-		return 's' . str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT);
134
+		$lastNumber = (int) str_replace('s', '', $lastKey);
135
+		return 's'.str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT);
136 136
 	}
137 137
 
138 138
 	private function getServersConfig($value) {
139
-		$regex = '/' . $value . '$/S';
139
+		$regex = '/'.$value.'$/S';
140 140
 
141 141
 		$keys = $this->config->getAppKeys('user_ldap');
142 142
 		$result = [];
@@ -169,11 +169,11 @@  discard block
 block discarded – undo
169 169
 			DELETE
170 170
 			FROM `*PREFIX*appconfig`
171 171
 			WHERE `configkey` LIKE ?
172
-				' . $saveOtherConfigurations . '
172
+				' . $saveOtherConfigurations.'
173 173
 				AND `appid` = \'user_ldap\'
174 174
 				AND `configkey` NOT IN (\'enabled\', \'installed_version\', \'types\', \'bgjUpdateGroupsLastRun\')
175 175
 		');
176
-		$delRows = $query->execute([$prefix . '%']);
176
+		$delRows = $query->execute([$prefix.'%']);
177 177
 
178 178
 		if ($delRows === null) {
179 179
 			return false;
@@ -254,7 +254,7 @@  discard block
 block discarded – undo
254 254
 		}
255 255
 
256 256
 		if (!is_string($dn)) {
257
-			throw new \LogicException('String expected ' . \gettype($dn) . ' given');
257
+			throw new \LogicException('String expected '.\gettype($dn).' given');
258 258
 		}
259 259
 
260 260
 		if (($sanitizedDn = $this->sanitizeDnCache->get($dn)) !== null) {
Please login to merge, or discard this patch.
apps/user_ldap/lib/Mapping/UserMapping.php 2 patches
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -29,12 +29,12 @@
 block discarded – undo
29 29
  */
30 30
 class UserMapping extends AbstractMapping {
31 31
 
32
-	/**
33
-	 * returns the DB table name which holds the mappings
34
-	 * @return string
35
-	 */
36
-	protected function getTableName(bool $includePrefix = true) {
37
-		$p = $includePrefix ? '*PREFIX*' : '';
38
-		return $p . 'ldap_user_mapping';
39
-	}
32
+    /**
33
+     * returns the DB table name which holds the mappings
34
+     * @return string
35
+     */
36
+    protected function getTableName(bool $includePrefix = true) {
37
+        $p = $includePrefix ? '*PREFIX*' : '';
38
+        return $p . 'ldap_user_mapping';
39
+    }
40 40
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -35,6 +35,6 @@
 block discarded – undo
35 35
 	 */
36 36
 	protected function getTableName(bool $includePrefix = true) {
37 37
 		$p = $includePrefix ? '*PREFIX*' : '';
38
-		return $p . 'ldap_user_mapping';
38
+		return $p.'ldap_user_mapping';
39 39
 	}
40 40
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Mapping/AbstractMapping.php 2 patches
Indentation   +320 added lines, -320 removed lines patch added patch discarded remove patch
@@ -34,352 +34,352 @@
 block discarded – undo
34 34
  * @package OCA\User_LDAP\Mapping
35 35
  */
36 36
 abstract class AbstractMapping {
37
-	/**
38
-	 * @var \OCP\IDBConnection $dbc
39
-	 */
40
-	protected $dbc;
41
-
42
-	/**
43
-	 * returns the DB table name which holds the mappings
44
-	 *
45
-	 * @return string
46
-	 */
47
-	abstract protected function getTableName(bool $includePrefix = true);
48
-
49
-	/**
50
-	 * @param \OCP\IDBConnection $dbc
51
-	 */
52
-	public function __construct(\OCP\IDBConnection $dbc) {
53
-		$this->dbc = $dbc;
54
-	}
55
-
56
-	/** @var array caches Names (value) by DN (key) */
57
-	protected $cache = [];
58
-
59
-	/**
60
-	 * checks whether a provided string represents an existing table col
61
-	 *
62
-	 * @param string $col
63
-	 * @return bool
64
-	 */
65
-	public function isColNameValid($col) {
66
-		switch ($col) {
67
-			case 'ldap_dn':
68
-			case 'owncloud_name':
69
-			case 'directory_uuid':
70
-				return true;
71
-			default:
72
-				return false;
73
-		}
74
-	}
75
-
76
-	/**
77
-	 * Gets the value of one column based on a provided value of another column
78
-	 *
79
-	 * @param string $fetchCol
80
-	 * @param string $compareCol
81
-	 * @param string $search
82
-	 * @return string|false
83
-	 * @throws \Exception
84
-	 */
85
-	protected function getXbyY($fetchCol, $compareCol, $search) {
86
-		if (!$this->isColNameValid($fetchCol)) {
87
-			//this is used internally only, but we don't want to risk
88
-			//having SQL injection at all.
89
-			throw new \Exception('Invalid Column Name');
90
-		}
91
-		$query = $this->dbc->prepare('
37
+    /**
38
+     * @var \OCP\IDBConnection $dbc
39
+     */
40
+    protected $dbc;
41
+
42
+    /**
43
+     * returns the DB table name which holds the mappings
44
+     *
45
+     * @return string
46
+     */
47
+    abstract protected function getTableName(bool $includePrefix = true);
48
+
49
+    /**
50
+     * @param \OCP\IDBConnection $dbc
51
+     */
52
+    public function __construct(\OCP\IDBConnection $dbc) {
53
+        $this->dbc = $dbc;
54
+    }
55
+
56
+    /** @var array caches Names (value) by DN (key) */
57
+    protected $cache = [];
58
+
59
+    /**
60
+     * checks whether a provided string represents an existing table col
61
+     *
62
+     * @param string $col
63
+     * @return bool
64
+     */
65
+    public function isColNameValid($col) {
66
+        switch ($col) {
67
+            case 'ldap_dn':
68
+            case 'owncloud_name':
69
+            case 'directory_uuid':
70
+                return true;
71
+            default:
72
+                return false;
73
+        }
74
+    }
75
+
76
+    /**
77
+     * Gets the value of one column based on a provided value of another column
78
+     *
79
+     * @param string $fetchCol
80
+     * @param string $compareCol
81
+     * @param string $search
82
+     * @return string|false
83
+     * @throws \Exception
84
+     */
85
+    protected function getXbyY($fetchCol, $compareCol, $search) {
86
+        if (!$this->isColNameValid($fetchCol)) {
87
+            //this is used internally only, but we don't want to risk
88
+            //having SQL injection at all.
89
+            throw new \Exception('Invalid Column Name');
90
+        }
91
+        $query = $this->dbc->prepare('
92 92
 			SELECT `' . $fetchCol . '`
93 93
 			FROM `' . $this->getTableName() . '`
94 94
 			WHERE `' . $compareCol . '` = ?
95 95
 		');
96 96
 
97
-		$res = $query->execute([$search]);
98
-		if ($res !== false) {
99
-			return $query->fetchColumn();
100
-		}
101
-
102
-		return false;
103
-	}
104
-
105
-	/**
106
-	 * Performs a DELETE or UPDATE query to the database.
107
-	 *
108
-	 * @param \Doctrine\DBAL\Driver\Statement $query
109
-	 * @param array $parameters
110
-	 * @return bool true if at least one row was modified, false otherwise
111
-	 */
112
-	protected function modify($query, $parameters) {
113
-		$result = $query->execute($parameters);
114
-		return ($result === true && $query->rowCount() > 0);
115
-	}
116
-
117
-	/**
118
-	 * Gets the LDAP DN based on the provided name.
119
-	 * Replaces Access::ocname2dn
120
-	 *
121
-	 * @param string $name
122
-	 * @return string|false
123
-	 */
124
-	public function getDNByName($name) {
125
-		$dn = array_search($name, $this->cache);
126
-		if ($dn === false) {
127
-			$dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name);
128
-			$this->cache[$dn] = $name;
129
-		}
130
-		return $dn;
131
-	}
132
-
133
-	/**
134
-	 * Updates the DN based on the given UUID
135
-	 *
136
-	 * @param string $fdn
137
-	 * @param string $uuid
138
-	 * @return bool
139
-	 */
140
-	public function setDNbyUUID($fdn, $uuid) {
141
-		$oldDn = $this->getDnByUUID($uuid);
142
-		$query = $this->dbc->prepare('
97
+        $res = $query->execute([$search]);
98
+        if ($res !== false) {
99
+            return $query->fetchColumn();
100
+        }
101
+
102
+        return false;
103
+    }
104
+
105
+    /**
106
+     * Performs a DELETE or UPDATE query to the database.
107
+     *
108
+     * @param \Doctrine\DBAL\Driver\Statement $query
109
+     * @param array $parameters
110
+     * @return bool true if at least one row was modified, false otherwise
111
+     */
112
+    protected function modify($query, $parameters) {
113
+        $result = $query->execute($parameters);
114
+        return ($result === true && $query->rowCount() > 0);
115
+    }
116
+
117
+    /**
118
+     * Gets the LDAP DN based on the provided name.
119
+     * Replaces Access::ocname2dn
120
+     *
121
+     * @param string $name
122
+     * @return string|false
123
+     */
124
+    public function getDNByName($name) {
125
+        $dn = array_search($name, $this->cache);
126
+        if ($dn === false) {
127
+            $dn = $this->getXbyY('ldap_dn', 'owncloud_name', $name);
128
+            $this->cache[$dn] = $name;
129
+        }
130
+        return $dn;
131
+    }
132
+
133
+    /**
134
+     * Updates the DN based on the given UUID
135
+     *
136
+     * @param string $fdn
137
+     * @param string $uuid
138
+     * @return bool
139
+     */
140
+    public function setDNbyUUID($fdn, $uuid) {
141
+        $oldDn = $this->getDnByUUID($uuid);
142
+        $query = $this->dbc->prepare('
143 143
 			UPDATE `' . $this->getTableName() . '`
144 144
 			SET `ldap_dn` = ?
145 145
 			WHERE `directory_uuid` = ?
146 146
 		');
147 147
 
148
-		$r = $this->modify($query, [$fdn, $uuid]);
149
-
150
-		if ($r && is_string($oldDn) && isset($this->cache[$oldDn])) {
151
-			$this->cache[$fdn] = $this->cache[$oldDn];
152
-			unset($this->cache[$oldDn]);
153
-		}
154
-
155
-		return $r;
156
-	}
157
-
158
-	/**
159
-	 * Updates the UUID based on the given DN
160
-	 *
161
-	 * required by Migration/UUIDFix
162
-	 *
163
-	 * @param $uuid
164
-	 * @param $fdn
165
-	 * @return bool
166
-	 */
167
-	public function setUUIDbyDN($uuid, $fdn) {
168
-		$query = $this->dbc->prepare('
148
+        $r = $this->modify($query, [$fdn, $uuid]);
149
+
150
+        if ($r && is_string($oldDn) && isset($this->cache[$oldDn])) {
151
+            $this->cache[$fdn] = $this->cache[$oldDn];
152
+            unset($this->cache[$oldDn]);
153
+        }
154
+
155
+        return $r;
156
+    }
157
+
158
+    /**
159
+     * Updates the UUID based on the given DN
160
+     *
161
+     * required by Migration/UUIDFix
162
+     *
163
+     * @param $uuid
164
+     * @param $fdn
165
+     * @return bool
166
+     */
167
+    public function setUUIDbyDN($uuid, $fdn) {
168
+        $query = $this->dbc->prepare('
169 169
 			UPDATE `' . $this->getTableName() . '`
170 170
 			SET `directory_uuid` = ?
171 171
 			WHERE `ldap_dn` = ?
172 172
 		');
173 173
 
174
-		unset($this->cache[$fdn]);
175
-
176
-		return $this->modify($query, [$uuid, $fdn]);
177
-	}
178
-
179
-	/**
180
-	 * Gets the name based on the provided LDAP DN.
181
-	 *
182
-	 * @param string $fdn
183
-	 * @return string|false
184
-	 */
185
-	public function getNameByDN($fdn) {
186
-		if (!isset($this->cache[$fdn])) {
187
-			$this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn', $fdn);
188
-		}
189
-		return $this->cache[$fdn];
190
-	}
191
-
192
-	public function getListOfIdsByDn(array $fdns): array {
193
-		$qb = $this->dbc->getQueryBuilder();
194
-		$qb->select('owncloud_name', 'ldap_dn')
195
-			->from($this->getTableName(false))
196
-			->where($qb->expr()->in('ldap_dn', $qb->createNamedParameter($fdns, QueryBuilder::PARAM_STR_ARRAY)));
197
-		$stmt = $qb->execute();
198
-
199
-		$results = $stmt->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
200
-		foreach ($results as $key => $entry) {
201
-			unset($results[$key]);
202
-			$results[$entry['ldap_dn']] = $entry['owncloud_name'];
203
-			$this->cache[$entry['ldap_dn']] = $entry['owncloud_name'];
204
-		}
205
-
206
-		return $results;
207
-	}
208
-
209
-	/**
210
-	 * Searches mapped names by the giving string in the name column
211
-	 *
212
-	 * @param string $search
213
-	 * @param string $prefixMatch
214
-	 * @param string $postfixMatch
215
-	 * @return string[]
216
-	 */
217
-	public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
218
-		$query = $this->dbc->prepare('
174
+        unset($this->cache[$fdn]);
175
+
176
+        return $this->modify($query, [$uuid, $fdn]);
177
+    }
178
+
179
+    /**
180
+     * Gets the name based on the provided LDAP DN.
181
+     *
182
+     * @param string $fdn
183
+     * @return string|false
184
+     */
185
+    public function getNameByDN($fdn) {
186
+        if (!isset($this->cache[$fdn])) {
187
+            $this->cache[$fdn] = $this->getXbyY('owncloud_name', 'ldap_dn', $fdn);
188
+        }
189
+        return $this->cache[$fdn];
190
+    }
191
+
192
+    public function getListOfIdsByDn(array $fdns): array {
193
+        $qb = $this->dbc->getQueryBuilder();
194
+        $qb->select('owncloud_name', 'ldap_dn')
195
+            ->from($this->getTableName(false))
196
+            ->where($qb->expr()->in('ldap_dn', $qb->createNamedParameter($fdns, QueryBuilder::PARAM_STR_ARRAY)));
197
+        $stmt = $qb->execute();
198
+
199
+        $results = $stmt->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
200
+        foreach ($results as $key => $entry) {
201
+            unset($results[$key]);
202
+            $results[$entry['ldap_dn']] = $entry['owncloud_name'];
203
+            $this->cache[$entry['ldap_dn']] = $entry['owncloud_name'];
204
+        }
205
+
206
+        return $results;
207
+    }
208
+
209
+    /**
210
+     * Searches mapped names by the giving string in the name column
211
+     *
212
+     * @param string $search
213
+     * @param string $prefixMatch
214
+     * @param string $postfixMatch
215
+     * @return string[]
216
+     */
217
+    public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
218
+        $query = $this->dbc->prepare('
219 219
 			SELECT `owncloud_name`
220 220
 			FROM `' . $this->getTableName() . '`
221 221
 			WHERE `owncloud_name` LIKE ?
222 222
 		');
223 223
 
224
-		$res = $query->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
225
-		$names = [];
226
-		if ($res !== false) {
227
-			while ($row = $query->fetch()) {
228
-				$names[] = $row['owncloud_name'];
229
-			}
230
-		}
231
-		return $names;
232
-	}
233
-
234
-	/**
235
-	 * Gets the name based on the provided LDAP UUID.
236
-	 *
237
-	 * @param string $uuid
238
-	 * @return string|false
239
-	 */
240
-	public function getNameByUUID($uuid) {
241
-		return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
242
-	}
243
-
244
-	public function getDnByUUID($uuid) {
245
-		return $this->getXbyY('ldap_dn', 'directory_uuid', $uuid);
246
-	}
247
-
248
-	/**
249
-	 * Gets the UUID based on the provided LDAP DN
250
-	 *
251
-	 * @param string $dn
252
-	 * @return false|string
253
-	 * @throws \Exception
254
-	 */
255
-	public function getUUIDByDN($dn) {
256
-		return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
257
-	}
258
-
259
-	/**
260
-	 * gets a piece of the mapping list
261
-	 *
262
-	 * @param int $offset
263
-	 * @param int $limit
264
-	 * @return array
265
-	 */
266
-	public function getList($offset = null, $limit = null) {
267
-		$query = $this->dbc->prepare('
224
+        $res = $query->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
225
+        $names = [];
226
+        if ($res !== false) {
227
+            while ($row = $query->fetch()) {
228
+                $names[] = $row['owncloud_name'];
229
+            }
230
+        }
231
+        return $names;
232
+    }
233
+
234
+    /**
235
+     * Gets the name based on the provided LDAP UUID.
236
+     *
237
+     * @param string $uuid
238
+     * @return string|false
239
+     */
240
+    public function getNameByUUID($uuid) {
241
+        return $this->getXbyY('owncloud_name', 'directory_uuid', $uuid);
242
+    }
243
+
244
+    public function getDnByUUID($uuid) {
245
+        return $this->getXbyY('ldap_dn', 'directory_uuid', $uuid);
246
+    }
247
+
248
+    /**
249
+     * Gets the UUID based on the provided LDAP DN
250
+     *
251
+     * @param string $dn
252
+     * @return false|string
253
+     * @throws \Exception
254
+     */
255
+    public function getUUIDByDN($dn) {
256
+        return $this->getXbyY('directory_uuid', 'ldap_dn', $dn);
257
+    }
258
+
259
+    /**
260
+     * gets a piece of the mapping list
261
+     *
262
+     * @param int $offset
263
+     * @param int $limit
264
+     * @return array
265
+     */
266
+    public function getList($offset = null, $limit = null) {
267
+        $query = $this->dbc->prepare('
268 268
 			SELECT
269 269
 				`ldap_dn` AS `dn`,
270 270
 				`owncloud_name` AS `name`,
271 271
 				`directory_uuid` AS `uuid`
272 272
 			FROM `' . $this->getTableName() . '`',
273
-			$limit,
274
-			$offset
275
-		);
276
-
277
-		$query->execute();
278
-		return $query->fetchAll();
279
-	}
280
-
281
-	/**
282
-	 * attempts to map the given entry
283
-	 *
284
-	 * @param string $fdn fully distinguished name (from LDAP)
285
-	 * @param string $name
286
-	 * @param string $uuid a unique identifier as used in LDAP
287
-	 * @return bool
288
-	 */
289
-	public function map($fdn, $name, $uuid) {
290
-		if (mb_strlen($fdn) > 255) {
291
-			\OC::$server->getLogger()->error(
292
-				'Cannot map, because the DN exceeds 255 characters: {dn}',
293
-				[
294
-					'app' => 'user_ldap',
295
-					'dn' => $fdn,
296
-				]
297
-			);
298
-			return false;
299
-		}
300
-
301
-		$row = [
302
-			'ldap_dn' => $fdn,
303
-			'owncloud_name' => $name,
304
-			'directory_uuid' => $uuid
305
-		];
306
-
307
-		try {
308
-			$result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
309
-			if ((bool)$result === true) {
310
-				$this->cache[$fdn] = $name;
311
-			}
312
-			// insertIfNotExist returns values as int
313
-			return (bool)$result;
314
-		} catch (\Exception $e) {
315
-			return false;
316
-		}
317
-	}
318
-
319
-	/**
320
-	 * removes a mapping based on the owncloud_name of the entry
321
-	 *
322
-	 * @param string $name
323
-	 * @return bool
324
-	 */
325
-	public function unmap($name) {
326
-		$query = $this->dbc->prepare('
273
+            $limit,
274
+            $offset
275
+        );
276
+
277
+        $query->execute();
278
+        return $query->fetchAll();
279
+    }
280
+
281
+    /**
282
+     * attempts to map the given entry
283
+     *
284
+     * @param string $fdn fully distinguished name (from LDAP)
285
+     * @param string $name
286
+     * @param string $uuid a unique identifier as used in LDAP
287
+     * @return bool
288
+     */
289
+    public function map($fdn, $name, $uuid) {
290
+        if (mb_strlen($fdn) > 255) {
291
+            \OC::$server->getLogger()->error(
292
+                'Cannot map, because the DN exceeds 255 characters: {dn}',
293
+                [
294
+                    'app' => 'user_ldap',
295
+                    'dn' => $fdn,
296
+                ]
297
+            );
298
+            return false;
299
+        }
300
+
301
+        $row = [
302
+            'ldap_dn' => $fdn,
303
+            'owncloud_name' => $name,
304
+            'directory_uuid' => $uuid
305
+        ];
306
+
307
+        try {
308
+            $result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
309
+            if ((bool)$result === true) {
310
+                $this->cache[$fdn] = $name;
311
+            }
312
+            // insertIfNotExist returns values as int
313
+            return (bool)$result;
314
+        } catch (\Exception $e) {
315
+            return false;
316
+        }
317
+    }
318
+
319
+    /**
320
+     * removes a mapping based on the owncloud_name of the entry
321
+     *
322
+     * @param string $name
323
+     * @return bool
324
+     */
325
+    public function unmap($name) {
326
+        $query = $this->dbc->prepare('
327 327
 			DELETE FROM `' . $this->getTableName() . '`
328 328
 			WHERE `owncloud_name` = ?');
329 329
 
330
-		return $this->modify($query, [$name]);
331
-	}
332
-
333
-	/**
334
-	 * Truncate's the mapping table
335
-	 *
336
-	 * @return bool
337
-	 */
338
-	public function clear() {
339
-		$sql = $this->dbc
340
-			->getDatabasePlatform()
341
-			->getTruncateTableSQL('`' . $this->getTableName() . '`');
342
-		return $this->dbc->prepare($sql)->execute();
343
-	}
344
-
345
-	/**
346
-	 * clears the mapping table one by one and executing a callback with
347
-	 * each row's id (=owncloud_name col)
348
-	 *
349
-	 * @param callable $preCallback
350
-	 * @param callable $postCallback
351
-	 * @return bool true on success, false when at least one row was not
352
-	 * deleted
353
-	 */
354
-	public function clearCb(callable $preCallback, callable $postCallback): bool {
355
-		$picker = $this->dbc->getQueryBuilder();
356
-		$picker->select('owncloud_name')
357
-			->from($this->getTableName());
358
-		$cursor = $picker->execute();
359
-		$result = true;
360
-		while ($id = $cursor->fetchColumn(0)) {
361
-			$preCallback($id);
362
-			if ($isUnmapped = $this->unmap($id)) {
363
-				$postCallback($id);
364
-			}
365
-			$result &= $isUnmapped;
366
-		}
367
-		$cursor->closeCursor();
368
-		return $result;
369
-	}
370
-
371
-	/**
372
-	 * returns the number of entries in the mappings table
373
-	 *
374
-	 * @return int
375
-	 */
376
-	public function count() {
377
-		$qb = $this->dbc->getQueryBuilder();
378
-		$query = $qb->select($qb->func()->count('ldap_dn'))
379
-			->from($this->getTableName());
380
-		$res = $query->execute();
381
-		$count = $res->fetchColumn();
382
-		$res->closeCursor();
383
-		return (int)$count;
384
-	}
330
+        return $this->modify($query, [$name]);
331
+    }
332
+
333
+    /**
334
+     * Truncate's the mapping table
335
+     *
336
+     * @return bool
337
+     */
338
+    public function clear() {
339
+        $sql = $this->dbc
340
+            ->getDatabasePlatform()
341
+            ->getTruncateTableSQL('`' . $this->getTableName() . '`');
342
+        return $this->dbc->prepare($sql)->execute();
343
+    }
344
+
345
+    /**
346
+     * clears the mapping table one by one and executing a callback with
347
+     * each row's id (=owncloud_name col)
348
+     *
349
+     * @param callable $preCallback
350
+     * @param callable $postCallback
351
+     * @return bool true on success, false when at least one row was not
352
+     * deleted
353
+     */
354
+    public function clearCb(callable $preCallback, callable $postCallback): bool {
355
+        $picker = $this->dbc->getQueryBuilder();
356
+        $picker->select('owncloud_name')
357
+            ->from($this->getTableName());
358
+        $cursor = $picker->execute();
359
+        $result = true;
360
+        while ($id = $cursor->fetchColumn(0)) {
361
+            $preCallback($id);
362
+            if ($isUnmapped = $this->unmap($id)) {
363
+                $postCallback($id);
364
+            }
365
+            $result &= $isUnmapped;
366
+        }
367
+        $cursor->closeCursor();
368
+        return $result;
369
+    }
370
+
371
+    /**
372
+     * returns the number of entries in the mappings table
373
+     *
374
+     * @return int
375
+     */
376
+    public function count() {
377
+        $qb = $this->dbc->getQueryBuilder();
378
+        $query = $qb->select($qb->func()->count('ldap_dn'))
379
+            ->from($this->getTableName());
380
+        $res = $query->execute();
381
+        $count = $res->fetchColumn();
382
+        $res->closeCursor();
383
+        return (int)$count;
384
+    }
385 385
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -89,9 +89,9 @@  discard block
 block discarded – undo
89 89
 			throw new \Exception('Invalid Column Name');
90 90
 		}
91 91
 		$query = $this->dbc->prepare('
92
-			SELECT `' . $fetchCol . '`
93
-			FROM `' . $this->getTableName() . '`
94
-			WHERE `' . $compareCol . '` = ?
92
+			SELECT `' . $fetchCol.'`
93
+			FROM `' . $this->getTableName().'`
94
+			WHERE `' . $compareCol.'` = ?
95 95
 		');
96 96
 
97 97
 		$res = $query->execute([$search]);
@@ -140,7 +140,7 @@  discard block
 block discarded – undo
140 140
 	public function setDNbyUUID($fdn, $uuid) {
141 141
 		$oldDn = $this->getDnByUUID($uuid);
142 142
 		$query = $this->dbc->prepare('
143
-			UPDATE `' . $this->getTableName() . '`
143
+			UPDATE `' . $this->getTableName().'`
144 144
 			SET `ldap_dn` = ?
145 145
 			WHERE `directory_uuid` = ?
146 146
 		');
@@ -166,7 +166,7 @@  discard block
 block discarded – undo
166 166
 	 */
167 167
 	public function setUUIDbyDN($uuid, $fdn) {
168 168
 		$query = $this->dbc->prepare('
169
-			UPDATE `' . $this->getTableName() . '`
169
+			UPDATE `' . $this->getTableName().'`
170 170
 			SET `directory_uuid` = ?
171 171
 			WHERE `ldap_dn` = ?
172 172
 		');
@@ -217,11 +217,11 @@  discard block
 block discarded – undo
217 217
 	public function getNamesBySearch($search, $prefixMatch = "", $postfixMatch = "") {
218 218
 		$query = $this->dbc->prepare('
219 219
 			SELECT `owncloud_name`
220
-			FROM `' . $this->getTableName() . '`
220
+			FROM `' . $this->getTableName().'`
221 221
 			WHERE `owncloud_name` LIKE ?
222 222
 		');
223 223
 
224
-		$res = $query->execute([$prefixMatch . $this->dbc->escapeLikeParameter($search) . $postfixMatch]);
224
+		$res = $query->execute([$prefixMatch.$this->dbc->escapeLikeParameter($search).$postfixMatch]);
225 225
 		$names = [];
226 226
 		if ($res !== false) {
227 227
 			while ($row = $query->fetch()) {
@@ -269,7 +269,7 @@  discard block
 block discarded – undo
269 269
 				`ldap_dn` AS `dn`,
270 270
 				`owncloud_name` AS `name`,
271 271
 				`directory_uuid` AS `uuid`
272
-			FROM `' . $this->getTableName() . '`',
272
+			FROM `' . $this->getTableName().'`',
273 273
 			$limit,
274 274
 			$offset
275 275
 		);
@@ -306,11 +306,11 @@  discard block
 block discarded – undo
306 306
 
307 307
 		try {
308 308
 			$result = $this->dbc->insertIfNotExist($this->getTableName(), $row);
309
-			if ((bool)$result === true) {
309
+			if ((bool) $result === true) {
310 310
 				$this->cache[$fdn] = $name;
311 311
 			}
312 312
 			// insertIfNotExist returns values as int
313
-			return (bool)$result;
313
+			return (bool) $result;
314 314
 		} catch (\Exception $e) {
315 315
 			return false;
316 316
 		}
@@ -324,7 +324,7 @@  discard block
 block discarded – undo
324 324
 	 */
325 325
 	public function unmap($name) {
326 326
 		$query = $this->dbc->prepare('
327
-			DELETE FROM `' . $this->getTableName() . '`
327
+			DELETE FROM `' . $this->getTableName().'`
328 328
 			WHERE `owncloud_name` = ?');
329 329
 
330 330
 		return $this->modify($query, [$name]);
@@ -338,7 +338,7 @@  discard block
 block discarded – undo
338 338
 	public function clear() {
339 339
 		$sql = $this->dbc
340 340
 			->getDatabasePlatform()
341
-			->getTruncateTableSQL('`' . $this->getTableName() . '`');
341
+			->getTruncateTableSQL('`'.$this->getTableName().'`');
342 342
 		return $this->dbc->prepare($sql)->execute();
343 343
 	}
344 344
 
@@ -380,6 +380,6 @@  discard block
 block discarded – undo
380 380
 		$res = $query->execute();
381 381
 		$count = $res->fetchColumn();
382 382
 		$res->closeCursor();
383
-		return (int)$count;
383
+		return (int) $count;
384 384
 	}
385 385
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Mapping/GroupMapping.php 2 patches
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -29,12 +29,12 @@
 block discarded – undo
29 29
  */
30 30
 class GroupMapping extends AbstractMapping {
31 31
 
32
-	/**
33
-	 * returns the DB table name which holds the mappings
34
-	 * @return string
35
-	 */
36
-	protected function getTableName(bool $includePrefix = true) {
37
-		$p = $includePrefix ? '*PREFIX*' : '';
38
-		return $p . 'ldap_group_mapping';
39
-	}
32
+    /**
33
+     * returns the DB table name which holds the mappings
34
+     * @return string
35
+     */
36
+    protected function getTableName(bool $includePrefix = true) {
37
+        $p = $includePrefix ? '*PREFIX*' : '';
38
+        return $p . 'ldap_group_mapping';
39
+    }
40 40
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -35,6 +35,6 @@
 block discarded – undo
35 35
 	 */
36 36
 	protected function getTableName(bool $includePrefix = true) {
37 37
 		$p = $includePrefix ? '*PREFIX*' : '';
38
-		return $p . 'ldap_group_mapping';
38
+		return $p.'ldap_group_mapping';
39 39
 	}
40 40
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Access.php 2 patches
Indentation   +1998 added lines, -1998 removed lines patch added patch discarded remove patch
@@ -65,1790 +65,1790 @@  discard block
 block discarded – undo
65 65
  * @package OCA\User_LDAP
66 66
  */
67 67
 class Access extends LDAPUtility {
68
-	public const UUID_ATTRIBUTES = ['entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid'];
69
-
70
-	/** @var \OCA\User_LDAP\Connection */
71
-	public $connection;
72
-	/** @var Manager */
73
-	public $userManager;
74
-	//never ever check this var directly, always use getPagedSearchResultState
75
-	protected $pagedSearchedSuccessful;
76
-
77
-	/**
78
-	 * protected $cookies = [];
79
-	 *
80
-	 * @var AbstractMapping $userMapper
81
-	 */
82
-	protected $userMapper;
83
-
84
-	/**
85
-	 * @var AbstractMapping $userMapper
86
-	 */
87
-	protected $groupMapper;
88
-
89
-	/**
90
-	 * @var \OCA\User_LDAP\Helper
91
-	 */
92
-	private $helper;
93
-	/** @var IConfig */
94
-	private $config;
95
-	/** @var IUserManager */
96
-	private $ncUserManager;
97
-	/** @var string */
98
-	private $lastCookie = '';
99
-
100
-	public function __construct(
101
-		Connection $connection,
102
-		ILDAPWrapper $ldap,
103
-		Manager $userManager,
104
-		Helper $helper,
105
-		IConfig $config,
106
-		IUserManager $ncUserManager
107
-	) {
108
-		parent::__construct($ldap);
109
-		$this->connection = $connection;
110
-		$this->userManager = $userManager;
111
-		$this->userManager->setLdapAccess($this);
112
-		$this->helper = $helper;
113
-		$this->config = $config;
114
-		$this->ncUserManager = $ncUserManager;
115
-	}
116
-
117
-	/**
118
-	 * sets the User Mapper
119
-	 *
120
-	 * @param AbstractMapping $mapper
121
-	 */
122
-	public function setUserMapper(AbstractMapping $mapper) {
123
-		$this->userMapper = $mapper;
124
-	}
125
-
126
-	/**
127
-	 * returns the User Mapper
128
-	 *
129
-	 * @return AbstractMapping
130
-	 * @throws \Exception
131
-	 */
132
-	public function getUserMapper() {
133
-		if (is_null($this->userMapper)) {
134
-			throw new \Exception('UserMapper was not assigned to this Access instance.');
135
-		}
136
-		return $this->userMapper;
137
-	}
138
-
139
-	/**
140
-	 * sets the Group Mapper
141
-	 *
142
-	 * @param AbstractMapping $mapper
143
-	 */
144
-	public function setGroupMapper(AbstractMapping $mapper) {
145
-		$this->groupMapper = $mapper;
146
-	}
147
-
148
-	/**
149
-	 * returns the Group Mapper
150
-	 *
151
-	 * @return AbstractMapping
152
-	 * @throws \Exception
153
-	 */
154
-	public function getGroupMapper() {
155
-		if (is_null($this->groupMapper)) {
156
-			throw new \Exception('GroupMapper was not assigned to this Access instance.');
157
-		}
158
-		return $this->groupMapper;
159
-	}
160
-
161
-	/**
162
-	 * @return bool
163
-	 */
164
-	private function checkConnection() {
165
-		return ($this->connection instanceof Connection);
166
-	}
167
-
168
-	/**
169
-	 * returns the Connection instance
170
-	 *
171
-	 * @return \OCA\User_LDAP\Connection
172
-	 */
173
-	public function getConnection() {
174
-		return $this->connection;
175
-	}
176
-
177
-	/**
178
-	 * reads a given attribute for an LDAP record identified by a DN
179
-	 *
180
-	 * @param string $dn the record in question
181
-	 * @param string $attr the attribute that shall be retrieved
182
-	 *        if empty, just check the record's existence
183
-	 * @param string $filter
184
-	 * @return array|false an array of values on success or an empty
185
-	 *          array if $attr is empty, false otherwise
186
-	 * @throws ServerNotAvailableException
187
-	 */
188
-	public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
189
-		if (!$this->checkConnection()) {
190
-			\OCP\Util::writeLog('user_ldap',
191
-				'No LDAP Connector assigned, access impossible for readAttribute.',
192
-				ILogger::WARN);
193
-			return false;
194
-		}
195
-		$cr = $this->connection->getConnectionResource();
196
-		if (!$this->ldap->isResource($cr)) {
197
-			//LDAP not available
198
-			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
199
-			return false;
200
-		}
201
-		//Cancel possibly running Paged Results operation, otherwise we run in
202
-		//LDAP protocol errors
203
-		$this->abandonPagedSearch();
204
-		// openLDAP requires that we init a new Paged Search. Not needed by AD,
205
-		// but does not hurt either.
206
-		$pagingSize = (int)$this->connection->ldapPagingSize;
207
-		// 0 won't result in replies, small numbers may leave out groups
208
-		// (cf. #12306), 500 is default for paging and should work everywhere.
209
-		$maxResults = $pagingSize > 20 ? $pagingSize : 500;
210
-		$attr = mb_strtolower($attr, 'UTF-8');
211
-		// the actual read attribute later may contain parameters on a ranged
212
-		// request, e.g. member;range=99-199. Depends on server reply.
213
-		$attrToRead = $attr;
214
-
215
-		$values = [];
216
-		$isRangeRequest = false;
217
-		do {
218
-			$result = $this->executeRead($cr, $dn, $attrToRead, $filter, $maxResults);
219
-			if (is_bool($result)) {
220
-				// when an exists request was run and it was successful, an empty
221
-				// array must be returned
222
-				return $result ? [] : false;
223
-			}
224
-
225
-			if (!$isRangeRequest) {
226
-				$values = $this->extractAttributeValuesFromResult($result, $attr);
227
-				if (!empty($values)) {
228
-					return $values;
229
-				}
230
-			}
231
-
232
-			$isRangeRequest = false;
233
-			$result = $this->extractRangeData($result, $attr);
234
-			if (!empty($result)) {
235
-				$normalizedResult = $this->extractAttributeValuesFromResult(
236
-					[$attr => $result['values']],
237
-					$attr
238
-				);
239
-				$values = array_merge($values, $normalizedResult);
240
-
241
-				if ($result['rangeHigh'] === '*') {
242
-					// when server replies with * as high range value, there are
243
-					// no more results left
244
-					return $values;
245
-				} else {
246
-					$low = $result['rangeHigh'] + 1;
247
-					$attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
248
-					$isRangeRequest = true;
249
-				}
250
-			}
251
-		} while ($isRangeRequest);
252
-
253
-		\OCP\Util::writeLog('user_ldap', 'Requested attribute ' . $attr . ' not found for ' . $dn, ILogger::DEBUG);
254
-		return false;
255
-	}
256
-
257
-	/**
258
-	 * Runs an read operation against LDAP
259
-	 *
260
-	 * @param resource $cr the LDAP connection
261
-	 * @param string $dn
262
-	 * @param string $attribute
263
-	 * @param string $filter
264
-	 * @param int $maxResults
265
-	 * @return array|bool false if there was any error, true if an exists check
266
-	 *                    was performed and the requested DN found, array with the
267
-	 *                    returned data on a successful usual operation
268
-	 * @throws ServerNotAvailableException
269
-	 */
270
-	public function executeRead($cr, $dn, $attribute, $filter, $maxResults) {
271
-		$this->initPagedSearch($filter, $dn, [$attribute], $maxResults, 0);
272
-		$dn = $this->helper->DNasBaseParameter($dn);
273
-		$rr = @$this->invokeLDAPMethod('read', $cr, $dn, $filter, [$attribute]);
274
-		if (!$this->ldap->isResource($rr)) {
275
-			if ($attribute !== '') {
276
-				//do not throw this message on userExists check, irritates
277
-				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, ILogger::DEBUG);
278
-			}
279
-			//in case an error occurs , e.g. object does not exist
280
-			return false;
281
-		}
282
-		if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $cr, $rr) === 1)) {
283
-			\OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', ILogger::DEBUG);
284
-			return true;
285
-		}
286
-		$er = $this->invokeLDAPMethod('firstEntry', $cr, $rr);
287
-		if (!$this->ldap->isResource($er)) {
288
-			//did not match the filter, return false
289
-			return false;
290
-		}
291
-		//LDAP attributes are not case sensitive
292
-		$result = \OCP\Util::mb_array_change_key_case(
293
-			$this->invokeLDAPMethod('getAttributes', $cr, $er), MB_CASE_LOWER, 'UTF-8');
294
-
295
-		return $result;
296
-	}
297
-
298
-	/**
299
-	 * Normalizes a result grom getAttributes(), i.e. handles DNs and binary
300
-	 * data if present.
301
-	 *
302
-	 * @param array $result from ILDAPWrapper::getAttributes()
303
-	 * @param string $attribute the attribute name that was read
304
-	 * @return string[]
305
-	 */
306
-	public function extractAttributeValuesFromResult($result, $attribute) {
307
-		$values = [];
308
-		if (isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
309
-			$lowercaseAttribute = strtolower($attribute);
310
-			for ($i = 0; $i < $result[$attribute]['count']; $i++) {
311
-				if ($this->resemblesDN($attribute)) {
312
-					$values[] = $this->helper->sanitizeDN($result[$attribute][$i]);
313
-				} elseif ($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
314
-					$values[] = $this->convertObjectGUID2Str($result[$attribute][$i]);
315
-				} else {
316
-					$values[] = $result[$attribute][$i];
317
-				}
318
-			}
319
-		}
320
-		return $values;
321
-	}
322
-
323
-	/**
324
-	 * Attempts to find ranged data in a getAttribute results and extracts the
325
-	 * returned values as well as information on the range and full attribute
326
-	 * name for further processing.
327
-	 *
328
-	 * @param array $result from ILDAPWrapper::getAttributes()
329
-	 * @param string $attribute the attribute name that was read. Without ";range=…"
330
-	 * @return array If a range was detected with keys 'values', 'attributeName',
331
-	 *               'attributeFull' and 'rangeHigh', otherwise empty.
332
-	 */
333
-	public function extractRangeData($result, $attribute) {
334
-		$keys = array_keys($result);
335
-		foreach ($keys as $key) {
336
-			if ($key !== $attribute && strpos($key, $attribute) === 0) {
337
-				$queryData = explode(';', $key);
338
-				if (strpos($queryData[1], 'range=') === 0) {
339
-					$high = substr($queryData[1], 1 + strpos($queryData[1], '-'));
340
-					$data = [
341
-						'values' => $result[$key],
342
-						'attributeName' => $queryData[0],
343
-						'attributeFull' => $key,
344
-						'rangeHigh' => $high,
345
-					];
346
-					return $data;
347
-				}
348
-			}
349
-		}
350
-		return [];
351
-	}
352
-
353
-	/**
354
-	 * Set password for an LDAP user identified by a DN
355
-	 *
356
-	 * @param string $userDN the user in question
357
-	 * @param string $password the new password
358
-	 * @return bool
359
-	 * @throws HintException
360
-	 * @throws \Exception
361
-	 */
362
-	public function setPassword($userDN, $password) {
363
-		if ((int)$this->connection->turnOnPasswordChange !== 1) {
364
-			throw new \Exception('LDAP password changes are disabled.');
365
-		}
366
-		$cr = $this->connection->getConnectionResource();
367
-		if (!$this->ldap->isResource($cr)) {
368
-			//LDAP not available
369
-			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
370
-			return false;
371
-		}
372
-		try {
373
-			// try PASSWD extended operation first
374
-			return @$this->invokeLDAPMethod('exopPasswd', $cr, $userDN, '', $password) ||
375
-				@$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password);
376
-		} catch (ConstraintViolationException $e) {
377
-			throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ') . $e->getMessage(), $e->getCode());
378
-		}
379
-	}
380
-
381
-	/**
382
-	 * checks whether the given attributes value is probably a DN
383
-	 *
384
-	 * @param string $attr the attribute in question
385
-	 * @return boolean if so true, otherwise false
386
-	 */
387
-	private function resemblesDN($attr) {
388
-		$resemblingAttributes = [
389
-			'dn',
390
-			'uniquemember',
391
-			'member',
392
-			// memberOf is an "operational" attribute, without a definition in any RFC
393
-			'memberof'
394
-		];
395
-		return in_array($attr, $resemblingAttributes);
396
-	}
397
-
398
-	/**
399
-	 * checks whether the given string is probably a DN
400
-	 *
401
-	 * @param string $string
402
-	 * @return boolean
403
-	 */
404
-	public function stringResemblesDN($string) {
405
-		$r = $this->ldap->explodeDN($string, 0);
406
-		// if exploding a DN succeeds and does not end up in
407
-		// an empty array except for $r[count] being 0.
408
-		return (is_array($r) && count($r) > 1);
409
-	}
410
-
411
-	/**
412
-	 * returns a DN-string that is cleaned from not domain parts, e.g.
413
-	 * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
414
-	 * becomes dc=foobar,dc=server,dc=org
415
-	 *
416
-	 * @param string $dn
417
-	 * @return string
418
-	 */
419
-	public function getDomainDNFromDN($dn) {
420
-		$allParts = $this->ldap->explodeDN($dn, 0);
421
-		if ($allParts === false) {
422
-			//not a valid DN
423
-			return '';
424
-		}
425
-		$domainParts = [];
426
-		$dcFound = false;
427
-		foreach ($allParts as $part) {
428
-			if (!$dcFound && strpos($part, 'dc=') === 0) {
429
-				$dcFound = true;
430
-			}
431
-			if ($dcFound) {
432
-				$domainParts[] = $part;
433
-			}
434
-		}
435
-		return implode(',', $domainParts);
436
-	}
437
-
438
-	/**
439
-	 * returns the LDAP DN for the given internal Nextcloud name of the group
440
-	 *
441
-	 * @param string $name the Nextcloud name in question
442
-	 * @return string|false LDAP DN on success, otherwise false
443
-	 */
444
-	public function groupname2dn($name) {
445
-		return $this->groupMapper->getDNByName($name);
446
-	}
447
-
448
-	/**
449
-	 * returns the LDAP DN for the given internal Nextcloud name of the user
450
-	 *
451
-	 * @param string $name the Nextcloud name in question
452
-	 * @return string|false with the LDAP DN on success, otherwise false
453
-	 */
454
-	public function username2dn($name) {
455
-		$fdn = $this->userMapper->getDNByName($name);
456
-
457
-		//Check whether the DN belongs to the Base, to avoid issues on multi-
458
-		//server setups
459
-		if (is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
460
-			return $fdn;
461
-		}
462
-
463
-		return false;
464
-	}
465
-
466
-	/**
467
-	 * returns the internal Nextcloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
468
-	 *
469
-	 * @param string $fdn the dn of the group object
470
-	 * @param string $ldapName optional, the display name of the object
471
-	 * @return string|false with the name to use in Nextcloud, false on DN outside of search DN
472
-	 * @throws \Exception
473
-	 */
474
-	public function dn2groupname($fdn, $ldapName = null) {
475
-		//To avoid bypassing the base DN settings under certain circumstances
476
-		//with the group support, check whether the provided DN matches one of
477
-		//the given Bases
478
-		if (!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
479
-			return false;
480
-		}
481
-
482
-		return $this->dn2ocname($fdn, $ldapName, false);
483
-	}
484
-
485
-	/**
486
-	 * returns the internal Nextcloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
487
-	 *
488
-	 * @param string $dn the dn of the user object
489
-	 * @param string $ldapName optional, the display name of the object
490
-	 * @return string|false with with the name to use in Nextcloud
491
-	 * @throws \Exception
492
-	 */
493
-	public function dn2username($fdn, $ldapName = null) {
494
-		//To avoid bypassing the base DN settings under certain circumstances
495
-		//with the group support, check whether the provided DN matches one of
496
-		//the given Bases
497
-		if (!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
498
-			return false;
499
-		}
500
-
501
-		return $this->dn2ocname($fdn, $ldapName, true);
502
-	}
503
-
504
-	/**
505
-	 * returns an internal Nextcloud name for the given LDAP DN, false on DN outside of search DN
506
-	 *
507
-	 * @param string $fdn the dn of the user object
508
-	 * @param string|null $ldapName optional, the display name of the object
509
-	 * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
510
-	 * @param bool|null $newlyMapped
511
-	 * @param array|null $record
512
-	 * @return false|string with with the name to use in Nextcloud
513
-	 * @throws \Exception
514
-	 */
515
-	public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, array $record = null) {
516
-		$newlyMapped = false;
517
-		if ($isUser) {
518
-			$mapper = $this->getUserMapper();
519
-			$nameAttribute = $this->connection->ldapUserDisplayName;
520
-			$filter = $this->connection->ldapUserFilter;
521
-		} else {
522
-			$mapper = $this->getGroupMapper();
523
-			$nameAttribute = $this->connection->ldapGroupDisplayName;
524
-			$filter = $this->connection->ldapGroupFilter;
525
-		}
526
-
527
-		//let's try to retrieve the Nextcloud name from the mappings table
528
-		$ncName = $mapper->getNameByDN($fdn);
529
-		if (is_string($ncName)) {
530
-			return $ncName;
531
-		}
532
-
533
-		//second try: get the UUID and check if it is known. Then, update the DN and return the name.
534
-		$uuid = $this->getUUID($fdn, $isUser, $record);
535
-		if (is_string($uuid)) {
536
-			$ncName = $mapper->getNameByUUID($uuid);
537
-			if (is_string($ncName)) {
538
-				$mapper->setDNbyUUID($fdn, $uuid);
539
-				return $ncName;
540
-			}
541
-		} else {
542
-			//If the UUID can't be detected something is foul.
543
-			\OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for ' . $fdn . '. Skipping.', ILogger::INFO);
544
-			return false;
545
-		}
546
-
547
-		if (is_null($ldapName)) {
548
-			$ldapName = $this->readAttribute($fdn, $nameAttribute, $filter);
549
-			if (!isset($ldapName[0]) && empty($ldapName[0])) {
550
-				\OCP\Util::writeLog('user_ldap', 'No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ILogger::INFO);
551
-				return false;
552
-			}
553
-			$ldapName = $ldapName[0];
554
-		}
555
-
556
-		if ($isUser) {
557
-			$usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
558
-			if ($usernameAttribute !== '') {
559
-				$username = $this->readAttribute($fdn, $usernameAttribute);
560
-				$username = $username[0];
561
-			} else {
562
-				$username = $uuid;
563
-			}
564
-			try {
565
-				$intName = $this->sanitizeUsername($username);
566
-			} catch (\InvalidArgumentException $e) {
567
-				\OC::$server->getLogger()->logException($e, [
568
-					'app' => 'user_ldap',
569
-					'level' => ILogger::WARN,
570
-				]);
571
-				// we don't attempt to set a username here. We can go for
572
-				// for an alternative 4 digit random number as we would append
573
-				// otherwise, however it's likely not enough space in bigger
574
-				// setups, and most importantly: this is not intended.
575
-				return false;
576
-			}
577
-		} else {
578
-			$intName = $ldapName;
579
-		}
580
-
581
-		//a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
582
-		//disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
583
-		//NOTE: mind, disabling cache affects only this instance! Using it
584
-		// outside of core user management will still cache the user as non-existing.
585
-		$originalTTL = $this->connection->ldapCacheTTL;
586
-		$this->connection->setConfiguration(['ldapCacheTTL' => 0]);
587
-		if ($intName !== ''
588
-			&& (($isUser && !$this->ncUserManager->userExists($intName))
589
-				|| (!$isUser && !\OC::$server->getGroupManager()->groupExists($intName))
590
-			)
591
-		) {
592
-			$this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
593
-			$newlyMapped = $this->mapAndAnnounceIfApplicable($mapper, $fdn, $intName, $uuid, $isUser);
594
-			if ($newlyMapped) {
595
-				return $intName;
596
-			}
597
-		}
598
-
599
-		$this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
600
-		$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
601
-		if (is_string($altName)) {
602
-			if ($this->mapAndAnnounceIfApplicable($mapper, $fdn, $altName, $uuid, $isUser)) {
603
-				$newlyMapped = true;
604
-				return $altName;
605
-			}
606
-		}
607
-
608
-		//if everything else did not help..
609
-		\OCP\Util::writeLog('user_ldap', 'Could not create unique name for ' . $fdn . '.', ILogger::INFO);
610
-		return false;
611
-	}
612
-
613
-	public function mapAndAnnounceIfApplicable(
614
-		AbstractMapping $mapper,
615
-		string $fdn,
616
-		string $name,
617
-		string $uuid,
618
-		bool $isUser
619
-	): bool {
620
-		if ($mapper->map($fdn, $name, $uuid)) {
621
-			if ($this->ncUserManager instanceof PublicEmitter && $isUser) {
622
-				$this->cacheUserExists($name);
623
-				$this->ncUserManager->emit('\OC\User', 'assignedUserId', [$name]);
624
-			} elseif (!$isUser) {
625
-				$this->cacheGroupExists($name);
626
-			}
627
-			return true;
628
-		}
629
-		return false;
630
-	}
631
-
632
-	/**
633
-	 * gives back the user names as they are used ownClod internally
634
-	 *
635
-	 * @param array $ldapUsers as returned by fetchList()
636
-	 * @return array an array with the user names to use in Nextcloud
637
-	 *
638
-	 * gives back the user names as they are used ownClod internally
639
-	 * @throws \Exception
640
-	 */
641
-	public function nextcloudUserNames($ldapUsers) {
642
-		return $this->ldap2NextcloudNames($ldapUsers, true);
643
-	}
644
-
645
-	/**
646
-	 * gives back the group names as they are used ownClod internally
647
-	 *
648
-	 * @param array $ldapGroups as returned by fetchList()
649
-	 * @return array an array with the group names to use in Nextcloud
650
-	 *
651
-	 * gives back the group names as they are used ownClod internally
652
-	 * @throws \Exception
653
-	 */
654
-	public function nextcloudGroupNames($ldapGroups) {
655
-		return $this->ldap2NextcloudNames($ldapGroups, false);
656
-	}
657
-
658
-	/**
659
-	 * @param array $ldapObjects as returned by fetchList()
660
-	 * @param bool $isUsers
661
-	 * @return array
662
-	 * @throws \Exception
663
-	 */
664
-	private function ldap2NextcloudNames($ldapObjects, $isUsers) {
665
-		if ($isUsers) {
666
-			$nameAttribute = $this->connection->ldapUserDisplayName;
667
-			$sndAttribute = $this->connection->ldapUserDisplayName2;
668
-		} else {
669
-			$nameAttribute = $this->connection->ldapGroupDisplayName;
670
-		}
671
-		$nextcloudNames = [];
672
-
673
-		foreach ($ldapObjects as $ldapObject) {
674
-			$nameByLDAP = null;
675
-			if (isset($ldapObject[$nameAttribute])
676
-				&& is_array($ldapObject[$nameAttribute])
677
-				&& isset($ldapObject[$nameAttribute][0])
678
-			) {
679
-				// might be set, but not necessarily. if so, we use it.
680
-				$nameByLDAP = $ldapObject[$nameAttribute][0];
681
-			}
682
-
683
-			$ncName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
684
-			if ($ncName) {
685
-				$nextcloudNames[] = $ncName;
686
-				if ($isUsers) {
687
-					$this->updateUserState($ncName);
688
-					//cache the user names so it does not need to be retrieved
689
-					//again later (e.g. sharing dialogue).
690
-					if (is_null($nameByLDAP)) {
691
-						continue;
692
-					}
693
-					$sndName = isset($ldapObject[$sndAttribute][0])
694
-						? $ldapObject[$sndAttribute][0] : '';
695
-					$this->cacheUserDisplayName($ncName, $nameByLDAP, $sndName);
696
-				} elseif ($nameByLDAP !== null) {
697
-					$this->cacheGroupDisplayName($ncName, $nameByLDAP);
698
-				}
699
-			}
700
-		}
701
-		return $nextcloudNames;
702
-	}
703
-
704
-	/**
705
-	 * removes the deleted-flag of a user if it was set
706
-	 *
707
-	 * @param string $ncname
708
-	 * @throws \Exception
709
-	 */
710
-	public function updateUserState($ncname) {
711
-		$user = $this->userManager->get($ncname);
712
-		if ($user instanceof OfflineUser) {
713
-			$user->unmark();
714
-		}
715
-	}
716
-
717
-	/**
718
-	 * caches the user display name
719
-	 *
720
-	 * @param string $ocName the internal Nextcloud username
721
-	 * @param string|false $home the home directory path
722
-	 */
723
-	public function cacheUserHome($ocName, $home) {
724
-		$cacheKey = 'getHome' . $ocName;
725
-		$this->connection->writeToCache($cacheKey, $home);
726
-	}
727
-
728
-	/**
729
-	 * caches a user as existing
730
-	 *
731
-	 * @param string $ocName the internal Nextcloud username
732
-	 */
733
-	public function cacheUserExists($ocName) {
734
-		$this->connection->writeToCache('userExists' . $ocName, true);
735
-	}
736
-
737
-	/**
738
-	 * caches a group as existing
739
-	 */
740
-	public function cacheGroupExists(string $gid): void {
741
-		$this->connection->writeToCache('groupExists' . $gid, true);
742
-	}
743
-
744
-	/**
745
-	 * caches the user display name
746
-	 *
747
-	 * @param string $ocName the internal Nextcloud username
748
-	 * @param string $displayName the display name
749
-	 * @param string $displayName2 the second display name
750
-	 * @throws \Exception
751
-	 */
752
-	public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
753
-		$user = $this->userManager->get($ocName);
754
-		if ($user === null) {
755
-			return;
756
-		}
757
-		$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
758
-		$cacheKeyTrunk = 'getDisplayName';
759
-		$this->connection->writeToCache($cacheKeyTrunk . $ocName, $displayName);
760
-	}
761
-
762
-	public function cacheGroupDisplayName(string $ncName, string $displayName): void {
763
-		$cacheKey = 'group_getDisplayName' . $ncName;
764
-		$this->connection->writeToCache($cacheKey, $displayName);
765
-	}
766
-
767
-	/**
768
-	 * creates a unique name for internal Nextcloud use for users. Don't call it directly.
769
-	 *
770
-	 * @param string $name the display name of the object
771
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful
772
-	 *
773
-	 * Instead of using this method directly, call
774
-	 * createAltInternalOwnCloudName($name, true)
775
-	 */
776
-	private function _createAltInternalOwnCloudNameForUsers($name) {
777
-		$attempts = 0;
778
-		//while loop is just a precaution. If a name is not generated within
779
-		//20 attempts, something else is very wrong. Avoids infinite loop.
780
-		while ($attempts < 20) {
781
-			$altName = $name . '_' . rand(1000, 9999);
782
-			if (!$this->ncUserManager->userExists($altName)) {
783
-				return $altName;
784
-			}
785
-			$attempts++;
786
-		}
787
-		return false;
788
-	}
789
-
790
-	/**
791
-	 * creates a unique name for internal Nextcloud use for groups. Don't call it directly.
792
-	 *
793
-	 * @param string $name the display name of the object
794
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful.
795
-	 *
796
-	 * Instead of using this method directly, call
797
-	 * createAltInternalOwnCloudName($name, false)
798
-	 *
799
-	 * Group names are also used as display names, so we do a sequential
800
-	 * numbering, e.g. Developers_42 when there are 41 other groups called
801
-	 * "Developers"
802
-	 */
803
-	private function _createAltInternalOwnCloudNameForGroups($name) {
804
-		$usedNames = $this->groupMapper->getNamesBySearch($name, "", '_%');
805
-		if (!$usedNames || count($usedNames) === 0) {
806
-			$lastNo = 1; //will become name_2
807
-		} else {
808
-			natsort($usedNames);
809
-			$lastName = array_pop($usedNames);
810
-			$lastNo = (int)substr($lastName, strrpos($lastName, '_') + 1);
811
-		}
812
-		$altName = $name . '_' . (string)($lastNo + 1);
813
-		unset($usedNames);
814
-
815
-		$attempts = 1;
816
-		while ($attempts < 21) {
817
-			// Check to be really sure it is unique
818
-			// while loop is just a precaution. If a name is not generated within
819
-			// 20 attempts, something else is very wrong. Avoids infinite loop.
820
-			if (!\OC::$server->getGroupManager()->groupExists($altName)) {
821
-				return $altName;
822
-			}
823
-			$altName = $name . '_' . ($lastNo + $attempts);
824
-			$attempts++;
825
-		}
826
-		return false;
827
-	}
828
-
829
-	/**
830
-	 * creates a unique name for internal Nextcloud use.
831
-	 *
832
-	 * @param string $name the display name of the object
833
-	 * @param boolean $isUser whether name should be created for a user (true) or a group (false)
834
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful
835
-	 */
836
-	private function createAltInternalOwnCloudName($name, $isUser) {
837
-		$originalTTL = $this->connection->ldapCacheTTL;
838
-		$this->connection->setConfiguration(['ldapCacheTTL' => 0]);
839
-		if ($isUser) {
840
-			$altName = $this->_createAltInternalOwnCloudNameForUsers($name);
841
-		} else {
842
-			$altName = $this->_createAltInternalOwnCloudNameForGroups($name);
843
-		}
844
-		$this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
845
-
846
-		return $altName;
847
-	}
848
-
849
-	/**
850
-	 * fetches a list of users according to a provided loginName and utilizing
851
-	 * the login filter.
852
-	 *
853
-	 * @param string $loginName
854
-	 * @param array $attributes optional, list of attributes to read
855
-	 * @return array
856
-	 */
857
-	public function fetchUsersByLoginName($loginName, $attributes = ['dn']) {
858
-		$loginName = $this->escapeFilterPart($loginName);
859
-		$filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
860
-		return $this->fetchListOfUsers($filter, $attributes);
861
-	}
862
-
863
-	/**
864
-	 * counts the number of users according to a provided loginName and
865
-	 * utilizing the login filter.
866
-	 *
867
-	 * @param string $loginName
868
-	 * @return int
869
-	 */
870
-	public function countUsersByLoginName($loginName) {
871
-		$loginName = $this->escapeFilterPart($loginName);
872
-		$filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
873
-		return $this->countUsers($filter);
874
-	}
875
-
876
-	/**
877
-	 * @param string $filter
878
-	 * @param string|string[] $attr
879
-	 * @param int $limit
880
-	 * @param int $offset
881
-	 * @param bool $forceApplyAttributes
882
-	 * @return array
883
-	 * @throws \Exception
884
-	 */
885
-	public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null, $forceApplyAttributes = false) {
886
-		$ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
887
-		$recordsToUpdate = $ldapRecords;
888
-		if (!$forceApplyAttributes) {
889
-			$isBackgroundJobModeAjax = $this->config
890
-					->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
891
-			$listOfDNs = array_reduce($ldapRecords, function ($listOfDNs, $entry) {
892
-				$listOfDNs[] = $entry['dn'][0];
893
-				return $listOfDNs;
894
-			}, []);
895
-			$idsByDn = $this->userMapper->getListOfIdsByDn($listOfDNs);
896
-			$recordsToUpdate = array_filter($ldapRecords, function ($record) use ($isBackgroundJobModeAjax, $idsByDn) {
897
-				$newlyMapped = false;
898
-				$uid = $idsByDn[$record['dn'][0]] ?? null;
899
-				if ($uid === null) {
900
-					$uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record);
901
-				}
902
-				if (is_string($uid)) {
903
-					$this->cacheUserExists($uid);
904
-				}
905
-				return ($uid !== false) && ($newlyMapped || $isBackgroundJobModeAjax);
906
-			});
907
-		}
908
-		$this->batchApplyUserAttributes($recordsToUpdate);
909
-		return $this->fetchList($ldapRecords, $this->manyAttributes($attr));
910
-	}
911
-
912
-	/**
913
-	 * provided with an array of LDAP user records the method will fetch the
914
-	 * user object and requests it to process the freshly fetched attributes and
915
-	 * and their values
916
-	 *
917
-	 * @param array $ldapRecords
918
-	 * @throws \Exception
919
-	 */
920
-	public function batchApplyUserAttributes(array $ldapRecords) {
921
-		$displayNameAttribute = strtolower($this->connection->ldapUserDisplayName);
922
-		foreach ($ldapRecords as $userRecord) {
923
-			if (!isset($userRecord[$displayNameAttribute])) {
924
-				// displayName is obligatory
925
-				continue;
926
-			}
927
-			$ocName = $this->dn2ocname($userRecord['dn'][0], null, true);
928
-			if ($ocName === false) {
929
-				continue;
930
-			}
931
-			$this->updateUserState($ocName);
932
-			$user = $this->userManager->get($ocName);
933
-			if ($user !== null) {
934
-				$user->processAttributes($userRecord);
935
-			} else {
936
-				\OC::$server->getLogger()->debug(
937
-					"The ldap user manager returned null for $ocName",
938
-					['app' => 'user_ldap']
939
-				);
940
-			}
941
-		}
942
-	}
943
-
944
-	/**
945
-	 * @param string $filter
946
-	 * @param string|string[] $attr
947
-	 * @param int $limit
948
-	 * @param int $offset
949
-	 * @return array
950
-	 */
951
-	public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
952
-		$groupRecords = $this->searchGroups($filter, $attr, $limit, $offset);
953
-
954
-		$listOfDNs = array_reduce($groupRecords, function ($listOfDNs, $entry) {
955
-			$listOfDNs[] = $entry['dn'][0];
956
-			return $listOfDNs;
957
-		}, []);
958
-		$idsByDn = $this->groupMapper->getListOfIdsByDn($listOfDNs);
959
-
960
-		array_walk($groupRecords, function ($record) use ($idsByDn) {
961
-			$newlyMapped = false;
962
-			$gid = $uidsByDn[$record['dn'][0]] ?? null;
963
-			if ($gid === null) {
964
-				$gid = $this->dn2ocname($record['dn'][0], null, false, $newlyMapped, $record);
965
-			}
966
-			if (!$newlyMapped && is_string($gid)) {
967
-				$this->cacheGroupExists($gid);
968
-			}
969
-		});
970
-		return $this->fetchList($groupRecords, $this->manyAttributes($attr));
971
-	}
972
-
973
-	/**
974
-	 * @param array $list
975
-	 * @param bool $manyAttributes
976
-	 * @return array
977
-	 */
978
-	private function fetchList($list, $manyAttributes) {
979
-		if (is_array($list)) {
980
-			if ($manyAttributes) {
981
-				return $list;
982
-			} else {
983
-				$list = array_reduce($list, function ($carry, $item) {
984
-					$attribute = array_keys($item)[0];
985
-					$carry[] = $item[$attribute][0];
986
-					return $carry;
987
-				}, []);
988
-				return array_unique($list, SORT_LOCALE_STRING);
989
-			}
990
-		}
991
-
992
-		//error cause actually, maybe throw an exception in future.
993
-		return [];
994
-	}
995
-
996
-	/**
997
-	 * executes an LDAP search, optimized for Users
998
-	 *
999
-	 * @param string $filter the LDAP filter for the search
1000
-	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
1001
-	 * @param integer $limit
1002
-	 * @param integer $offset
1003
-	 * @return array with the search result
1004
-	 *
1005
-	 * Executes an LDAP search
1006
-	 * @throws ServerNotAvailableException
1007
-	 */
1008
-	public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
1009
-		$result = [];
1010
-		foreach ($this->connection->ldapBaseUsers as $base) {
1011
-			$result = array_merge($result, $this->search($filter, $base, $attr, $limit, $offset));
1012
-		}
1013
-		return $result;
1014
-	}
1015
-
1016
-	/**
1017
-	 * @param string $filter
1018
-	 * @param string|string[] $attr
1019
-	 * @param int $limit
1020
-	 * @param int $offset
1021
-	 * @return false|int
1022
-	 * @throws ServerNotAvailableException
1023
-	 */
1024
-	public function countUsers($filter, $attr = ['dn'], $limit = null, $offset = null) {
1025
-		$result = false;
1026
-		foreach ($this->connection->ldapBaseUsers as $base) {
1027
-			$count = $this->count($filter, [$base], $attr, $limit, $offset);
1028
-			$result = is_int($count) ? (int)$result + $count : $result;
1029
-		}
1030
-		return $result;
1031
-	}
1032
-
1033
-	/**
1034
-	 * executes an LDAP search, optimized for Groups
1035
-	 *
1036
-	 * @param string $filter the LDAP filter for the search
1037
-	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
1038
-	 * @param integer $limit
1039
-	 * @param integer $offset
1040
-	 * @return array with the search result
1041
-	 *
1042
-	 * Executes an LDAP search
1043
-	 * @throws ServerNotAvailableException
1044
-	 */
1045
-	public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
1046
-		$result = [];
1047
-		foreach ($this->connection->ldapBaseGroups as $base) {
1048
-			$result = array_merge($result, $this->search($filter, $base, $attr, $limit, $offset));
1049
-		}
1050
-		return $result;
1051
-	}
1052
-
1053
-	/**
1054
-	 * returns the number of available groups
1055
-	 *
1056
-	 * @param string $filter the LDAP search filter
1057
-	 * @param string[] $attr optional
1058
-	 * @param int|null $limit
1059
-	 * @param int|null $offset
1060
-	 * @return int|bool
1061
-	 * @throws ServerNotAvailableException
1062
-	 */
1063
-	public function countGroups($filter, $attr = ['dn'], $limit = null, $offset = null) {
1064
-		$result = false;
1065
-		foreach ($this->connection->ldapBaseGroups as $base) {
1066
-			$count = $this->count($filter, [$base], $attr, $limit, $offset);
1067
-			$result = is_int($count) ? (int)$result + $count : $result;
1068
-		}
1069
-		return $result;
1070
-	}
1071
-
1072
-	/**
1073
-	 * returns the number of available objects on the base DN
1074
-	 *
1075
-	 * @param int|null $limit
1076
-	 * @param int|null $offset
1077
-	 * @return int|bool
1078
-	 * @throws ServerNotAvailableException
1079
-	 */
1080
-	public function countObjects($limit = null, $offset = null) {
1081
-		$result = false;
1082
-		foreach ($this->connection->ldapBase as $base) {
1083
-			$count = $this->count('objectclass=*', [$base], ['dn'], $limit, $offset);
1084
-			$result = is_int($count) ? (int)$result + $count : $result;
1085
-		}
1086
-		return $result;
1087
-	}
1088
-
1089
-	/**
1090
-	 * Returns the LDAP handler
1091
-	 *
1092
-	 * @throws \OC\ServerNotAvailableException
1093
-	 */
1094
-
1095
-	/**
1096
-	 * @return mixed
1097
-	 * @throws \OC\ServerNotAvailableException
1098
-	 */
1099
-	private function invokeLDAPMethod() {
1100
-		$arguments = func_get_args();
1101
-		$command = array_shift($arguments);
1102
-		$cr = array_shift($arguments);
1103
-		if (!method_exists($this->ldap, $command)) {
1104
-			return null;
1105
-		}
1106
-		array_unshift($arguments, $cr);
1107
-		// php no longer supports call-time pass-by-reference
1108
-		// thus cannot support controlPagedResultResponse as the third argument
1109
-		// is a reference
1110
-		$doMethod = function () use ($command, &$arguments) {
1111
-			if ($command == 'controlPagedResultResponse') {
1112
-				throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.');
1113
-			} else {
1114
-				return call_user_func_array([$this->ldap, $command], $arguments);
1115
-			}
1116
-		};
1117
-		try {
1118
-			$ret = $doMethod();
1119
-		} catch (ServerNotAvailableException $e) {
1120
-			/* Server connection lost, attempt to reestablish it
68
+    public const UUID_ATTRIBUTES = ['entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid'];
69
+
70
+    /** @var \OCA\User_LDAP\Connection */
71
+    public $connection;
72
+    /** @var Manager */
73
+    public $userManager;
74
+    //never ever check this var directly, always use getPagedSearchResultState
75
+    protected $pagedSearchedSuccessful;
76
+
77
+    /**
78
+     * protected $cookies = [];
79
+     *
80
+     * @var AbstractMapping $userMapper
81
+     */
82
+    protected $userMapper;
83
+
84
+    /**
85
+     * @var AbstractMapping $userMapper
86
+     */
87
+    protected $groupMapper;
88
+
89
+    /**
90
+     * @var \OCA\User_LDAP\Helper
91
+     */
92
+    private $helper;
93
+    /** @var IConfig */
94
+    private $config;
95
+    /** @var IUserManager */
96
+    private $ncUserManager;
97
+    /** @var string */
98
+    private $lastCookie = '';
99
+
100
+    public function __construct(
101
+        Connection $connection,
102
+        ILDAPWrapper $ldap,
103
+        Manager $userManager,
104
+        Helper $helper,
105
+        IConfig $config,
106
+        IUserManager $ncUserManager
107
+    ) {
108
+        parent::__construct($ldap);
109
+        $this->connection = $connection;
110
+        $this->userManager = $userManager;
111
+        $this->userManager->setLdapAccess($this);
112
+        $this->helper = $helper;
113
+        $this->config = $config;
114
+        $this->ncUserManager = $ncUserManager;
115
+    }
116
+
117
+    /**
118
+     * sets the User Mapper
119
+     *
120
+     * @param AbstractMapping $mapper
121
+     */
122
+    public function setUserMapper(AbstractMapping $mapper) {
123
+        $this->userMapper = $mapper;
124
+    }
125
+
126
+    /**
127
+     * returns the User Mapper
128
+     *
129
+     * @return AbstractMapping
130
+     * @throws \Exception
131
+     */
132
+    public function getUserMapper() {
133
+        if (is_null($this->userMapper)) {
134
+            throw new \Exception('UserMapper was not assigned to this Access instance.');
135
+        }
136
+        return $this->userMapper;
137
+    }
138
+
139
+    /**
140
+     * sets the Group Mapper
141
+     *
142
+     * @param AbstractMapping $mapper
143
+     */
144
+    public function setGroupMapper(AbstractMapping $mapper) {
145
+        $this->groupMapper = $mapper;
146
+    }
147
+
148
+    /**
149
+     * returns the Group Mapper
150
+     *
151
+     * @return AbstractMapping
152
+     * @throws \Exception
153
+     */
154
+    public function getGroupMapper() {
155
+        if (is_null($this->groupMapper)) {
156
+            throw new \Exception('GroupMapper was not assigned to this Access instance.');
157
+        }
158
+        return $this->groupMapper;
159
+    }
160
+
161
+    /**
162
+     * @return bool
163
+     */
164
+    private function checkConnection() {
165
+        return ($this->connection instanceof Connection);
166
+    }
167
+
168
+    /**
169
+     * returns the Connection instance
170
+     *
171
+     * @return \OCA\User_LDAP\Connection
172
+     */
173
+    public function getConnection() {
174
+        return $this->connection;
175
+    }
176
+
177
+    /**
178
+     * reads a given attribute for an LDAP record identified by a DN
179
+     *
180
+     * @param string $dn the record in question
181
+     * @param string $attr the attribute that shall be retrieved
182
+     *        if empty, just check the record's existence
183
+     * @param string $filter
184
+     * @return array|false an array of values on success or an empty
185
+     *          array if $attr is empty, false otherwise
186
+     * @throws ServerNotAvailableException
187
+     */
188
+    public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
189
+        if (!$this->checkConnection()) {
190
+            \OCP\Util::writeLog('user_ldap',
191
+                'No LDAP Connector assigned, access impossible for readAttribute.',
192
+                ILogger::WARN);
193
+            return false;
194
+        }
195
+        $cr = $this->connection->getConnectionResource();
196
+        if (!$this->ldap->isResource($cr)) {
197
+            //LDAP not available
198
+            \OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
199
+            return false;
200
+        }
201
+        //Cancel possibly running Paged Results operation, otherwise we run in
202
+        //LDAP protocol errors
203
+        $this->abandonPagedSearch();
204
+        // openLDAP requires that we init a new Paged Search. Not needed by AD,
205
+        // but does not hurt either.
206
+        $pagingSize = (int)$this->connection->ldapPagingSize;
207
+        // 0 won't result in replies, small numbers may leave out groups
208
+        // (cf. #12306), 500 is default for paging and should work everywhere.
209
+        $maxResults = $pagingSize > 20 ? $pagingSize : 500;
210
+        $attr = mb_strtolower($attr, 'UTF-8');
211
+        // the actual read attribute later may contain parameters on a ranged
212
+        // request, e.g. member;range=99-199. Depends on server reply.
213
+        $attrToRead = $attr;
214
+
215
+        $values = [];
216
+        $isRangeRequest = false;
217
+        do {
218
+            $result = $this->executeRead($cr, $dn, $attrToRead, $filter, $maxResults);
219
+            if (is_bool($result)) {
220
+                // when an exists request was run and it was successful, an empty
221
+                // array must be returned
222
+                return $result ? [] : false;
223
+            }
224
+
225
+            if (!$isRangeRequest) {
226
+                $values = $this->extractAttributeValuesFromResult($result, $attr);
227
+                if (!empty($values)) {
228
+                    return $values;
229
+                }
230
+            }
231
+
232
+            $isRangeRequest = false;
233
+            $result = $this->extractRangeData($result, $attr);
234
+            if (!empty($result)) {
235
+                $normalizedResult = $this->extractAttributeValuesFromResult(
236
+                    [$attr => $result['values']],
237
+                    $attr
238
+                );
239
+                $values = array_merge($values, $normalizedResult);
240
+
241
+                if ($result['rangeHigh'] === '*') {
242
+                    // when server replies with * as high range value, there are
243
+                    // no more results left
244
+                    return $values;
245
+                } else {
246
+                    $low = $result['rangeHigh'] + 1;
247
+                    $attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
248
+                    $isRangeRequest = true;
249
+                }
250
+            }
251
+        } while ($isRangeRequest);
252
+
253
+        \OCP\Util::writeLog('user_ldap', 'Requested attribute ' . $attr . ' not found for ' . $dn, ILogger::DEBUG);
254
+        return false;
255
+    }
256
+
257
+    /**
258
+     * Runs an read operation against LDAP
259
+     *
260
+     * @param resource $cr the LDAP connection
261
+     * @param string $dn
262
+     * @param string $attribute
263
+     * @param string $filter
264
+     * @param int $maxResults
265
+     * @return array|bool false if there was any error, true if an exists check
266
+     *                    was performed and the requested DN found, array with the
267
+     *                    returned data on a successful usual operation
268
+     * @throws ServerNotAvailableException
269
+     */
270
+    public function executeRead($cr, $dn, $attribute, $filter, $maxResults) {
271
+        $this->initPagedSearch($filter, $dn, [$attribute], $maxResults, 0);
272
+        $dn = $this->helper->DNasBaseParameter($dn);
273
+        $rr = @$this->invokeLDAPMethod('read', $cr, $dn, $filter, [$attribute]);
274
+        if (!$this->ldap->isResource($rr)) {
275
+            if ($attribute !== '') {
276
+                //do not throw this message on userExists check, irritates
277
+                \OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, ILogger::DEBUG);
278
+            }
279
+            //in case an error occurs , e.g. object does not exist
280
+            return false;
281
+        }
282
+        if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $cr, $rr) === 1)) {
283
+            \OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', ILogger::DEBUG);
284
+            return true;
285
+        }
286
+        $er = $this->invokeLDAPMethod('firstEntry', $cr, $rr);
287
+        if (!$this->ldap->isResource($er)) {
288
+            //did not match the filter, return false
289
+            return false;
290
+        }
291
+        //LDAP attributes are not case sensitive
292
+        $result = \OCP\Util::mb_array_change_key_case(
293
+            $this->invokeLDAPMethod('getAttributes', $cr, $er), MB_CASE_LOWER, 'UTF-8');
294
+
295
+        return $result;
296
+    }
297
+
298
+    /**
299
+     * Normalizes a result grom getAttributes(), i.e. handles DNs and binary
300
+     * data if present.
301
+     *
302
+     * @param array $result from ILDAPWrapper::getAttributes()
303
+     * @param string $attribute the attribute name that was read
304
+     * @return string[]
305
+     */
306
+    public function extractAttributeValuesFromResult($result, $attribute) {
307
+        $values = [];
308
+        if (isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
309
+            $lowercaseAttribute = strtolower($attribute);
310
+            for ($i = 0; $i < $result[$attribute]['count']; $i++) {
311
+                if ($this->resemblesDN($attribute)) {
312
+                    $values[] = $this->helper->sanitizeDN($result[$attribute][$i]);
313
+                } elseif ($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
314
+                    $values[] = $this->convertObjectGUID2Str($result[$attribute][$i]);
315
+                } else {
316
+                    $values[] = $result[$attribute][$i];
317
+                }
318
+            }
319
+        }
320
+        return $values;
321
+    }
322
+
323
+    /**
324
+     * Attempts to find ranged data in a getAttribute results and extracts the
325
+     * returned values as well as information on the range and full attribute
326
+     * name for further processing.
327
+     *
328
+     * @param array $result from ILDAPWrapper::getAttributes()
329
+     * @param string $attribute the attribute name that was read. Without ";range=…"
330
+     * @return array If a range was detected with keys 'values', 'attributeName',
331
+     *               'attributeFull' and 'rangeHigh', otherwise empty.
332
+     */
333
+    public function extractRangeData($result, $attribute) {
334
+        $keys = array_keys($result);
335
+        foreach ($keys as $key) {
336
+            if ($key !== $attribute && strpos($key, $attribute) === 0) {
337
+                $queryData = explode(';', $key);
338
+                if (strpos($queryData[1], 'range=') === 0) {
339
+                    $high = substr($queryData[1], 1 + strpos($queryData[1], '-'));
340
+                    $data = [
341
+                        'values' => $result[$key],
342
+                        'attributeName' => $queryData[0],
343
+                        'attributeFull' => $key,
344
+                        'rangeHigh' => $high,
345
+                    ];
346
+                    return $data;
347
+                }
348
+            }
349
+        }
350
+        return [];
351
+    }
352
+
353
+    /**
354
+     * Set password for an LDAP user identified by a DN
355
+     *
356
+     * @param string $userDN the user in question
357
+     * @param string $password the new password
358
+     * @return bool
359
+     * @throws HintException
360
+     * @throws \Exception
361
+     */
362
+    public function setPassword($userDN, $password) {
363
+        if ((int)$this->connection->turnOnPasswordChange !== 1) {
364
+            throw new \Exception('LDAP password changes are disabled.');
365
+        }
366
+        $cr = $this->connection->getConnectionResource();
367
+        if (!$this->ldap->isResource($cr)) {
368
+            //LDAP not available
369
+            \OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
370
+            return false;
371
+        }
372
+        try {
373
+            // try PASSWD extended operation first
374
+            return @$this->invokeLDAPMethod('exopPasswd', $cr, $userDN, '', $password) ||
375
+                @$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password);
376
+        } catch (ConstraintViolationException $e) {
377
+            throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ') . $e->getMessage(), $e->getCode());
378
+        }
379
+    }
380
+
381
+    /**
382
+     * checks whether the given attributes value is probably a DN
383
+     *
384
+     * @param string $attr the attribute in question
385
+     * @return boolean if so true, otherwise false
386
+     */
387
+    private function resemblesDN($attr) {
388
+        $resemblingAttributes = [
389
+            'dn',
390
+            'uniquemember',
391
+            'member',
392
+            // memberOf is an "operational" attribute, without a definition in any RFC
393
+            'memberof'
394
+        ];
395
+        return in_array($attr, $resemblingAttributes);
396
+    }
397
+
398
+    /**
399
+     * checks whether the given string is probably a DN
400
+     *
401
+     * @param string $string
402
+     * @return boolean
403
+     */
404
+    public function stringResemblesDN($string) {
405
+        $r = $this->ldap->explodeDN($string, 0);
406
+        // if exploding a DN succeeds and does not end up in
407
+        // an empty array except for $r[count] being 0.
408
+        return (is_array($r) && count($r) > 1);
409
+    }
410
+
411
+    /**
412
+     * returns a DN-string that is cleaned from not domain parts, e.g.
413
+     * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
414
+     * becomes dc=foobar,dc=server,dc=org
415
+     *
416
+     * @param string $dn
417
+     * @return string
418
+     */
419
+    public function getDomainDNFromDN($dn) {
420
+        $allParts = $this->ldap->explodeDN($dn, 0);
421
+        if ($allParts === false) {
422
+            //not a valid DN
423
+            return '';
424
+        }
425
+        $domainParts = [];
426
+        $dcFound = false;
427
+        foreach ($allParts as $part) {
428
+            if (!$dcFound && strpos($part, 'dc=') === 0) {
429
+                $dcFound = true;
430
+            }
431
+            if ($dcFound) {
432
+                $domainParts[] = $part;
433
+            }
434
+        }
435
+        return implode(',', $domainParts);
436
+    }
437
+
438
+    /**
439
+     * returns the LDAP DN for the given internal Nextcloud name of the group
440
+     *
441
+     * @param string $name the Nextcloud name in question
442
+     * @return string|false LDAP DN on success, otherwise false
443
+     */
444
+    public function groupname2dn($name) {
445
+        return $this->groupMapper->getDNByName($name);
446
+    }
447
+
448
+    /**
449
+     * returns the LDAP DN for the given internal Nextcloud name of the user
450
+     *
451
+     * @param string $name the Nextcloud name in question
452
+     * @return string|false with the LDAP DN on success, otherwise false
453
+     */
454
+    public function username2dn($name) {
455
+        $fdn = $this->userMapper->getDNByName($name);
456
+
457
+        //Check whether the DN belongs to the Base, to avoid issues on multi-
458
+        //server setups
459
+        if (is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
460
+            return $fdn;
461
+        }
462
+
463
+        return false;
464
+    }
465
+
466
+    /**
467
+     * returns the internal Nextcloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
468
+     *
469
+     * @param string $fdn the dn of the group object
470
+     * @param string $ldapName optional, the display name of the object
471
+     * @return string|false with the name to use in Nextcloud, false on DN outside of search DN
472
+     * @throws \Exception
473
+     */
474
+    public function dn2groupname($fdn, $ldapName = null) {
475
+        //To avoid bypassing the base DN settings under certain circumstances
476
+        //with the group support, check whether the provided DN matches one of
477
+        //the given Bases
478
+        if (!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
479
+            return false;
480
+        }
481
+
482
+        return $this->dn2ocname($fdn, $ldapName, false);
483
+    }
484
+
485
+    /**
486
+     * returns the internal Nextcloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
487
+     *
488
+     * @param string $dn the dn of the user object
489
+     * @param string $ldapName optional, the display name of the object
490
+     * @return string|false with with the name to use in Nextcloud
491
+     * @throws \Exception
492
+     */
493
+    public function dn2username($fdn, $ldapName = null) {
494
+        //To avoid bypassing the base DN settings under certain circumstances
495
+        //with the group support, check whether the provided DN matches one of
496
+        //the given Bases
497
+        if (!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
498
+            return false;
499
+        }
500
+
501
+        return $this->dn2ocname($fdn, $ldapName, true);
502
+    }
503
+
504
+    /**
505
+     * returns an internal Nextcloud name for the given LDAP DN, false on DN outside of search DN
506
+     *
507
+     * @param string $fdn the dn of the user object
508
+     * @param string|null $ldapName optional, the display name of the object
509
+     * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
510
+     * @param bool|null $newlyMapped
511
+     * @param array|null $record
512
+     * @return false|string with with the name to use in Nextcloud
513
+     * @throws \Exception
514
+     */
515
+    public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, array $record = null) {
516
+        $newlyMapped = false;
517
+        if ($isUser) {
518
+            $mapper = $this->getUserMapper();
519
+            $nameAttribute = $this->connection->ldapUserDisplayName;
520
+            $filter = $this->connection->ldapUserFilter;
521
+        } else {
522
+            $mapper = $this->getGroupMapper();
523
+            $nameAttribute = $this->connection->ldapGroupDisplayName;
524
+            $filter = $this->connection->ldapGroupFilter;
525
+        }
526
+
527
+        //let's try to retrieve the Nextcloud name from the mappings table
528
+        $ncName = $mapper->getNameByDN($fdn);
529
+        if (is_string($ncName)) {
530
+            return $ncName;
531
+        }
532
+
533
+        //second try: get the UUID and check if it is known. Then, update the DN and return the name.
534
+        $uuid = $this->getUUID($fdn, $isUser, $record);
535
+        if (is_string($uuid)) {
536
+            $ncName = $mapper->getNameByUUID($uuid);
537
+            if (is_string($ncName)) {
538
+                $mapper->setDNbyUUID($fdn, $uuid);
539
+                return $ncName;
540
+            }
541
+        } else {
542
+            //If the UUID can't be detected something is foul.
543
+            \OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for ' . $fdn . '. Skipping.', ILogger::INFO);
544
+            return false;
545
+        }
546
+
547
+        if (is_null($ldapName)) {
548
+            $ldapName = $this->readAttribute($fdn, $nameAttribute, $filter);
549
+            if (!isset($ldapName[0]) && empty($ldapName[0])) {
550
+                \OCP\Util::writeLog('user_ldap', 'No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ILogger::INFO);
551
+                return false;
552
+            }
553
+            $ldapName = $ldapName[0];
554
+        }
555
+
556
+        if ($isUser) {
557
+            $usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
558
+            if ($usernameAttribute !== '') {
559
+                $username = $this->readAttribute($fdn, $usernameAttribute);
560
+                $username = $username[0];
561
+            } else {
562
+                $username = $uuid;
563
+            }
564
+            try {
565
+                $intName = $this->sanitizeUsername($username);
566
+            } catch (\InvalidArgumentException $e) {
567
+                \OC::$server->getLogger()->logException($e, [
568
+                    'app' => 'user_ldap',
569
+                    'level' => ILogger::WARN,
570
+                ]);
571
+                // we don't attempt to set a username here. We can go for
572
+                // for an alternative 4 digit random number as we would append
573
+                // otherwise, however it's likely not enough space in bigger
574
+                // setups, and most importantly: this is not intended.
575
+                return false;
576
+            }
577
+        } else {
578
+            $intName = $ldapName;
579
+        }
580
+
581
+        //a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
582
+        //disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
583
+        //NOTE: mind, disabling cache affects only this instance! Using it
584
+        // outside of core user management will still cache the user as non-existing.
585
+        $originalTTL = $this->connection->ldapCacheTTL;
586
+        $this->connection->setConfiguration(['ldapCacheTTL' => 0]);
587
+        if ($intName !== ''
588
+            && (($isUser && !$this->ncUserManager->userExists($intName))
589
+                || (!$isUser && !\OC::$server->getGroupManager()->groupExists($intName))
590
+            )
591
+        ) {
592
+            $this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
593
+            $newlyMapped = $this->mapAndAnnounceIfApplicable($mapper, $fdn, $intName, $uuid, $isUser);
594
+            if ($newlyMapped) {
595
+                return $intName;
596
+            }
597
+        }
598
+
599
+        $this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
600
+        $altName = $this->createAltInternalOwnCloudName($intName, $isUser);
601
+        if (is_string($altName)) {
602
+            if ($this->mapAndAnnounceIfApplicable($mapper, $fdn, $altName, $uuid, $isUser)) {
603
+                $newlyMapped = true;
604
+                return $altName;
605
+            }
606
+        }
607
+
608
+        //if everything else did not help..
609
+        \OCP\Util::writeLog('user_ldap', 'Could not create unique name for ' . $fdn . '.', ILogger::INFO);
610
+        return false;
611
+    }
612
+
613
+    public function mapAndAnnounceIfApplicable(
614
+        AbstractMapping $mapper,
615
+        string $fdn,
616
+        string $name,
617
+        string $uuid,
618
+        bool $isUser
619
+    ): bool {
620
+        if ($mapper->map($fdn, $name, $uuid)) {
621
+            if ($this->ncUserManager instanceof PublicEmitter && $isUser) {
622
+                $this->cacheUserExists($name);
623
+                $this->ncUserManager->emit('\OC\User', 'assignedUserId', [$name]);
624
+            } elseif (!$isUser) {
625
+                $this->cacheGroupExists($name);
626
+            }
627
+            return true;
628
+        }
629
+        return false;
630
+    }
631
+
632
+    /**
633
+     * gives back the user names as they are used ownClod internally
634
+     *
635
+     * @param array $ldapUsers as returned by fetchList()
636
+     * @return array an array with the user names to use in Nextcloud
637
+     *
638
+     * gives back the user names as they are used ownClod internally
639
+     * @throws \Exception
640
+     */
641
+    public function nextcloudUserNames($ldapUsers) {
642
+        return $this->ldap2NextcloudNames($ldapUsers, true);
643
+    }
644
+
645
+    /**
646
+     * gives back the group names as they are used ownClod internally
647
+     *
648
+     * @param array $ldapGroups as returned by fetchList()
649
+     * @return array an array with the group names to use in Nextcloud
650
+     *
651
+     * gives back the group names as they are used ownClod internally
652
+     * @throws \Exception
653
+     */
654
+    public function nextcloudGroupNames($ldapGroups) {
655
+        return $this->ldap2NextcloudNames($ldapGroups, false);
656
+    }
657
+
658
+    /**
659
+     * @param array $ldapObjects as returned by fetchList()
660
+     * @param bool $isUsers
661
+     * @return array
662
+     * @throws \Exception
663
+     */
664
+    private function ldap2NextcloudNames($ldapObjects, $isUsers) {
665
+        if ($isUsers) {
666
+            $nameAttribute = $this->connection->ldapUserDisplayName;
667
+            $sndAttribute = $this->connection->ldapUserDisplayName2;
668
+        } else {
669
+            $nameAttribute = $this->connection->ldapGroupDisplayName;
670
+        }
671
+        $nextcloudNames = [];
672
+
673
+        foreach ($ldapObjects as $ldapObject) {
674
+            $nameByLDAP = null;
675
+            if (isset($ldapObject[$nameAttribute])
676
+                && is_array($ldapObject[$nameAttribute])
677
+                && isset($ldapObject[$nameAttribute][0])
678
+            ) {
679
+                // might be set, but not necessarily. if so, we use it.
680
+                $nameByLDAP = $ldapObject[$nameAttribute][0];
681
+            }
682
+
683
+            $ncName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
684
+            if ($ncName) {
685
+                $nextcloudNames[] = $ncName;
686
+                if ($isUsers) {
687
+                    $this->updateUserState($ncName);
688
+                    //cache the user names so it does not need to be retrieved
689
+                    //again later (e.g. sharing dialogue).
690
+                    if (is_null($nameByLDAP)) {
691
+                        continue;
692
+                    }
693
+                    $sndName = isset($ldapObject[$sndAttribute][0])
694
+                        ? $ldapObject[$sndAttribute][0] : '';
695
+                    $this->cacheUserDisplayName($ncName, $nameByLDAP, $sndName);
696
+                } elseif ($nameByLDAP !== null) {
697
+                    $this->cacheGroupDisplayName($ncName, $nameByLDAP);
698
+                }
699
+            }
700
+        }
701
+        return $nextcloudNames;
702
+    }
703
+
704
+    /**
705
+     * removes the deleted-flag of a user if it was set
706
+     *
707
+     * @param string $ncname
708
+     * @throws \Exception
709
+     */
710
+    public function updateUserState($ncname) {
711
+        $user = $this->userManager->get($ncname);
712
+        if ($user instanceof OfflineUser) {
713
+            $user->unmark();
714
+        }
715
+    }
716
+
717
+    /**
718
+     * caches the user display name
719
+     *
720
+     * @param string $ocName the internal Nextcloud username
721
+     * @param string|false $home the home directory path
722
+     */
723
+    public function cacheUserHome($ocName, $home) {
724
+        $cacheKey = 'getHome' . $ocName;
725
+        $this->connection->writeToCache($cacheKey, $home);
726
+    }
727
+
728
+    /**
729
+     * caches a user as existing
730
+     *
731
+     * @param string $ocName the internal Nextcloud username
732
+     */
733
+    public function cacheUserExists($ocName) {
734
+        $this->connection->writeToCache('userExists' . $ocName, true);
735
+    }
736
+
737
+    /**
738
+     * caches a group as existing
739
+     */
740
+    public function cacheGroupExists(string $gid): void {
741
+        $this->connection->writeToCache('groupExists' . $gid, true);
742
+    }
743
+
744
+    /**
745
+     * caches the user display name
746
+     *
747
+     * @param string $ocName the internal Nextcloud username
748
+     * @param string $displayName the display name
749
+     * @param string $displayName2 the second display name
750
+     * @throws \Exception
751
+     */
752
+    public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
753
+        $user = $this->userManager->get($ocName);
754
+        if ($user === null) {
755
+            return;
756
+        }
757
+        $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
758
+        $cacheKeyTrunk = 'getDisplayName';
759
+        $this->connection->writeToCache($cacheKeyTrunk . $ocName, $displayName);
760
+    }
761
+
762
+    public function cacheGroupDisplayName(string $ncName, string $displayName): void {
763
+        $cacheKey = 'group_getDisplayName' . $ncName;
764
+        $this->connection->writeToCache($cacheKey, $displayName);
765
+    }
766
+
767
+    /**
768
+     * creates a unique name for internal Nextcloud use for users. Don't call it directly.
769
+     *
770
+     * @param string $name the display name of the object
771
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful
772
+     *
773
+     * Instead of using this method directly, call
774
+     * createAltInternalOwnCloudName($name, true)
775
+     */
776
+    private function _createAltInternalOwnCloudNameForUsers($name) {
777
+        $attempts = 0;
778
+        //while loop is just a precaution. If a name is not generated within
779
+        //20 attempts, something else is very wrong. Avoids infinite loop.
780
+        while ($attempts < 20) {
781
+            $altName = $name . '_' . rand(1000, 9999);
782
+            if (!$this->ncUserManager->userExists($altName)) {
783
+                return $altName;
784
+            }
785
+            $attempts++;
786
+        }
787
+        return false;
788
+    }
789
+
790
+    /**
791
+     * creates a unique name for internal Nextcloud use for groups. Don't call it directly.
792
+     *
793
+     * @param string $name the display name of the object
794
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful.
795
+     *
796
+     * Instead of using this method directly, call
797
+     * createAltInternalOwnCloudName($name, false)
798
+     *
799
+     * Group names are also used as display names, so we do a sequential
800
+     * numbering, e.g. Developers_42 when there are 41 other groups called
801
+     * "Developers"
802
+     */
803
+    private function _createAltInternalOwnCloudNameForGroups($name) {
804
+        $usedNames = $this->groupMapper->getNamesBySearch($name, "", '_%');
805
+        if (!$usedNames || count($usedNames) === 0) {
806
+            $lastNo = 1; //will become name_2
807
+        } else {
808
+            natsort($usedNames);
809
+            $lastName = array_pop($usedNames);
810
+            $lastNo = (int)substr($lastName, strrpos($lastName, '_') + 1);
811
+        }
812
+        $altName = $name . '_' . (string)($lastNo + 1);
813
+        unset($usedNames);
814
+
815
+        $attempts = 1;
816
+        while ($attempts < 21) {
817
+            // Check to be really sure it is unique
818
+            // while loop is just a precaution. If a name is not generated within
819
+            // 20 attempts, something else is very wrong. Avoids infinite loop.
820
+            if (!\OC::$server->getGroupManager()->groupExists($altName)) {
821
+                return $altName;
822
+            }
823
+            $altName = $name . '_' . ($lastNo + $attempts);
824
+            $attempts++;
825
+        }
826
+        return false;
827
+    }
828
+
829
+    /**
830
+     * creates a unique name for internal Nextcloud use.
831
+     *
832
+     * @param string $name the display name of the object
833
+     * @param boolean $isUser whether name should be created for a user (true) or a group (false)
834
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful
835
+     */
836
+    private function createAltInternalOwnCloudName($name, $isUser) {
837
+        $originalTTL = $this->connection->ldapCacheTTL;
838
+        $this->connection->setConfiguration(['ldapCacheTTL' => 0]);
839
+        if ($isUser) {
840
+            $altName = $this->_createAltInternalOwnCloudNameForUsers($name);
841
+        } else {
842
+            $altName = $this->_createAltInternalOwnCloudNameForGroups($name);
843
+        }
844
+        $this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
845
+
846
+        return $altName;
847
+    }
848
+
849
+    /**
850
+     * fetches a list of users according to a provided loginName and utilizing
851
+     * the login filter.
852
+     *
853
+     * @param string $loginName
854
+     * @param array $attributes optional, list of attributes to read
855
+     * @return array
856
+     */
857
+    public function fetchUsersByLoginName($loginName, $attributes = ['dn']) {
858
+        $loginName = $this->escapeFilterPart($loginName);
859
+        $filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
860
+        return $this->fetchListOfUsers($filter, $attributes);
861
+    }
862
+
863
+    /**
864
+     * counts the number of users according to a provided loginName and
865
+     * utilizing the login filter.
866
+     *
867
+     * @param string $loginName
868
+     * @return int
869
+     */
870
+    public function countUsersByLoginName($loginName) {
871
+        $loginName = $this->escapeFilterPart($loginName);
872
+        $filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
873
+        return $this->countUsers($filter);
874
+    }
875
+
876
+    /**
877
+     * @param string $filter
878
+     * @param string|string[] $attr
879
+     * @param int $limit
880
+     * @param int $offset
881
+     * @param bool $forceApplyAttributes
882
+     * @return array
883
+     * @throws \Exception
884
+     */
885
+    public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null, $forceApplyAttributes = false) {
886
+        $ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
887
+        $recordsToUpdate = $ldapRecords;
888
+        if (!$forceApplyAttributes) {
889
+            $isBackgroundJobModeAjax = $this->config
890
+                    ->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
891
+            $listOfDNs = array_reduce($ldapRecords, function ($listOfDNs, $entry) {
892
+                $listOfDNs[] = $entry['dn'][0];
893
+                return $listOfDNs;
894
+            }, []);
895
+            $idsByDn = $this->userMapper->getListOfIdsByDn($listOfDNs);
896
+            $recordsToUpdate = array_filter($ldapRecords, function ($record) use ($isBackgroundJobModeAjax, $idsByDn) {
897
+                $newlyMapped = false;
898
+                $uid = $idsByDn[$record['dn'][0]] ?? null;
899
+                if ($uid === null) {
900
+                    $uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record);
901
+                }
902
+                if (is_string($uid)) {
903
+                    $this->cacheUserExists($uid);
904
+                }
905
+                return ($uid !== false) && ($newlyMapped || $isBackgroundJobModeAjax);
906
+            });
907
+        }
908
+        $this->batchApplyUserAttributes($recordsToUpdate);
909
+        return $this->fetchList($ldapRecords, $this->manyAttributes($attr));
910
+    }
911
+
912
+    /**
913
+     * provided with an array of LDAP user records the method will fetch the
914
+     * user object and requests it to process the freshly fetched attributes and
915
+     * and their values
916
+     *
917
+     * @param array $ldapRecords
918
+     * @throws \Exception
919
+     */
920
+    public function batchApplyUserAttributes(array $ldapRecords) {
921
+        $displayNameAttribute = strtolower($this->connection->ldapUserDisplayName);
922
+        foreach ($ldapRecords as $userRecord) {
923
+            if (!isset($userRecord[$displayNameAttribute])) {
924
+                // displayName is obligatory
925
+                continue;
926
+            }
927
+            $ocName = $this->dn2ocname($userRecord['dn'][0], null, true);
928
+            if ($ocName === false) {
929
+                continue;
930
+            }
931
+            $this->updateUserState($ocName);
932
+            $user = $this->userManager->get($ocName);
933
+            if ($user !== null) {
934
+                $user->processAttributes($userRecord);
935
+            } else {
936
+                \OC::$server->getLogger()->debug(
937
+                    "The ldap user manager returned null for $ocName",
938
+                    ['app' => 'user_ldap']
939
+                );
940
+            }
941
+        }
942
+    }
943
+
944
+    /**
945
+     * @param string $filter
946
+     * @param string|string[] $attr
947
+     * @param int $limit
948
+     * @param int $offset
949
+     * @return array
950
+     */
951
+    public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
952
+        $groupRecords = $this->searchGroups($filter, $attr, $limit, $offset);
953
+
954
+        $listOfDNs = array_reduce($groupRecords, function ($listOfDNs, $entry) {
955
+            $listOfDNs[] = $entry['dn'][0];
956
+            return $listOfDNs;
957
+        }, []);
958
+        $idsByDn = $this->groupMapper->getListOfIdsByDn($listOfDNs);
959
+
960
+        array_walk($groupRecords, function ($record) use ($idsByDn) {
961
+            $newlyMapped = false;
962
+            $gid = $uidsByDn[$record['dn'][0]] ?? null;
963
+            if ($gid === null) {
964
+                $gid = $this->dn2ocname($record['dn'][0], null, false, $newlyMapped, $record);
965
+            }
966
+            if (!$newlyMapped && is_string($gid)) {
967
+                $this->cacheGroupExists($gid);
968
+            }
969
+        });
970
+        return $this->fetchList($groupRecords, $this->manyAttributes($attr));
971
+    }
972
+
973
+    /**
974
+     * @param array $list
975
+     * @param bool $manyAttributes
976
+     * @return array
977
+     */
978
+    private function fetchList($list, $manyAttributes) {
979
+        if (is_array($list)) {
980
+            if ($manyAttributes) {
981
+                return $list;
982
+            } else {
983
+                $list = array_reduce($list, function ($carry, $item) {
984
+                    $attribute = array_keys($item)[0];
985
+                    $carry[] = $item[$attribute][0];
986
+                    return $carry;
987
+                }, []);
988
+                return array_unique($list, SORT_LOCALE_STRING);
989
+            }
990
+        }
991
+
992
+        //error cause actually, maybe throw an exception in future.
993
+        return [];
994
+    }
995
+
996
+    /**
997
+     * executes an LDAP search, optimized for Users
998
+     *
999
+     * @param string $filter the LDAP filter for the search
1000
+     * @param string|string[] $attr optional, when a certain attribute shall be filtered out
1001
+     * @param integer $limit
1002
+     * @param integer $offset
1003
+     * @return array with the search result
1004
+     *
1005
+     * Executes an LDAP search
1006
+     * @throws ServerNotAvailableException
1007
+     */
1008
+    public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
1009
+        $result = [];
1010
+        foreach ($this->connection->ldapBaseUsers as $base) {
1011
+            $result = array_merge($result, $this->search($filter, $base, $attr, $limit, $offset));
1012
+        }
1013
+        return $result;
1014
+    }
1015
+
1016
+    /**
1017
+     * @param string $filter
1018
+     * @param string|string[] $attr
1019
+     * @param int $limit
1020
+     * @param int $offset
1021
+     * @return false|int
1022
+     * @throws ServerNotAvailableException
1023
+     */
1024
+    public function countUsers($filter, $attr = ['dn'], $limit = null, $offset = null) {
1025
+        $result = false;
1026
+        foreach ($this->connection->ldapBaseUsers as $base) {
1027
+            $count = $this->count($filter, [$base], $attr, $limit, $offset);
1028
+            $result = is_int($count) ? (int)$result + $count : $result;
1029
+        }
1030
+        return $result;
1031
+    }
1032
+
1033
+    /**
1034
+     * executes an LDAP search, optimized for Groups
1035
+     *
1036
+     * @param string $filter the LDAP filter for the search
1037
+     * @param string|string[] $attr optional, when a certain attribute shall be filtered out
1038
+     * @param integer $limit
1039
+     * @param integer $offset
1040
+     * @return array with the search result
1041
+     *
1042
+     * Executes an LDAP search
1043
+     * @throws ServerNotAvailableException
1044
+     */
1045
+    public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
1046
+        $result = [];
1047
+        foreach ($this->connection->ldapBaseGroups as $base) {
1048
+            $result = array_merge($result, $this->search($filter, $base, $attr, $limit, $offset));
1049
+        }
1050
+        return $result;
1051
+    }
1052
+
1053
+    /**
1054
+     * returns the number of available groups
1055
+     *
1056
+     * @param string $filter the LDAP search filter
1057
+     * @param string[] $attr optional
1058
+     * @param int|null $limit
1059
+     * @param int|null $offset
1060
+     * @return int|bool
1061
+     * @throws ServerNotAvailableException
1062
+     */
1063
+    public function countGroups($filter, $attr = ['dn'], $limit = null, $offset = null) {
1064
+        $result = false;
1065
+        foreach ($this->connection->ldapBaseGroups as $base) {
1066
+            $count = $this->count($filter, [$base], $attr, $limit, $offset);
1067
+            $result = is_int($count) ? (int)$result + $count : $result;
1068
+        }
1069
+        return $result;
1070
+    }
1071
+
1072
+    /**
1073
+     * returns the number of available objects on the base DN
1074
+     *
1075
+     * @param int|null $limit
1076
+     * @param int|null $offset
1077
+     * @return int|bool
1078
+     * @throws ServerNotAvailableException
1079
+     */
1080
+    public function countObjects($limit = null, $offset = null) {
1081
+        $result = false;
1082
+        foreach ($this->connection->ldapBase as $base) {
1083
+            $count = $this->count('objectclass=*', [$base], ['dn'], $limit, $offset);
1084
+            $result = is_int($count) ? (int)$result + $count : $result;
1085
+        }
1086
+        return $result;
1087
+    }
1088
+
1089
+    /**
1090
+     * Returns the LDAP handler
1091
+     *
1092
+     * @throws \OC\ServerNotAvailableException
1093
+     */
1094
+
1095
+    /**
1096
+     * @return mixed
1097
+     * @throws \OC\ServerNotAvailableException
1098
+     */
1099
+    private function invokeLDAPMethod() {
1100
+        $arguments = func_get_args();
1101
+        $command = array_shift($arguments);
1102
+        $cr = array_shift($arguments);
1103
+        if (!method_exists($this->ldap, $command)) {
1104
+            return null;
1105
+        }
1106
+        array_unshift($arguments, $cr);
1107
+        // php no longer supports call-time pass-by-reference
1108
+        // thus cannot support controlPagedResultResponse as the third argument
1109
+        // is a reference
1110
+        $doMethod = function () use ($command, &$arguments) {
1111
+            if ($command == 'controlPagedResultResponse') {
1112
+                throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.');
1113
+            } else {
1114
+                return call_user_func_array([$this->ldap, $command], $arguments);
1115
+            }
1116
+        };
1117
+        try {
1118
+            $ret = $doMethod();
1119
+        } catch (ServerNotAvailableException $e) {
1120
+            /* Server connection lost, attempt to reestablish it
1121 1121
 			 * Maybe implement exponential backoff?
1122 1122
 			 * This was enough to get solr indexer working which has large delays between LDAP fetches.
1123 1123
 			 */
1124
-			\OCP\Util::writeLog('user_ldap', "Connection lost on $command, attempting to reestablish.", ILogger::DEBUG);
1125
-			$this->connection->resetConnectionResource();
1126
-			$cr = $this->connection->getConnectionResource();
1127
-
1128
-			if (!$this->ldap->isResource($cr)) {
1129
-				// Seems like we didn't find any resource.
1130
-				\OCP\Util::writeLog('user_ldap', "Could not $command, because resource is missing.", ILogger::DEBUG);
1131
-				throw $e;
1132
-			}
1133
-
1134
-			$arguments[0] = $cr;
1135
-			$ret = $doMethod();
1136
-		}
1137
-		return $ret;
1138
-	}
1139
-
1140
-	/**
1141
-	 * retrieved. Results will according to the order in the array.
1142
-	 *
1143
-	 * @param string $filter
1144
-	 * @param string $base
1145
-	 * @param string[] $attr
1146
-	 * @param int|null $limit optional, maximum results to be counted
1147
-	 * @param int|null $offset optional, a starting point
1148
-	 * @return array|false array with the search result as first value and pagedSearchOK as
1149
-	 * second | false if not successful
1150
-	 * @throws ServerNotAvailableException
1151
-	 */
1152
-	private function executeSearch(
1153
-		string $filter,
1154
-		string $base,
1155
-		?array &$attr,
1156
-		?int $limit,
1157
-		?int $offset
1158
-	) {
1159
-		// See if we have a resource, in case not cancel with message
1160
-		$cr = $this->connection->getConnectionResource();
1161
-		if (!$this->ldap->isResource($cr)) {
1162
-			// Seems like we didn't find any resource.
1163
-			// Return an empty array just like before.
1164
-			\OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', ILogger::DEBUG);
1165
-			return false;
1166
-		}
1167
-
1168
-		//check whether paged search should be attempted
1169
-		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, (int)$offset);
1170
-
1171
-		$sr = $this->invokeLDAPMethod('search', $cr, $base, $filter, $attr);
1172
-		// cannot use $cr anymore, might have changed in the previous call!
1173
-		$error = $this->ldap->errno($this->connection->getConnectionResource());
1174
-		if (!$this->ldap->isResource($sr) || $error !== 0) {
1175
-			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  ' . print_r($pagedSearchOK, true), ILogger::ERROR);
1176
-			return false;
1177
-		}
1178
-
1179
-		return [$sr, $pagedSearchOK];
1180
-	}
1181
-
1182
-	/**
1183
-	 * processes an LDAP paged search operation
1184
-	 *
1185
-	 * @param resource $sr the array containing the LDAP search resources
1186
-	 * @param int $foundItems number of results in the single search operation
1187
-	 * @param int $limit maximum results to be counted
1188
-	 * @param bool $pagedSearchOK whether a paged search has been executed
1189
-	 * @param bool $skipHandling required for paged search when cookies to
1190
-	 * prior results need to be gained
1191
-	 * @return bool cookie validity, true if we have more pages, false otherwise.
1192
-	 * @throws ServerNotAvailableException
1193
-	 */
1194
-	private function processPagedSearchStatus(
1195
-		$sr,
1196
-		int $foundItems,
1197
-		int $limit,
1198
-		bool $pagedSearchOK,
1199
-		bool $skipHandling
1200
-	): bool {
1201
-		$cookie = null;
1202
-		if ($pagedSearchOK) {
1203
-			$cr = $this->connection->getConnectionResource();
1204
-			if ($this->ldap->controlPagedResultResponse($cr, $sr, $cookie)) {
1205
-				$this->lastCookie = $cookie;
1206
-			}
1207
-
1208
-			//browsing through prior pages to get the cookie for the new one
1209
-			if ($skipHandling) {
1210
-				return false;
1211
-			}
1212
-			// if count is bigger, then the server does not support
1213
-			// paged search. Instead, he did a normal search. We set a
1214
-			// flag here, so the callee knows how to deal with it.
1215
-			if ($foundItems <= $limit) {
1216
-				$this->pagedSearchedSuccessful = true;
1217
-			}
1218
-		} else {
1219
-			if (!is_null($limit) && (int)$this->connection->ldapPagingSize !== 0) {
1220
-				\OC::$server->getLogger()->debug(
1221
-					'Paged search was not available',
1222
-					['app' => 'user_ldap']
1223
-				);
1224
-			}
1225
-		}
1226
-		/* ++ Fixing RHDS searches with pages with zero results ++
1124
+            \OCP\Util::writeLog('user_ldap', "Connection lost on $command, attempting to reestablish.", ILogger::DEBUG);
1125
+            $this->connection->resetConnectionResource();
1126
+            $cr = $this->connection->getConnectionResource();
1127
+
1128
+            if (!$this->ldap->isResource($cr)) {
1129
+                // Seems like we didn't find any resource.
1130
+                \OCP\Util::writeLog('user_ldap', "Could not $command, because resource is missing.", ILogger::DEBUG);
1131
+                throw $e;
1132
+            }
1133
+
1134
+            $arguments[0] = $cr;
1135
+            $ret = $doMethod();
1136
+        }
1137
+        return $ret;
1138
+    }
1139
+
1140
+    /**
1141
+     * retrieved. Results will according to the order in the array.
1142
+     *
1143
+     * @param string $filter
1144
+     * @param string $base
1145
+     * @param string[] $attr
1146
+     * @param int|null $limit optional, maximum results to be counted
1147
+     * @param int|null $offset optional, a starting point
1148
+     * @return array|false array with the search result as first value and pagedSearchOK as
1149
+     * second | false if not successful
1150
+     * @throws ServerNotAvailableException
1151
+     */
1152
+    private function executeSearch(
1153
+        string $filter,
1154
+        string $base,
1155
+        ?array &$attr,
1156
+        ?int $limit,
1157
+        ?int $offset
1158
+    ) {
1159
+        // See if we have a resource, in case not cancel with message
1160
+        $cr = $this->connection->getConnectionResource();
1161
+        if (!$this->ldap->isResource($cr)) {
1162
+            // Seems like we didn't find any resource.
1163
+            // Return an empty array just like before.
1164
+            \OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', ILogger::DEBUG);
1165
+            return false;
1166
+        }
1167
+
1168
+        //check whether paged search should be attempted
1169
+        $pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, (int)$offset);
1170
+
1171
+        $sr = $this->invokeLDAPMethod('search', $cr, $base, $filter, $attr);
1172
+        // cannot use $cr anymore, might have changed in the previous call!
1173
+        $error = $this->ldap->errno($this->connection->getConnectionResource());
1174
+        if (!$this->ldap->isResource($sr) || $error !== 0) {
1175
+            \OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  ' . print_r($pagedSearchOK, true), ILogger::ERROR);
1176
+            return false;
1177
+        }
1178
+
1179
+        return [$sr, $pagedSearchOK];
1180
+    }
1181
+
1182
+    /**
1183
+     * processes an LDAP paged search operation
1184
+     *
1185
+     * @param resource $sr the array containing the LDAP search resources
1186
+     * @param int $foundItems number of results in the single search operation
1187
+     * @param int $limit maximum results to be counted
1188
+     * @param bool $pagedSearchOK whether a paged search has been executed
1189
+     * @param bool $skipHandling required for paged search when cookies to
1190
+     * prior results need to be gained
1191
+     * @return bool cookie validity, true if we have more pages, false otherwise.
1192
+     * @throws ServerNotAvailableException
1193
+     */
1194
+    private function processPagedSearchStatus(
1195
+        $sr,
1196
+        int $foundItems,
1197
+        int $limit,
1198
+        bool $pagedSearchOK,
1199
+        bool $skipHandling
1200
+    ): bool {
1201
+        $cookie = null;
1202
+        if ($pagedSearchOK) {
1203
+            $cr = $this->connection->getConnectionResource();
1204
+            if ($this->ldap->controlPagedResultResponse($cr, $sr, $cookie)) {
1205
+                $this->lastCookie = $cookie;
1206
+            }
1207
+
1208
+            //browsing through prior pages to get the cookie for the new one
1209
+            if ($skipHandling) {
1210
+                return false;
1211
+            }
1212
+            // if count is bigger, then the server does not support
1213
+            // paged search. Instead, he did a normal search. We set a
1214
+            // flag here, so the callee knows how to deal with it.
1215
+            if ($foundItems <= $limit) {
1216
+                $this->pagedSearchedSuccessful = true;
1217
+            }
1218
+        } else {
1219
+            if (!is_null($limit) && (int)$this->connection->ldapPagingSize !== 0) {
1220
+                \OC::$server->getLogger()->debug(
1221
+                    'Paged search was not available',
1222
+                    ['app' => 'user_ldap']
1223
+                );
1224
+            }
1225
+        }
1226
+        /* ++ Fixing RHDS searches with pages with zero results ++
1227 1227
 		 * Return cookie status. If we don't have more pages, with RHDS
1228 1228
 		 * cookie is null, with openldap cookie is an empty string and
1229 1229
 		 * to 386ds '0' is a valid cookie. Even if $iFoundItems == 0
1230 1230
 		 */
1231
-		return !empty($cookie) || $cookie === '0';
1232
-	}
1233
-
1234
-	/**
1235
-	 * executes an LDAP search, but counts the results only
1236
-	 *
1237
-	 * @param string $filter the LDAP filter for the search
1238
-	 * @param array $bases an array containing the LDAP subtree(s) that shall be searched
1239
-	 * @param string|string[] $attr optional, array, one or more attributes that shall be
1240
-	 * retrieved. Results will according to the order in the array.
1241
-	 * @param int $limit optional, maximum results to be counted
1242
-	 * @param int $offset optional, a starting point
1243
-	 * @param bool $skipHandling indicates whether the pages search operation is
1244
-	 * completed
1245
-	 * @return int|false Integer or false if the search could not be initialized
1246
-	 * @throws ServerNotAvailableException
1247
-	 */
1248
-	private function count(
1249
-		string $filter,
1250
-		array $bases,
1251
-		$attr = null,
1252
-		?int $limit = null,
1253
-		?int $offset = null,
1254
-		bool $skipHandling = false
1255
-	) {
1256
-		\OC::$server->getLogger()->debug('Count filter: {filter}', [
1257
-			'app' => 'user_ldap',
1258
-			'filter' => $filter
1259
-		]);
1260
-
1261
-		if (!is_null($attr) && !is_array($attr)) {
1262
-			$attr = [mb_strtolower($attr, 'UTF-8')];
1263
-		}
1264
-
1265
-		$limitPerPage = (int)$this->connection->ldapPagingSize;
1266
-		if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1267
-			$limitPerPage = $limit;
1268
-		}
1269
-
1270
-		$counter = 0;
1271
-		$count = null;
1272
-		$this->connection->getConnectionResource();
1273
-
1274
-		foreach ($bases as $base) {
1275
-			do {
1276
-				$search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1277
-				if ($search === false) {
1278
-					return $counter > 0 ? $counter : false;
1279
-				}
1280
-				list($sr, $pagedSearchOK) = $search;
1281
-
1282
-				/* ++ Fixing RHDS searches with pages with zero results ++
1231
+        return !empty($cookie) || $cookie === '0';
1232
+    }
1233
+
1234
+    /**
1235
+     * executes an LDAP search, but counts the results only
1236
+     *
1237
+     * @param string $filter the LDAP filter for the search
1238
+     * @param array $bases an array containing the LDAP subtree(s) that shall be searched
1239
+     * @param string|string[] $attr optional, array, one or more attributes that shall be
1240
+     * retrieved. Results will according to the order in the array.
1241
+     * @param int $limit optional, maximum results to be counted
1242
+     * @param int $offset optional, a starting point
1243
+     * @param bool $skipHandling indicates whether the pages search operation is
1244
+     * completed
1245
+     * @return int|false Integer or false if the search could not be initialized
1246
+     * @throws ServerNotAvailableException
1247
+     */
1248
+    private function count(
1249
+        string $filter,
1250
+        array $bases,
1251
+        $attr = null,
1252
+        ?int $limit = null,
1253
+        ?int $offset = null,
1254
+        bool $skipHandling = false
1255
+    ) {
1256
+        \OC::$server->getLogger()->debug('Count filter: {filter}', [
1257
+            'app' => 'user_ldap',
1258
+            'filter' => $filter
1259
+        ]);
1260
+
1261
+        if (!is_null($attr) && !is_array($attr)) {
1262
+            $attr = [mb_strtolower($attr, 'UTF-8')];
1263
+        }
1264
+
1265
+        $limitPerPage = (int)$this->connection->ldapPagingSize;
1266
+        if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1267
+            $limitPerPage = $limit;
1268
+        }
1269
+
1270
+        $counter = 0;
1271
+        $count = null;
1272
+        $this->connection->getConnectionResource();
1273
+
1274
+        foreach ($bases as $base) {
1275
+            do {
1276
+                $search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1277
+                if ($search === false) {
1278
+                    return $counter > 0 ? $counter : false;
1279
+                }
1280
+                list($sr, $pagedSearchOK) = $search;
1281
+
1282
+                /* ++ Fixing RHDS searches with pages with zero results ++
1283 1283
 				 * countEntriesInSearchResults() method signature changed
1284 1284
 				 * by removing $limit and &$hasHitLimit parameters
1285 1285
 				 */
1286
-				$count = $this->countEntriesInSearchResults($sr);
1287
-				$counter += $count;
1286
+                $count = $this->countEntriesInSearchResults($sr);
1287
+                $counter += $count;
1288 1288
 
1289
-				$hasMorePages = $this->processPagedSearchStatus($sr, $count, $limitPerPage, $pagedSearchOK, $skipHandling);
1290
-				$offset += $limitPerPage;
1291
-				/* ++ Fixing RHDS searches with pages with zero results ++
1289
+                $hasMorePages = $this->processPagedSearchStatus($sr, $count, $limitPerPage, $pagedSearchOK, $skipHandling);
1290
+                $offset += $limitPerPage;
1291
+                /* ++ Fixing RHDS searches with pages with zero results ++
1292 1292
 				 * Continue now depends on $hasMorePages value
1293 1293
 				 */
1294
-				$continue = $pagedSearchOK && $hasMorePages;
1295
-			} while ($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
1296
-		}
1297
-
1298
-		return $counter;
1299
-	}
1300
-
1301
-	/**
1302
-	 * @param resource $sr
1303
-	 * @return int
1304
-	 * @throws ServerNotAvailableException
1305
-	 */
1306
-	private function countEntriesInSearchResults($sr): int {
1307
-		return (int)$this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $sr);
1308
-	}
1309
-
1310
-	/**
1311
-	 * Executes an LDAP search
1312
-	 *
1313
-	 * @throws ServerNotAvailableException
1314
-	 */
1315
-	public function search(
1316
-		string $filter,
1317
-		string $base,
1318
-		?array $attr = null,
1319
-		?int $limit = null,
1320
-		?int $offset = null,
1321
-		bool $skipHandling = false
1322
-	): array {
1323
-		$limitPerPage = (int)$this->connection->ldapPagingSize;
1324
-		if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1325
-			$limitPerPage = $limit;
1326
-		}
1327
-
1328
-		if (!is_null($attr) && !is_array($attr)) {
1329
-			$attr = [mb_strtolower($attr, 'UTF-8')];
1330
-		}
1331
-
1332
-		/* ++ Fixing RHDS searches with pages with zero results ++
1294
+                $continue = $pagedSearchOK && $hasMorePages;
1295
+            } while ($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
1296
+        }
1297
+
1298
+        return $counter;
1299
+    }
1300
+
1301
+    /**
1302
+     * @param resource $sr
1303
+     * @return int
1304
+     * @throws ServerNotAvailableException
1305
+     */
1306
+    private function countEntriesInSearchResults($sr): int {
1307
+        return (int)$this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $sr);
1308
+    }
1309
+
1310
+    /**
1311
+     * Executes an LDAP search
1312
+     *
1313
+     * @throws ServerNotAvailableException
1314
+     */
1315
+    public function search(
1316
+        string $filter,
1317
+        string $base,
1318
+        ?array $attr = null,
1319
+        ?int $limit = null,
1320
+        ?int $offset = null,
1321
+        bool $skipHandling = false
1322
+    ): array {
1323
+        $limitPerPage = (int)$this->connection->ldapPagingSize;
1324
+        if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1325
+            $limitPerPage = $limit;
1326
+        }
1327
+
1328
+        if (!is_null($attr) && !is_array($attr)) {
1329
+            $attr = [mb_strtolower($attr, 'UTF-8')];
1330
+        }
1331
+
1332
+        /* ++ Fixing RHDS searches with pages with zero results ++
1333 1333
 		 * As we can have pages with zero results and/or pages with less
1334 1334
 		 * than $limit results but with a still valid server 'cookie',
1335 1335
 		 * loops through until we get $continue equals true and
1336 1336
 		 * $findings['count'] < $limit
1337 1337
 		 */
1338
-		$findings = [];
1339
-		$savedoffset = $offset;
1340
-		$iFoundItems = 0;
1341
-
1342
-		do {
1343
-			$search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1344
-			if ($search === false) {
1345
-				return [];
1346
-			}
1347
-			list($sr, $pagedSearchOK) = $search;
1348
-			$cr = $this->connection->getConnectionResource();
1349
-
1350
-			if ($skipHandling) {
1351
-				//i.e. result do not need to be fetched, we just need the cookie
1352
-				//thus pass 1 or any other value as $iFoundItems because it is not
1353
-				//used
1354
-				$this->processPagedSearchStatus($sr, 1, $limitPerPage, $pagedSearchOK, $skipHandling);
1355
-				return [];
1356
-			}
1357
-
1358
-			$findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $sr));
1359
-			$iFoundItems = max($iFoundItems, $findings['count']);
1360
-			unset($findings['count']);
1361
-
1362
-			$continue = $this->processPagedSearchStatus($sr, $iFoundItems, $limitPerPage, $pagedSearchOK, $skipHandling);
1363
-			$offset += $limitPerPage;
1364
-		} while ($continue && $pagedSearchOK && ($limit === null || count($findings) < $limit));
1365
-
1366
-		// resetting offset
1367
-		$offset = $savedoffset;
1368
-
1369
-		// if we're here, probably no connection resource is returned.
1370
-		// to make Nextcloud behave nicely, we simply give back an empty array.
1371
-		if (is_null($findings)) {
1372
-			return [];
1373
-		}
1374
-
1375
-		if (!is_null($attr)) {
1376
-			$selection = [];
1377
-			$i = 0;
1378
-			foreach ($findings as $item) {
1379
-				if (!is_array($item)) {
1380
-					continue;
1381
-				}
1382
-				$item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
1383
-				foreach ($attr as $key) {
1384
-					if (isset($item[$key])) {
1385
-						if (is_array($item[$key]) && isset($item[$key]['count'])) {
1386
-							unset($item[$key]['count']);
1387
-						}
1388
-						if ($key !== 'dn') {
1389
-							if ($this->resemblesDN($key)) {
1390
-								$selection[$i][$key] = $this->helper->sanitizeDN($item[$key]);
1391
-							} elseif ($key === 'objectguid' || $key === 'guid') {
1392
-								$selection[$i][$key] = [$this->convertObjectGUID2Str($item[$key][0])];
1393
-							} else {
1394
-								$selection[$i][$key] = $item[$key];
1395
-							}
1396
-						} else {
1397
-							$selection[$i][$key] = [$this->helper->sanitizeDN($item[$key])];
1398
-						}
1399
-					}
1400
-				}
1401
-				$i++;
1402
-			}
1403
-			$findings = $selection;
1404
-		}
1405
-		//we slice the findings, when
1406
-		//a) paged search unsuccessful, though attempted
1407
-		//b) no paged search, but limit set
1408
-		if ((!$this->getPagedSearchResultState()
1409
-				&& $pagedSearchOK)
1410
-			|| (
1411
-				!$pagedSearchOK
1412
-				&& !is_null($limit)
1413
-			)
1414
-		) {
1415
-			$findings = array_slice($findings, (int)$offset, $limit);
1416
-		}
1417
-		return $findings;
1418
-	}
1419
-
1420
-	/**
1421
-	 * @param string $name
1422
-	 * @return string
1423
-	 * @throws \InvalidArgumentException
1424
-	 */
1425
-	public function sanitizeUsername($name) {
1426
-		$name = trim($name);
1427
-
1428
-		if ($this->connection->ldapIgnoreNamingRules) {
1429
-			return $name;
1430
-		}
1431
-
1432
-		// Transliteration to ASCII
1433
-		$transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT', $name);
1434
-		if ($transliterated !== false) {
1435
-			// depending on system config iconv can work or not
1436
-			$name = $transliterated;
1437
-		}
1438
-
1439
-		// Replacements
1440
-		$name = str_replace(' ', '_', $name);
1441
-
1442
-		// Every remaining disallowed characters will be removed
1443
-		$name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
1444
-
1445
-		if ($name === '') {
1446
-			throw new \InvalidArgumentException('provided name template for username does not contain any allowed characters');
1447
-		}
1448
-
1449
-		return $name;
1450
-	}
1451
-
1452
-	/**
1453
-	 * escapes (user provided) parts for LDAP filter
1454
-	 *
1455
-	 * @param string $input , the provided value
1456
-	 * @param bool $allowAsterisk whether in * at the beginning should be preserved
1457
-	 * @return string the escaped string
1458
-	 */
1459
-	public function escapeFilterPart($input, $allowAsterisk = false) {
1460
-		$asterisk = '';
1461
-		if ($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1462
-			$asterisk = '*';
1463
-			$input = mb_substr($input, 1, null, 'UTF-8');
1464
-		}
1465
-		$search = ['*', '\\', '(', ')'];
1466
-		$replace = ['\\*', '\\\\', '\\(', '\\)'];
1467
-		return $asterisk . str_replace($search, $replace, $input);
1468
-	}
1469
-
1470
-	/**
1471
-	 * combines the input filters with AND
1472
-	 *
1473
-	 * @param string[] $filters the filters to connect
1474
-	 * @return string the combined filter
1475
-	 */
1476
-	public function combineFilterWithAnd($filters) {
1477
-		return $this->combineFilter($filters, '&');
1478
-	}
1479
-
1480
-	/**
1481
-	 * combines the input filters with OR
1482
-	 *
1483
-	 * @param string[] $filters the filters to connect
1484
-	 * @return string the combined filter
1485
-	 * Combines Filter arguments with OR
1486
-	 */
1487
-	public function combineFilterWithOr($filters) {
1488
-		return $this->combineFilter($filters, '|');
1489
-	}
1490
-
1491
-	/**
1492
-	 * combines the input filters with given operator
1493
-	 *
1494
-	 * @param string[] $filters the filters to connect
1495
-	 * @param string $operator either & or |
1496
-	 * @return string the combined filter
1497
-	 */
1498
-	private function combineFilter($filters, $operator) {
1499
-		$combinedFilter = '(' . $operator;
1500
-		foreach ($filters as $filter) {
1501
-			if ($filter !== '' && $filter[0] !== '(') {
1502
-				$filter = '(' . $filter . ')';
1503
-			}
1504
-			$combinedFilter .= $filter;
1505
-		}
1506
-		$combinedFilter .= ')';
1507
-		return $combinedFilter;
1508
-	}
1509
-
1510
-	/**
1511
-	 * creates a filter part for to perform search for users
1512
-	 *
1513
-	 * @param string $search the search term
1514
-	 * @return string the final filter part to use in LDAP searches
1515
-	 */
1516
-	public function getFilterPartForUserSearch($search) {
1517
-		return $this->getFilterPartForSearch($search,
1518
-			$this->connection->ldapAttributesForUserSearch,
1519
-			$this->connection->ldapUserDisplayName);
1520
-	}
1521
-
1522
-	/**
1523
-	 * creates a filter part for to perform search for groups
1524
-	 *
1525
-	 * @param string $search the search term
1526
-	 * @return string the final filter part to use in LDAP searches
1527
-	 */
1528
-	public function getFilterPartForGroupSearch($search) {
1529
-		return $this->getFilterPartForSearch($search,
1530
-			$this->connection->ldapAttributesForGroupSearch,
1531
-			$this->connection->ldapGroupDisplayName);
1532
-	}
1533
-
1534
-	/**
1535
-	 * creates a filter part for searches by splitting up the given search
1536
-	 * string into single words
1537
-	 *
1538
-	 * @param string $search the search term
1539
-	 * @param string[] $searchAttributes needs to have at least two attributes,
1540
-	 * otherwise it does not make sense :)
1541
-	 * @return string the final filter part to use in LDAP searches
1542
-	 * @throws \Exception
1543
-	 */
1544
-	private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
1545
-		if (!is_array($searchAttributes) || count($searchAttributes) < 2) {
1546
-			throw new \Exception('searchAttributes must be an array with at least two string');
1547
-		}
1548
-		$searchWords = explode(' ', trim($search));
1549
-		$wordFilters = [];
1550
-		foreach ($searchWords as $word) {
1551
-			$word = $this->prepareSearchTerm($word);
1552
-			//every word needs to appear at least once
1553
-			$wordMatchOneAttrFilters = [];
1554
-			foreach ($searchAttributes as $attr) {
1555
-				$wordMatchOneAttrFilters[] = $attr . '=' . $word;
1556
-			}
1557
-			$wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1558
-		}
1559
-		return $this->combineFilterWithAnd($wordFilters);
1560
-	}
1561
-
1562
-	/**
1563
-	 * creates a filter part for searches
1564
-	 *
1565
-	 * @param string $search the search term
1566
-	 * @param string[]|null $searchAttributes
1567
-	 * @param string $fallbackAttribute a fallback attribute in case the user
1568
-	 * did not define search attributes. Typically the display name attribute.
1569
-	 * @return string the final filter part to use in LDAP searches
1570
-	 */
1571
-	private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
1572
-		$filter = [];
1573
-		$haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
1574
-		if ($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1575
-			try {
1576
-				return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
1577
-			} catch (\Exception $e) {
1578
-				\OCP\Util::writeLog(
1579
-					'user_ldap',
1580
-					'Creating advanced filter for search failed, falling back to simple method.',
1581
-					ILogger::INFO
1582
-				);
1583
-			}
1584
-		}
1585
-
1586
-		$search = $this->prepareSearchTerm($search);
1587
-		if (!is_array($searchAttributes) || count($searchAttributes) === 0) {
1588
-			if ($fallbackAttribute === '') {
1589
-				return '';
1590
-			}
1591
-			$filter[] = $fallbackAttribute . '=' . $search;
1592
-		} else {
1593
-			foreach ($searchAttributes as $attribute) {
1594
-				$filter[] = $attribute . '=' . $search;
1595
-			}
1596
-		}
1597
-		if (count($filter) === 1) {
1598
-			return '(' . $filter[0] . ')';
1599
-		}
1600
-		return $this->combineFilterWithOr($filter);
1601
-	}
1602
-
1603
-	/**
1604
-	 * returns the search term depending on whether we are allowed
1605
-	 * list users found by ldap with the current input appended by
1606
-	 * a *
1607
-	 *
1608
-	 * @return string
1609
-	 */
1610
-	private function prepareSearchTerm($term) {
1611
-		$config = \OC::$server->getConfig();
1612
-
1613
-		$allowEnum = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes');
1614
-
1615
-		$result = $term;
1616
-		if ($term === '') {
1617
-			$result = '*';
1618
-		} elseif ($allowEnum !== 'no') {
1619
-			$result = $term . '*';
1620
-		}
1621
-		return $result;
1622
-	}
1623
-
1624
-	/**
1625
-	 * returns the filter used for counting users
1626
-	 *
1627
-	 * @return string
1628
-	 */
1629
-	public function getFilterForUserCount() {
1630
-		$filter = $this->combineFilterWithAnd([
1631
-			$this->connection->ldapUserFilter,
1632
-			$this->connection->ldapUserDisplayName . '=*'
1633
-		]);
1634
-
1635
-		return $filter;
1636
-	}
1637
-
1638
-	/**
1639
-	 * @param string $name
1640
-	 * @param string $password
1641
-	 * @return bool
1642
-	 */
1643
-	public function areCredentialsValid($name, $password) {
1644
-		$name = $this->helper->DNasBaseParameter($name);
1645
-		$testConnection = clone $this->connection;
1646
-		$credentials = [
1647
-			'ldapAgentName' => $name,
1648
-			'ldapAgentPassword' => $password
1649
-		];
1650
-		if (!$testConnection->setConfiguration($credentials)) {
1651
-			return false;
1652
-		}
1653
-		return $testConnection->bind();
1654
-	}
1655
-
1656
-	/**
1657
-	 * reverse lookup of a DN given a known UUID
1658
-	 *
1659
-	 * @param string $uuid
1660
-	 * @return string
1661
-	 * @throws \Exception
1662
-	 */
1663
-	public function getUserDnByUuid($uuid) {
1664
-		$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1665
-		$filter = $this->connection->ldapUserFilter;
1666
-		$bases = $this->connection->ldapBaseUsers;
1667
-
1668
-		if ($this->connection->ldapUuidUserAttribute === 'auto' && $uuidOverride === '') {
1669
-			// Sacrebleu! The UUID attribute is unknown :( We need first an
1670
-			// existing DN to be able to reliably detect it.
1671
-			foreach ($bases as $base) {
1672
-				$result = $this->search($filter, $base, ['dn'], 1);
1673
-				if (!isset($result[0]) || !isset($result[0]['dn'])) {
1674
-					continue;
1675
-				}
1676
-				$dn = $result[0]['dn'][0];
1677
-				if ($hasFound = $this->detectUuidAttribute($dn, true)) {
1678
-					break;
1679
-				}
1680
-			}
1681
-			if (!isset($hasFound) || !$hasFound) {
1682
-				throw new \Exception('Cannot determine UUID attribute');
1683
-			}
1684
-		} else {
1685
-			// The UUID attribute is either known or an override is given.
1686
-			// By calling this method we ensure that $this->connection->$uuidAttr
1687
-			// is definitely set
1688
-			if (!$this->detectUuidAttribute('', true)) {
1689
-				throw new \Exception('Cannot determine UUID attribute');
1690
-			}
1691
-		}
1692
-
1693
-		$uuidAttr = $this->connection->ldapUuidUserAttribute;
1694
-		if ($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1695
-			$uuid = $this->formatGuid2ForFilterUser($uuid);
1696
-		}
1697
-
1698
-		$filter = $uuidAttr . '=' . $uuid;
1699
-		$result = $this->searchUsers($filter, ['dn'], 2);
1700
-		if (is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1701
-			// we put the count into account to make sure that this is
1702
-			// really unique
1703
-			return $result[0]['dn'][0];
1704
-		}
1705
-
1706
-		throw new \Exception('Cannot determine UUID attribute');
1707
-	}
1708
-
1709
-	/**
1710
-	 * auto-detects the directory's UUID attribute
1711
-	 *
1712
-	 * @param string $dn a known DN used to check against
1713
-	 * @param bool $isUser
1714
-	 * @param bool $force the detection should be run, even if it is not set to auto
1715
-	 * @param array|null $ldapRecord
1716
-	 * @return bool true on success, false otherwise
1717
-	 * @throws ServerNotAvailableException
1718
-	 */
1719
-	private function detectUuidAttribute($dn, $isUser = true, $force = false, array $ldapRecord = null) {
1720
-		if ($isUser) {
1721
-			$uuidAttr = 'ldapUuidUserAttribute';
1722
-			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1723
-		} else {
1724
-			$uuidAttr = 'ldapUuidGroupAttribute';
1725
-			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1726
-		}
1727
-
1728
-		if (!$force) {
1729
-			if ($this->connection->$uuidAttr !== 'auto') {
1730
-				return true;
1731
-			} elseif (is_string($uuidOverride) && trim($uuidOverride) !== '') {
1732
-				$this->connection->$uuidAttr = $uuidOverride;
1733
-				return true;
1734
-			}
1735
-
1736
-			$attribute = $this->connection->getFromCache($uuidAttr);
1737
-			if (!$attribute === null) {
1738
-				$this->connection->$uuidAttr = $attribute;
1739
-				return true;
1740
-			}
1741
-		}
1742
-
1743
-		foreach (self::UUID_ATTRIBUTES as $attribute) {
1744
-			if ($ldapRecord !== null) {
1745
-				// we have the info from LDAP already, we don't need to talk to the server again
1746
-				if (isset($ldapRecord[$attribute])) {
1747
-					$this->connection->$uuidAttr = $attribute;
1748
-					return true;
1749
-				}
1750
-			}
1751
-
1752
-			$value = $this->readAttribute($dn, $attribute);
1753
-			if (is_array($value) && isset($value[0]) && !empty($value[0])) {
1754
-				\OC::$server->getLogger()->debug(
1755
-					'Setting {attribute} as {subject}',
1756
-					[
1757
-						'app' => 'user_ldap',
1758
-						'attribute' => $attribute,
1759
-						'subject' => $uuidAttr
1760
-					]
1761
-				);
1762
-				$this->connection->$uuidAttr = $attribute;
1763
-				$this->connection->writeToCache($uuidAttr, $attribute);
1764
-				return true;
1765
-			}
1766
-		}
1767
-		\OC::$server->getLogger()->debug('Could not autodetect the UUID attribute', ['app' => 'user_ldap']);
1768
-
1769
-		return false;
1770
-	}
1771
-
1772
-	/**
1773
-	 * @param string $dn
1774
-	 * @param bool $isUser
1775
-	 * @param null $ldapRecord
1776
-	 * @return bool|string
1777
-	 * @throws ServerNotAvailableException
1778
-	 */
1779
-	public function getUUID($dn, $isUser = true, $ldapRecord = null) {
1780
-		if ($isUser) {
1781
-			$uuidAttr = 'ldapUuidUserAttribute';
1782
-			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1783
-		} else {
1784
-			$uuidAttr = 'ldapUuidGroupAttribute';
1785
-			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1786
-		}
1787
-
1788
-		$uuid = false;
1789
-		if ($this->detectUuidAttribute($dn, $isUser, false, $ldapRecord)) {
1790
-			$attr = $this->connection->$uuidAttr;
1791
-			$uuid = isset($ldapRecord[$attr]) ? $ldapRecord[$attr] : $this->readAttribute($dn, $attr);
1792
-			if (!is_array($uuid)
1793
-				&& $uuidOverride !== ''
1794
-				&& $this->detectUuidAttribute($dn, $isUser, true, $ldapRecord)) {
1795
-				$uuid = isset($ldapRecord[$this->connection->$uuidAttr])
1796
-					? $ldapRecord[$this->connection->$uuidAttr]
1797
-					: $this->readAttribute($dn, $this->connection->$uuidAttr);
1798
-			}
1799
-			if (is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1800
-				$uuid = $uuid[0];
1801
-			}
1802
-		}
1803
-
1804
-		return $uuid;
1805
-	}
1806
-
1807
-	/**
1808
-	 * converts a binary ObjectGUID into a string representation
1809
-	 *
1810
-	 * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD
1811
-	 * @return string
1812
-	 * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198
1813
-	 */
1814
-	private function convertObjectGUID2Str($oguid) {
1815
-		$hex_guid = bin2hex($oguid);
1816
-		$hex_guid_to_guid_str = '';
1817
-		for ($k = 1; $k <= 4; ++$k) {
1818
-			$hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1819
-		}
1820
-		$hex_guid_to_guid_str .= '-';
1821
-		for ($k = 1; $k <= 2; ++$k) {
1822
-			$hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1823
-		}
1824
-		$hex_guid_to_guid_str .= '-';
1825
-		for ($k = 1; $k <= 2; ++$k) {
1826
-			$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1827
-		}
1828
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1829
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1830
-
1831
-		return strtoupper($hex_guid_to_guid_str);
1832
-	}
1833
-
1834
-	/**
1835
-	 * the first three blocks of the string-converted GUID happen to be in
1836
-	 * reverse order. In order to use it in a filter, this needs to be
1837
-	 * corrected. Furthermore the dashes need to be replaced and \\ preprended
1838
-	 * to every two hax figures.
1839
-	 *
1840
-	 * If an invalid string is passed, it will be returned without change.
1841
-	 *
1842
-	 * @param string $guid
1843
-	 * @return string
1844
-	 */
1845
-	public function formatGuid2ForFilterUser($guid) {
1846
-		if (!is_string($guid)) {
1847
-			throw new \InvalidArgumentException('String expected');
1848
-		}
1849
-		$blocks = explode('-', $guid);
1850
-		if (count($blocks) !== 5) {
1851
-			/*
1338
+        $findings = [];
1339
+        $savedoffset = $offset;
1340
+        $iFoundItems = 0;
1341
+
1342
+        do {
1343
+            $search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1344
+            if ($search === false) {
1345
+                return [];
1346
+            }
1347
+            list($sr, $pagedSearchOK) = $search;
1348
+            $cr = $this->connection->getConnectionResource();
1349
+
1350
+            if ($skipHandling) {
1351
+                //i.e. result do not need to be fetched, we just need the cookie
1352
+                //thus pass 1 or any other value as $iFoundItems because it is not
1353
+                //used
1354
+                $this->processPagedSearchStatus($sr, 1, $limitPerPage, $pagedSearchOK, $skipHandling);
1355
+                return [];
1356
+            }
1357
+
1358
+            $findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $sr));
1359
+            $iFoundItems = max($iFoundItems, $findings['count']);
1360
+            unset($findings['count']);
1361
+
1362
+            $continue = $this->processPagedSearchStatus($sr, $iFoundItems, $limitPerPage, $pagedSearchOK, $skipHandling);
1363
+            $offset += $limitPerPage;
1364
+        } while ($continue && $pagedSearchOK && ($limit === null || count($findings) < $limit));
1365
+
1366
+        // resetting offset
1367
+        $offset = $savedoffset;
1368
+
1369
+        // if we're here, probably no connection resource is returned.
1370
+        // to make Nextcloud behave nicely, we simply give back an empty array.
1371
+        if (is_null($findings)) {
1372
+            return [];
1373
+        }
1374
+
1375
+        if (!is_null($attr)) {
1376
+            $selection = [];
1377
+            $i = 0;
1378
+            foreach ($findings as $item) {
1379
+                if (!is_array($item)) {
1380
+                    continue;
1381
+                }
1382
+                $item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
1383
+                foreach ($attr as $key) {
1384
+                    if (isset($item[$key])) {
1385
+                        if (is_array($item[$key]) && isset($item[$key]['count'])) {
1386
+                            unset($item[$key]['count']);
1387
+                        }
1388
+                        if ($key !== 'dn') {
1389
+                            if ($this->resemblesDN($key)) {
1390
+                                $selection[$i][$key] = $this->helper->sanitizeDN($item[$key]);
1391
+                            } elseif ($key === 'objectguid' || $key === 'guid') {
1392
+                                $selection[$i][$key] = [$this->convertObjectGUID2Str($item[$key][0])];
1393
+                            } else {
1394
+                                $selection[$i][$key] = $item[$key];
1395
+                            }
1396
+                        } else {
1397
+                            $selection[$i][$key] = [$this->helper->sanitizeDN($item[$key])];
1398
+                        }
1399
+                    }
1400
+                }
1401
+                $i++;
1402
+            }
1403
+            $findings = $selection;
1404
+        }
1405
+        //we slice the findings, when
1406
+        //a) paged search unsuccessful, though attempted
1407
+        //b) no paged search, but limit set
1408
+        if ((!$this->getPagedSearchResultState()
1409
+                && $pagedSearchOK)
1410
+            || (
1411
+                !$pagedSearchOK
1412
+                && !is_null($limit)
1413
+            )
1414
+        ) {
1415
+            $findings = array_slice($findings, (int)$offset, $limit);
1416
+        }
1417
+        return $findings;
1418
+    }
1419
+
1420
+    /**
1421
+     * @param string $name
1422
+     * @return string
1423
+     * @throws \InvalidArgumentException
1424
+     */
1425
+    public function sanitizeUsername($name) {
1426
+        $name = trim($name);
1427
+
1428
+        if ($this->connection->ldapIgnoreNamingRules) {
1429
+            return $name;
1430
+        }
1431
+
1432
+        // Transliteration to ASCII
1433
+        $transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT', $name);
1434
+        if ($transliterated !== false) {
1435
+            // depending on system config iconv can work or not
1436
+            $name = $transliterated;
1437
+        }
1438
+
1439
+        // Replacements
1440
+        $name = str_replace(' ', '_', $name);
1441
+
1442
+        // Every remaining disallowed characters will be removed
1443
+        $name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
1444
+
1445
+        if ($name === '') {
1446
+            throw new \InvalidArgumentException('provided name template for username does not contain any allowed characters');
1447
+        }
1448
+
1449
+        return $name;
1450
+    }
1451
+
1452
+    /**
1453
+     * escapes (user provided) parts for LDAP filter
1454
+     *
1455
+     * @param string $input , the provided value
1456
+     * @param bool $allowAsterisk whether in * at the beginning should be preserved
1457
+     * @return string the escaped string
1458
+     */
1459
+    public function escapeFilterPart($input, $allowAsterisk = false) {
1460
+        $asterisk = '';
1461
+        if ($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1462
+            $asterisk = '*';
1463
+            $input = mb_substr($input, 1, null, 'UTF-8');
1464
+        }
1465
+        $search = ['*', '\\', '(', ')'];
1466
+        $replace = ['\\*', '\\\\', '\\(', '\\)'];
1467
+        return $asterisk . str_replace($search, $replace, $input);
1468
+    }
1469
+
1470
+    /**
1471
+     * combines the input filters with AND
1472
+     *
1473
+     * @param string[] $filters the filters to connect
1474
+     * @return string the combined filter
1475
+     */
1476
+    public function combineFilterWithAnd($filters) {
1477
+        return $this->combineFilter($filters, '&');
1478
+    }
1479
+
1480
+    /**
1481
+     * combines the input filters with OR
1482
+     *
1483
+     * @param string[] $filters the filters to connect
1484
+     * @return string the combined filter
1485
+     * Combines Filter arguments with OR
1486
+     */
1487
+    public function combineFilterWithOr($filters) {
1488
+        return $this->combineFilter($filters, '|');
1489
+    }
1490
+
1491
+    /**
1492
+     * combines the input filters with given operator
1493
+     *
1494
+     * @param string[] $filters the filters to connect
1495
+     * @param string $operator either & or |
1496
+     * @return string the combined filter
1497
+     */
1498
+    private function combineFilter($filters, $operator) {
1499
+        $combinedFilter = '(' . $operator;
1500
+        foreach ($filters as $filter) {
1501
+            if ($filter !== '' && $filter[0] !== '(') {
1502
+                $filter = '(' . $filter . ')';
1503
+            }
1504
+            $combinedFilter .= $filter;
1505
+        }
1506
+        $combinedFilter .= ')';
1507
+        return $combinedFilter;
1508
+    }
1509
+
1510
+    /**
1511
+     * creates a filter part for to perform search for users
1512
+     *
1513
+     * @param string $search the search term
1514
+     * @return string the final filter part to use in LDAP searches
1515
+     */
1516
+    public function getFilterPartForUserSearch($search) {
1517
+        return $this->getFilterPartForSearch($search,
1518
+            $this->connection->ldapAttributesForUserSearch,
1519
+            $this->connection->ldapUserDisplayName);
1520
+    }
1521
+
1522
+    /**
1523
+     * creates a filter part for to perform search for groups
1524
+     *
1525
+     * @param string $search the search term
1526
+     * @return string the final filter part to use in LDAP searches
1527
+     */
1528
+    public function getFilterPartForGroupSearch($search) {
1529
+        return $this->getFilterPartForSearch($search,
1530
+            $this->connection->ldapAttributesForGroupSearch,
1531
+            $this->connection->ldapGroupDisplayName);
1532
+    }
1533
+
1534
+    /**
1535
+     * creates a filter part for searches by splitting up the given search
1536
+     * string into single words
1537
+     *
1538
+     * @param string $search the search term
1539
+     * @param string[] $searchAttributes needs to have at least two attributes,
1540
+     * otherwise it does not make sense :)
1541
+     * @return string the final filter part to use in LDAP searches
1542
+     * @throws \Exception
1543
+     */
1544
+    private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
1545
+        if (!is_array($searchAttributes) || count($searchAttributes) < 2) {
1546
+            throw new \Exception('searchAttributes must be an array with at least two string');
1547
+        }
1548
+        $searchWords = explode(' ', trim($search));
1549
+        $wordFilters = [];
1550
+        foreach ($searchWords as $word) {
1551
+            $word = $this->prepareSearchTerm($word);
1552
+            //every word needs to appear at least once
1553
+            $wordMatchOneAttrFilters = [];
1554
+            foreach ($searchAttributes as $attr) {
1555
+                $wordMatchOneAttrFilters[] = $attr . '=' . $word;
1556
+            }
1557
+            $wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1558
+        }
1559
+        return $this->combineFilterWithAnd($wordFilters);
1560
+    }
1561
+
1562
+    /**
1563
+     * creates a filter part for searches
1564
+     *
1565
+     * @param string $search the search term
1566
+     * @param string[]|null $searchAttributes
1567
+     * @param string $fallbackAttribute a fallback attribute in case the user
1568
+     * did not define search attributes. Typically the display name attribute.
1569
+     * @return string the final filter part to use in LDAP searches
1570
+     */
1571
+    private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
1572
+        $filter = [];
1573
+        $haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
1574
+        if ($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1575
+            try {
1576
+                return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
1577
+            } catch (\Exception $e) {
1578
+                \OCP\Util::writeLog(
1579
+                    'user_ldap',
1580
+                    'Creating advanced filter for search failed, falling back to simple method.',
1581
+                    ILogger::INFO
1582
+                );
1583
+            }
1584
+        }
1585
+
1586
+        $search = $this->prepareSearchTerm($search);
1587
+        if (!is_array($searchAttributes) || count($searchAttributes) === 0) {
1588
+            if ($fallbackAttribute === '') {
1589
+                return '';
1590
+            }
1591
+            $filter[] = $fallbackAttribute . '=' . $search;
1592
+        } else {
1593
+            foreach ($searchAttributes as $attribute) {
1594
+                $filter[] = $attribute . '=' . $search;
1595
+            }
1596
+        }
1597
+        if (count($filter) === 1) {
1598
+            return '(' . $filter[0] . ')';
1599
+        }
1600
+        return $this->combineFilterWithOr($filter);
1601
+    }
1602
+
1603
+    /**
1604
+     * returns the search term depending on whether we are allowed
1605
+     * list users found by ldap with the current input appended by
1606
+     * a *
1607
+     *
1608
+     * @return string
1609
+     */
1610
+    private function prepareSearchTerm($term) {
1611
+        $config = \OC::$server->getConfig();
1612
+
1613
+        $allowEnum = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes');
1614
+
1615
+        $result = $term;
1616
+        if ($term === '') {
1617
+            $result = '*';
1618
+        } elseif ($allowEnum !== 'no') {
1619
+            $result = $term . '*';
1620
+        }
1621
+        return $result;
1622
+    }
1623
+
1624
+    /**
1625
+     * returns the filter used for counting users
1626
+     *
1627
+     * @return string
1628
+     */
1629
+    public function getFilterForUserCount() {
1630
+        $filter = $this->combineFilterWithAnd([
1631
+            $this->connection->ldapUserFilter,
1632
+            $this->connection->ldapUserDisplayName . '=*'
1633
+        ]);
1634
+
1635
+        return $filter;
1636
+    }
1637
+
1638
+    /**
1639
+     * @param string $name
1640
+     * @param string $password
1641
+     * @return bool
1642
+     */
1643
+    public function areCredentialsValid($name, $password) {
1644
+        $name = $this->helper->DNasBaseParameter($name);
1645
+        $testConnection = clone $this->connection;
1646
+        $credentials = [
1647
+            'ldapAgentName' => $name,
1648
+            'ldapAgentPassword' => $password
1649
+        ];
1650
+        if (!$testConnection->setConfiguration($credentials)) {
1651
+            return false;
1652
+        }
1653
+        return $testConnection->bind();
1654
+    }
1655
+
1656
+    /**
1657
+     * reverse lookup of a DN given a known UUID
1658
+     *
1659
+     * @param string $uuid
1660
+     * @return string
1661
+     * @throws \Exception
1662
+     */
1663
+    public function getUserDnByUuid($uuid) {
1664
+        $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1665
+        $filter = $this->connection->ldapUserFilter;
1666
+        $bases = $this->connection->ldapBaseUsers;
1667
+
1668
+        if ($this->connection->ldapUuidUserAttribute === 'auto' && $uuidOverride === '') {
1669
+            // Sacrebleu! The UUID attribute is unknown :( We need first an
1670
+            // existing DN to be able to reliably detect it.
1671
+            foreach ($bases as $base) {
1672
+                $result = $this->search($filter, $base, ['dn'], 1);
1673
+                if (!isset($result[0]) || !isset($result[0]['dn'])) {
1674
+                    continue;
1675
+                }
1676
+                $dn = $result[0]['dn'][0];
1677
+                if ($hasFound = $this->detectUuidAttribute($dn, true)) {
1678
+                    break;
1679
+                }
1680
+            }
1681
+            if (!isset($hasFound) || !$hasFound) {
1682
+                throw new \Exception('Cannot determine UUID attribute');
1683
+            }
1684
+        } else {
1685
+            // The UUID attribute is either known or an override is given.
1686
+            // By calling this method we ensure that $this->connection->$uuidAttr
1687
+            // is definitely set
1688
+            if (!$this->detectUuidAttribute('', true)) {
1689
+                throw new \Exception('Cannot determine UUID attribute');
1690
+            }
1691
+        }
1692
+
1693
+        $uuidAttr = $this->connection->ldapUuidUserAttribute;
1694
+        if ($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1695
+            $uuid = $this->formatGuid2ForFilterUser($uuid);
1696
+        }
1697
+
1698
+        $filter = $uuidAttr . '=' . $uuid;
1699
+        $result = $this->searchUsers($filter, ['dn'], 2);
1700
+        if (is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1701
+            // we put the count into account to make sure that this is
1702
+            // really unique
1703
+            return $result[0]['dn'][0];
1704
+        }
1705
+
1706
+        throw new \Exception('Cannot determine UUID attribute');
1707
+    }
1708
+
1709
+    /**
1710
+     * auto-detects the directory's UUID attribute
1711
+     *
1712
+     * @param string $dn a known DN used to check against
1713
+     * @param bool $isUser
1714
+     * @param bool $force the detection should be run, even if it is not set to auto
1715
+     * @param array|null $ldapRecord
1716
+     * @return bool true on success, false otherwise
1717
+     * @throws ServerNotAvailableException
1718
+     */
1719
+    private function detectUuidAttribute($dn, $isUser = true, $force = false, array $ldapRecord = null) {
1720
+        if ($isUser) {
1721
+            $uuidAttr = 'ldapUuidUserAttribute';
1722
+            $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1723
+        } else {
1724
+            $uuidAttr = 'ldapUuidGroupAttribute';
1725
+            $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1726
+        }
1727
+
1728
+        if (!$force) {
1729
+            if ($this->connection->$uuidAttr !== 'auto') {
1730
+                return true;
1731
+            } elseif (is_string($uuidOverride) && trim($uuidOverride) !== '') {
1732
+                $this->connection->$uuidAttr = $uuidOverride;
1733
+                return true;
1734
+            }
1735
+
1736
+            $attribute = $this->connection->getFromCache($uuidAttr);
1737
+            if (!$attribute === null) {
1738
+                $this->connection->$uuidAttr = $attribute;
1739
+                return true;
1740
+            }
1741
+        }
1742
+
1743
+        foreach (self::UUID_ATTRIBUTES as $attribute) {
1744
+            if ($ldapRecord !== null) {
1745
+                // we have the info from LDAP already, we don't need to talk to the server again
1746
+                if (isset($ldapRecord[$attribute])) {
1747
+                    $this->connection->$uuidAttr = $attribute;
1748
+                    return true;
1749
+                }
1750
+            }
1751
+
1752
+            $value = $this->readAttribute($dn, $attribute);
1753
+            if (is_array($value) && isset($value[0]) && !empty($value[0])) {
1754
+                \OC::$server->getLogger()->debug(
1755
+                    'Setting {attribute} as {subject}',
1756
+                    [
1757
+                        'app' => 'user_ldap',
1758
+                        'attribute' => $attribute,
1759
+                        'subject' => $uuidAttr
1760
+                    ]
1761
+                );
1762
+                $this->connection->$uuidAttr = $attribute;
1763
+                $this->connection->writeToCache($uuidAttr, $attribute);
1764
+                return true;
1765
+            }
1766
+        }
1767
+        \OC::$server->getLogger()->debug('Could not autodetect the UUID attribute', ['app' => 'user_ldap']);
1768
+
1769
+        return false;
1770
+    }
1771
+
1772
+    /**
1773
+     * @param string $dn
1774
+     * @param bool $isUser
1775
+     * @param null $ldapRecord
1776
+     * @return bool|string
1777
+     * @throws ServerNotAvailableException
1778
+     */
1779
+    public function getUUID($dn, $isUser = true, $ldapRecord = null) {
1780
+        if ($isUser) {
1781
+            $uuidAttr = 'ldapUuidUserAttribute';
1782
+            $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1783
+        } else {
1784
+            $uuidAttr = 'ldapUuidGroupAttribute';
1785
+            $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1786
+        }
1787
+
1788
+        $uuid = false;
1789
+        if ($this->detectUuidAttribute($dn, $isUser, false, $ldapRecord)) {
1790
+            $attr = $this->connection->$uuidAttr;
1791
+            $uuid = isset($ldapRecord[$attr]) ? $ldapRecord[$attr] : $this->readAttribute($dn, $attr);
1792
+            if (!is_array($uuid)
1793
+                && $uuidOverride !== ''
1794
+                && $this->detectUuidAttribute($dn, $isUser, true, $ldapRecord)) {
1795
+                $uuid = isset($ldapRecord[$this->connection->$uuidAttr])
1796
+                    ? $ldapRecord[$this->connection->$uuidAttr]
1797
+                    : $this->readAttribute($dn, $this->connection->$uuidAttr);
1798
+            }
1799
+            if (is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1800
+                $uuid = $uuid[0];
1801
+            }
1802
+        }
1803
+
1804
+        return $uuid;
1805
+    }
1806
+
1807
+    /**
1808
+     * converts a binary ObjectGUID into a string representation
1809
+     *
1810
+     * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD
1811
+     * @return string
1812
+     * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198
1813
+     */
1814
+    private function convertObjectGUID2Str($oguid) {
1815
+        $hex_guid = bin2hex($oguid);
1816
+        $hex_guid_to_guid_str = '';
1817
+        for ($k = 1; $k <= 4; ++$k) {
1818
+            $hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1819
+        }
1820
+        $hex_guid_to_guid_str .= '-';
1821
+        for ($k = 1; $k <= 2; ++$k) {
1822
+            $hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1823
+        }
1824
+        $hex_guid_to_guid_str .= '-';
1825
+        for ($k = 1; $k <= 2; ++$k) {
1826
+            $hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1827
+        }
1828
+        $hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1829
+        $hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1830
+
1831
+        return strtoupper($hex_guid_to_guid_str);
1832
+    }
1833
+
1834
+    /**
1835
+     * the first three blocks of the string-converted GUID happen to be in
1836
+     * reverse order. In order to use it in a filter, this needs to be
1837
+     * corrected. Furthermore the dashes need to be replaced and \\ preprended
1838
+     * to every two hax figures.
1839
+     *
1840
+     * If an invalid string is passed, it will be returned without change.
1841
+     *
1842
+     * @param string $guid
1843
+     * @return string
1844
+     */
1845
+    public function formatGuid2ForFilterUser($guid) {
1846
+        if (!is_string($guid)) {
1847
+            throw new \InvalidArgumentException('String expected');
1848
+        }
1849
+        $blocks = explode('-', $guid);
1850
+        if (count($blocks) !== 5) {
1851
+            /*
1852 1852
 			 * Why not throw an Exception instead? This method is a utility
1853 1853
 			 * called only when trying to figure out whether a "missing" known
1854 1854
 			 * LDAP user was or was not renamed on the LDAP server. And this
@@ -1859,241 +1859,241 @@  discard block
 block discarded – undo
1859 1859
 			 * an exception here would kill the experience for a valid, acting
1860 1860
 			 * user. Instead we write a log message.
1861 1861
 			 */
1862
-			\OC::$server->getLogger()->info(
1863
-				'Passed string does not resemble a valid GUID. Known UUID ' .
1864
-				'({uuid}) probably does not match UUID configuration.',
1865
-				['app' => 'user_ldap', 'uuid' => $guid]
1866
-			);
1867
-			return $guid;
1868
-		}
1869
-		for ($i = 0; $i < 3; $i++) {
1870
-			$pairs = str_split($blocks[$i], 2);
1871
-			$pairs = array_reverse($pairs);
1872
-			$blocks[$i] = implode('', $pairs);
1873
-		}
1874
-		for ($i = 0; $i < 5; $i++) {
1875
-			$pairs = str_split($blocks[$i], 2);
1876
-			$blocks[$i] = '\\' . implode('\\', $pairs);
1877
-		}
1878
-		return implode('', $blocks);
1879
-	}
1880
-
1881
-	/**
1882
-	 * gets a SID of the domain of the given dn
1883
-	 *
1884
-	 * @param string $dn
1885
-	 * @return string|bool
1886
-	 * @throws ServerNotAvailableException
1887
-	 */
1888
-	public function getSID($dn) {
1889
-		$domainDN = $this->getDomainDNFromDN($dn);
1890
-		$cacheKey = 'getSID-' . $domainDN;
1891
-		$sid = $this->connection->getFromCache($cacheKey);
1892
-		if (!is_null($sid)) {
1893
-			return $sid;
1894
-		}
1895
-
1896
-		$objectSid = $this->readAttribute($domainDN, 'objectsid');
1897
-		if (!is_array($objectSid) || empty($objectSid)) {
1898
-			$this->connection->writeToCache($cacheKey, false);
1899
-			return false;
1900
-		}
1901
-		$domainObjectSid = $this->convertSID2Str($objectSid[0]);
1902
-		$this->connection->writeToCache($cacheKey, $domainObjectSid);
1903
-
1904
-		return $domainObjectSid;
1905
-	}
1906
-
1907
-	/**
1908
-	 * converts a binary SID into a string representation
1909
-	 *
1910
-	 * @param string $sid
1911
-	 * @return string
1912
-	 */
1913
-	public function convertSID2Str($sid) {
1914
-		// The format of a SID binary string is as follows:
1915
-		// 1 byte for the revision level
1916
-		// 1 byte for the number n of variable sub-ids
1917
-		// 6 bytes for identifier authority value
1918
-		// n*4 bytes for n sub-ids
1919
-		//
1920
-		// Example: 010400000000000515000000a681e50e4d6c6c2bca32055f
1921
-		//  Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444
1922
-		$revision = ord($sid[0]);
1923
-		$numberSubID = ord($sid[1]);
1924
-
1925
-		$subIdStart = 8; // 1 + 1 + 6
1926
-		$subIdLength = 4;
1927
-		if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) {
1928
-			// Incorrect number of bytes present.
1929
-			return '';
1930
-		}
1931
-
1932
-		// 6 bytes = 48 bits can be represented using floats without loss of
1933
-		// precision (see https://gist.github.com/bantu/886ac680b0aef5812f71)
1934
-		$iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', '');
1935
-
1936
-		$subIDs = [];
1937
-		for ($i = 0; $i < $numberSubID; $i++) {
1938
-			$subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength));
1939
-			$subIDs[] = sprintf('%u', $subID[1]);
1940
-		}
1941
-
1942
-		// Result for example above: S-1-5-21-249921958-728525901-1594176202
1943
-		return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs));
1944
-	}
1945
-
1946
-	/**
1947
-	 * checks if the given DN is part of the given base DN(s)
1948
-	 *
1949
-	 * @param string $dn the DN
1950
-	 * @param string[] $bases array containing the allowed base DN or DNs
1951
-	 * @return bool
1952
-	 */
1953
-	public function isDNPartOfBase($dn, $bases) {
1954
-		$belongsToBase = false;
1955
-		$bases = $this->helper->sanitizeDN($bases);
1956
-
1957
-		foreach ($bases as $base) {
1958
-			$belongsToBase = true;
1959
-			if (mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8') - mb_strlen($base, 'UTF-8'))) {
1960
-				$belongsToBase = false;
1961
-			}
1962
-			if ($belongsToBase) {
1963
-				break;
1964
-			}
1965
-		}
1966
-		return $belongsToBase;
1967
-	}
1968
-
1969
-	/**
1970
-	 * resets a running Paged Search operation
1971
-	 *
1972
-	 * @throws ServerNotAvailableException
1973
-	 */
1974
-	private function abandonPagedSearch() {
1975
-		if ($this->lastCookie === '') {
1976
-			return;
1977
-		}
1978
-		$cr = $this->connection->getConnectionResource();
1979
-		$this->invokeLDAPMethod('controlPagedResult', $cr, 0, false);
1980
-		$this->getPagedSearchResultState();
1981
-		$this->lastCookie = '';
1982
-	}
1983
-
1984
-	/**
1985
-	 * checks whether an LDAP paged search operation has more pages that can be
1986
-	 * retrieved, typically when offset and limit are provided.
1987
-	 *
1988
-	 * Be very careful to use it: the last cookie value, which is inspected, can
1989
-	 * be reset by other operations. Best, call it immediately after a search(),
1990
-	 * searchUsers() or searchGroups() call. count-methods are probably safe as
1991
-	 * well. Don't rely on it with any fetchList-method.
1992
-	 *
1993
-	 * @return bool
1994
-	 */
1995
-	public function hasMoreResults() {
1996
-		if (empty($this->lastCookie) && $this->lastCookie !== '0') {
1997
-			// as in RFC 2696, when all results are returned, the cookie will
1998
-			// be empty.
1999
-			return false;
2000
-		}
2001
-
2002
-		return true;
2003
-	}
2004
-
2005
-	/**
2006
-	 * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
2007
-	 *
2008
-	 * @return boolean|null true on success, null or false otherwise
2009
-	 */
2010
-	public function getPagedSearchResultState() {
2011
-		$result = $this->pagedSearchedSuccessful;
2012
-		$this->pagedSearchedSuccessful = null;
2013
-		return $result;
2014
-	}
2015
-
2016
-	/**
2017
-	 * Prepares a paged search, if possible
2018
-	 *
2019
-	 * @param string $filter the LDAP filter for the search
2020
-	 * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched
2021
-	 * @param string[] $attr optional, when a certain attribute shall be filtered outside
2022
-	 * @param int $limit
2023
-	 * @param int $offset
2024
-	 * @return bool|true
2025
-	 * @throws ServerNotAvailableException
2026
-	 */
2027
-	private function initPagedSearch(
2028
-		string $filter,
2029
-		string $base,
2030
-		?array $attr,
2031
-		int $limit,
2032
-		int $offset
2033
-	): bool {
2034
-		$pagedSearchOK = false;
2035
-		if ($limit !== 0) {
2036
-			\OC::$server->getLogger()->debug(
2037
-				'initializing paged search for filter {filter}, base {base}, attr {attr}, limit {limit}, offset {offset}',
2038
-				[
2039
-					'app' => 'user_ldap',
2040
-					'filter' => $filter,
2041
-					'base' => $base,
2042
-					'attr' => $attr,
2043
-					'limit' => $limit,
2044
-					'offset' => $offset
2045
-				]
2046
-			);
2047
-			//get the cookie from the search for the previous search, required by LDAP
2048
-			if (empty($this->lastCookie) && $this->lastCookie !== "0" && ($offset > 0)) {
2049
-				// no cookie known from a potential previous search. We need
2050
-				// to start from 0 to come to the desired page. cookie value
2051
-				// of '0' is valid, because 389ds
2052
-				$reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
2053
-				$this->search($filter, $base, $attr, $limit, $reOffset, true);
2054
-			}
2055
-			if ($this->lastCookie !== '' && $offset === 0) {
2056
-				//since offset = 0, this is a new search. We abandon other searches that might be ongoing.
2057
-				$this->abandonPagedSearch();
2058
-			}
2059
-			$pagedSearchOK = true === $this->invokeLDAPMethod(
2060
-					'controlPagedResult', $this->connection->getConnectionResource(), $limit, false
2061
-				);
2062
-			if ($pagedSearchOK) {
2063
-				\OC::$server->getLogger()->debug('Ready for a paged search', ['app' => 'user_ldap']);
2064
-			}
2065
-			/* ++ Fixing RHDS searches with pages with zero results ++
1862
+            \OC::$server->getLogger()->info(
1863
+                'Passed string does not resemble a valid GUID. Known UUID ' .
1864
+                '({uuid}) probably does not match UUID configuration.',
1865
+                ['app' => 'user_ldap', 'uuid' => $guid]
1866
+            );
1867
+            return $guid;
1868
+        }
1869
+        for ($i = 0; $i < 3; $i++) {
1870
+            $pairs = str_split($blocks[$i], 2);
1871
+            $pairs = array_reverse($pairs);
1872
+            $blocks[$i] = implode('', $pairs);
1873
+        }
1874
+        for ($i = 0; $i < 5; $i++) {
1875
+            $pairs = str_split($blocks[$i], 2);
1876
+            $blocks[$i] = '\\' . implode('\\', $pairs);
1877
+        }
1878
+        return implode('', $blocks);
1879
+    }
1880
+
1881
+    /**
1882
+     * gets a SID of the domain of the given dn
1883
+     *
1884
+     * @param string $dn
1885
+     * @return string|bool
1886
+     * @throws ServerNotAvailableException
1887
+     */
1888
+    public function getSID($dn) {
1889
+        $domainDN = $this->getDomainDNFromDN($dn);
1890
+        $cacheKey = 'getSID-' . $domainDN;
1891
+        $sid = $this->connection->getFromCache($cacheKey);
1892
+        if (!is_null($sid)) {
1893
+            return $sid;
1894
+        }
1895
+
1896
+        $objectSid = $this->readAttribute($domainDN, 'objectsid');
1897
+        if (!is_array($objectSid) || empty($objectSid)) {
1898
+            $this->connection->writeToCache($cacheKey, false);
1899
+            return false;
1900
+        }
1901
+        $domainObjectSid = $this->convertSID2Str($objectSid[0]);
1902
+        $this->connection->writeToCache($cacheKey, $domainObjectSid);
1903
+
1904
+        return $domainObjectSid;
1905
+    }
1906
+
1907
+    /**
1908
+     * converts a binary SID into a string representation
1909
+     *
1910
+     * @param string $sid
1911
+     * @return string
1912
+     */
1913
+    public function convertSID2Str($sid) {
1914
+        // The format of a SID binary string is as follows:
1915
+        // 1 byte for the revision level
1916
+        // 1 byte for the number n of variable sub-ids
1917
+        // 6 bytes for identifier authority value
1918
+        // n*4 bytes for n sub-ids
1919
+        //
1920
+        // Example: 010400000000000515000000a681e50e4d6c6c2bca32055f
1921
+        //  Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444
1922
+        $revision = ord($sid[0]);
1923
+        $numberSubID = ord($sid[1]);
1924
+
1925
+        $subIdStart = 8; // 1 + 1 + 6
1926
+        $subIdLength = 4;
1927
+        if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) {
1928
+            // Incorrect number of bytes present.
1929
+            return '';
1930
+        }
1931
+
1932
+        // 6 bytes = 48 bits can be represented using floats without loss of
1933
+        // precision (see https://gist.github.com/bantu/886ac680b0aef5812f71)
1934
+        $iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', '');
1935
+
1936
+        $subIDs = [];
1937
+        for ($i = 0; $i < $numberSubID; $i++) {
1938
+            $subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength));
1939
+            $subIDs[] = sprintf('%u', $subID[1]);
1940
+        }
1941
+
1942
+        // Result for example above: S-1-5-21-249921958-728525901-1594176202
1943
+        return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs));
1944
+    }
1945
+
1946
+    /**
1947
+     * checks if the given DN is part of the given base DN(s)
1948
+     *
1949
+     * @param string $dn the DN
1950
+     * @param string[] $bases array containing the allowed base DN or DNs
1951
+     * @return bool
1952
+     */
1953
+    public function isDNPartOfBase($dn, $bases) {
1954
+        $belongsToBase = false;
1955
+        $bases = $this->helper->sanitizeDN($bases);
1956
+
1957
+        foreach ($bases as $base) {
1958
+            $belongsToBase = true;
1959
+            if (mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8') - mb_strlen($base, 'UTF-8'))) {
1960
+                $belongsToBase = false;
1961
+            }
1962
+            if ($belongsToBase) {
1963
+                break;
1964
+            }
1965
+        }
1966
+        return $belongsToBase;
1967
+    }
1968
+
1969
+    /**
1970
+     * resets a running Paged Search operation
1971
+     *
1972
+     * @throws ServerNotAvailableException
1973
+     */
1974
+    private function abandonPagedSearch() {
1975
+        if ($this->lastCookie === '') {
1976
+            return;
1977
+        }
1978
+        $cr = $this->connection->getConnectionResource();
1979
+        $this->invokeLDAPMethod('controlPagedResult', $cr, 0, false);
1980
+        $this->getPagedSearchResultState();
1981
+        $this->lastCookie = '';
1982
+    }
1983
+
1984
+    /**
1985
+     * checks whether an LDAP paged search operation has more pages that can be
1986
+     * retrieved, typically when offset and limit are provided.
1987
+     *
1988
+     * Be very careful to use it: the last cookie value, which is inspected, can
1989
+     * be reset by other operations. Best, call it immediately after a search(),
1990
+     * searchUsers() or searchGroups() call. count-methods are probably safe as
1991
+     * well. Don't rely on it with any fetchList-method.
1992
+     *
1993
+     * @return bool
1994
+     */
1995
+    public function hasMoreResults() {
1996
+        if (empty($this->lastCookie) && $this->lastCookie !== '0') {
1997
+            // as in RFC 2696, when all results are returned, the cookie will
1998
+            // be empty.
1999
+            return false;
2000
+        }
2001
+
2002
+        return true;
2003
+    }
2004
+
2005
+    /**
2006
+     * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
2007
+     *
2008
+     * @return boolean|null true on success, null or false otherwise
2009
+     */
2010
+    public function getPagedSearchResultState() {
2011
+        $result = $this->pagedSearchedSuccessful;
2012
+        $this->pagedSearchedSuccessful = null;
2013
+        return $result;
2014
+    }
2015
+
2016
+    /**
2017
+     * Prepares a paged search, if possible
2018
+     *
2019
+     * @param string $filter the LDAP filter for the search
2020
+     * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched
2021
+     * @param string[] $attr optional, when a certain attribute shall be filtered outside
2022
+     * @param int $limit
2023
+     * @param int $offset
2024
+     * @return bool|true
2025
+     * @throws ServerNotAvailableException
2026
+     */
2027
+    private function initPagedSearch(
2028
+        string $filter,
2029
+        string $base,
2030
+        ?array $attr,
2031
+        int $limit,
2032
+        int $offset
2033
+    ): bool {
2034
+        $pagedSearchOK = false;
2035
+        if ($limit !== 0) {
2036
+            \OC::$server->getLogger()->debug(
2037
+                'initializing paged search for filter {filter}, base {base}, attr {attr}, limit {limit}, offset {offset}',
2038
+                [
2039
+                    'app' => 'user_ldap',
2040
+                    'filter' => $filter,
2041
+                    'base' => $base,
2042
+                    'attr' => $attr,
2043
+                    'limit' => $limit,
2044
+                    'offset' => $offset
2045
+                ]
2046
+            );
2047
+            //get the cookie from the search for the previous search, required by LDAP
2048
+            if (empty($this->lastCookie) && $this->lastCookie !== "0" && ($offset > 0)) {
2049
+                // no cookie known from a potential previous search. We need
2050
+                // to start from 0 to come to the desired page. cookie value
2051
+                // of '0' is valid, because 389ds
2052
+                $reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
2053
+                $this->search($filter, $base, $attr, $limit, $reOffset, true);
2054
+            }
2055
+            if ($this->lastCookie !== '' && $offset === 0) {
2056
+                //since offset = 0, this is a new search. We abandon other searches that might be ongoing.
2057
+                $this->abandonPagedSearch();
2058
+            }
2059
+            $pagedSearchOK = true === $this->invokeLDAPMethod(
2060
+                    'controlPagedResult', $this->connection->getConnectionResource(), $limit, false
2061
+                );
2062
+            if ($pagedSearchOK) {
2063
+                \OC::$server->getLogger()->debug('Ready for a paged search', ['app' => 'user_ldap']);
2064
+            }
2065
+            /* ++ Fixing RHDS searches with pages with zero results ++
2066 2066
 			 * We coudn't get paged searches working with our RHDS for login ($limit = 0),
2067 2067
 			 * due to pages with zero results.
2068 2068
 			 * So we added "&& !empty($this->lastCookie)" to this test to ignore pagination
2069 2069
 			 * if we don't have a previous paged search.
2070 2070
 			 */
2071
-		} elseif ($limit === 0 && !empty($this->lastCookie)) {
2072
-			// a search without limit was requested. However, if we do use
2073
-			// Paged Search once, we always must do it. This requires us to
2074
-			// initialize it with the configured page size.
2075
-			$this->abandonPagedSearch();
2076
-			// in case someone set it to 0 … use 500, otherwise no results will
2077
-			// be returned.
2078
-			$pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500;
2079
-			$pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
2080
-				$this->connection->getConnectionResource(),
2081
-				$pageSize, false);
2082
-		}
2083
-
2084
-		return $pagedSearchOK;
2085
-	}
2086
-
2087
-	/**
2088
-	 * Is more than one $attr used for search?
2089
-	 *
2090
-	 * @param string|string[]|null $attr
2091
-	 * @return bool
2092
-	 */
2093
-	private function manyAttributes($attr): bool {
2094
-		if (\is_array($attr)) {
2095
-			return \count($attr) > 1;
2096
-		}
2097
-		return false;
2098
-	}
2071
+        } elseif ($limit === 0 && !empty($this->lastCookie)) {
2072
+            // a search without limit was requested. However, if we do use
2073
+            // Paged Search once, we always must do it. This requires us to
2074
+            // initialize it with the configured page size.
2075
+            $this->abandonPagedSearch();
2076
+            // in case someone set it to 0 … use 500, otherwise no results will
2077
+            // be returned.
2078
+            $pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500;
2079
+            $pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
2080
+                $this->connection->getConnectionResource(),
2081
+                $pageSize, false);
2082
+        }
2083
+
2084
+        return $pagedSearchOK;
2085
+    }
2086
+
2087
+    /**
2088
+     * Is more than one $attr used for search?
2089
+     *
2090
+     * @param string|string[]|null $attr
2091
+     * @return bool
2092
+     */
2093
+    private function manyAttributes($attr): bool {
2094
+        if (\is_array($attr)) {
2095
+            return \count($attr) > 1;
2096
+        }
2097
+        return false;
2098
+    }
2099 2099
 }
Please login to merge, or discard this patch.
Spacing   +52 added lines, -52 removed lines patch added patch discarded remove patch
@@ -203,7 +203,7 @@  discard block
 block discarded – undo
203 203
 		$this->abandonPagedSearch();
204 204
 		// openLDAP requires that we init a new Paged Search. Not needed by AD,
205 205
 		// but does not hurt either.
206
-		$pagingSize = (int)$this->connection->ldapPagingSize;
206
+		$pagingSize = (int) $this->connection->ldapPagingSize;
207 207
 		// 0 won't result in replies, small numbers may leave out groups
208 208
 		// (cf. #12306), 500 is default for paging and should work everywhere.
209 209
 		$maxResults = $pagingSize > 20 ? $pagingSize : 500;
@@ -244,13 +244,13 @@  discard block
 block discarded – undo
244 244
 					return $values;
245 245
 				} else {
246 246
 					$low = $result['rangeHigh'] + 1;
247
-					$attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
247
+					$attrToRead = $result['attributeName'].';range='.$low.'-*';
248 248
 					$isRangeRequest = true;
249 249
 				}
250 250
 			}
251 251
 		} while ($isRangeRequest);
252 252
 
253
-		\OCP\Util::writeLog('user_ldap', 'Requested attribute ' . $attr . ' not found for ' . $dn, ILogger::DEBUG);
253
+		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, ILogger::DEBUG);
254 254
 		return false;
255 255
 	}
256 256
 
@@ -274,13 +274,13 @@  discard block
 block discarded – undo
274 274
 		if (!$this->ldap->isResource($rr)) {
275 275
 			if ($attribute !== '') {
276 276
 				//do not throw this message on userExists check, irritates
277
-				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, ILogger::DEBUG);
277
+				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, ILogger::DEBUG);
278 278
 			}
279 279
 			//in case an error occurs , e.g. object does not exist
280 280
 			return false;
281 281
 		}
282 282
 		if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $cr, $rr) === 1)) {
283
-			\OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', ILogger::DEBUG);
283
+			\OCP\Util::writeLog('user_ldap', 'readAttribute: '.$dn.' found', ILogger::DEBUG);
284 284
 			return true;
285 285
 		}
286 286
 		$er = $this->invokeLDAPMethod('firstEntry', $cr, $rr);
@@ -360,7 +360,7 @@  discard block
 block discarded – undo
360 360
 	 * @throws \Exception
361 361
 	 */
362 362
 	public function setPassword($userDN, $password) {
363
-		if ((int)$this->connection->turnOnPasswordChange !== 1) {
363
+		if ((int) $this->connection->turnOnPasswordChange !== 1) {
364 364
 			throw new \Exception('LDAP password changes are disabled.');
365 365
 		}
366 366
 		$cr = $this->connection->getConnectionResource();
@@ -374,7 +374,7 @@  discard block
 block discarded – undo
374 374
 			return @$this->invokeLDAPMethod('exopPasswd', $cr, $userDN, '', $password) ||
375 375
 				@$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password);
376 376
 		} catch (ConstraintViolationException $e) {
377
-			throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ') . $e->getMessage(), $e->getCode());
377
+			throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ').$e->getMessage(), $e->getCode());
378 378
 		}
379 379
 	}
380 380
 
@@ -540,21 +540,21 @@  discard block
 block discarded – undo
540 540
 			}
541 541
 		} else {
542 542
 			//If the UUID can't be detected something is foul.
543
-			\OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for ' . $fdn . '. Skipping.', ILogger::INFO);
543
+			\OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$fdn.'. Skipping.', ILogger::INFO);
544 544
 			return false;
545 545
 		}
546 546
 
547 547
 		if (is_null($ldapName)) {
548 548
 			$ldapName = $this->readAttribute($fdn, $nameAttribute, $filter);
549 549
 			if (!isset($ldapName[0]) && empty($ldapName[0])) {
550
-				\OCP\Util::writeLog('user_ldap', 'No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ILogger::INFO);
550
+				\OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.' with filter '.$filter.'.', ILogger::INFO);
551 551
 				return false;
552 552
 			}
553 553
 			$ldapName = $ldapName[0];
554 554
 		}
555 555
 
556 556
 		if ($isUser) {
557
-			$usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
557
+			$usernameAttribute = (string) $this->connection->ldapExpertUsernameAttr;
558 558
 			if ($usernameAttribute !== '') {
559 559
 				$username = $this->readAttribute($fdn, $usernameAttribute);
560 560
 				$username = $username[0];
@@ -606,7 +606,7 @@  discard block
 block discarded – undo
606 606
 		}
607 607
 
608 608
 		//if everything else did not help..
609
-		\OCP\Util::writeLog('user_ldap', 'Could not create unique name for ' . $fdn . '.', ILogger::INFO);
609
+		\OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$fdn.'.', ILogger::INFO);
610 610
 		return false;
611 611
 	}
612 612
 
@@ -721,7 +721,7 @@  discard block
 block discarded – undo
721 721
 	 * @param string|false $home the home directory path
722 722
 	 */
723 723
 	public function cacheUserHome($ocName, $home) {
724
-		$cacheKey = 'getHome' . $ocName;
724
+		$cacheKey = 'getHome'.$ocName;
725 725
 		$this->connection->writeToCache($cacheKey, $home);
726 726
 	}
727 727
 
@@ -731,14 +731,14 @@  discard block
 block discarded – undo
731 731
 	 * @param string $ocName the internal Nextcloud username
732 732
 	 */
733 733
 	public function cacheUserExists($ocName) {
734
-		$this->connection->writeToCache('userExists' . $ocName, true);
734
+		$this->connection->writeToCache('userExists'.$ocName, true);
735 735
 	}
736 736
 
737 737
 	/**
738 738
 	 * caches a group as existing
739 739
 	 */
740 740
 	public function cacheGroupExists(string $gid): void {
741
-		$this->connection->writeToCache('groupExists' . $gid, true);
741
+		$this->connection->writeToCache('groupExists'.$gid, true);
742 742
 	}
743 743
 
744 744
 	/**
@@ -756,11 +756,11 @@  discard block
 block discarded – undo
756 756
 		}
757 757
 		$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
758 758
 		$cacheKeyTrunk = 'getDisplayName';
759
-		$this->connection->writeToCache($cacheKeyTrunk . $ocName, $displayName);
759
+		$this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName);
760 760
 	}
761 761
 
762 762
 	public function cacheGroupDisplayName(string $ncName, string $displayName): void {
763
-		$cacheKey = 'group_getDisplayName' . $ncName;
763
+		$cacheKey = 'group_getDisplayName'.$ncName;
764 764
 		$this->connection->writeToCache($cacheKey, $displayName);
765 765
 	}
766 766
 
@@ -778,7 +778,7 @@  discard block
 block discarded – undo
778 778
 		//while loop is just a precaution. If a name is not generated within
779 779
 		//20 attempts, something else is very wrong. Avoids infinite loop.
780 780
 		while ($attempts < 20) {
781
-			$altName = $name . '_' . rand(1000, 9999);
781
+			$altName = $name.'_'.rand(1000, 9999);
782 782
 			if (!$this->ncUserManager->userExists($altName)) {
783 783
 				return $altName;
784 784
 			}
@@ -807,9 +807,9 @@  discard block
 block discarded – undo
807 807
 		} else {
808 808
 			natsort($usedNames);
809 809
 			$lastName = array_pop($usedNames);
810
-			$lastNo = (int)substr($lastName, strrpos($lastName, '_') + 1);
810
+			$lastNo = (int) substr($lastName, strrpos($lastName, '_') + 1);
811 811
 		}
812
-		$altName = $name . '_' . (string)($lastNo + 1);
812
+		$altName = $name.'_'.(string) ($lastNo + 1);
813 813
 		unset($usedNames);
814 814
 
815 815
 		$attempts = 1;
@@ -820,7 +820,7 @@  discard block
 block discarded – undo
820 820
 			if (!\OC::$server->getGroupManager()->groupExists($altName)) {
821 821
 				return $altName;
822 822
 			}
823
-			$altName = $name . '_' . ($lastNo + $attempts);
823
+			$altName = $name.'_'.($lastNo + $attempts);
824 824
 			$attempts++;
825 825
 		}
826 826
 		return false;
@@ -888,12 +888,12 @@  discard block
 block discarded – undo
888 888
 		if (!$forceApplyAttributes) {
889 889
 			$isBackgroundJobModeAjax = $this->config
890 890
 					->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
891
-			$listOfDNs = array_reduce($ldapRecords, function ($listOfDNs, $entry) {
891
+			$listOfDNs = array_reduce($ldapRecords, function($listOfDNs, $entry) {
892 892
 				$listOfDNs[] = $entry['dn'][0];
893 893
 				return $listOfDNs;
894 894
 			}, []);
895 895
 			$idsByDn = $this->userMapper->getListOfIdsByDn($listOfDNs);
896
-			$recordsToUpdate = array_filter($ldapRecords, function ($record) use ($isBackgroundJobModeAjax, $idsByDn) {
896
+			$recordsToUpdate = array_filter($ldapRecords, function($record) use ($isBackgroundJobModeAjax, $idsByDn) {
897 897
 				$newlyMapped = false;
898 898
 				$uid = $idsByDn[$record['dn'][0]] ?? null;
899 899
 				if ($uid === null) {
@@ -951,13 +951,13 @@  discard block
 block discarded – undo
951 951
 	public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
952 952
 		$groupRecords = $this->searchGroups($filter, $attr, $limit, $offset);
953 953
 
954
-		$listOfDNs = array_reduce($groupRecords, function ($listOfDNs, $entry) {
954
+		$listOfDNs = array_reduce($groupRecords, function($listOfDNs, $entry) {
955 955
 			$listOfDNs[] = $entry['dn'][0];
956 956
 			return $listOfDNs;
957 957
 		}, []);
958 958
 		$idsByDn = $this->groupMapper->getListOfIdsByDn($listOfDNs);
959 959
 
960
-		array_walk($groupRecords, function ($record) use ($idsByDn) {
960
+		array_walk($groupRecords, function($record) use ($idsByDn) {
961 961
 			$newlyMapped = false;
962 962
 			$gid = $uidsByDn[$record['dn'][0]] ?? null;
963 963
 			if ($gid === null) {
@@ -980,7 +980,7 @@  discard block
 block discarded – undo
980 980
 			if ($manyAttributes) {
981 981
 				return $list;
982 982
 			} else {
983
-				$list = array_reduce($list, function ($carry, $item) {
983
+				$list = array_reduce($list, function($carry, $item) {
984 984
 					$attribute = array_keys($item)[0];
985 985
 					$carry[] = $item[$attribute][0];
986 986
 					return $carry;
@@ -1025,7 +1025,7 @@  discard block
 block discarded – undo
1025 1025
 		$result = false;
1026 1026
 		foreach ($this->connection->ldapBaseUsers as $base) {
1027 1027
 			$count = $this->count($filter, [$base], $attr, $limit, $offset);
1028
-			$result = is_int($count) ? (int)$result + $count : $result;
1028
+			$result = is_int($count) ? (int) $result + $count : $result;
1029 1029
 		}
1030 1030
 		return $result;
1031 1031
 	}
@@ -1064,7 +1064,7 @@  discard block
 block discarded – undo
1064 1064
 		$result = false;
1065 1065
 		foreach ($this->connection->ldapBaseGroups as $base) {
1066 1066
 			$count = $this->count($filter, [$base], $attr, $limit, $offset);
1067
-			$result = is_int($count) ? (int)$result + $count : $result;
1067
+			$result = is_int($count) ? (int) $result + $count : $result;
1068 1068
 		}
1069 1069
 		return $result;
1070 1070
 	}
@@ -1081,7 +1081,7 @@  discard block
 block discarded – undo
1081 1081
 		$result = false;
1082 1082
 		foreach ($this->connection->ldapBase as $base) {
1083 1083
 			$count = $this->count('objectclass=*', [$base], ['dn'], $limit, $offset);
1084
-			$result = is_int($count) ? (int)$result + $count : $result;
1084
+			$result = is_int($count) ? (int) $result + $count : $result;
1085 1085
 		}
1086 1086
 		return $result;
1087 1087
 	}
@@ -1107,7 +1107,7 @@  discard block
 block discarded – undo
1107 1107
 		// php no longer supports call-time pass-by-reference
1108 1108
 		// thus cannot support controlPagedResultResponse as the third argument
1109 1109
 		// is a reference
1110
-		$doMethod = function () use ($command, &$arguments) {
1110
+		$doMethod = function() use ($command, &$arguments) {
1111 1111
 			if ($command == 'controlPagedResultResponse') {
1112 1112
 				throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.');
1113 1113
 			} else {
@@ -1166,13 +1166,13 @@  discard block
 block discarded – undo
1166 1166
 		}
1167 1167
 
1168 1168
 		//check whether paged search should be attempted
1169
-		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, (int)$offset);
1169
+		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int) $limit, (int) $offset);
1170 1170
 
1171 1171
 		$sr = $this->invokeLDAPMethod('search', $cr, $base, $filter, $attr);
1172 1172
 		// cannot use $cr anymore, might have changed in the previous call!
1173 1173
 		$error = $this->ldap->errno($this->connection->getConnectionResource());
1174 1174
 		if (!$this->ldap->isResource($sr) || $error !== 0) {
1175
-			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  ' . print_r($pagedSearchOK, true), ILogger::ERROR);
1175
+			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), ILogger::ERROR);
1176 1176
 			return false;
1177 1177
 		}
1178 1178
 
@@ -1216,7 +1216,7 @@  discard block
 block discarded – undo
1216 1216
 				$this->pagedSearchedSuccessful = true;
1217 1217
 			}
1218 1218
 		} else {
1219
-			if (!is_null($limit) && (int)$this->connection->ldapPagingSize !== 0) {
1219
+			if (!is_null($limit) && (int) $this->connection->ldapPagingSize !== 0) {
1220 1220
 				\OC::$server->getLogger()->debug(
1221 1221
 					'Paged search was not available',
1222 1222
 					['app' => 'user_ldap']
@@ -1262,7 +1262,7 @@  discard block
 block discarded – undo
1262 1262
 			$attr = [mb_strtolower($attr, 'UTF-8')];
1263 1263
 		}
1264 1264
 
1265
-		$limitPerPage = (int)$this->connection->ldapPagingSize;
1265
+		$limitPerPage = (int) $this->connection->ldapPagingSize;
1266 1266
 		if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1267 1267
 			$limitPerPage = $limit;
1268 1268
 		}
@@ -1304,7 +1304,7 @@  discard block
 block discarded – undo
1304 1304
 	 * @throws ServerNotAvailableException
1305 1305
 	 */
1306 1306
 	private function countEntriesInSearchResults($sr): int {
1307
-		return (int)$this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $sr);
1307
+		return (int) $this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $sr);
1308 1308
 	}
1309 1309
 
1310 1310
 	/**
@@ -1320,7 +1320,7 @@  discard block
 block discarded – undo
1320 1320
 		?int $offset = null,
1321 1321
 		bool $skipHandling = false
1322 1322
 	): array {
1323
-		$limitPerPage = (int)$this->connection->ldapPagingSize;
1323
+		$limitPerPage = (int) $this->connection->ldapPagingSize;
1324 1324
 		if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1325 1325
 			$limitPerPage = $limit;
1326 1326
 		}
@@ -1412,7 +1412,7 @@  discard block
 block discarded – undo
1412 1412
 				&& !is_null($limit)
1413 1413
 			)
1414 1414
 		) {
1415
-			$findings = array_slice($findings, (int)$offset, $limit);
1415
+			$findings = array_slice($findings, (int) $offset, $limit);
1416 1416
 		}
1417 1417
 		return $findings;
1418 1418
 	}
@@ -1464,7 +1464,7 @@  discard block
 block discarded – undo
1464 1464
 		}
1465 1465
 		$search = ['*', '\\', '(', ')'];
1466 1466
 		$replace = ['\\*', '\\\\', '\\(', '\\)'];
1467
-		return $asterisk . str_replace($search, $replace, $input);
1467
+		return $asterisk.str_replace($search, $replace, $input);
1468 1468
 	}
1469 1469
 
1470 1470
 	/**
@@ -1496,10 +1496,10 @@  discard block
 block discarded – undo
1496 1496
 	 * @return string the combined filter
1497 1497
 	 */
1498 1498
 	private function combineFilter($filters, $operator) {
1499
-		$combinedFilter = '(' . $operator;
1499
+		$combinedFilter = '('.$operator;
1500 1500
 		foreach ($filters as $filter) {
1501 1501
 			if ($filter !== '' && $filter[0] !== '(') {
1502
-				$filter = '(' . $filter . ')';
1502
+				$filter = '('.$filter.')';
1503 1503
 			}
1504 1504
 			$combinedFilter .= $filter;
1505 1505
 		}
@@ -1552,7 +1552,7 @@  discard block
 block discarded – undo
1552 1552
 			//every word needs to appear at least once
1553 1553
 			$wordMatchOneAttrFilters = [];
1554 1554
 			foreach ($searchAttributes as $attr) {
1555
-				$wordMatchOneAttrFilters[] = $attr . '=' . $word;
1555
+				$wordMatchOneAttrFilters[] = $attr.'='.$word;
1556 1556
 			}
1557 1557
 			$wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1558 1558
 		}
@@ -1588,14 +1588,14 @@  discard block
 block discarded – undo
1588 1588
 			if ($fallbackAttribute === '') {
1589 1589
 				return '';
1590 1590
 			}
1591
-			$filter[] = $fallbackAttribute . '=' . $search;
1591
+			$filter[] = $fallbackAttribute.'='.$search;
1592 1592
 		} else {
1593 1593
 			foreach ($searchAttributes as $attribute) {
1594
-				$filter[] = $attribute . '=' . $search;
1594
+				$filter[] = $attribute.'='.$search;
1595 1595
 			}
1596 1596
 		}
1597 1597
 		if (count($filter) === 1) {
1598
-			return '(' . $filter[0] . ')';
1598
+			return '('.$filter[0].')';
1599 1599
 		}
1600 1600
 		return $this->combineFilterWithOr($filter);
1601 1601
 	}
@@ -1616,7 +1616,7 @@  discard block
 block discarded – undo
1616 1616
 		if ($term === '') {
1617 1617
 			$result = '*';
1618 1618
 		} elseif ($allowEnum !== 'no') {
1619
-			$result = $term . '*';
1619
+			$result = $term.'*';
1620 1620
 		}
1621 1621
 		return $result;
1622 1622
 	}
@@ -1629,7 +1629,7 @@  discard block
 block discarded – undo
1629 1629
 	public function getFilterForUserCount() {
1630 1630
 		$filter = $this->combineFilterWithAnd([
1631 1631
 			$this->connection->ldapUserFilter,
1632
-			$this->connection->ldapUserDisplayName . '=*'
1632
+			$this->connection->ldapUserDisplayName.'=*'
1633 1633
 		]);
1634 1634
 
1635 1635
 		return $filter;
@@ -1695,7 +1695,7 @@  discard block
 block discarded – undo
1695 1695
 			$uuid = $this->formatGuid2ForFilterUser($uuid);
1696 1696
 		}
1697 1697
 
1698
-		$filter = $uuidAttr . '=' . $uuid;
1698
+		$filter = $uuidAttr.'='.$uuid;
1699 1699
 		$result = $this->searchUsers($filter, ['dn'], 2);
1700 1700
 		if (is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1701 1701
 			// we put the count into account to make sure that this is
@@ -1825,8 +1825,8 @@  discard block
 block discarded – undo
1825 1825
 		for ($k = 1; $k <= 2; ++$k) {
1826 1826
 			$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1827 1827
 		}
1828
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1829
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1828
+		$hex_guid_to_guid_str .= '-'.substr($hex_guid, 16, 4);
1829
+		$hex_guid_to_guid_str .= '-'.substr($hex_guid, 20);
1830 1830
 
1831 1831
 		return strtoupper($hex_guid_to_guid_str);
1832 1832
 	}
@@ -1860,7 +1860,7 @@  discard block
 block discarded – undo
1860 1860
 			 * user. Instead we write a log message.
1861 1861
 			 */
1862 1862
 			\OC::$server->getLogger()->info(
1863
-				'Passed string does not resemble a valid GUID. Known UUID ' .
1863
+				'Passed string does not resemble a valid GUID. Known UUID '.
1864 1864
 				'({uuid}) probably does not match UUID configuration.',
1865 1865
 				['app' => 'user_ldap', 'uuid' => $guid]
1866 1866
 			);
@@ -1873,7 +1873,7 @@  discard block
 block discarded – undo
1873 1873
 		}
1874 1874
 		for ($i = 0; $i < 5; $i++) {
1875 1875
 			$pairs = str_split($blocks[$i], 2);
1876
-			$blocks[$i] = '\\' . implode('\\', $pairs);
1876
+			$blocks[$i] = '\\'.implode('\\', $pairs);
1877 1877
 		}
1878 1878
 		return implode('', $blocks);
1879 1879
 	}
@@ -1887,7 +1887,7 @@  discard block
 block discarded – undo
1887 1887
 	 */
1888 1888
 	public function getSID($dn) {
1889 1889
 		$domainDN = $this->getDomainDNFromDN($dn);
1890
-		$cacheKey = 'getSID-' . $domainDN;
1890
+		$cacheKey = 'getSID-'.$domainDN;
1891 1891
 		$sid = $this->connection->getFromCache($cacheKey);
1892 1892
 		if (!is_null($sid)) {
1893 1893
 			return $sid;
@@ -2075,7 +2075,7 @@  discard block
 block discarded – undo
2075 2075
 			$this->abandonPagedSearch();
2076 2076
 			// in case someone set it to 0 … use 500, otherwise no results will
2077 2077
 			// be returned.
2078
-			$pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500;
2078
+			$pageSize = (int) $this->connection->ldapPagingSize > 0 ? (int) $this->connection->ldapPagingSize : 500;
2079 2079
 			$pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
2080 2080
 				$this->connection->getConnectionResource(),
2081 2081
 				$pageSize, false);
Please login to merge, or discard this patch.
apps/user_ldap/lib/Proxy.php 2 patches
Indentation   +180 added lines, -180 removed lines patch added patch discarded remove patch
@@ -38,184 +38,184 @@
 block discarded – undo
38 38
 use OCA\User_LDAP\User\Manager;
39 39
 
40 40
 abstract class Proxy {
41
-	private static $accesses = [];
42
-	private $ldap = null;
43
-	/** @var bool */
44
-	private $isSingleBackend;
45
-
46
-	/** @var \OCP\ICache|null */
47
-	private $cache;
48
-
49
-	/**
50
-	 * @param ILDAPWrapper $ldap
51
-	 */
52
-	public function __construct(ILDAPWrapper $ldap) {
53
-		$this->ldap = $ldap;
54
-		$memcache = \OC::$server->getMemCacheFactory();
55
-		if ($memcache->isAvailable()) {
56
-			$this->cache = $memcache->createDistributed();
57
-		}
58
-	}
59
-
60
-	/**
61
-	 * @param string $configPrefix
62
-	 */
63
-	private function addAccess($configPrefix) {
64
-		static $ocConfig;
65
-		static $fs;
66
-		static $log;
67
-		static $avatarM;
68
-		static $userMap;
69
-		static $groupMap;
70
-		static $db;
71
-		static $coreUserManager;
72
-		static $coreNotificationManager;
73
-		if ($fs === null) {
74
-			$ocConfig = \OC::$server->getConfig();
75
-			$fs = new FilesystemHelper();
76
-			$log = new LogWrapper();
77
-			$avatarM = \OC::$server->getAvatarManager();
78
-			$db = \OC::$server->getDatabaseConnection();
79
-			$userMap = new UserMapping($db);
80
-			$groupMap = new GroupMapping($db);
81
-			$coreUserManager = \OC::$server->getUserManager();
82
-			$coreNotificationManager = \OC::$server->getNotificationManager();
83
-		}
84
-		$userManager =
85
-			new Manager($ocConfig, $fs, $log, $avatarM, new \OCP\Image(), $db,
86
-				$coreUserManager, $coreNotificationManager);
87
-		$connector = new Connection($this->ldap, $configPrefix);
88
-		$access = new Access($connector, $this->ldap, $userManager, new Helper($ocConfig), $ocConfig, $coreUserManager);
89
-		$access->setUserMapper($userMap);
90
-		$access->setGroupMapper($groupMap);
91
-		self::$accesses[$configPrefix] = $access;
92
-	}
93
-
94
-	/**
95
-	 * @param string $configPrefix
96
-	 * @return mixed
97
-	 */
98
-	protected function getAccess($configPrefix) {
99
-		if (!isset(self::$accesses[$configPrefix])) {
100
-			$this->addAccess($configPrefix);
101
-		}
102
-		return self::$accesses[$configPrefix];
103
-	}
104
-
105
-	/**
106
-	 * @param string $uid
107
-	 * @return string
108
-	 */
109
-	protected function getUserCacheKey($uid) {
110
-		return 'user-' . $uid . '-lastSeenOn';
111
-	}
112
-
113
-	/**
114
-	 * @param string $gid
115
-	 * @return string
116
-	 */
117
-	protected function getGroupCacheKey($gid) {
118
-		return 'group-' . $gid . '-lastSeenOn';
119
-	}
120
-
121
-	/**
122
-	 * @param string $id
123
-	 * @param string $method
124
-	 * @param array $parameters
125
-	 * @param bool $passOnWhen
126
-	 * @return mixed
127
-	 */
128
-	abstract protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
129
-
130
-	/**
131
-	 * @param string $id
132
-	 * @param string $method
133
-	 * @param array $parameters
134
-	 * @return mixed
135
-	 */
136
-	abstract protected function walkBackends($id, $method, $parameters);
137
-
138
-	/**
139
-	 * @param string $id
140
-	 * @return Access
141
-	 */
142
-	abstract public function getLDAPAccess($id);
143
-
144
-	abstract protected function activeBackends(): int;
145
-
146
-	protected function isSingleBackend(): bool {
147
-		if ($this->isSingleBackend === null) {
148
-			$this->isSingleBackend = $this->activeBackends() === 1;
149
-		}
150
-		return $this->isSingleBackend;
151
-	}
152
-
153
-	/**
154
-	 * Takes care of the request to the User backend
155
-	 *
156
-	 * @param string $id
157
-	 * @param string $method string, the method of the user backend that shall be called
158
-	 * @param array $parameters an array of parameters to be passed
159
-	 * @param bool $passOnWhen
160
-	 * @return mixed, the result of the specified method
161
-	 */
162
-	protected function handleRequest($id, $method, $parameters, $passOnWhen = false) {
163
-		if (!$this->isSingleBackend()) {
164
-			$result = $this->callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
165
-		}
166
-		if (!isset($result) || $result === $passOnWhen) {
167
-			$result = $this->walkBackends($id, $method, $parameters);
168
-		}
169
-		return $result;
170
-	}
171
-
172
-	/**
173
-	 * @param string|null $key
174
-	 * @return string
175
-	 */
176
-	private function getCacheKey($key) {
177
-		$prefix = 'LDAP-Proxy-';
178
-		if ($key === null) {
179
-			return $prefix;
180
-		}
181
-		return $prefix . hash('sha256', $key);
182
-	}
183
-
184
-	/**
185
-	 * @param string $key
186
-	 * @return mixed|null
187
-	 */
188
-	public function getFromCache($key) {
189
-		if ($this->cache === null) {
190
-			return null;
191
-		}
192
-
193
-		$key = $this->getCacheKey($key);
194
-		$value = $this->cache->get($key);
195
-		if ($value === null) {
196
-			return null;
197
-		}
198
-
199
-		return json_decode(base64_decode($value));
200
-	}
201
-
202
-	/**
203
-	 * @param string $key
204
-	 * @param mixed $value
205
-	 */
206
-	public function writeToCache($key, $value) {
207
-		if ($this->cache === null) {
208
-			return;
209
-		}
210
-		$key = $this->getCacheKey($key);
211
-		$value = base64_encode(json_encode($value));
212
-		$this->cache->set($key, $value, 2592000);
213
-	}
214
-
215
-	public function clearCache() {
216
-		if ($this->cache === null) {
217
-			return;
218
-		}
219
-		$this->cache->clear($this->getCacheKey(null));
220
-	}
41
+    private static $accesses = [];
42
+    private $ldap = null;
43
+    /** @var bool */
44
+    private $isSingleBackend;
45
+
46
+    /** @var \OCP\ICache|null */
47
+    private $cache;
48
+
49
+    /**
50
+     * @param ILDAPWrapper $ldap
51
+     */
52
+    public function __construct(ILDAPWrapper $ldap) {
53
+        $this->ldap = $ldap;
54
+        $memcache = \OC::$server->getMemCacheFactory();
55
+        if ($memcache->isAvailable()) {
56
+            $this->cache = $memcache->createDistributed();
57
+        }
58
+    }
59
+
60
+    /**
61
+     * @param string $configPrefix
62
+     */
63
+    private function addAccess($configPrefix) {
64
+        static $ocConfig;
65
+        static $fs;
66
+        static $log;
67
+        static $avatarM;
68
+        static $userMap;
69
+        static $groupMap;
70
+        static $db;
71
+        static $coreUserManager;
72
+        static $coreNotificationManager;
73
+        if ($fs === null) {
74
+            $ocConfig = \OC::$server->getConfig();
75
+            $fs = new FilesystemHelper();
76
+            $log = new LogWrapper();
77
+            $avatarM = \OC::$server->getAvatarManager();
78
+            $db = \OC::$server->getDatabaseConnection();
79
+            $userMap = new UserMapping($db);
80
+            $groupMap = new GroupMapping($db);
81
+            $coreUserManager = \OC::$server->getUserManager();
82
+            $coreNotificationManager = \OC::$server->getNotificationManager();
83
+        }
84
+        $userManager =
85
+            new Manager($ocConfig, $fs, $log, $avatarM, new \OCP\Image(), $db,
86
+                $coreUserManager, $coreNotificationManager);
87
+        $connector = new Connection($this->ldap, $configPrefix);
88
+        $access = new Access($connector, $this->ldap, $userManager, new Helper($ocConfig), $ocConfig, $coreUserManager);
89
+        $access->setUserMapper($userMap);
90
+        $access->setGroupMapper($groupMap);
91
+        self::$accesses[$configPrefix] = $access;
92
+    }
93
+
94
+    /**
95
+     * @param string $configPrefix
96
+     * @return mixed
97
+     */
98
+    protected function getAccess($configPrefix) {
99
+        if (!isset(self::$accesses[$configPrefix])) {
100
+            $this->addAccess($configPrefix);
101
+        }
102
+        return self::$accesses[$configPrefix];
103
+    }
104
+
105
+    /**
106
+     * @param string $uid
107
+     * @return string
108
+     */
109
+    protected function getUserCacheKey($uid) {
110
+        return 'user-' . $uid . '-lastSeenOn';
111
+    }
112
+
113
+    /**
114
+     * @param string $gid
115
+     * @return string
116
+     */
117
+    protected function getGroupCacheKey($gid) {
118
+        return 'group-' . $gid . '-lastSeenOn';
119
+    }
120
+
121
+    /**
122
+     * @param string $id
123
+     * @param string $method
124
+     * @param array $parameters
125
+     * @param bool $passOnWhen
126
+     * @return mixed
127
+     */
128
+    abstract protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
129
+
130
+    /**
131
+     * @param string $id
132
+     * @param string $method
133
+     * @param array $parameters
134
+     * @return mixed
135
+     */
136
+    abstract protected function walkBackends($id, $method, $parameters);
137
+
138
+    /**
139
+     * @param string $id
140
+     * @return Access
141
+     */
142
+    abstract public function getLDAPAccess($id);
143
+
144
+    abstract protected function activeBackends(): int;
145
+
146
+    protected function isSingleBackend(): bool {
147
+        if ($this->isSingleBackend === null) {
148
+            $this->isSingleBackend = $this->activeBackends() === 1;
149
+        }
150
+        return $this->isSingleBackend;
151
+    }
152
+
153
+    /**
154
+     * Takes care of the request to the User backend
155
+     *
156
+     * @param string $id
157
+     * @param string $method string, the method of the user backend that shall be called
158
+     * @param array $parameters an array of parameters to be passed
159
+     * @param bool $passOnWhen
160
+     * @return mixed, the result of the specified method
161
+     */
162
+    protected function handleRequest($id, $method, $parameters, $passOnWhen = false) {
163
+        if (!$this->isSingleBackend()) {
164
+            $result = $this->callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
165
+        }
166
+        if (!isset($result) || $result === $passOnWhen) {
167
+            $result = $this->walkBackends($id, $method, $parameters);
168
+        }
169
+        return $result;
170
+    }
171
+
172
+    /**
173
+     * @param string|null $key
174
+     * @return string
175
+     */
176
+    private function getCacheKey($key) {
177
+        $prefix = 'LDAP-Proxy-';
178
+        if ($key === null) {
179
+            return $prefix;
180
+        }
181
+        return $prefix . hash('sha256', $key);
182
+    }
183
+
184
+    /**
185
+     * @param string $key
186
+     * @return mixed|null
187
+     */
188
+    public function getFromCache($key) {
189
+        if ($this->cache === null) {
190
+            return null;
191
+        }
192
+
193
+        $key = $this->getCacheKey($key);
194
+        $value = $this->cache->get($key);
195
+        if ($value === null) {
196
+            return null;
197
+        }
198
+
199
+        return json_decode(base64_decode($value));
200
+    }
201
+
202
+    /**
203
+     * @param string $key
204
+     * @param mixed $value
205
+     */
206
+    public function writeToCache($key, $value) {
207
+        if ($this->cache === null) {
208
+            return;
209
+        }
210
+        $key = $this->getCacheKey($key);
211
+        $value = base64_encode(json_encode($value));
212
+        $this->cache->set($key, $value, 2592000);
213
+    }
214
+
215
+    public function clearCache() {
216
+        if ($this->cache === null) {
217
+            return;
218
+        }
219
+        $this->cache->clear($this->getCacheKey(null));
220
+    }
221 221
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -107,7 +107,7 @@  discard block
 block discarded – undo
107 107
 	 * @return string
108 108
 	 */
109 109
 	protected function getUserCacheKey($uid) {
110
-		return 'user-' . $uid . '-lastSeenOn';
110
+		return 'user-'.$uid.'-lastSeenOn';
111 111
 	}
112 112
 
113 113
 	/**
@@ -115,7 +115,7 @@  discard block
 block discarded – undo
115 115
 	 * @return string
116 116
 	 */
117 117
 	protected function getGroupCacheKey($gid) {
118
-		return 'group-' . $gid . '-lastSeenOn';
118
+		return 'group-'.$gid.'-lastSeenOn';
119 119
 	}
120 120
 
121 121
 	/**
@@ -178,7 +178,7 @@  discard block
 block discarded – undo
178 178
 		if ($key === null) {
179 179
 			return $prefix;
180 180
 		}
181
-		return $prefix . hash('sha256', $key);
181
+		return $prefix.hash('sha256', $key);
182 182
 	}
183 183
 
184 184
 	/**
Please login to merge, or discard this patch.